move markread into its own module file
This commit is contained in:
parent
50a95c6686
commit
768435d810
4 changed files with 106 additions and 64 deletions
|
|
@ -125,7 +125,6 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
Box::new(TagMsg),
|
Box::new(TagMsg),
|
||||||
Box::new(ChatHistory),
|
Box::new(ChatHistory),
|
||||||
Box::new(Redact),
|
Box::new(Redact),
|
||||||
Box::new(MarkRead),
|
|
||||||
Box::new(Batch),
|
Box::new(Batch),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
@ -160,56 +159,6 @@ impl Command for Batch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// MARKREAD — draft/read-marker. `MARKREAD <target> [timestamp=<iso>]`. With a
|
|
||||||
/// timestamp it sets the read marker (only ever advancing) and echoes it to every
|
|
||||||
/// connection sharing the user's identity (multi-device); without one it returns
|
|
||||||
/// the stored marker (`*` if unset).
|
|
||||||
struct MarkRead;
|
|
||||||
impl Command for MarkRead {
|
|
||||||
fn name(&self) -> &'static str {
|
|
||||||
"MARKREAD"
|
|
||||||
}
|
|
||||||
fn min_params(&self) -> usize {
|
|
||||||
1
|
|
||||||
}
|
|
||||||
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
|
||||||
let target = params[0].clone();
|
|
||||||
let tkey = target.to_ascii_lowercase();
|
|
||||||
let id = s.marker_id(uid);
|
|
||||||
match params.get(1).and_then(|p| p.strip_prefix("timestamp=")) {
|
|
||||||
Some(ts_str) => {
|
|
||||||
let cur = s
|
|
||||||
.read_markers
|
|
||||||
.get(&id)
|
|
||||||
.and_then(|m| m.get(&tkey))
|
|
||||||
.copied()
|
|
||||||
.unwrap_or(0);
|
|
||||||
let ts = parse_iso(ts_str).unwrap_or(0).max(cur); // markers only advance
|
|
||||||
s.read_markers
|
|
||||||
.entry(id.clone())
|
|
||||||
.or_default()
|
|
||||||
.insert(tkey, ts);
|
|
||||||
let line = format!(":{} MARKREAD {target} timestamp={}", s.name, iso_time(ts));
|
|
||||||
let uids: Vec<Uid> = s.users.keys().copied().collect();
|
|
||||||
for p in uids.into_iter().filter(|&p| s.marker_id(p) == id) {
|
|
||||||
s.send(p, line.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
let val = s
|
|
||||||
.read_markers
|
|
||||||
.get(&id)
|
|
||||||
.and_then(|m| m.get(&tkey))
|
|
||||||
.copied()
|
|
||||||
.map(|t| format!("timestamp={}", iso_time(t)))
|
|
||||||
.unwrap_or_else(|| "*".to_string());
|
|
||||||
s.send(uid, format!(":{} MARKREAD {target} {val}", s.name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
CmdResult::Ok
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// REDACT — delete a previously-sent channel message (draft/message-redaction).
|
/// REDACT — delete a previously-sent channel message (draft/message-redaction).
|
||||||
/// `REDACT <#chan> <msgid> [:reason]`. Allowed for the message's author, a channel
|
/// `REDACT <#chan> <msgid> [:reason]`. Allowed for the message's author, a channel
|
||||||
/// half-op-or-above, or an oper. Relayed to channel members who enabled the cap,
|
/// half-op-or-above, or an oper. Relayed to channel members who enabled the cap,
|
||||||
|
|
|
||||||
103
src/modules/markread.rs
Normal file
103
src/modules/markread.rs
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
//! markread — InspIRCd's `m_ircv3_read_marker` (draft/read-marker). A client sets
|
||||||
|
//! or queries the "last read" timestamp per conversation; markers are keyed by
|
||||||
|
//! account when logged in (so they're shared across a user's devices and survive
|
||||||
|
//! reconnects) and echoed to every connection sharing that identity. Self-contained:
|
||||||
|
//! the marker store lives in `Server.ext`, cleaned up by the on_user_quit hook.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::command::{CmdResult, Command};
|
||||||
|
use crate::module::Module;
|
||||||
|
use crate::server::{iso_time, parse_iso, Server};
|
||||||
|
use crate::Uid;
|
||||||
|
|
||||||
|
/// identity -> target (lowercased) -> read timestamp. Stored in `Server.ext`.
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct ReadMarkers(pub HashMap<String, HashMap<String, u64>>);
|
||||||
|
|
||||||
|
/// The read-marker identity for `uid`: their account when logged in (so markers
|
||||||
|
/// are shared across their devices and survive reconnects), else a per-session
|
||||||
|
/// key. The on_user_quit hook prunes the session key on disconnect.
|
||||||
|
pub fn marker_id(s: &Server, uid: Uid) -> String {
|
||||||
|
s.users
|
||||||
|
.get(&uid)
|
||||||
|
.and_then(|u| u.account.clone())
|
||||||
|
.unwrap_or_else(|| format!("~{uid}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Cleanup hook: drop a user's session markers on disconnect (account-keyed
|
||||||
|
/// markers are intentionally kept so they persist across reconnects).
|
||||||
|
pub struct MarkRead;
|
||||||
|
impl Module for MarkRead {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"markread"
|
||||||
|
}
|
||||||
|
fn on_user_quit(&mut self, s: &mut Server, uid: Uid, _reason: &str) {
|
||||||
|
if let Some(m) = s.ext.get_mut::<ReadMarkers>() {
|
||||||
|
m.0.remove(&format!("~{uid}"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
|
vec![Box::new(MarkReadCmd)]
|
||||||
|
}
|
||||||
|
|
||||||
|
/// MARKREAD — `MARKREAD <target> [timestamp=<iso>]`. With a timestamp it sets the
|
||||||
|
/// read marker (only ever advancing) and echoes it to every connection sharing the
|
||||||
|
/// user's identity (multi-device); without one it returns the stored marker (`*` if
|
||||||
|
/// unset).
|
||||||
|
struct MarkReadCmd;
|
||||||
|
impl Command for MarkReadCmd {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"MARKREAD"
|
||||||
|
}
|
||||||
|
fn min_params(&self) -> usize {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
|
let target = params[0].clone();
|
||||||
|
let tkey = target.to_ascii_lowercase();
|
||||||
|
let id = marker_id(s, uid);
|
||||||
|
match params.get(1).and_then(|p| p.strip_prefix("timestamp=")) {
|
||||||
|
Some(ts_str) => {
|
||||||
|
let cur = s
|
||||||
|
.ext
|
||||||
|
.get::<ReadMarkers>()
|
||||||
|
.and_then(|m| m.0.get(&id))
|
||||||
|
.and_then(|m| m.get(&tkey))
|
||||||
|
.copied()
|
||||||
|
.unwrap_or(0);
|
||||||
|
let ts = parse_iso(ts_str).unwrap_or(0).max(cur); // markers only advance
|
||||||
|
s.ext
|
||||||
|
.get_or_insert_with::<ReadMarkers>(ReadMarkers::default)
|
||||||
|
.0
|
||||||
|
.entry(id.clone())
|
||||||
|
.or_default()
|
||||||
|
.insert(tkey, ts);
|
||||||
|
let line = format!(":{} MARKREAD {target} timestamp={}", s.name, iso_time(ts));
|
||||||
|
let recips: Vec<Uid> = s
|
||||||
|
.users
|
||||||
|
.keys()
|
||||||
|
.copied()
|
||||||
|
.filter(|&p| marker_id(s, p) == id)
|
||||||
|
.collect();
|
||||||
|
for p in recips {
|
||||||
|
s.send(p, line.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let val = s
|
||||||
|
.ext
|
||||||
|
.get::<ReadMarkers>()
|
||||||
|
.and_then(|m| m.0.get(&id))
|
||||||
|
.and_then(|m| m.get(&tkey))
|
||||||
|
.copied()
|
||||||
|
.map(|t| format!("timestamp={}", iso_time(t)))
|
||||||
|
.unwrap_or_else(|| "*".to_string());
|
||||||
|
s.send(uid, format!(":{} MARKREAD {target} {val}", s.name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmdResult::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ pub mod cloak;
|
||||||
pub mod dnsbl;
|
pub mod dnsbl;
|
||||||
pub mod filter;
|
pub mod filter;
|
||||||
pub mod flood;
|
pub mod flood;
|
||||||
|
pub mod markread;
|
||||||
pub mod metadata;
|
pub mod metadata;
|
||||||
pub mod snoop;
|
pub mod snoop;
|
||||||
|
|
||||||
|
|
@ -23,6 +24,7 @@ pub fn default_modules() -> Vec<Box<dyn Module>> {
|
||||||
Box::new(antimixedutf8::AntiMixedUtf8),
|
Box::new(antimixedutf8::AntiMixedUtf8),
|
||||||
Box::new(filter::Filter),
|
Box::new(filter::Filter),
|
||||||
Box::new(metadata::Metadata),
|
Box::new(metadata::Metadata),
|
||||||
|
Box::new(markread::MarkRead),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -32,5 +34,6 @@ pub fn module_commands() -> Vec<Box<dyn Command>> {
|
||||||
filter::commands()
|
filter::commands()
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(metadata::commands())
|
.chain(metadata::commands())
|
||||||
|
.chain(markread::commands())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,6 @@ pub struct Server {
|
||||||
// output primitives are `&self`.
|
// output primitives are `&self`.
|
||||||
pub label_capture: RefCell<Option<(Uid, Vec<String>)>>,
|
pub label_capture: RefCell<Option<(Uid, Vec<String>)>>,
|
||||||
pub history: HashMap<String, VecDeque<HistMsg>>, // channel key -> recent messages (CHATHISTORY)
|
pub history: HashMap<String, VecDeque<HistMsg>>, // channel key -> recent messages (CHATHISTORY)
|
||||||
pub read_markers: HashMap<String, HashMap<String, u64>>, // identity -> target -> read ts (MARKREAD)
|
|
||||||
pub mline: HashMap<Uid, MlineBatch>, // in-progress inbound multiline batches
|
pub mline: HashMap<Uid, MlineBatch>, // in-progress inbound multiline batches
|
||||||
pub event_tx: Sender<Event>, // self-inject events (DNS results)
|
pub event_tx: Sender<Event>, // self-inject events (DNS results)
|
||||||
pub conn_counter: Arc<AtomicU64>, // mints connection uids (for CONNECT dials)
|
pub conn_counter: Arc<AtomicU64>, // mints connection uids (for CONNECT dials)
|
||||||
|
|
@ -214,7 +213,6 @@ impl Server {
|
||||||
webirc: cfg.webirc,
|
webirc: cfg.webirc,
|
||||||
label_capture: RefCell::new(None),
|
label_capture: RefCell::new(None),
|
||||||
history: HashMap::new(),
|
history: HashMap::new(),
|
||||||
read_markers: HashMap::new(),
|
|
||||||
mline: HashMap::new(),
|
mline: HashMap::new(),
|
||||||
event_tx,
|
event_tx,
|
||||||
conn_counter,
|
conn_counter,
|
||||||
|
|
@ -329,16 +327,6 @@ impl Server {
|
||||||
Some((mb.target, mb.notice, lines))
|
Some((mb.target, mb.notice, lines))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The read-marker identity for `uid`: their account when logged in (so markers
|
|
||||||
/// are shared across their devices and survive reconnects), else a per-session
|
|
||||||
/// key. `remove_user` prunes the session key on disconnect.
|
|
||||||
pub fn marker_id(&self, uid: Uid) -> String {
|
|
||||||
self.users
|
|
||||||
.get(&uid)
|
|
||||||
.and_then(|u| u.account.clone())
|
|
||||||
.unwrap_or_else(|| format!("~{uid}"))
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- connection lifecycle ------------------------------------------------
|
// --- connection lifecycle ------------------------------------------------
|
||||||
|
|
||||||
pub fn add_conn(
|
pub fn add_conn(
|
||||||
|
|
@ -516,7 +504,6 @@ impl Server {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
self.uuid_local.remove(&user.uuid);
|
self.uuid_local.remove(&user.uuid);
|
||||||
self.read_markers.remove(&format!("~{uid}")); // session read-markers (kept if account-keyed)
|
|
||||||
self.mline.remove(&uid); // any half-open multiline batch
|
self.mline.remove(&uid); // any half-open multiline batch
|
||||||
if user.registered {
|
if user.registered {
|
||||||
self.push_whowas(
|
self.push_whowas(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue