diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7be50b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +Cargo.lock +target +*.json +*.DS_Store +**.DS_Store +scpt/*.json diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..ab75886 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ai" +version = "0.0.1" +edition = "2021" + +[dependencies] +seahorse = "*" +reqwest = { version = "*", features = ["blocking", "json"] } +tokio = { version = "1", features = ["full"] } +shellexpand = "*" +config = "*" +serde = "*" +serde_json = "*" +serde_derive = "*" +url = { version = "2.0", features = ["serde"] } +rustc-serialize = "*" +toml = "*" +iso8601-timestamp = "*" diff --git a/README.md b/README.md index c7b3451..e35cdb2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,50 @@ ## ai `bot` + + +```sh +$ ai +``` + ```sh $ cargo build -$ ./target/debug/ai +$ ./target/debug/ai ai -t ``` + +### login + +```sh +# ai token $handle -p $password +$ ai t yui.syui.ai -p password + +$ cat ~/.config/ai/token.toml +``` + +```sh +# ai token $handle -p $password -s $server +$ ai t ai.syu.is -p password -s syu.is +``` + +### refresh + +``` +$ ai r +``` + +``` +# server: syu.is +$ ai r -s syu.is +``` + +### notify + +``` +$ ai n +``` + +### bot + +``` +$ ai bot +``` + diff --git a/ai.zsh b/ai.zsh new file mode 100755 index 0000000..21c3171 --- /dev/null +++ b/ai.zsh @@ -0,0 +1,27 @@ +#!/bin/zsh +case $OSTYPE in + darwin*) + alias date="/opt/homebrew/bin/gdate" + ;; +esac +d=${0:a:h}/scpt +source $d/env +source $d/refresh.zsh +source $d/token.zsh +source $d/reply.zsh +source $d/notify.zsh + +case $1 in + refresh|r) + refresh + ;; + token|t) + token + ;; + reply) + reply + ;; + notify|n) + notify + ;; +esac diff --git a/icon/ai.png b/icon/ai.png new file mode 100644 index 0000000..d21dabe Binary files /dev/null and b/icon/ai.png differ diff --git a/icon/avatar.png b/icon/avatar.png new file mode 100644 index 0000000..f091ef1 Binary files /dev/null and b/icon/avatar.png differ diff --git a/scpt/env b/scpt/env new file mode 100644 index 0000000..22b692b --- /dev/null +++ b/scpt/env @@ -0,0 +1,15 @@ +cfg=~/.config/ai/test.json +host=`cat $cfg|jq -r .host` +handle=`cat $cfg|jq -r .handle` +pass=`cat $cfg|jq -r .password` +date=`date --iso-8601=seconds` + +if [ ! -f $cfg.t ];then + $d/token.zsh +fi + +if [ -f $cfg.t ];then + token=`cat $cfg.t|jq -r .accessJwt` + refresh=`cat $cfg.t|jq -r .refreshJwt` + did=`cat $cfg.t|jq -r .did` +fi diff --git a/scpt/notify.zsh b/scpt/notify.zsh new file mode 100644 index 0000000..594aba7 --- /dev/null +++ b/scpt/notify.zsh @@ -0,0 +1,27 @@ +function notify() { + url=https://$host/xrpc/app.bsky.notification.listNotifications + if [ ! -f $d/notify.json ];then + curl -sL "Content-Type: application/json" -H "Authorization: Bearer $token" "$url?limit=100" >! $d/notify.json + fi + #cat $d/notify.json + + for ((i=0;i<=99;i++)) + do + cid=`cat $d/notify.json|jq ".|.[].[$i]?|.cid?"` + uri=`cat $d/notify.json|jq ".|.[].[$i]?|.uri?"` + echo $cid + echo $uri + cid_r=`cat $d/notify.json|jq ".[]|.[$i]?|.record.reply.root.cid?"` + + if [ "$cid_r" = "null" ];then + continue + fi + uri_r=`cat $d/notify.json|jq ".[]|.[$i]?|.record.reply.root.uri?"` + cid_p=`cat $d/notify.json|jq ".[]|.[$i]?|.record.reply.parent.cid?"` + uri_p=`cat $d/notify.json|jq ".[]|.[$i]?|.record.reply.parent.uri?"` + echo $cid_r + echo $uri_r + echo $cid_p + echo $uri_p + done +} diff --git a/scpt/refresh.zsh b/scpt/refresh.zsh new file mode 100755 index 0000000..81998ce --- /dev/null +++ b/scpt/refresh.zsh @@ -0,0 +1,11 @@ +function refresh(){ + token=`cat $cfg.t|jq -r .accessJwt` + refresh=`cat $cfg.t|jq -r .refreshJwt` + if [ ! -f $cfg ];then + token + fi + url=https://$host/xrpc/com.atproto.server.refreshSession + j=`curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $refresh" $url` + echo $j + echo $j >! $cfg.t +} diff --git a/scpt/reply.zsh b/scpt/reply.zsh new file mode 100755 index 0000000..af7d7b0 --- /dev/null +++ b/scpt/reply.zsh @@ -0,0 +1,29 @@ +function reply() { + + url="https://$host/xrpc/com.atproto.repo.createRecord" + col="app.bsky.feed.post" + +json="{ + \"repo\": \"$handle\", + \"did\": \"$did\", + \"collection\": \"$col\", + \"record\": { + \"text\": \"$text\", + \"createdAt\": \"$date\", + \"reply\": { + \"root\": { + \"cid\": \"$cid\", + \"uri\": \"$uri\" + }, + \"parent\": { + \"cid\": \"$cid_p\", + \"uri\": \"$uri_p\" + } + } + } +}" + +echo $json|jq . +url=https://$host/xrpc/com.atproto.repo.createRecord +j=`curl -sL -X POST -H "Content-Type: application/json" -H "Authorization: Bearer $token" -d "$json" $url` +} diff --git a/scpt/token.zsh b/scpt/token.zsh new file mode 100755 index 0000000..0e5fda6 --- /dev/null +++ b/scpt/token.zsh @@ -0,0 +1,6 @@ +function token() { + url=https://$host/xrpc/com.atproto.server.createSession + j=`curl -sL -X POST -H "Content-Type: application/json" -d "{\"identifier\":\"$handle\",\"password\":\"$pass\"}" $url` + echo $j + echo $j >! $cfg.t +} diff --git a/src/ascii.rs b/src/ascii.rs new file mode 100644 index 0000000..0b57ace --- /dev/null +++ b/src/ascii.rs @@ -0,0 +1,80 @@ +pub fn c_ascii(x: bool) { + let logo = " +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⠋⠉⠀⠀⠀⠀⠀⠈⠉⠛⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⣸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⢹⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣤⣤⣤⣶⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⠀⠀⠀⠀ +⠀⠀⠀⢀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀ +⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀ +⠀⠀⠘⠛⠛⠛⠛⠉⠉⠉⠉⠁⠀⠀⠀⠀⠈⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠟⠋⠀⠀⠀⠀⠀⠉⠉⠉⠉⠙⠛⠛⠛⠛⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠙⠛⠿⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠟⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +"; + + let avatar = " +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣁⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⠖⠋⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠙⠒⢦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⢿⣷⣦⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣤⣴⣿⠷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣴⣶⠿⠟⠛⠛⠋⠉⠋⠉⠉⠉⠉⠙⠛⠙⠛⠛⠻⠿⢷⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠚⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠙⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣤⣴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣦⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠋⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⠘⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⣼⡀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢠⣷⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠏⣼⣿⡇⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢁⣾⣿⡆⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢰⣿⣿⣧⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠃⣾⣿⣿⡇⣿⣿⣿⢻⣿⣿⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⣾⣿⣿⣿⡄⣿⣿⣿⣿⣿⣿⣿⣿⠏⣼⣿⣿⣿⡇⣿⡿⢣⠈⣿⣿⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⣿⣿⣿⣿⣿⣿⢹⠀⣿⣿⣿⣿⣧⢹⣿⡏⢹⣿⣿⣿⠏⣼⣿⡿⠿⢿⢣⡿⣡⣿⠀⣿⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⢃⠸⠀⣵⡶⠶⢶⣿⡆⢿⢁⠸⣿⣿⢋⣼⣯⡴⠒⠒⠒⣼⡑⢟⣿⢠⣿⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⢿⣿⣿⣿⣿⡟⠈⣄⠈⠡⡐⢿⣦⠹⣿⡌⢸⡆⠿⣡⣾⣿⡟⢐⠇⠙⣡⡀⣿⡎⢿⠈⢐⡲⣆⢻⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢳⡘⣿⣿⣿⣿⡇⣸⣿⡄⣶⡄⠀⣿⠀⣿⣿⣜⣷⣼⣿⣿⣿⡇⢸⡇⠀⣿⠃⣿⣿⣸⠀⡌⢇⣿⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠑⠘⣿⣿⣿⡇⣿⣿⣧⡘⢷⣾⠟⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣌⣿⣿⣃⣼⣿⣿⡟⠀⣿⣾⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⡇⢸⣿⣿⣿⣾⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠡⠾⢛⡁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⢿⡘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢃⣶⡾⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡅⢀⡹⣿⣿⣿⣿⣿⣿⣯⣛⡛⣛⣛⣭⣿⣿⣿⣿⣿⠿⢋⣤⠟⢉⠠⣶⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠃⣸⣿⣦⡍⠛⠻⠿⣿⣿⣿⣿⣿⣿⣿⠿⠿⢛⡉⢠⡶⠋⡄⢺⣿⡀⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⣿⣿⢿⠇⣼⢹⡀⣶⣦⣭⣭⣭⣴⣶⣿⠂⡟⠀⢋⣴⡇⡇⣾⣿⡇⢻⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠀⣿⡿⢸⢀⠇⣸⡀⣿⣿⣿⣿⣿⣿⣿⣿⠀⣠⠀⣿⣿⡇⠀⣿⢹⣷⠸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⠀⣿⡇⡟⠘⢠⡿⠃⣿⣿⣿⣿⣿⣿⣿⣿⡀⣤⠀⣿⣿⠃⢰⣿⠸⣿⡀⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢜⣽⠀⣿⠁⡇⡰⣢⣼⣰⣿⣿⣿⣿⣿⣿⣿⣿⣷⣌⠀⣿⣿⠀⣼⣿⡄⣿⣧⠸⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⣠⠞⣋⠀⣿⢸⠃⣴⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿⣿⢠⣬⣍⣁⣙⠛⠦⠹⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢋⠜⣡⣿⣿⢠⡿⠘⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢠⣿⡇⣸⣿⣿⣿⣿⣿⣷⣦⣍⠻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠞⡰⢫⣾⣿⡟⣵⢸⡇⠀⢸⡿⠿⢛⣋⣉⣿⣿⣿⣿⣿⣿⣫⣭⡍⢸⣿⠇⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⡌⢦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡴⢃⡞⣰⣿⣿⣿⣦⡛⢸⡇⠀⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⠀⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⢷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡞⢡⡏⣼⣿⣿⣿⣿⣿⣿⢸⡇⠀⣭⣭⣭⣤⣶⣦⢠⣭⣙⠻⣿⣿⣿⣿⡇⣾⡇⢰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⡄⢻⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡟⢰⣿⢰⣿⣿⣿⣿⣿⣿⣿⠸⣇⠀⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⡜⣿⣿⣿⡇⣿⠀⢸⣿⡿⠿⠿⠛⠿⠿⠟⣛⣛⣉⣥⣿⡈⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀ + +"; + + match x { + true => println!("{}", avatar), + false => println!("{}", logo), + } + +} diff --git a/src/data.rs b/src/data.rs new file mode 100644 index 0000000..d40b729 --- /dev/null +++ b/src/data.rs @@ -0,0 +1,435 @@ +use config::{Config, ConfigError, File}; +use serde_derive::{Deserialize, Serialize}; +use std::fs; +use std::io::Write; + +pub fn data_file(s: &str) -> String { + let file = "/.config/ai/token"; + let mut f = shellexpand::tilde("~").to_string(); + f.push_str(&file); + match &*s { + "toml" => f + &".toml", + "json" => f + &".json", + _ => f + &"." + &s, + } +} + +impl Token { + pub fn new() -> Result { + let d = data_file("json"); + let s = Config::builder() + .add_source(File::with_name(&d)) + .add_source(config::Environment::with_prefix("APP")) + .build()?; + s.try_deserialize() + } +} + +impl Data { + pub fn new() -> Result { + let d = data_file("toml"); + let s = Config::builder() + .add_source(File::with_name(&d)) + .add_source(config::Environment::with_prefix("APP")) + .build()?; + s.try_deserialize() + } +} + +#[derive(Debug, Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Token { + pub did: String, + pub handle: String, + pub accessJwt: String, + pub refreshJwt: String, +} + +#[derive(Debug, Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Data { + pub host: String, + pub did: String, + pub handle: String, + pub access: String, + pub refresh: String, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct BaseUrl { + pub profile_get: String, + pub thread_get: String, + pub describe: String, + pub record_list: String, + pub record_create: String, + pub record_delete: String, + pub session_create: String, + pub session_refresh: String, + pub session_get: String, + pub timeline_get: String, + pub timeline_author: String, + pub upload_blob: String, + pub update_handle: String, + pub account_create: String, + pub notify_count: String, + pub notify_list: String, + pub notify_update: String, + pub repo_update: String, + pub like: String, + pub repost: String, + pub follow: String, + pub follows: String, + pub followers: String, +} + +pub fn url(s: &str) -> String { + let s = String::from(s); + let data = Data::new().unwrap(); + let data = Data { + host: data.host, + handle: data.handle, + did: data.did, + access: data.access, + refresh: data.refresh, + }; + let t = "https://".to_string() + &data.host.to_string() + &"/xrpc/".to_string(); + let baseurl = BaseUrl { + profile_get: "com.atproto.identity.resolveHandle".to_string(), + thread_get: "app.bsky.feed.getPostThread".to_string(), + record_create: "com.atproto.repo.createRecord".to_string(), + record_delete: "com.atproto.repo.deleteRecord".to_string(), + describe: "com.atproto.repo.describeRepo".to_string(), + record_list: "com.atproto.repo.listRecords".to_string(), + session_create: "com.atproto.server.createSession".to_string(), + session_refresh: "com.atproto.server.refreshSession".to_string(), + session_get: "com.atproto.server.getSession".to_string(), + timeline_get: "app.bsky.feed.getTimeline".to_string(), + timeline_author: "app.bsky.feed.getAuthorFeed".to_string(), + like: "app.bsky.feed.like".to_string(), + repost: "app.bsky.feed.repost".to_string(), + follow: "app.bsky.graph.follow".to_string(), + follows: "app.bsky.graph.getFollows".to_string(), + followers: "app.bsky.graph.getFollowers".to_string(), + upload_blob: "com.atproto.repo.uploadBlob".to_string(), + account_create: "com.atproto.server.createAccount".to_string(), + update_handle: "com.atproto.identity.updateHandle".to_string(), + notify_count: "app.bsky.notification.getUnreadCount".to_string(), + notify_list: "app.bsky.notification.listNotifications".to_string(), + notify_update: "app.bsky.notification.updateSeen".to_string(), + repo_update: "com.atproto.sync.updateRepo".to_string(), + }; + + match &*s { + "profile_get" => t.to_string() + &baseurl.profile_get, + "thread_get" => t.to_string() + &baseurl.thread_get, + "describe" => t.to_string() + &baseurl.describe, + "record_list" => t.to_string() + &baseurl.record_list, + "record_create" => t.to_string() + &baseurl.record_create, + "record_delete" => t.to_string() + &baseurl.record_delete, + "session_create" => t.to_string() + &baseurl.session_create, + "session_refresh" => t.to_string() + &baseurl.session_refresh, + "session_get" => t.to_string() + &baseurl.session_get, + "timeline_get" => t.to_string() + &baseurl.timeline_get, + "timeline_author" => t.to_string() + &baseurl.timeline_get, + "upload_blob" => t.to_string() + &baseurl.upload_blob, + "account_create" => t.to_string() + &baseurl.account_create, + "update_handle" => t.to_string() + &baseurl.update_handle, + "notify_list" => t.to_string() + &baseurl.notify_list, + "notify_count" => t.to_string() + &baseurl.notify_count, + "notify_update" => t.to_string() + &baseurl.notify_update, + "repo_update" => t.to_string() + &baseurl.repo_update, + "like" => t.to_string() + &baseurl.like, + "repost" => t.to_string() + &baseurl.repost, + "follow" => t.to_string() + &baseurl.follow, + "follows" => t.to_string() + &baseurl.follows, + "followers" => t.to_string() + &baseurl.followers, + _ => s, + } +} + +pub fn data_toml(s: &str) -> String { + let s = String::from(s); + let data = Data::new().unwrap(); + let data = Data { + host: data.host, + handle: data.handle, + did: data.did, + access: data.access, + refresh: data.refresh, + }; + match &*s { + "host" => data.handle, + "handle" => data.handle, + "did" => data.did, + "access" => data.access, + "refresh" => data.refresh, + _ => s, + } +} + +pub fn w_cfg(h: &str, res: &str) { + let f = data_file(&"json"); + let ff = data_file(&"toml"); + let mut f = fs::File::create(f.clone()).unwrap(); + let mut ff = fs::File::create(ff.clone()).unwrap(); + f.write_all(&res.as_bytes()).unwrap(); + let json: Token = serde_json::from_str(&res).unwrap(); + let datas = Data { + host: h.to_string(), + did: json.did.to_string(), + handle: json.handle.to_string(), + access: json.accessJwt.to_string(), + refresh: json.refreshJwt.to_string(), + }; + let toml = toml::to_string(&datas).unwrap(); + ff.write_all(&toml.as_bytes()).unwrap(); +} + +#[derive(Serialize, Deserialize)] +pub struct Notify { + pub notifications: Vec +} + +#[derive(Serialize, Deserialize)] +pub struct Status { + pub handle: String, + pub did: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct DidDocs { + pub verificationMethod: Vec, + pub service: Vec, + pub id: String, + pub alsoKnownAs: Vec, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct VerificationMethod { + pub id: String, + pub r#type: String, + pub controller: String, + pub publicKeyMultibase: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Service { + pub id: String, + pub r#type: String, + pub serviceEndpoint: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct AlsoKnownAs { +} + +#[derive(Serialize, Deserialize)] +pub struct Timeline { + pub feed: Vec +} +#[derive(Serialize, Deserialize)] +pub struct Session { + pub did: String, + pub email: String, + pub handle: String, +} +#[derive(Serialize, Deserialize)] +pub struct Follow { + pub follows: Vec, + pub cursor: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Notifications { + pub uri: String, + pub cid: String, + pub author: Author, + pub reason: String, + //pub reasonSubject: String, + pub record: Record, + pub isRead: bool, + pub indexedAt: String, + //pub labels: Labels, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Thread { + pub r#type: String, + pub post: String, + pub root: String, + pub author: Author, + pub reason: String, + //pub reasonSubject: String, + pub record: Record, + pub isRead: bool, + pub indexedAt: String, + //pub labels: Labels, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Author { + pub did: String, + //pub declaration: Declaration, + pub description: Option, + pub displayName: Option, + pub handle: String, + pub avatar: Option, + pub viewer: Viewer, + //pub labels: Labels, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Labels { + pub src: Option, + pub uri: Option, + pub cid: Option, + pub val: Option, + pub cts: Option, + pub neg: Option, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Declaration { + pub actorType: String, + pub cid: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Viewer { + pub muted: bool, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct Record { + pub text: Option, + pub createdAt: String, + pub reply: Option, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct Reply { + pub parent: ReplyParent, + pub root: ReplyRoot, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct ReplyRoot { + pub cid: String, + pub uri: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +#[derive(Debug)] +pub struct ReplyParent { + pub cid: String, + pub uri: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Langs { +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Feed { + pub post: Post, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Post { + pub did: Option, + pub uri: String, + pub cid: String, + pub collection: Option, + pub record: Record, + pub author: Author, + pub reason: Option, + pub indexedAt: String, + pub replyCount: i32, + pub postCount: Option, + pub repostCount: i32, + pub likeCount: i32, +} + +#[derive(Serialize, Deserialize)] +pub struct Cid { + pub cid: String +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Img { + pub blob: Blob +} + +#[derive(Serialize, Deserialize)] +pub struct Blob { + pub r#ref: Ref, +} + +#[derive(Serialize, Deserialize)] +pub struct Ref { + pub link: String, +} + +#[derive(Serialize, Deserialize)] +pub struct Handle { + pub handle: String +} + +//#[derive(Serialize, Deserialize)] +//pub struct Did { +// pub did: String +//} + +//#[derive(Serialize, Deserialize)] +//pub struct Labels { +//} +// +//#[derive(Serialize, Deserialize)] +//pub struct Viewer { +// pub muted: bool, +// pub blockedBy: bool, +//} +// +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct ProfileIdentityResolve { + pub did: String, +} + +#[derive(Serialize, Deserialize)] +#[allow(non_snake_case)] +pub struct Profile { + pub did: String, + pub handle: String, + pub followsCount: Option, + pub followersCount: Option, + pub postsCount: i32, + pub indexedAt: Option, + pub avatar: Option, + pub banner: Option, + pub displayName: Option, + pub description: Option, + pub viewer: Viewer, + pub labels: Labels, +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..c3d1718 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,168 @@ + +use seahorse::{App, Command, Context, Flag, FlagType}; +use std::env; + +use crate::ascii::c_ascii; +use crate::data::data_toml; +use crate::data::url; +use crate::data::w_cfg; +use data::Notify as Notify; + +pub mod data; +pub mod refresh; +pub mod token; +pub mod notify; +pub mod notify_read; +pub mod reply; +pub mod reply_link; +pub mod ascii; + +fn main() { + let args: Vec = env::args().collect(); + let app = App::new(env!("CARGO_PKG_NAME")) + .command( + Command::new("ai") + .alias("a") + .action(c_ascii_art) + .flag( + Flag::new("type", FlagType::Bool) + .description("type flag") + .alias("t"), + ) + ) + .command( + Command::new("token") + .alias("t") + .description("handle\n\t\t\t$ ai t syui.bsky.social -p password") + .action(c_token) + .flag( + Flag::new("password", FlagType::String) + .description("password flag") + .alias("p"), + ) + .flag( + Flag::new("server", FlagType::String) + .description("server flag") + .alias("s"), + ) + ) + .command( + Command::new("refresh") + .alias("r") + .action(c_refresh) + .flag( + Flag::new("server", FlagType::String) + .description("server flag") + .alias("s"), + ) + ) + .command( + Command::new("notify") + .alias("n") + .action(c_notify), + ) + .command( + Command::new("bot") + .alias("b") + .action(c_bot), + ) + ; + app.run(args); +} + +fn c_ascii_art(c: &Context) { + c_ascii(c.bool_flag("type")); +} + +fn token(c: &Context) { + let m = c.args[0].to_string(); + let h = async { + if let Ok(p) = c.string_flag("password") { + if let Ok(s) = c.string_flag("server") { + let res = token::post_request(m.to_string(), p.to_string(), s.to_string()).await; + w_cfg(&s, &res) + } else { + let res = token::post_request(m.to_string(), p.to_string(), "bsky.social".to_string()).await; + w_cfg(&"bsky.social", &res) + } + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn c_token(c: &Context) { + token(c); +} + +fn refresh(c: &Context) { + let h = async { + let res = refresh::post_request().await; + if let Ok(s) = c.string_flag("server") { + w_cfg(&s, &res) + } else { + w_cfg("bsky.social", &res) + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn c_refresh(c: &Context) { + refresh(c); +} + +fn notify() { + let h = async { + let j = notify::get_request(100).await; + println!("{}", j); + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn c_notify(_c: &Context) { + notify(); +} + +pub fn char_c(i: String) -> String { + let l = 250; + let mut s = String::new(); + for ii in i.chars().enumerate() { + match ii.0 { + n if n > l.try_into().unwrap() => {break} + _ => {s.push(ii.1)} + } + } + return s +} + +fn bot(_c: &Context) { + let h = async { + let notify = notify::get_request(100).await; + let notify: Notify = serde_json::from_str(¬ify).unwrap(); + let n = notify.notifications; + let length = &n.len(); + let reason = &n[0].reason; + let handle = &n[0].author.handle; + let did = &n[0].author.did; + let read = n[0].isRead; + let cid = &n[0].cid; + let uri = &n[0].uri; + println!("{}", length); + println!("{}", reason); + println!("{}", handle); + println!("{}", did); + println!("{}", read); + println!("{} {}", cid, uri); + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res + +} + +fn c_bot(c: &Context) { + loop { + bot(c); + } +} diff --git a/src/notify.rs b/src/notify.rs new file mode 100644 index 0000000..51afa7e --- /dev/null +++ b/src/notify.rs @@ -0,0 +1,31 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +//use serde_json::json; + +pub async fn get_request(limit: i32, ) -> String { + + let token = data_toml(&"access"); + let url = url(&"notify_list"); + + let client = reqwest::Client::new(); + let res = client + .get(url) + .query(&[("limit", limit)]) + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap(); + + let status_ref = res.error_for_status_ref(); + + match status_ref { + Ok(_) => { + return res.text().await.unwrap(); + }, + Err(_e) => { + let e = "err".to_string(); + return e + } + } +} diff --git a/src/notify_read.rs b/src/notify_read.rs new file mode 100644 index 0000000..ad97ac7 --- /dev/null +++ b/src/notify_read.rs @@ -0,0 +1,28 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; + +pub async fn post_request(time: String) -> String { + + let token = data_toml(&"access"); + let url = url(&"notify_update"); + + let post = Some(json!({ + "seenAt": time.to_string(), + })); + + let client = reqwest::Client::new(); + let res = client + .post(url) + .json(&post) + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +} diff --git a/src/refresh.rs b/src/refresh.rs new file mode 100644 index 0000000..5f5f13e --- /dev/null +++ b/src/refresh.rs @@ -0,0 +1,21 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; + +pub async fn post_request() -> String { + let refresh = data_toml(&"refresh"); + let url = url(&"session_refresh"); + + let client = reqwest::Client::new(); + let res = client + .post(url) + .header("Authorization", "Bearer ".to_owned() + &refresh) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +} diff --git a/src/reply.rs b/src/reply.rs new file mode 100644 index 0000000..478fbce --- /dev/null +++ b/src/reply.rs @@ -0,0 +1,53 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; +use iso8601_timestamp::Timestamp; + +pub async fn post_request(text: String, cid: String, uri: String, cid_p: String, uri_p: String) -> String { + + let token = data_toml(&"access"); + let did = data_toml(&"did"); + let handle = data_toml(&"handle"); + + let url = url(&"record_create"); + //let url = "https://bsky.social/xrpc/com.atproto.repo.createRecord"; + let col = "app.bsky.feed.post".to_string(); + + let d = Timestamp::now_utc(); + let d = d.to_string(); + + let post = Some(json!({ + "repo": handle.to_string(), + "did": did.to_string(), + "collection": col.to_string(), + "record": { + "text": text.to_string(), + "createdAt": d.to_string(), + "reply": { + "root": { + "cid": cid.to_string(), + "uri": uri.to_string() + }, + "parent": { + "cid": cid_p.to_string(), + "uri": uri_p.to_string() + } + } + }, + })); + + let client = reqwest::Client::new(); + let res = client + .post(url) + .json(&post) + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +} diff --git a/src/reply_link.rs b/src/reply_link.rs new file mode 100644 index 0000000..1bae66b --- /dev/null +++ b/src/reply_link.rs @@ -0,0 +1,66 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; +use iso8601_timestamp::Timestamp; + +pub async fn post_request(text: String, link: String, s: i32, e: i32, cid: String, uri: String, cid_b: String, uri_b: String) -> String { + + let token = data_toml(&"access"); + let did = data_toml(&"did"); + let handle = data_toml(&"handle"); + + let url = url(&"record_create"); + let col = "app.bsky.feed.post".to_string(); + + let d = Timestamp::now_utc(); + let d = d.to_string(); + + let post = Some(json!({ + "repo": handle.to_string(), + "did": did.to_string(), + "collection": col.to_string(), + "record": { + "text": link.to_string() + &" ".to_string() + &text.to_string(), + "createdAt": d.to_string(), + "reply": { + "root": { + "cid": cid.to_string(), + "uri": uri.to_string() + }, + "parent": { + "cid": cid_b.to_string(), + "uri": uri_b.to_string() + } + }, + "facets": [ + { + "index": { + "byteStart": s, + "byteEnd": e + }, + "features": [ + { + "$type": "app.bsky.richtext.facet#link", + "uri": link.to_string() + } + ] + } + ], + }, + })); + + let client = reqwest::Client::new(); + let res = client + .post(url) + .json(&post) + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +} diff --git a/src/token.rs b/src/token.rs new file mode 100644 index 0000000..4f0f715 --- /dev/null +++ b/src/token.rs @@ -0,0 +1,24 @@ +extern crate reqwest; +use std::collections::HashMap; + +pub async fn post_request(handle: String, pass: String, host: String) -> String { + + let url = "https://".to_owned() + &host.to_string() + &"/xrpc/com.atproto.server.createSession".to_string(); + + let mut map = HashMap::new(); + map.insert("identifier", &handle); + map.insert("password", &pass); + + let client = reqwest::Client::new(); + let res = client + .post(url) + .json(&map) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +}