markread (draft/read-marker, identity-shared) + advertise UTF8ONLY
This commit is contained in:
parent
283f8f5683
commit
a70ee42aee
3 changed files with 67 additions and 3 deletions
|
|
@ -125,9 +125,60 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
|||
Box::new(TagMsg),
|
||||
Box::new(ChatHistory),
|
||||
Box::new(Redact),
|
||||
Box::new(MarkRead),
|
||||
]
|
||||
}
|
||||
|
||||
/// 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 <#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,
|
||||
|
|
|
|||
|
|
@ -150,8 +150,9 @@ pub struct Server {
|
|||
// output primitives are `&self`.
|
||||
pub label_capture: RefCell<Option<(Uid, Vec<String>)>>,
|
||||
pub history: HashMap<String, VecDeque<HistMsg>>, // channel key -> recent messages (CHATHISTORY)
|
||||
pub event_tx: Sender<Event>, // self-inject events (DNS results)
|
||||
pub conn_counter: Arc<AtomicU64>, // mints connection uids (for CONNECT dials)
|
||||
pub read_markers: HashMap<String, HashMap<String, u64>>, // identity -> target -> read ts (MARKREAD)
|
||||
pub event_tx: Sender<Event>, // self-inject events (DNS results)
|
||||
pub conn_counter: Arc<AtomicU64>, // mints connection uids (for CONNECT dials)
|
||||
}
|
||||
|
||||
impl Server {
|
||||
|
|
@ -194,6 +195,7 @@ impl Server {
|
|||
webirc: cfg.webirc,
|
||||
label_capture: RefCell::new(None),
|
||||
history: HashMap::new(),
|
||||
read_markers: HashMap::new(),
|
||||
event_tx,
|
||||
conn_counter,
|
||||
}
|
||||
|
|
@ -248,6 +250,16 @@ impl Server {
|
|||
}
|
||||
}
|
||||
|
||||
/// 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 ------------------------------------------------
|
||||
|
||||
pub fn add_conn(
|
||||
|
|
@ -425,6 +437,7 @@ impl Server {
|
|||
return;
|
||||
};
|
||||
self.uuid_local.remove(&user.uuid);
|
||||
self.read_markers.remove(&format!("~{uid}")); // session read-markers (kept if account-keyed)
|
||||
if user.registered {
|
||||
self.push_whowas(
|
||||
&user.nick,
|
||||
|
|
|
|||
|
|
@ -400,7 +400,7 @@ impl Server {
|
|||
uid,
|
||||
RPL_ISUPPORT,
|
||||
&format!(
|
||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFL,CGMNORSTcimnpstuz EXTBAN=,cmn WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g WHOX CHATHISTORY=256 MSGREFTYPES=timestamp,msgid CASEMAPPING=ascii NICKLEN=30 CHANNELLEN=50 NETWORK={} :are supported by this server",
|
||||
"CHANTYPES=# PREFIX=(qaohv)~&@%+ CHANMODES=beIg,k,lfjFL,CGMNORSTcimnpstuz EXTBAN=,cmn WATCH=128 MONITOR=128 SILENCE=32 CALLERID=g WHOX CHATHISTORY=256 MSGREFTYPES=timestamp,msgid UTF8ONLY CASEMAPPING=ascii NICKLEN=30 CHANNELLEN=50 NETWORK={} :are supported by this server",
|
||||
self.network
|
||||
),
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue