diff --git a/Cargo.toml b/Cargo.toml index 0550884..ab75886 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,3 +15,4 @@ serde_derive = "*" url = { version = "2.0", features = ["serde"] } rustc-serialize = "*" toml = "*" +iso8601-timestamp = "*" diff --git a/README.md b/README.md index a58b53e..336b472 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,31 @@ $ ./target/debug/ai ai -t ### login ```sh -# ai t $handle -p $password +# 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 +``` + diff --git a/src/data.rs b/src/data.rs index c9bebeb..d40b729 100644 --- a/src/data.rs +++ b/src/data.rs @@ -185,3 +185,251 @@ pub fn w_cfg(h: &str, res: &str) { 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 index 5a8701c..c3d1718 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,11 +6,15 @@ 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() { @@ -36,17 +40,32 @@ fn main() { .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), + .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); } @@ -59,9 +78,13 @@ fn token(c: &Context) { let m = c.args[0].to_string(); let h = async { if let Ok(p) = c.string_flag("password") { - let res = token::post_request(m.to_string(), p.to_string()).await; - println!("{}", res); - w_cfg("bsky.social", &res) + 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); @@ -72,22 +95,23 @@ fn c_token(c: &Context) { token(c); } -fn refresh() { +fn refresh(c: &Context) { let h = async { let res = refresh::post_request().await; - println!("{}", res); - w_cfg("bsky.social", &res) + 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(); +fn c_refresh(c: &Context) { + refresh(c); } - - fn notify() { let h = async { let j = notify::get_request(100).await; @@ -100,3 +124,45 @@ fn notify() { 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_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/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 index 2b35127..4f0f715 100644 --- a/src/token.rs +++ b/src/token.rs @@ -1,10 +1,9 @@ extern crate reqwest; 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(); map.insert("identifier", &handle);