add reply

This commit is contained in:
2026-02-04 22:36:01 +09:00
parent f5fac72002
commit 8446bd4da6
4 changed files with 143 additions and 0 deletions

View File

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

View File

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

View File

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