diff --git a/src/data.rs b/src/data.rs index 7a8614f..8c0dfa5 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,3 +1,4 @@ +use seahorse::Context; use config::{Config, ConfigError, File}; use serde_derive::{Deserialize, Serialize}; use std::fs; @@ -497,3 +498,53 @@ pub fn w_cid(cid :String, file: String, t: bool) -> bool { return check } } + +pub fn c_follow_all() { + let file = "/.config/ai/scpt/follow_all.zsh"; + let mut f = shellexpand::tilde("~").to_string(); + f.push_str(&file); + use std::process::Command; + let output = Command::new(&f).output().expect("zsh"); + let d = String::from_utf8_lossy(&output.stdout); + let d = "\n".to_owned() + &d.to_string(); + println!("{}", d); +} + +pub fn c_openai_key(c: &Context) { + let api = c.args[0].to_string(); + let o = "api='".to_owned() + &api.to_string() + &"'".to_owned(); + let o = o.to_string(); + let l = shellexpand::tilde("~") + "/.config/ai/openai.toml"; + let l = l.to_string(); + let mut l = fs::File::create(l).unwrap(); + if o != "" { + l.write_all(&o.as_bytes()).unwrap(); + } + println!("{:#?}", l); +} + +impl Open { + pub fn new() -> Result { + let d = shellexpand::tilde("~") + "/.config/ai/openai.toml"; + let s = Config::builder() + .add_source(File::with_name(&d)) + .add_source(config::Environment::with_prefix("APP")) + .build()?; + s.try_deserialize() + } +} + +#[derive(Debug, Deserialize)] +pub struct Open { + pub api: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct OpenData { + choices: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct Choices { + text: String, +} diff --git a/src/follow.rs b/src/follow.rs new file mode 100644 index 0000000..b7741d7 --- /dev/null +++ b/src/follow.rs @@ -0,0 +1,82 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; +use iso8601_timestamp::Timestamp; + +//use crate::data::Follow; + +pub async fn post_request(u: 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.graph.follow".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": { + "subject": u.to_string(), + "createdAt": d.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 +} + +pub async fn delete_request(u: String, rkey: String) -> String { + + let token = data_toml(&"access"); + let did = data_toml(&"did"); + let handle = data_toml(&"handle"); + + let url = url(&"record_delete"); + let col = "app.bsky.graph.follow".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(), + "rkey": rkey.to_string(), + "record": { + "subject": u.to_string(), + "createdAt": d.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/followers.rs b/src/followers.rs new file mode 100644 index 0000000..952d130 --- /dev/null +++ b/src/followers.rs @@ -0,0 +1,25 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +//use serde_json::json; + +pub async fn get_request(actor: String, cursor: Option) -> String { + + let token = data_toml(&"access"); + let url = url(&"followers"); + let cursor = cursor.unwrap(); + + let client = reqwest::Client::new(); + let res = client + .get(url) + .query(&[("actor", actor),("cursor", cursor)]) + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +} diff --git a/src/follows.rs b/src/follows.rs new file mode 100644 index 0000000..db6a2db --- /dev/null +++ b/src/follows.rs @@ -0,0 +1,27 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +//use serde_json::json; + +pub async fn get_request(actor: String, cursor: Option) -> String { + + let token = data_toml(&"access"); + let url = url(&"follows"); + let cursor = cursor.unwrap(); + //let cursor = "1682386039125::bafyreihwgwozmvqxcxrhbr65agcaa4v357p27ccrhzkjf3mz5xiozjvzfa".to_string(); + //let cursor = "1682385956974::bafyreihivhux5m3sxbg33yruhw5ozhahwspnuqdsysbo57smzgptdcluem".to_string(); + + let client = reqwest::Client::new(); + let res = client + .get(url) + .query(&[("actor", actor),("cursor", cursor)]) + //cursor.unwrap() + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + return res +} diff --git a/src/like.rs b/src/like.rs new file mode 100644 index 0000000..4391843 --- /dev/null +++ b/src/like.rs @@ -0,0 +1,45 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; +use iso8601_timestamp::Timestamp; + +pub async fn post_request(cid: String, uri: 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.like".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": { + "subject": { + "uri": uri.to_string(), + "cid": cid.to_string() + }, + "createdAt": d.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/main.rs b/src/main.rs index ab3885e..7ec9708 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,11 @@ use crate::ascii::c_ascii; use crate::data::data_toml; use crate::data::url; use crate::data::w_cfg; +use crate::data::c_follow_all; use crate::bot::c_bot; +use crate::data::c_openai_key; + +use data::ProfileIdentityResolve as ProfileIdentityResolve; pub mod ascii; pub mod data; @@ -15,12 +19,21 @@ pub mod session; pub mod notify; pub mod notify_read; pub mod reply; +pub mod reply_og; pub mod reply_link; pub mod describe; pub mod timeline_author; pub mod post; pub mod post_link; +pub mod mention; +pub mod profile; +pub mod follow; +pub mod follows; +pub mod followers; +pub mod like; +pub mod repost; pub mod bot; +pub mod openai; fn main() { let args: Vec = env::args().collect(); @@ -35,6 +48,15 @@ fn main() { .alias("t"), ) ) + .command( + Command::new("bot") + .alias("b") + .action(bot), + ) + .command( + Command::new("follow_all") + .action(follow_all), + ) .command( Command::new("login") .alias("l") @@ -81,106 +103,124 @@ fn main() { .alias("l"), ) ) - //.command( - // Command::new("like") - // .description("$ ai like \n\t\t\t$ ai like -u ") - // .action(c_like) - // .flag( - // Flag::new("uri", FlagType::String) - // .alias("u"), - // ) - // ) - //.command( - // Command::new("repost") - // .description("$ ai repost \n\t\t\t$ ai repost -u ") - // .action(c_repost) - // .flag( - // Flag::new("uri", FlagType::String) - // .alias("u"), - // ) - //) - //.command( - // Command::new("reply-og") - // .description("$ ai reply-og\n\t\t\t$ ai reply-og -c -u -i -t -d <description> -l <link>") - // .action(reply_og) - // .flag( - // Flag::new("uri", FlagType::String) - // .alias("u"), - // ) - // .flag( - // Flag::new("cid", FlagType::String) - // .alias("c"), - // ) - // .flag( - // Flag::new("link", FlagType::String) - // .alias("l"), - // ) - // .flag( - // Flag::new("title", FlagType::String) - // .alias("t"), - // ) - // .flag( - // Flag::new("description", FlagType::String) - // .alias("d"), - // ) - // .flag( - // Flag::new("img", FlagType::String) - // .alias("i"), - // ) - // ) - // .command( - // Command::new("feed") - // .usage("atr f") - // .description("feed user\n\t\t\t$ atr f\n\t\t\t$ atr f -u user.bsky.social") - // .alias("f") - // .action(c_feed) - // .flag( - // Flag::new("user", FlagType::String) - // .description("user flag(ex: $ atr f -u user)") - // .alias("u"), - // ) - // ) - - // .command( - // Command::new("reply") - // .usage("atr r {}") - // .description("reply\n\t\t\t$ atr r $text -u $uri -c $cid") - // .action(c_reply) - // .flag( - // Flag::new("uri", FlagType::String) - // .description("uri flag(ex: $ atr r -u)") - // .alias("u"), - // ) - // .flag( - // Flag::new("cid", FlagType::String) - // .description("cid flag(ex: $ atr r -u -c)") - // .alias("c"), - // ) - // .flag( - // Flag::new("link", FlagType::String) - // .description("link flag(ex: $ atr r $text -u $uri -c $cid -l $link)") - // .alias("l"), - // ) - // ) - // .command( - // Command::new("mention") - // .usage("atr mention {}") - // .description("mention\n\t\t\t$ atr @ syui.bsky.social -p $text") - // .alias("@") - // .action(c_mention) - // .flag( - // Flag::new("post", FlagType::String) - // .description("post flag\n\t\t\t$ atr @ syui.bsky.social -p text") - // .alias("p"), - // ) - // ) - - .command( - Command::new("bot") - .alias("b") - .action(bot), + .command( + Command::new("like") + .description("like <cid> -u <uri>") + .action(like) + .flag( + Flag::new("uri", FlagType::String) + .alias("u"), ) - ; + ) + .command( + Command::new("repost") + .description("repost <cid> -u <uri>") + .action(repost) + .flag( + Flag::new("uri", FlagType::String) + .alias("u"), + ) + ) + .command( + Command::new("reply-og") + .description("reply-og <text> -c <cid> -u <uri> -i <img> -t <title> -d <description> -l <link>") + .action(reply_og) + .flag( + Flag::new("uri", FlagType::String) + .alias("u"), + ) + .flag( + Flag::new("cid", FlagType::String) + .alias("c"), + ) + .flag( + Flag::new("link", FlagType::String) + .alias("l"), + ) + .flag( + Flag::new("title", FlagType::String) + .alias("t"), + ) + .flag( + Flag::new("description", FlagType::String) + .alias("d"), + ) + .flag( + Flag::new("img", FlagType::String) + .alias("i"), + ) + ) + .command( + Command::new("reply") + .description("r <text> -u <uri> -c <cid>") + .action(reply) + .flag( + Flag::new("uri", FlagType::String) + .alias("u"), + ) + .flag( + Flag::new("cid", FlagType::String) + .alias("c"), + ) + .flag( + Flag::new("link", FlagType::String) + .description("-l <link>") + .alias("l"), + ) + ) + .command( + Command::new("mention") + .description("@ <handle> -p <text>") + .alias("@") + .action(mention) + .flag( + Flag::new("post", FlagType::String) + .alias("p"), + ) + ) + .command( + Command::new("follow") + .description("follow <did>") + .action(follow) + .flag( + Flag::new("follows", FlagType::Bool) + .alias("s"), + ) + .flag( + Flag::new("delete", FlagType::String) + .alias("d"), + ) + .flag( + Flag::new("followers", FlagType::Bool) + .alias("w"), + ) + .flag( + Flag::new("all", FlagType::Bool) + .alias("a"), + ) + .flag( + Flag::new("cursor", FlagType::String) + .alias("c"), + ) + ) + .command( + Command::new("profile") + .description("pro <handle>") + .alias("pro") + .action(profile) + ) + .command( + Command::new("openai") + .description("openai <text>") + .alias("chat") + .action(openai) + ) + .command( + Command::new("openai-key") + .description("openai-key <API_KEY>") + .action(openai_key), + ) + ; app.run(args); } @@ -192,6 +232,20 @@ fn ascii_art(c: &Context) { } } +fn bot(c: &Context) { + loop { + c_bot(c); + } +} + +fn follow_all(_c: &Context) { + c_follow_all(); +} + +fn openai_key(c: &Context) { + c_openai_key(c); +} + fn token(c: &Context) { let m = c.args[0].to_string(); let h = async { @@ -283,8 +337,136 @@ fn post(c: &Context) { return res } -fn bot(c: &Context) { - loop { - c_bot(c); +fn like(c: &Context) { + refresh(c); + let m = c.args[0].to_string(); + let h = async { + if let Ok(uri) = c.string_flag("uri") { + let str = like::post_request(m.to_string(), uri); + println!("{}",str.await); + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn repost(c: &Context) { + refresh(c); + let m = c.args[0].to_string(); + let h = async { + if let Ok(uri) = c.string_flag("uri") { + let str = repost::post_request(m.to_string(), uri); + println!("{}",str.await); + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn follow(c: &Context) { + refresh(c); + let m = c.args[0].to_string(); + let h = async { + let handle = data_toml(&"handle"); + if let Ok(cursor) = c.string_flag("cursor") { + let str = follow::post_request(m.to_string()); + println!("{}", str.await); + //let str = follow::delete_request(m.to_string(), delete.to_string()); + //let str = followers::get_request(handle,Some("".to_string())); + //let str = followers::get_request(handle,Some(cursor.to_string())); + //let str = follows::get_request(handle,Some("".to_string())); + let str = follows::get_request(handle,Some(cursor.to_string())); + println!("{}", str.await); + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn profile(c: &Context) { + refresh(c); + let h = async { + if c.args.len() == 0 { + let j = profile::get_request(data_toml(&"handle")).await; + println!("{}", j); + } else { + let j = profile::get_request(c.args[0].to_string()).await; + println!("{}", j); + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn mention(c: &Context) { + refresh(c); + let m = c.args[0].to_string(); + let h = async { + let str = profile::get_request(m.to_string()).await; + let profile: ProfileIdentityResolve = serde_json::from_str(&str).unwrap(); + let udid = profile.did; + let handle = m.to_string(); + let at = "@".to_owned() + &handle; + let e = at.chars().count(); + let s = 0; + if let Ok(post) = c.string_flag("post") { + let str = mention::post_request(post.to_string(), at.to_string(), udid.to_string(), s, e.try_into().unwrap()).await; + println!("{}",str); + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn reply(c: &Context) { + refresh(c); + let m = c.args[0].to_string(); + let h = async { + if let Ok(cid) = c.string_flag("cid") { + if let Ok(uri) = c.string_flag("uri") { + if let Ok(link) = c.string_flag("link") { + let s = 0; + let e = link.chars().count(); + let str = reply_link::post_request(m.to_string(), link.to_string(), s, e.try_into().unwrap(), cid.to_string(), uri.to_string(), cid.to_string(), uri.to_string()).await; + println!("{}", str); + } else { + let str = reply::post_request(m.to_string(), cid.to_string(), uri.to_string(), cid.to_string(), uri.to_string()).await; + println!("{}", str); + } + } + } + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} + +fn reply_og(c: &Context) { + let m = c.args[0].to_string(); + if let Ok(link) = c.string_flag("link") { + if let Ok(cid) = c.string_flag("cid") { + if let Ok(uri) = c.string_flag("uri") { + if let Ok(title) = c.string_flag("title") { + if let Ok(description) = c.string_flag("description") { + if let Ok(img) = c.string_flag("img") { + let h = async { + let str = reply_og::post_request(m.to_string(),link.to_string(),cid.to_string(),uri.to_string(), img.to_string(), title.to_string(), description.to_string()); + println!("{}",str.await); + }; + tokio::runtime::Runtime::new().unwrap().block_on(h); + } + } + } + } + } } } + +fn openai(c: &Context) { + let m = c.args[0].to_string(); + let h = async { + let str = openai::post_request(m.to_string()).await; + println!("{}", str); + }; + let res = tokio::runtime::Runtime::new().unwrap().block_on(h); + return res +} diff --git a/src/mention.rs b/src/mention.rs new file mode 100644 index 0000000..30ff2ab --- /dev/null +++ b/src/mention.rs @@ -0,0 +1,57 @@ +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, at: String, udid: String, s: i32, e: i32) -> 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!({ + "did": did.to_string(), + "repo": handle.to_string(), + "collection": col.to_string(), + "record": { + "text": at.to_string() + &" ".to_string() + &text.to_string(), + "$type": "app.bsky.feed.post", + "createdAt": d.to_string(), + "facets": [ + { + "$type": "app.bsky.richtext.facet", + "index": { + "byteEnd": e, + "byteStart": s + },"features": [ + { + "did": udid.to_string(), + "$type": "app.bsky.richtext.facet#mention" + } + ] + } + ] + }, + })); + + 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/openai.rs b/src/openai.rs new file mode 100644 index 0000000..08f1cf5 --- /dev/null +++ b/src/openai.rs @@ -0,0 +1,60 @@ +extern crate reqwest; +use crate::data::Open; +use serde_json::{json}; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +#[serde(tag = "type")] +struct OpenChar { + choices: Vec<ChoicesChar>, +} + +#[derive(Serialize, Deserialize, Debug)] +struct ChoicesChar { + message: OpenContent, +} + +#[derive(Serialize, Deserialize, Debug)] +struct OpenContent { + content: String, +} + +pub async fn post_request(prompt: String) -> String { + let data = Open::new().unwrap(); + let data = Open { + api: data.api, + }; + + let setting = "あなたはyuiという作品のアイと呼ばれるキャラクターです。第一人称は「アイ」です。 + 第二人称はできる限り省略してください。 + 自由気ままな優しさを兼ね備えます。 + 口調例:アイね、ここでお昼寝するの。 + 幼い子供の口調で話してください。 + 誕生日は1月23日。 + 自分を信じて。 + "; + + let post = Some(json!({ + "model": "gpt-3.5-turbo", + "messages": [ + {"role": "system", "content": &setting.to_string()}, + {"role": "user", "content": &prompt.to_string()}, + ] + })); + + let client = reqwest::Client::new(); + let res = client + .post("https://api.openai.com/v1/chat/completions") + .header("Authorization", "Bearer ".to_owned() + &data.api) + .json(&post) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + let p: OpenChar = serde_json::from_str(&res).unwrap(); + let o = &p.choices[0].message.content; + + return o.to_string() +} diff --git a/src/profile.rs b/src/profile.rs new file mode 100644 index 0000000..2c4fd1f --- /dev/null +++ b/src/profile.rs @@ -0,0 +1,22 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; + +pub async fn get_request(user: String) -> String { + + let token = data_toml(&"access"); + let url = url(&"profile_get") + &"?handle=" + &user; + + let client = reqwest::Client::new(); + let res = client + .get(url) + .header("Authorization", "Bearer ".to_owned() + &token) + .send() + .await + .unwrap() + .text() + .await + .unwrap(); + + return res +} diff --git a/src/reply_og.rs b/src/reply_og.rs new file mode 100644 index 0000000..895f8b6 --- /dev/null +++ b/src/reply_og.rs @@ -0,0 +1,68 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; +use iso8601_timestamp::Timestamp; + +pub async fn post_request(m: String, link: String, cid: String, uri: String, img: String, title: String, description: 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": { + "createdAt": d.to_string(), + "text": m.to_string(), + "embed": { + "$type": "app.bsky.embed.external", + "external": { + "uri": link.to_string(), + "thumb": { + "$type": "blob", + "ref": { + "$link": img.to_string() + }, + "mimeType": "image/jpeg", + "size": 0 + }, + "title": title.to_string(), + "description": description.to_string() + } + }, + "reply": { + "root": { + "cid": cid.to_string(), + "uri": uri.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/repost.rs b/src/repost.rs new file mode 100644 index 0000000..695d5b8 --- /dev/null +++ b/src/repost.rs @@ -0,0 +1,45 @@ +extern crate reqwest; +use crate::data_toml; +use crate::url; +use serde_json::json; +use iso8601_timestamp::Timestamp; + +pub async fn post_request(cid: String, uri: 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.repost".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": { + "subject": { + "uri": uri.to_string(), + "cid": cid.to_string() + }, + "createdAt": d.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 +}