add reply
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
pub mod auth;
|
pub mod auth;
|
||||||
pub mod token;
|
pub mod token;
|
||||||
pub mod record;
|
pub mod record;
|
||||||
|
pub mod reply;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod push;
|
pub mod push;
|
||||||
pub mod notify;
|
pub mod notify;
|
||||||
|
|||||||
@@ -113,15 +113,37 @@ pub async fn listen(interval_secs: u64, reasons: &[String]) -> Result<()> {
|
|||||||
|
|
||||||
// Output in chronological order (oldest first)
|
// Output in chronological order (oldest first)
|
||||||
for notif in new_items.iter().rev() {
|
for notif in new_items.iter().rev() {
|
||||||
|
// Build root/parent for reply threading
|
||||||
|
let root = if notif["record"]["reply"]["root"]["uri"].is_string() {
|
||||||
|
serde_json::json!({
|
||||||
|
"uri": notif["record"]["reply"]["root"]["uri"],
|
||||||
|
"cid": notif["record"]["reply"]["root"]["cid"],
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// This post is the root
|
||||||
|
serde_json::json!({
|
||||||
|
"uri": notif["uri"],
|
||||||
|
"cid": notif["cid"],
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let parent = serde_json::json!({
|
||||||
|
"uri": notif["uri"],
|
||||||
|
"cid": notif["cid"],
|
||||||
|
});
|
||||||
|
|
||||||
let out = serde_json::json!({
|
let out = serde_json::json!({
|
||||||
"reason": notif["reason"],
|
"reason": notif["reason"],
|
||||||
"uri": notif["uri"],
|
"uri": notif["uri"],
|
||||||
|
"cid": notif["cid"],
|
||||||
"author": {
|
"author": {
|
||||||
"did": notif["author"]["did"],
|
"did": notif["author"]["did"],
|
||||||
"handle": notif["author"]["handle"],
|
"handle": notif["author"]["handle"],
|
||||||
},
|
},
|
||||||
"text": notif["record"]["text"],
|
"text": notif["record"]["text"],
|
||||||
"indexedAt": notif["indexedAt"],
|
"indexedAt": notif["indexedAt"],
|
||||||
|
"root": root,
|
||||||
|
"parent": parent,
|
||||||
});
|
});
|
||||||
writeln!(stdout, "{}", serde_json::to_string(&out)?)?;
|
writeln!(stdout, "{}", serde_json::to_string(&out)?)?;
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
|
|||||||
86
src/commands/reply.rs
Normal file
86
src/commands/reply.rs
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
use anyhow::{Context, Result};
|
||||||
|
use serde_json::Value;
|
||||||
|
|
||||||
|
use super::auth;
|
||||||
|
use crate::lexicons::com_atproto_repo;
|
||||||
|
use crate::tid;
|
||||||
|
use crate::types::{PutRecordRequest, PutRecordResponse};
|
||||||
|
use crate::xrpc::XrpcClient;
|
||||||
|
|
||||||
|
/// Reply to a post on ATProto.
|
||||||
|
///
|
||||||
|
/// `root_uri`, `root_cid`: thread root
|
||||||
|
/// `parent_uri`, `parent_cid`: direct parent being replied to
|
||||||
|
pub async fn reply(
|
||||||
|
text: &str,
|
||||||
|
root_uri: &str,
|
||||||
|
root_cid: &str,
|
||||||
|
parent_uri: &str,
|
||||||
|
parent_cid: &str,
|
||||||
|
) -> Result<()> {
|
||||||
|
let session = auth::refresh_session().await?;
|
||||||
|
let pds = session.pds.as_deref().unwrap_or("bsky.social");
|
||||||
|
let client = XrpcClient::new(pds);
|
||||||
|
|
||||||
|
let now = chrono::Utc::now()
|
||||||
|
.format("%Y-%m-%dT%H:%M:%S%.3fZ")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let record = serde_json::json!({
|
||||||
|
"$type": "app.bsky.feed.post",
|
||||||
|
"text": text,
|
||||||
|
"reply": {
|
||||||
|
"root": {
|
||||||
|
"uri": root_uri,
|
||||||
|
"cid": root_cid,
|
||||||
|
},
|
||||||
|
"parent": {
|
||||||
|
"uri": parent_uri,
|
||||||
|
"cid": parent_cid,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"createdAt": now,
|
||||||
|
});
|
||||||
|
|
||||||
|
let rkey = tid::generate_tid();
|
||||||
|
|
||||||
|
let req = PutRecordRequest {
|
||||||
|
repo: session.did.clone(),
|
||||||
|
collection: "app.bsky.feed.post".to_string(),
|
||||||
|
rkey,
|
||||||
|
record,
|
||||||
|
};
|
||||||
|
|
||||||
|
let result: PutRecordResponse = client
|
||||||
|
.call(&com_atproto_repo::PUT_RECORD, &req, &session.access_jwt)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let out = serde_json::json!({
|
||||||
|
"uri": result.uri,
|
||||||
|
"cid": result.cid,
|
||||||
|
});
|
||||||
|
println!("{}", serde_json::to_string_pretty(&out)?);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Reply from JSON input (stdin or argument).
|
||||||
|
/// Expects fields: text, root.uri, root.cid, parent.uri, parent.cid
|
||||||
|
pub async fn reply_json(json_str: &str, text: &str) -> Result<()> {
|
||||||
|
let v: Value = serde_json::from_str(json_str).context("Invalid JSON input")?;
|
||||||
|
|
||||||
|
let root_uri = v["root"]["uri"]
|
||||||
|
.as_str()
|
||||||
|
.context("missing root.uri")?;
|
||||||
|
let root_cid = v["root"]["cid"]
|
||||||
|
.as_str()
|
||||||
|
.context("missing root.cid")?;
|
||||||
|
let parent_uri = v["parent"]["uri"]
|
||||||
|
.as_str()
|
||||||
|
.context("missing parent.uri")?;
|
||||||
|
let parent_cid = v["parent"]["cid"]
|
||||||
|
.as_str()
|
||||||
|
.context("missing parent.cid")?;
|
||||||
|
|
||||||
|
reply(text, root_uri, root_cid, parent_uri, parent_cid).await
|
||||||
|
}
|
||||||
34
src/main.rs
34
src/main.rs
@@ -135,6 +135,27 @@ enum Commands {
|
|||||||
server: String,
|
server: String,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// Reply to a post
|
||||||
|
Reply {
|
||||||
|
/// Reply text
|
||||||
|
text: String,
|
||||||
|
/// Parent post URI (at://...)
|
||||||
|
#[arg(long)]
|
||||||
|
uri: Option<String>,
|
||||||
|
/// Parent post CID
|
||||||
|
#[arg(long)]
|
||||||
|
cid: Option<String>,
|
||||||
|
/// Root post URI (defaults to parent if omitted)
|
||||||
|
#[arg(long)]
|
||||||
|
root_uri: Option<String>,
|
||||||
|
/// Root post CID (defaults to parent if omitted)
|
||||||
|
#[arg(long)]
|
||||||
|
root_cid: Option<String>,
|
||||||
|
/// JSON with root/parent info (from `notify listen` output)
|
||||||
|
#[arg(long)]
|
||||||
|
json: Option<String>,
|
||||||
|
},
|
||||||
|
|
||||||
/// Chat with AI
|
/// Chat with AI
|
||||||
#[command(alias = "c")]
|
#[command(alias = "c")]
|
||||||
Chat {
|
Chat {
|
||||||
@@ -248,6 +269,19 @@ async fn main() -> Result<()> {
|
|||||||
Commands::Did { handle, server } => {
|
Commands::Did { handle, server } => {
|
||||||
commands::did::resolve(&handle, &server).await?;
|
commands::did::resolve(&handle, &server).await?;
|
||||||
}
|
}
|
||||||
|
Commands::Reply { text, uri, cid, root_uri, root_cid, json } => {
|
||||||
|
if let Some(json_str) = json {
|
||||||
|
commands::reply::reply_json(&json_str, &text).await?;
|
||||||
|
} else {
|
||||||
|
let parent_uri = uri.as_deref()
|
||||||
|
.expect("--uri is required (or use --json)");
|
||||||
|
let parent_cid = cid.as_deref()
|
||||||
|
.expect("--cid is required (or use --json)");
|
||||||
|
let r_uri = root_uri.as_deref().unwrap_or(parent_uri);
|
||||||
|
let r_cid = root_cid.as_deref().unwrap_or(parent_cid);
|
||||||
|
commands::reply::reply(&text, r_uri, r_cid, parent_uri, parent_cid).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
Commands::Chat { message, new } => {
|
Commands::Chat { message, new } => {
|
||||||
lms::chat::run(message.as_deref(), new).await?;
|
lms::chat::run(message.as_deref(), new).await?;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user