1
0

Compare commits

...

4 Commits

Author SHA1 Message Date
564ecad29f
fix 2024-02-08 15:36:52 +09:00
09930024cb
add scpt notify 2024-02-08 15:33:51 +09:00
ebe7c97d69
test bot 2024-02-07 13:48:22 +09:00
4b13aa48a3
fix host 2024-02-07 12:11:56 +09:00
14 changed files with 561 additions and 16 deletions

2
.gitignore vendored
View File

@ -3,4 +3,4 @@ target
*.json *.json
*.DS_Store *.DS_Store
**.DS_Store **.DS_Store
scpt/*.json

View File

@ -15,3 +15,4 @@ serde_derive = "*"
url = { version = "2.0", features = ["serde"] } url = { version = "2.0", features = ["serde"] }
rustc-serialize = "*" rustc-serialize = "*"
toml = "*" toml = "*"
iso8601-timestamp = "*"

View File

@ -14,15 +14,37 @@ $ ./target/debug/ai ai -t
### login ### login
```sh ```sh
# ai t $handle -p $password # ai token $handle -p $password
$ ai t yui.syui.ai -p password $ ai t yui.syui.ai -p password
$ cat ~/.config/ai/token.toml $ cat ~/.config/ai/token.toml
``` ```
```sh
# ai token $handle -p $password -s $server
$ ai t ai.syu.is -p password -s syu.is
```
### refresh ### refresh
``` ```
$ ai r $ ai r
``` ```
```
# server: syu.is
$ ai r -s syu.is
```
### notify
```
$ ai n
```
### bot
```
$ ai bot
```

27
ai.zsh Executable file
View File

@ -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

15
scpt/env Normal file
View File

@ -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

27
scpt/notify.zsh Normal file
View File

@ -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
}

11
scpt/refresh.zsh Executable file
View File

@ -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
}

29
scpt/reply.zsh Executable file
View File

@ -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`
}

6
scpt/token.zsh Executable file
View File

@ -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
}

View File

@ -185,3 +185,251 @@ pub fn w_cfg(h: &str, res: &str) {
ff.write_all(&toml.as_bytes()).unwrap(); ff.write_all(&toml.as_bytes()).unwrap();
} }
#[derive(Serialize, Deserialize)]
pub struct Notify {
pub notifications: Vec<Notifications>
}
#[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<VerificationMethod>,
pub service: Vec<Service>,
pub id: String,
pub alsoKnownAs: Vec<AlsoKnownAs>,
}
#[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<Feed>
}
#[derive(Serialize, Deserialize)]
pub struct Session {
pub did: String,
pub email: String,
pub handle: String,
}
#[derive(Serialize, Deserialize)]
pub struct Follow {
pub follows: Vec<Author>,
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<String>,
pub displayName: Option<String>,
pub handle: String,
pub avatar: Option<String>,
pub viewer: Viewer,
//pub labels: Labels,
}
#[derive(Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Labels {
pub src: Option<String>,
pub uri: Option<String>,
pub cid: Option<String>,
pub val: Option<String>,
pub cts: Option<String>,
pub neg: Option<bool>,
}
#[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<String>,
pub createdAt: String,
pub reply: Option<Reply>,
}
#[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<String>,
pub uri: String,
pub cid: String,
pub collection: Option<String>,
pub record: Record,
pub author: Author,
pub reason: Option<String>,
pub indexedAt: String,
pub replyCount: i32,
pub postCount: Option<i32>,
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<i32>,
pub followersCount: Option<i32>,
pub postsCount: i32,
pub indexedAt: Option<String>,
pub avatar: Option<String>,
pub banner: Option<String>,
pub displayName: Option<String>,
pub description: Option<String>,
pub viewer: Viewer,
pub labels: Labels,
}

View File

@ -6,11 +6,15 @@ use crate::ascii::c_ascii;
use crate::data::data_toml; use crate::data::data_toml;
use crate::data::url; use crate::data::url;
use crate::data::w_cfg; use crate::data::w_cfg;
use data::Notify as Notify;
pub mod data; pub mod data;
pub mod refresh; pub mod refresh;
pub mod token; pub mod token;
pub mod notify; pub mod notify;
pub mod notify_read;
pub mod reply;
pub mod reply_link;
pub mod ascii; pub mod ascii;
fn main() { fn main() {
@ -36,17 +40,32 @@ fn main() {
.description("password flag") .description("password flag")
.alias("p"), .alias("p"),
) )
.flag(
Flag::new("server", FlagType::String)
.description("server flag")
.alias("s"),
)
) )
.command( .command(
Command::new("refresh") Command::new("refresh")
.alias("r") .alias("r")
.action(c_refresh), .action(c_refresh)
.flag(
Flag::new("server", FlagType::String)
.description("server flag")
.alias("s"),
)
) )
.command( .command(
Command::new("notify") Command::new("notify")
.alias("n") .alias("n")
.action(c_notify), .action(c_notify),
) )
.command(
Command::new("bot")
.alias("b")
.action(c_bot),
)
; ;
app.run(args); app.run(args);
} }
@ -59,9 +78,13 @@ fn token(c: &Context) {
let m = c.args[0].to_string(); let m = c.args[0].to_string();
let h = async { let h = async {
if let Ok(p) = c.string_flag("password") { if let Ok(p) = c.string_flag("password") {
let res = token::post_request(m.to_string(), p.to_string()).await; if let Ok(s) = c.string_flag("server") {
println!("{}", res); let res = token::post_request(m.to_string(), p.to_string(), s.to_string()).await;
w_cfg("bsky.social", &res) 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); let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
@ -72,22 +95,23 @@ fn c_token(c: &Context) {
token(c); token(c);
} }
fn refresh() { fn refresh(c: &Context) {
let h = async { let h = async {
let res = refresh::post_request().await; let res = refresh::post_request().await;
println!("{}", res); if let Ok(s) = c.string_flag("server") {
w_cfg(&s, &res)
} else {
w_cfg("bsky.social", &res) w_cfg("bsky.social", &res)
}
}; };
let res = tokio::runtime::Runtime::new().unwrap().block_on(h); let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res return res
} }
fn c_refresh(_c: &Context) { fn c_refresh(c: &Context) {
refresh(); refresh(c);
} }
fn notify() { fn notify() {
let h = async { let h = async {
let j = notify::get_request(100).await; let j = notify::get_request(100).await;
@ -100,3 +124,45 @@ fn notify() {
fn c_notify(_c: &Context) { fn c_notify(_c: &Context) {
notify(); 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(&notify).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);
}
}

28
src/notify_read.rs Normal file
View File

@ -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
}

66
src/reply_link.rs Normal file
View File

@ -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
}

View File

@ -1,10 +1,9 @@
extern crate reqwest; extern crate reqwest;
use std::collections::HashMap; use std::collections::HashMap;
use crate::url;
pub async fn post_request(handle: String, pass: String) -> String { pub async fn post_request(handle: String, pass: String, host: String) -> String {
let url = url(&"session_create"); let url = "https://".to_owned() + &host.to_string() + &"/xrpc/com.atproto.server.createSession".to_string();
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("identifier", &handle); map.insert("identifier", &handle);