fix notify
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use anyhow::Result;
|
||||
use serde_json::Value;
|
||||
use std::io::Write;
|
||||
|
||||
use super::auth;
|
||||
use crate::lexicons::app_bsky_notification;
|
||||
@@ -65,3 +66,90 @@ pub async fn update_seen() -> Result<()> {
|
||||
println!("{}", serde_json::to_string_pretty(&result)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Poll notifications and output new mentions as NDJSON.
|
||||
/// Runs until interrupted (Ctrl+C).
|
||||
pub async fn listen(interval_secs: u64, reasons: &[String]) -> Result<()> {
|
||||
let mut last_seen: Option<String> = None;
|
||||
let mut stdout = std::io::stdout();
|
||||
|
||||
loop {
|
||||
let session = auth::refresh_session().await?;
|
||||
let pds = session.pds.as_deref().unwrap_or("bsky.social");
|
||||
let client = XrpcClient::new(pds);
|
||||
|
||||
let body: Value = client
|
||||
.query_auth(
|
||||
&app_bsky_notification::LIST_NOTIFICATIONS,
|
||||
&[("limit", "50")],
|
||||
&session.access_jwt,
|
||||
)
|
||||
.await?;
|
||||
|
||||
if let Some(notifications) = body["notifications"].as_array() {
|
||||
let mut new_items: Vec<&Value> = Vec::new();
|
||||
|
||||
for notif in notifications {
|
||||
// Stop at already-seen notifications
|
||||
if let Some(ref seen) = last_seen {
|
||||
if notif["indexedAt"].as_str() == Some(seen.as_str()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Skip read notifications on first run
|
||||
if last_seen.is_none() && notif["isRead"].as_bool() == Some(true) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Filter by reason
|
||||
let reason = notif["reason"].as_str().unwrap_or("");
|
||||
if !reasons.is_empty() && !reasons.iter().any(|r| r == reason) {
|
||||
continue;
|
||||
}
|
||||
|
||||
new_items.push(notif);
|
||||
}
|
||||
|
||||
// Output in chronological order (oldest first)
|
||||
for notif in new_items.iter().rev() {
|
||||
let out = serde_json::json!({
|
||||
"reason": notif["reason"],
|
||||
"uri": notif["uri"],
|
||||
"author": {
|
||||
"did": notif["author"]["did"],
|
||||
"handle": notif["author"]["handle"],
|
||||
},
|
||||
"text": notif["record"]["text"],
|
||||
"indexedAt": notif["indexedAt"],
|
||||
});
|
||||
writeln!(stdout, "{}", serde_json::to_string(&out)?)?;
|
||||
stdout.flush()?;
|
||||
}
|
||||
|
||||
// Update last_seen to newest notification
|
||||
if let Some(newest) = notifications.first() {
|
||||
if let Some(ts) = newest["indexedAt"].as_str() {
|
||||
last_seen = Some(ts.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
// Mark as seen
|
||||
if !new_items.is_empty() {
|
||||
let now = chrono::Utc::now()
|
||||
.format("%Y-%m-%dT%H:%M:%S%.3fZ")
|
||||
.to_string();
|
||||
let seen_body = serde_json::json!({ "seenAt": now });
|
||||
let _ = client
|
||||
.call_no_response(
|
||||
&app_bsky_notification::UPDATE_SEEN,
|
||||
&seen_body,
|
||||
&session.access_jwt,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
|
||||
tokio::time::sleep(std::time::Duration::from_secs(interval_secs)).await;
|
||||
}
|
||||
}
|
||||
|
||||
12
src/main.rs
12
src/main.rs
@@ -188,6 +188,15 @@ enum NotifyCommands {
|
||||
Count,
|
||||
/// Mark all notifications as seen
|
||||
Seen,
|
||||
/// Poll for new notifications (runs continuously, NDJSON output)
|
||||
Listen {
|
||||
/// Poll interval in seconds
|
||||
#[arg(short, long, default_value = "30")]
|
||||
interval: u64,
|
||||
/// Filter by reason (mention, reply, like, repost, follow, quote)
|
||||
#[arg(short, long)]
|
||||
reason: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Subcommand)]
|
||||
@@ -262,6 +271,9 @@ async fn main() -> Result<()> {
|
||||
NotifyCommands::Seen => {
|
||||
commands::notify::update_seen().await?;
|
||||
}
|
||||
NotifyCommands::Listen { interval, reason } => {
|
||||
commands::notify::listen(interval, &reason).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Commands::Pds { command } => {
|
||||
|
||||
Reference in New Issue
Block a user