add reply
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
pub mod auth;
|
||||
pub mod token;
|
||||
pub mod record;
|
||||
pub mod reply;
|
||||
pub mod sync;
|
||||
pub mod push;
|
||||
pub mod notify;
|
||||
|
||||
@@ -113,15 +113,37 @@ pub async fn listen(interval_secs: u64, reasons: &[String]) -> Result<()> {
|
||||
|
||||
// Output in chronological order (oldest first)
|
||||
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!({
|
||||
"reason": notif["reason"],
|
||||
"uri": notif["uri"],
|
||||
"cid": notif["cid"],
|
||||
"author": {
|
||||
"did": notif["author"]["did"],
|
||||
"handle": notif["author"]["handle"],
|
||||
},
|
||||
"text": notif["record"]["text"],
|
||||
"indexedAt": notif["indexedAt"],
|
||||
"root": root,
|
||||
"parent": parent,
|
||||
});
|
||||
writeln!(stdout, "{}", serde_json::to_string(&out)?)?;
|
||||
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,
|
||||
},
|
||||
|
||||
/// 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
|
||||
#[command(alias = "c")]
|
||||
Chat {
|
||||
@@ -248,6 +269,19 @@ async fn main() -> Result<()> {
|
||||
Commands::Did { handle, server } => {
|
||||
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 } => {
|
||||
lms::chat::run(message.as_deref(), new).await?;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user