diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dac5de9 --- /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..c590021 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,62 @@ ## ai `bot` + + +- name : ai bot +- base : [rust](https://www.rust-lang.org) +- host : [yui.syui.ai](https://bsky.app/profile/yui.syui.ai), [ai.syu.is](https://web.syu.is/profile/ai.syu.is) + +```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 +``` + +``` +$ ai n | jq . +``` + +### bot + +``` +$ ai bot +``` + +|command|type|body| +|---|---|---| +|@yui.syui.ai did|mention, reply| plc.directory/$did/log | + diff --git a/ai.zsh b/ai.zsh new file mode 100755 index 0000000..22db7d0 --- /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|rep) + 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..b7f65c3 --- /dev/null +++ b/scpt/env @@ -0,0 +1,19 @@ +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 + +if [ ! -d $d/json ];then + mkdir -p $d/json +fi diff --git a/scpt/notify.zsh b/scpt/notify.zsh new file mode 100644 index 0000000..8f5879f --- /dev/null +++ b/scpt/notify.zsh @@ -0,0 +1,37 @@ +function notify() { + url=https://$host/xrpc/app.bsky.notification.listNotifications + f=$d/json/notify.json + if [ ! -f $f ];then + curl -sL "Content-Type: application/json" -H "Authorization: Bearer $token" "$url?limit=100" >! $f + fi + + for ((i=0;i<=99;i++)) + do + echo "[$i]---" + cid=`cat $f|jq -r ".|.[].[$i]?|.cid?"` + uri=`cat $f|jq -r ".|.[].[$i]?|.uri?"` + echo cid: $cid + echo uri: $uri + cid_r=`cat $f|jq -r ".[]|.[$i]?|.record.reply.root.cid?"` + + if [ "$cid_r" = "null" ];then + continue + fi + uri_r=`cat $f|jq -r ".[]|.[$i]?|.record.reply.root.uri?"` + cid_p=`cat $f|jq -r ".[]|.[$i]?|.record.reply.parent.cid?"` + uri_p=`cat $f|jq -r ".[]|.[$i]?|.record.reply.parent.uri?"` + did_p=`echo $uri_p|cut -d / -f 3` + if [ "$did_p" != "did:plc:uqzpqmrjnptsxezjx4xuh2mn" ];then + continue + fi + echo cid_root: $cid_r + echo uri_root: $uri_r + echo cid_parent: $cid_p + echo uri_parent: $uri_p + echo --- + echo uri: $uri|sed "s#at://#https://bsky.app/profile/#g"|sed 's/app.bsky.feed.post/post/g' + echo uri_root: $uri_r|sed "s#at://#https://bsky.app/profile/#g"|sed 's/app.bsky.feed.post/post/g' + echo uri_parent: $uri_p|sed "s#at://#https://bsky.app/profile/#g"|sed 's/app.bsky.feed.post/post/g' + echo --- + 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..c0ebed4 --- /dev/null +++ b/scpt/reply.zsh @@ -0,0 +1,40 @@ +function reply() { + + #uri: https://bsky.app/profile/did:plc:4hqjfn7m6n5hno3doamuhgef/post/3kkumyv72w22o + #uri_root: https://bsky.app/profile/did:plc:uqzpqmrjnptsxezjx4xuh2mn/post/3kkumysfipk2p + #uri_parent: https://bsky.app/profile/did:plc:uqzpqmrjnptsxezjx4xuh2mn/post/3kkumysfipk2p + + cid=bafyreiaxz6hbqgylsxglqita73c5gzxzoatupgitd35rwjpd6dzpa4ctwi + uri=at://did:plc:4hqjfn7m6n5hno3doamuhgef/app.bsky.feed.post/3kkumyv72w22o + cid_root=bafyreiacxuk4ypaxbg7qxnmrvpnaej5o7azewqioelfgbuikp77jevy6hq + uri_root=at://did:plc:uqzpqmrjnptsxezjx4xuh2mn/app.bsky.feed.post/3kkumysfipk2p + cid_parent=bafyreiacxuk4ypaxbg7qxnmrvpnaej5o7azewqioelfgbuikp77jevy6hq + uri_parent=at://did:plc:uqzpqmrjnptsxezjx4xuh2mn/app.bsky.feed.post/3kkumysfipk2p + + 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_root\", + \"uri\": \"$uri_root\" + }, + \"parent\": { + \"cid\": \"$cid\", + \"uri\": \"$uri\" + } + } + } +}" + + echo $json|jq . + url=https://$host/xrpc/com.atproto.repo.createRecord + 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..7a8614f --- /dev/null +++ b/src/data.rs @@ -0,0 +1,499 @@ +use config::{Config, ConfigError, File}; +use serde_derive::{Deserialize, Serialize}; +use std::fs; +use std::io::Write; +use std::io::Read; +use std::fs::OpenOptions; +use std::path::Path; + +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, + } +} + +pub fn log_file(s: &str) -> String { + let file = "/.config/ai/txt/"; + let mut f = shellexpand::tilde("~").to_string(); + f.push_str(&file); + let path = Path::new(&f); + if path.is_dir() == false { + let _ = fs::create_dir_all(f.clone()); + } + match &*s { + "n1" => f + &"notify_cid.txt", + "n2" => f + &"notify_cid_run.txt", + _ => 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, + } +} + +#[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, +} + +pub fn c_char(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 +} + +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(); +} + +pub fn w_cid(cid :String, file: String, t: bool) -> bool { + let f = file; + let mut file = match OpenOptions::new() + .create(true) + .write(true) + .read(true) + .append(true) + .open(f.clone()) + { + Err(why) => panic!("Couldn't open {}: {}", f, why), + Ok(file) => file, + }; + let mut contents = String::new(); + + match file.read_to_string(&mut contents) { + Err(why) => panic!("Couldn't read {}: {}", f, why), + Ok(_) => (), + } + if contents.contains(&cid) == false { + if t { + let cid = cid + "\n"; + match file.write_all(cid.as_bytes()) { + Err(why) => panic!("Couldn't write \"{}\" to {}: {}", contents, f, why), + Ok(_) => println!("finished"), + } + } + let check = false; + return check + } else { + let check = true; + return check + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..f928050 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,217 @@ + +use seahorse::{App, Command, Context, Flag, FlagType}; +use std::env; + +use crate::ascii::c_ascii; +use crate::data::data_toml; +use crate::data::log_file; +use crate::data::url; +use crate::data::w_cfg; +use crate::data::w_cid; +use crate::data::c_char; +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 yui.syui.ai -p password\n\t\t\t$ ai t yui.syui.ai -p password -s bsky.social") + .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") + .description("refresh\n\t\t\t$ ai r\n\t\t\t$ ai r -s bsky.social") + .action(c_refresh) + .flag( + Flag::new("server", FlagType::String) + .description("server flag") + .alias("s"), + ) + ) + .command( + Command::new("notify") + .alias("n") + .description("notify\n\t\t\t$ ai n") + .action(c_notify), + ) + .command( + Command::new("bot") + .alias("b") + .description("bot\n\t\t\t$ ai b\n\t\t\t$ ai b -s bsky.social") + .action(c_bot) + .flag( + Flag::new("server", FlagType::String) + .description("server flag") + .alias("s"), + ) + ) + ; + 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(); +} + +fn bot(c: &Context) { + + let h = async { + let server = "bsky.social"; + let mut notify = notify::get_request(100).await; + if notify == "err" { + if let Ok(s) = c.string_flag("server") { + let res = refresh::post_request().await; + w_cfg(&s, &res); + } else { + let res = refresh::post_request().await; + w_cfg(&server, &res); + } + notify = notify::get_request(100).await; + } + let notify: Notify = serde_json::from_str(¬ify).unwrap(); + + let n = notify.notifications; + let length = &n.len(); + let su = 0..*length; + for i in su { + let reason = &n[i].reason; + let handle = &n[i].author.handle; + let did = &n[i].author.did; + let read = n[i].isRead; + let cid = &n[i].cid; + let uri = &n[i].uri; + let time = &n[i].indexedAt; + let mut cid_root = cid; + let mut uri_root = uri; + let check_cid = w_cid(cid.to_string(), log_file(&"n1"), false); + let check_cid_run = w_cid(cid.to_string(), log_file(&"n2"), false); + // thread + if ! n[i].record.reply.is_none() { + cid_root = &n[i].record.reply.as_ref().unwrap().root.cid; + uri_root = &n[i].record.reply.as_ref().unwrap().root.uri; + } + println!("{}", read); + println!("{}", handle); + println!("{} {}", cid, uri); + let mut text = ""; + if ! n[i].record.text.is_none() { + text = &n[i].record.text.as_ref().unwrap(); + } + let vec: Vec<&str> = text.split_whitespace().collect(); + let rep_com = &vec[0..].join(" "); + + if check_cid == false && { reason == "mention" || reason == "reply" } || check_cid_run == false && { reason == "mention" || reason == "reply" } { + w_cid(cid.to_string(), log_file(&"n2"), true); + if rep_com.contains("did") == true || rep_com.contains("/did") == true { + let link = "https://plc.directory/".to_owned() + &did + &"/log"; + let s = 0; + let e = link.chars().count(); + + let d = "\n".to_owned() + &did.to_string(); + let text_limit = c_char(d); + + if text_limit.len() > 3 { + let str_rep = reply_link::post_request(text_limit.to_string(), link.to_string(), s, e.try_into().unwrap(), cid.to_string(), uri.to_string(), cid_root.to_string(), uri_root.to_string()).await; + let str_notify = notify_read::post_request(time.to_string()).await; + + w_cid(cid.to_string(), log_file(&"n1"), true); + println!("{}", str_rep); + println!("{}", str_notify); + } + } + } + } + }; + 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..1e3fee6 --- /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_root: String, uri_root: 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_root.to_string(), + "uri": uri_root.to_string() + }, + "parent": { + "cid": cid.to_string(), + "uri": uri.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..1376e95 --- /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_root: String, uri_root: 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_root.to_string(), + "uri": uri_root.to_string() + }, + "parent": { + "cid": cid.to_string(), + "uri": uri.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 +}