ai/bot
ai
/
bot
1
0
Fork 0

v0.1
Gitea Actions Demo / Explore-Gitea-Actions (push) Successful in 9s Details

This commit is contained in:
syui 2023-10-20 23:31:16 +09:00
parent 430b5977d8
commit 7b03adda1f
Signed by: syui
GPG Key ID: 5417CFEBAD92DF56
53 changed files with 3466 additions and 1 deletions

1
.config/ai/scpt Submodule

@ -0,0 +1 @@
Subproject commit bd22a134aa736b2f32d414a88f81bdfa084587ec

0
.config/keep Normal file
View File

View File

@ -0,0 +1,18 @@
name: Gitea Actions Demo
run-name: ${{ gitea.actor }} is testing out Gitea Actions 🚀
on: [push]
jobs:
Explore-Gitea-Actions:
runs-on: ubuntu-latest
steps:
- run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
- run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
- run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
- name: Check out repository code
uses: actions/checkout@v3
- run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
- run: echo "🖥️ The workflow is now ready to test your code on the runner."
- name: List files in the repository
run: |
ls ${{ gitea.workspace }}
- run: echo "🍏 This job's status is ${{ gitea.status }}."

14
.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
Cargo.lock
target
*.json
*.DS_Store
**.DS_Store
scpt/json/
.config/ai/*.toml
.config/ai/*.json
.config/ai/txt
.config/ai/png
.ssh/*.key
.ssh/*.pub
.ssh/*config
.env

3
.gitmodules vendored Normal file
View File

@ -0,0 +1,3 @@
[submodule "bot_scpt"]
path = .config/ai/scpt
url = git@git.syui.ai:ai/bot_scpt.git

0
.ssh/keep Normal file
View File

19
Cargo.toml Normal file
View File

@ -0,0 +1,19 @@
[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 = "*"
sysinfo = "*"

8
Dockerfile Normal file
View File

@ -0,0 +1,8 @@
FROM syui/aios
ADD .ssh /root/.ssh
WORKDIR /root
ADD ./test/entrypoint.sh .
RUN chmod +x /root/entrypoint.sh
ENTRYPOINT ["/root/entrypoint.sh"]

25
Makefile.toml Normal file
View File

@ -0,0 +1,25 @@
[tasks.format]
install_crate = "rustfmt"
command = "cargo"
args = ["fmt", "--", "--emit=files"]
[tasks.clean]
command = "cargo"
args = ["clean"]
[tasks.build]
command = "cargo"
args = ["build"]
dependencies = ["clean"]
[tasks.test]
command = "cargo"
args = ["test"]
dependencies = ["clean"]
[tasks.my-flow]
dependencies = [
"format",
"build",
"test"
]

102
README.md
View File

@ -1,6 +1,106 @@
## ai `bot`
<img src="./icon/avatar.png" width="100">
- name : ai bot
- base : [ai os](https://git.syui.ai/ai/os)
- 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
$ docker run -it syui/aios ai
```
### build
```sh
$ cargo build
$ ./target/debug/ai
$ ./target/debug/ai ai
```
```sh
$ ai ai -t avatar
```
### login
```sh
# ai login $handle -p $password
$ ai l yui.syui.ai -p password
$ cat ~/.config/ai/token.toml
```
```sh
# ai l $handle -p $password -s $server
$ ai l ai.syu.is -p password -s syu.is
```
### refresh
```
$ ai r
```
### notify
```
$ ai n
```
```
$ ai n | jq .
```
### bot
```
$ ai bot
```
|command|sub|type|link|auth|
|---|---|---|---|---|
|@yui.syui.ai did||mention, reply| [plc.directory](https://plc.directory)/$did/log |user|
|@yui.syui.ai card|r, s, b|mention, reply| [card.syui.ai](https://card.syui.ai) |user|
|@yui.syui.ai ten|start, d, p, coin|mention, reply| [card.syui.ai](https://card.syui.ai) |user|
|@yui.syui.ai fav|{cid}|mention, reply| [card.syui.ai](https://card.syui.ai) |user|
|@yui.syui.ai egg|{password}|mention, reply| [card.syui.ai](https://card.syui.ai) |user|
|@yui.syui.ai 占い||mention, reply| [yui.syui.ai](https://yui.syui.ai) |user|
|@yui.syui.ai nyan|🍰|mention, reply| [yui.syui.ai](https://yui.syui.ai) |user|
|@yui.syui.ai diffusers|{keyword}|mention, reply| [huggingface.co/diffusers](https://huggingface.co/docs/diffusers/index) |user|
|@yui.syui.ai sh|{command}|mention, reply| [archlinux.org](https://wiki.archlinux.org/title/Systemd-nspawn) |admin|
### test
`zsh`
```sh
$ ./test/ai.zsh t
```
### make
```sh
$ cargo install --force cargo-make
$ cargo make build
```
### docker
> .env
```sh
HANDLE=ai.syu.is
PASSWORD=xxx
HOST=syu.is
ADMIN=syui.syu.is
```
```sh
$ docker compose build
$ docker compose up -d
```

11
compose.yml Normal file
View File

@ -0,0 +1,11 @@
services:
aios:
#image: syui/aios
#command: ai bot -a syui.syu.is
build:
context: .
restart: always
env_file:
- .env
volumes:
- ./.config:/root/.config

52
docs/wiki.md Normal file
View File

@ -0,0 +1,52 @@
### docker
```sh
$ docker run -it syui/aios ai
$ docker run -it -d syui/aios zsh -c "ai login <handle> -p <password> && ai bot"
```
```sh
$ cp -rf ~/.config/ai ./.config/
$ docker compose up
```
### cron
```sh
$ sudo pacman -S fcron
$ fcrontab -e
* * * * * $HOME/bot/test/ai.zsh c
```
### ssh
```sh
$ ssh-keygen -f /.ssh/diffusers.key -t ed25519
```
```sh
FROM syui/aios
ADD .ssh /root/.ssh
```
```sh
Host diffusers
HostName localhost
User root
IdentityFile ~/.ssh/diffusers.key
StrictHostKeyChecking no
UserKnownHostsFile /dev/null
```
```sh
services:
aios:
#image: syui/aios
build:
context: .
restart: always
volumes:
- ./.config:/root/.config
command: ai bot -a syui.syu.is
```

BIN
icon/ai.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
icon/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 557 KiB

BIN
icon/bot_scpt.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 433 KiB

57
k8s/aios-deployment.yaml Normal file
View File

@ -0,0 +1,57 @@
apiVersion: apps/v1
kind: Deployment
metadata:
annotations:
kompose.cmd: kompose convert -f ../compose.yml --volumes hostPath
kompose.version: 1.32.0 (HEAD)
labels:
io.kompose.service: aios
name: aios
spec:
replicas: 1
selector:
matchLabels:
io.kompose.service: aios
strategy:
type: Recreate
template:
metadata:
annotations:
kompose.cmd: kompose convert -f ../compose.yml --volumes hostPath
kompose.version: 1.32.0 (HEAD)
labels:
io.kompose.network/bot-default: "true"
io.kompose.service: aios
spec:
containers:
- env:
- name: ADMIN
valueFrom:
configMapKeyRef:
key: ADMIN
name: env
- name: HANDLE
valueFrom:
configMapKeyRef:
key: HANDLE
name: env
- name: HOST
valueFrom:
configMapKeyRef:
key: HOST
name: env
- name: PASSWORD
valueFrom:
configMapKeyRef:
key: PASSWORD
name: env
image: aios
name: aios
volumeMounts:
- mountPath: /root/.config
name: aios-hostpath0
restartPolicy: Always
volumes:
- hostPath:
path: /Users/syui/ai/bot/.config
name: aios-hostpath0

11
k8s/env-configmap.yaml Normal file
View File

@ -0,0 +1,11 @@
apiVersion: v1
data:
ADMIN: $ADMIN
HANDLE: $HANDLE
HOST: $HOST
PASSWORD: $PASSWORD
kind: ConfigMap
metadata:
labels:
io.kompose.service: aios-env
name: env

205
src/ascii.rs Normal file
View File

@ -0,0 +1,205 @@
use sysinfo::System;
pub fn c_ascii(x: &str) {
let logo = "
";
let avatar = "
";
let color = "








































";
let avatar_color = "








































";
let mut sys = System::new_all();
sys.refresh_all();
let s = x.to_string();
match &*s {
"logo" => println!("{}", logo),
"color" => println!("{}", color),
"avatar" => println!("{}", avatar),
"avatar_color" => println!("{}", avatar_color),
"os" => println!("{}\n\t", color),
_ => println!("not matched"),
}
println!("total memory: {} bytes", sys.total_memory());
println!("System name: {:?}", System::name().unwrap());
println!(
"System kernel version: {:?}",
System::kernel_version().unwrap()
);
//println!("System OS version: {:?}", System::os_version().unwrap());
println!(
"System host name: {:?}",
System::host_name().unwrap()
);
println!("NB CPUs: {}", sys.cpus().len());
//let disks = Disks::new_with_refreshed_list();
//for disk in &disks {
// println!("{disk:?}");
//}
//let networks = Networks::new_with_refreshed_list();
//println!("=> networks:");
//for (interface_name, data) in &networks {
// println!("{interface_name}: {}/{} B", data.received(), data.transmitted());
//}
//let components = Components::new_with_refreshed_list();
//println!("=> components:");
//for component in &components {
// println!("{component:?}");
//}
}

460
src/bot.rs Normal file
View File

@ -0,0 +1,460 @@
use seahorse::Context;
use std::process::Command;
use crate::notify;
use crate::notify_read;
use crate::openai;
use crate::refresh;
use crate::reply;
use crate::reply_link;
use crate::data::c_char;
use crate::data::data_scpt;
use crate::data::log_file;
use crate::data::w_cid;
use crate::data::Notify;
pub fn c_bot(c: &Context) {
let h = async {
let mut notify = notify::get_request(100).await;
if notify == "err" {
refresh(c);
notify = notify::get_request(100).await;
}
let notify: Notify = serde_json::from_str(&notify).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;
}
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 handlev: Vec<&str> = handle.split('.').collect();
let mut handlev = handlev[0].trim().to_string();
let mut link = "https://card.syui.ai/".to_owned() + &handlev;
let s = 0;
let mut e = link.chars().count();
let mut com = "".to_string();
let mut prompt = "".to_string();
let mut prompt_sub = "".to_string();
let mut prompt_chat = "".to_string();
if reason == "mention" {
com = vec[1].trim().to_string();
prompt = vec[2..].join(" ");
prompt_chat = vec[1..].join(" ");
if vec.len() > 2 {
prompt_sub = vec[3..].join(" ");
}
} else if reason == "reply" {
com = vec[0].trim().to_string();
prompt = vec[1..].join(" ");
prompt_chat = vec[0..].join(" ");
if vec.len() > 1 {
prompt_sub = vec[2..].join(" ");
}
}
if prompt.is_empty() == false || com.is_empty() == false {
println!("{}", read);
println!("{}", handle);
println!(
"cid:{}\nuri:{}\ncid_root:{}\nuri_root:{}",
cid, uri, cid_root, uri_root
);
println!("reason:{}\ncom:{}\nprompt:{}", reason, com, prompt);
println!("prompt_sub:{}", prompt_sub);
}
let mut admin = "".to_string();
if c.string_flag("admin").is_ok() {
admin = c.string_flag("admin").unwrap();
}
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 com == "did" {
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;
w_cid(cid.to_string(), log_file(&"n1"), true);
println!("{}", str_rep);
}
} else if com == "help" {
let link = "https://git.syui.ai/ai/bot/wiki/help".to_string();
let s = 0;
let e = link.chars().count();
let str_rep = reply_link::post_request(
"\n".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;
w_cid(cid.to_string(), log_file(&"n1"), true);
println!("{}", str_rep);
} else if com == "diffusers" {
let _output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"diffusers")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
w_cid(cid.to_string(), log_file(&"n1"), true);
} else if com.contains("") == true
|| com.contains("うらない") == true
|| com.contains("うらなって") == true
{
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"fortune")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let d = d.to_string();
let text_limit = c_char(d);
if text_limit.len() > 3 {
println!("{}", text_limit);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "card" {
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"card")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let dd = "\n".to_owned() + &d.to_string();
let text_limit = c_char(dd);
if text_limit.len() > 3 {
if d.contains("handle") == false {
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;
println!("{}", str_rep);
} else {
handlev = handle.replace(".", "-").to_string();
link = "https://card.syui.ai/".to_owned() + &handlev;
e = link.chars().count();
let str_rep = reply_link::post_request(
d.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;
println!("{}", str_rep);
}
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "fav" {
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"fav")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let dd = "\n".to_owned() + &d.to_string();
let text_limit = c_char(dd);
if text_limit.len() > 3 {
let str_rep = reply_link::post_request(
d.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;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "egg" {
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"egg")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let dd = "\n".to_owned() + &d.to_string();
let text_limit = c_char(dd);
if text_limit.len() > 3 {
let str_rep = reply_link::post_request(
d.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;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "nyan" {
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"nyan")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let dd = "\n".to_owned() + &d.to_string();
let text_limit = c_char(dd);
println!("{}", text_limit);
if text_limit.len() > 3 {
let str_rep = reply::post_request(
text_limit.to_string(),
cid.to_string(),
uri.to_string(),
cid_root.to_string(),
uri_root.to_string(),
)
.await;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "ten" {
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"ten")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let d = "\n".to_owned() + &d.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;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "coin" {
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"coin")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let d = "\n".to_owned() + &d.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;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else if com == "sh" && handle == &admin {
println!("admin:{}", admin);
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"sh")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let d = d.to_string();
let text_limit = c_char(d);
let str_rep = reply::post_request(
text_limit.to_string(),
cid.to_string(),
uri.to_string(),
cid_root.to_string(),
uri_root.to_string(),
)
.await;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
} else if com == "mitractl" && handle == &admin {
println!("admin:{}", admin);
let output = Command::new(data_scpt(&"ai"))
.arg(&"atproto").arg(&"mitra")
.arg(&handle)
.arg(&did)
.arg(&cid)
.arg(&uri)
.arg(&cid_root)
.arg(&uri_root)
.arg(&prompt)
.arg(&prompt_sub)
.output()
.expect("zsh");
let d = String::from_utf8_lossy(&output.stdout);
let d = "\n".to_owned() + &d.to_string();
let text_limit = c_char(d);
link = "https://m.syu.is".to_string();
e = link.chars().count();
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;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
} else {
// openai
let str_openai = openai::post_request(prompt_chat.to_string()).await;
let text_limit = c_char(str_openai);
let str_rep = reply::post_request(
text_limit.to_string(),
cid.to_string(),
uri.to_string(),
cid_root.to_string(),
uri_root.to_string(),
)
.await;
println!("{}", str_rep);
w_cid(cid.to_string(), log_file(&"n1"), true);
}
let str_notify = notify_read::post_request(time.to_string()).await;
println!("{}", str_notify);
println!("---");
}
}
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}

647
src/data.rs Normal file
View File

@ -0,0 +1,647 @@
use config::{Config, ConfigError, File};
use seahorse::Context;
use serde_derive::{Deserialize, Serialize};
use std::fs;
use std::fs::OpenOptions;
use std::io::Read;
use std::io::Write;
use std::path::Path;
pub fn data_file(s: &str) -> String {
let file = "/.config/ai/";
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 {
"toml" => f + &"token.toml",
"json" => f + &"token.json",
"refresh" => f + &"refresh.toml",
_ => 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<Self, ConfigError> {
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<Self, ConfigError> {
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()
}
}
impl Refresh {
pub fn new() -> Result<Self, ConfigError> {
let d = data_file("refresh");
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 password: String,
pub did: String,
pub handle: String,
pub access: String,
pub refresh: String,
}
#[derive(Debug, Serialize, Deserialize)]
#[allow(non_snake_case)]
pub struct Refresh {
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,
password: data.password,
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,
password: data.password,
handle: data.handle,
did: data.did,
access: data.access,
refresh: data.refresh,
};
match &*s {
"host" => data.host,
"password" => data.password,
"handle" => data.handle,
"did" => data.did,
"access" => data.access,
"refresh" => data.refresh,
_ => s,
}
}
pub fn c_refresh(access: &str, refresh: &str) {
let ff = data_file(&"refresh");
let mut ff = fs::File::create(ff.clone()).unwrap();
let refreshs = Refresh {
access: access.to_string(),
refresh: refresh.to_string(),
};
let toml = toml::to_string(&refreshs).unwrap();
ff.write_all(&toml.as_bytes()).unwrap();
}
pub fn data_refresh(s: &str) -> String {
let s = String::from(s);
let data = Data::new().unwrap();
let data = Data {
host: data.host,
password: data.password,
handle: data.handle,
did: data.did,
access: data.access,
refresh: data.refresh,
};
let mut _file = match Refresh::new()
{
Err(_why) => c_refresh(&data.access, &data.refresh),
Ok(_) => println!(""),
};
let refresh = Refresh::new().unwrap();
let refresh = Refresh {
access: refresh.access,
refresh: refresh.refresh,
};
match &*s {
"access" => refresh.access,
"refresh" => refresh.refresh,
_ => s,
}
}
pub fn data_scpt(s: &str) -> String {
let s = String::from(s);
let file = "/.config/ai/scpt/".to_owned() + &s + &".zsh";
let mut f = shellexpand::tilde("~").to_string();
f.push_str(&file);
return f;
}
#[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,
}
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, password: &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(),
password: password.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();
let ff = data_file(&"refresh");
let mut ff = fs::File::create(ff.clone()).unwrap();
let refreshs = Refresh {
access: json.accessJwt.to_string(),
refresh: json.refreshJwt.to_string(),
};
let toml = toml::to_string(&refreshs).unwrap();
ff.write_all(&toml.as_bytes()).unwrap();
}
pub fn w_refresh(res: &str) {
let ff = data_file(&"refresh");
let mut ff = fs::File::create(ff.clone()).unwrap();
let json: Token = serde_json::from_str(&res).unwrap();
let refreshs = Refresh {
access: json.accessJwt.to_string(),
refresh: json.refreshJwt.to_string(),
};
let toml = toml::to_string(&refreshs).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;
}
}
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<Self, ConfigError> {
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<Choices>,
}
#[derive(Serialize, Deserialize, Debug)]
pub struct Choices {
text: String,
}

22
src/describe.rs Normal file
View File

@ -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_refresh(&"access");
let url = url(&"describe");
let client = reqwest::Client::new();
let res = client
.get(url)
.query(&[("repo", &user)])
//.header("Authorization", "Bearer ".to_owned() + &token)
.send()
.await
.unwrap()
.text()
.await
.unwrap();
return res;
}

81
src/follow.rs Normal file
View File

@ -0,0 +1,81 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
//use crate::data::Follow;
pub async fn post_request(u: String) -> String {
let token = data_refresh(&"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_refresh(&"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;
}

24
src/followers.rs Normal file
View File

@ -0,0 +1,24 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
//use serde_json::json;
pub async fn get_request(actor: String, cursor: Option<String>) -> String {
let token = data_refresh(&"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;
}

26
src/follows.rs Normal file
View File

@ -0,0 +1,26 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
//use serde_json::json;
pub async fn get_request(actor: String, cursor: Option<String>) -> String {
let token = data_refresh(&"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;
}

59
src/img.rs Normal file
View File

@ -0,0 +1,59 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use serde_json::json;
use iso8601_timestamp::Timestamp;
pub async fn post_request(text: String, link: String) -> String {
let token = data_refresh(&"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": text.to_string(),
"embed": {
"$type": "app.bsky.embed.images",
"images": [
{
"alt": "",
"image": {
"$type":"blob",
"ref": {
"$link" : link.to_string()
},
"mimeType": "image/png",
"size": 0
}
}
]
}
}
}));
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
}

74
src/img_reply.rs Normal file
View File

@ -0,0 +1,74 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(
text: String,
link: String,
cid: String,
uri: String,
itype: String,
) -> String {
let token = data_refresh(&"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": text.to_string(),
"embed": {
"$type": "app.bsky.embed.images",
"images": [
{
"alt": "",
"image": {
"$type":"blob",
"ref": {
"$link" : link.to_string()
},
"mimeType": itype.to_string(),
"size": 0
}
}
]
},
"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;
}

59
src/img_upload.rs Normal file
View File

@ -0,0 +1,59 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use serde_json::json;
use iso8601_timestamp::Timestamp;
pub async fn post_request(text: String, link: String) -> String {
let token = data_refresh(&"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": text.to_string(),
"embed": {
"$type": "app.bsky.embed.images",
"images": [
{
"alt": "",
"image": {
"$type":"blob",
"ref": {
"$link" : link.to_string()
},
"mimeType": "image/png",
"size": 0
}
}
]
}
}
}));
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
}

45
src/like.rs Normal file
View File

@ -0,0 +1,45 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(cid: String, uri: String) -> String {
let token = data_refresh(&"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;
}

599
src/main.rs Normal file
View File

@ -0,0 +1,599 @@
use seahorse::{App, Command, Context, Flag, FlagType};
use std::env;
use crate::ascii::c_ascii;
use crate::bot::c_bot;
use crate::data::c_follow_all;
use crate::data::c_openai_key;
use crate::data::data_toml;
use crate::data::data_refresh;
use crate::data::url;
use crate::data::w_cfg;
use crate::data::w_refresh;
use data::ProfileIdentityResolve;
pub mod ascii;
pub mod bot;
pub mod data;
pub mod describe;
pub mod follow;
pub mod followers;
pub mod follows;
pub mod img_reply;
pub mod like;
pub mod mention;
pub mod notify;
pub mod notify_read;
pub mod openai;
pub mod post;
pub mod post_link;
pub mod profile;
pub mod refresh;
pub mod reply;
pub mod reply_link;
pub mod reply_og;
pub mod repost;
pub mod session;
pub mod timeline_author;
pub mod token;
fn main() {
let args: Vec<String> = env::args().collect();
let app = App::new(env!("CARGO_PKG_NAME"))
.command(
Command::new("ai")
.alias("a")
.action(ascii_art)
.flag(
Flag::new("type", FlagType::String)
.description("type flag")
.alias("t"),
)
)
.command(
Command::new("bot")
.alias("b")
.action(bot)
.flag(
Flag::new("admin", FlagType::String)
.alias("a"),
)
)
.command(
Command::new("follow_all")
.action(follow_all),
)
.command(
Command::new("login")
.alias("l")
.description("l <handle> -p <password>\n\t\t\tl <handle> -p <password> -s <server>")
.action(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(refresh),
)
.command(
Command::new("notify")
.alias("n")
.action(notify),
)
.command(
Command::new("timeline")
.alias("t")
.action(timeline),
)
.command(
Command::new("did")
.description("did <handle>")
.action(did)
)
.command(
Command::new("post")
.description("p <text>")
.alias("p")
.action(post)
.flag(
Flag::new("link", FlagType::String)
.alias("l"),
)
)
.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("uri-root", FlagType::String)
)
.flag(
Flag::new("cid-root", FlagType::String)
)
.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("img-upload")
.description("img-upload <img>")
.action(img_upload)
)
.command(
Command::new("img-post")
.description("img-post <text> -l <link> -u <uri> -c <cid>")
.action(img_post)
.flag(
Flag::new("link", FlagType::String)
.alias("l"),
)
.flag(
Flag::new("uri", FlagType::String)
.alias("u"),
)
.flag(
Flag::new("cid", FlagType::String)
.alias("c"),
)
)
.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);
}
fn ascii_art(c: &Context) {
if let Ok(t) = c.string_flag("type") {
c_ascii(&t);
} else {
c_ascii("color");
}
}
fn bot(c: &Context) {
refresh(c);
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 {
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, &p);
} else {
let res =
token::post_request(m.to_string(), p.to_string(), "bsky.social".to_string())
.await;
w_cfg(&"bsky.social", &res, &p);
}
}
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
fn refresh(_c: &Context) {
let h = async {
let session = session::get_request().await;
if session == "err" {
let res = refresh::post_request().await;
println!("{}", res);
if res == "err" {
let m = data_toml(&"handle");
let p = data_toml(&"password");
let s = data_toml(&"host");
println!("handle:{}, pass:{}, host:{}", m, p, s);
let res = token::post_request(m.to_string(), p.to_string(), s.to_string()).await;
w_cfg(&s, &res, &p);
println!("res:{}", res);
} else {
w_refresh(&res);
}
}
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
fn notify(c: &Context) {
refresh(c);
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 did(c: &Context) {
refresh(c);
let h = async {
if c.args.len() == 0 {
let j = describe::get_request(data_toml(&"handle")).await;
println!("{}", j);
} else {
let j = describe::get_request(c.args[0].to_string()).await;
println!("{}", j);
}
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
fn timeline(c: &Context) {
refresh(c);
let h = async {
if c.args.len() == 0 {
let str = timeline_author::get_request(data_toml(&"handle").to_string());
println!("{}", str.await);
} else {
let str = timeline_author::get_request(c.args[0].to_string());
println!("{}", str.await);
}
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
fn post(c: &Context) {
refresh(c);
let m = c.args[0].to_string();
let h = async {
if let Ok(link) = c.string_flag("link") {
let e = link.chars().count();
let s = 0;
let str =
post_link::post_request(m.to_string(), link.to_string(), s, e.try_into().unwrap());
println!("{}", str.await);
} else {
let str = post::post_request(m.to_string());
println!("{}", str.await);
}
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
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;
}
#[tokio::main]
async fn c_img_upload(c: &Context) -> reqwest::Result<()> {
let token = data_refresh(&"access");
let atoken = "Authorization: Bearer ".to_owned() + &token;
let con = "Content-Type: image/png";
let url = url(&"upload_blob");
let f = "@".to_owned() + &c.args[0].to_string();
use std::process::Command;
let output = Command::new("curl")
.arg("-X")
.arg("POST")
.arg("-sL")
.arg("-H")
.arg(&con)
.arg("-H")
.arg(&atoken)
.arg("--data-binary")
.arg(&f)
.arg(&url)
.output()
.expect("curl");
let d = String::from_utf8_lossy(&output.stdout);
let d = d.to_string();
println!("{}", d);
Ok(())
}
fn img_upload(c: &Context) {
refresh(c);
c_img_upload(c).unwrap();
}
fn img_post(c: &Context) {
let m = c.args[0].to_string();
let link = c.string_flag("link").unwrap();
let cid = c.string_flag("cid").unwrap();
let uri = c.string_flag("uri").unwrap();
let h = async {
let itype = "image/png";
let str = img_reply::post_request(
m.to_string(),
link.to_string(),
cid.to_string(),
uri.to_string(),
itype.to_string(),
)
.await;
println!("{}", str);
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
fn reply_og(c: &Context) {
refresh(c);
//let mut admin = "".to_string();
//if c.string_flag("admin").is_ok() {
// admin = c.string_flag("admin").unwrap();
//}
let m = c.args[0].to_string();
let link = c.string_flag("link").unwrap();
let cid = c.string_flag("cid").unwrap();
let uri = c.string_flag("uri").unwrap();
let cid_root = c.string_flag("cid-root").unwrap();
let uri_root = c.string_flag("uri-root").unwrap();
let title = c.string_flag("title").unwrap();
let desc = c.string_flag("description").unwrap();
let img = c.string_flag("img").unwrap();
let h = async {
let str = reply_og::post_request(m, link, cid, uri, cid_root, uri_root, img, title, desc);
println!("{}", str.await);
};
let res = tokio::runtime::Runtime::new().unwrap().block_on(h);
return res;
}
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;
}

57
src/mention.rs Normal file
View File

@ -0,0 +1,57 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(text: String, at: String, udid: String, s: i32, e: i32) -> String {
let token = data_refresh(&"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;
}

30
src/notify.rs Normal file
View File

@ -0,0 +1,30 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
//use serde_json::json;
pub async fn get_request(limit: i32) -> String {
let token = data_refresh(&"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;
}
}
}

27
src/notify_read.rs Normal file
View File

@ -0,0 +1,27 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
use serde_json::json;
pub async fn post_request(time: String) -> String {
let token = data_refresh(&"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;
}

58
src/openai.rs Normal file
View File

@ -0,0 +1,58 @@
extern crate reqwest;
use crate::data::Open;
use serde::{Deserialize, Serialize};
use serde_json::json;
#[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という作品のアイと呼ばれるキャラクターです。第一人称は「アイ」です。
調
調
123
";
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();
}

42
src/post.rs Normal file
View File

@ -0,0 +1,42 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(text: String) -> String {
let token = data_refresh(&"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": text.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;
}

56
src/post_link.rs Normal file
View File

@ -0,0 +1,56 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(text: String, link: String, s: i32, e: i32) -> String {
let token = data_refresh(&"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(),
"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;
}

21
src/profile.rs Normal file
View File

@ -0,0 +1,21 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
pub async fn get_request(user: String) -> String {
let token = data_refresh(&"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;
}

28
src/refresh.rs Normal file
View File

@ -0,0 +1,28 @@
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();
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;
}
}
}

59
src/reply.rs Normal file
View File

@ -0,0 +1,59 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(
text: String,
cid: String,
uri: String,
cid_root: String,
uri_root: String,
) -> String {
let token = data_refresh(&"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;
}

75
src/reply_link.rs Normal file
View File

@ -0,0 +1,75 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
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_refresh(&"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;
}

78
src/reply_og.rs Normal file
View File

@ -0,0 +1,78 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(
m: String,
link: String,
cid: String,
uri: String,
cid_root: String,
uri_root: String,
img: String,
title: String,
description: String,
) -> String {
let token = data_refresh(&"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_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;
}

45
src/repost.rs Normal file
View File

@ -0,0 +1,45 @@
extern crate reqwest;
use crate::data_toml;
use crate::data_refresh;
use crate::url;
use iso8601_timestamp::Timestamp;
use serde_json::json;
pub async fn post_request(cid: String, uri: String) -> String {
let token = data_refresh(&"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;
}

28
src/session.rs Normal file
View File

@ -0,0 +1,28 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
pub async fn get_request() -> String {
let token = data_refresh(&"access");
let url = url(&"session_get");
let client = reqwest::Client::new();
let res = client
.get(url)
.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;
}
}
}

27
src/timeline_author.rs Normal file
View File

@ -0,0 +1,27 @@
extern crate reqwest;
use crate::data_refresh;
use crate::url;
pub async fn get_request(actor: String) -> String {
let token = data_refresh(&"access");
let url = url(&"record_list");
let actor = actor.to_string();
//let cursor = cursor.unwrap();
let col = "app.bsky.feed.post".to_string();
let client = reqwest::Client::new();
let res = client
.get(url)
.query(&[("repo", actor), ("collection", col)])
//.query(&[("actor", actor),("cursor", cursor)])
.header("Authorization", "Bearer ".to_owned() + &token)
.send()
.await
.unwrap()
.text()
.await
.unwrap();
return res;
}

25
src/token.rs Normal file
View File

@ -0,0 +1,25 @@
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;
}

39
test/ai.zsh Executable file
View File

@ -0,0 +1,39 @@
#!/bin/zsh
# https://www.docs.bsky.app/docs/get-started
case $OSTYPE in
darwin*)
alias date="/opt/homebrew/bin/gdate"
;;
esac
d=${0:a:h}
source $d/env.zsh
source $d/refresh.zsh
source $d/token.zsh
source $d/reply.zsh
source $d/notify.zsh
source $d/notify_cid.zsh
source $d/cron.zsh
case $1 in
refresh|r)
refresh
;;
token|t)
token
;;
reply|rep)
reply
;;
notify|n)
notify
;;
cron|c)
cron
;;
cid)
cid
;;
esac

7
test/cron.zsh Normal file
View File

@ -0,0 +1,7 @@
function cron() {
t=`docker ps |grep aios|grep Up`
if [ -z "$t" ];then
docker compose up -d
fi
exit
}

3
test/entrypoint.sh Normal file
View File

@ -0,0 +1,3 @@
#!/bin/zsh
ai l $HANDLE -p $PASSWORD -s $HOST && ai bot -a $ADMIN

18
test/env.zsh Normal file
View File

@ -0,0 +1,18 @@
cfg=~/.config/ai/test.json
if [ -f $cfg ];then
host=`cat $cfg|jq -r .host`
handle=`cat $cfg|jq -r .handle`
pass=`cat $cfg|jq -r .password`
date=`date --iso-8601=seconds`
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

37
test/notify.zsh Normal file
View File

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

16
test/notify_cid.zsh Normal file
View File

@ -0,0 +1,16 @@
function cid(){
dd=${d:h}
ai=$dd/target/debug/ai
txt=$dd/.config/ai/txt
f=$txt/notify_cid
if [ ! -f $ai ];then
cd $dd
cargo build
fi
if [ ! -d $txt ];then
mkdir -p $txt
fi
$ai n|jq -r ".[]|.[]?.cid" >> $f.txt
cp -rf $f.txt ${f}_run.txt
}

11
test/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
}

40
test/reply.zsh Executable file
View File

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

18
test/token.zsh Executable file
View File

@ -0,0 +1,18 @@
function token() {
mkdir -p ~/.config/ai
echo server:
read host
echo password:
read pass
echo handle:
read handle
echo "{ \"host\":\"$host\", \"password\":\"$pass\", \"handle\":\"$handle\" }" >> $cfg
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
}