chathistory: store per-conversation DM history too (canonical pair key, original target preserved)
This commit is contained in:
parent
cb2ac3de81
commit
ab3d9a07f0
2 changed files with 42 additions and 5 deletions
|
|
@ -132,6 +132,17 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
/// `<selector>` is `*`, `timestamp=<iso>` or `msgid=<id>`. Only members get a
|
/// `<selector>` is `*`, `timestamp=<iso>` or `msgid=<id>`. Only members get a
|
||||||
/// channel's history; the reply is a `chathistory` batch of the original
|
/// channel's history; the reply is a `chathistory` batch of the original
|
||||||
/// PRIVMSG/NOTICE lines, each carrying its stored server-time + msgid.
|
/// PRIVMSG/NOTICE lines, each carrying its stored server-time + msgid.
|
||||||
|
/// Canonical CHATHISTORY key for a DM between two nicks (order-independent; the
|
||||||
|
/// `\0` prefix keeps it from ever colliding with a `#channel` key).
|
||||||
|
fn dm_key(a: &str, b: &str) -> String {
|
||||||
|
let (a, b) = (a.to_ascii_lowercase(), b.to_ascii_lowercase());
|
||||||
|
if a <= b {
|
||||||
|
format!("\0{a}\0{b}")
|
||||||
|
} else {
|
||||||
|
format!("\0{b}\0{a}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
struct ChatHistory;
|
struct ChatHistory;
|
||||||
impl Command for ChatHistory {
|
impl Command for ChatHistory {
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
|
|
@ -143,7 +154,19 @@ impl Command for ChatHistory {
|
||||||
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
let sub = params[0].to_ascii_uppercase();
|
let sub = params[0].to_ascii_uppercase();
|
||||||
let target = params[1].clone();
|
let target = params[1].clone();
|
||||||
let key = target.to_ascii_lowercase();
|
// channel target → channel key (members only); a nick → the DM pair key
|
||||||
|
// (the requester is inherently part of it, so no membership check)
|
||||||
|
let is_channel = target.starts_with('#');
|
||||||
|
let key = if is_channel {
|
||||||
|
target.to_ascii_lowercase()
|
||||||
|
} else {
|
||||||
|
let me = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.clone())
|
||||||
|
.unwrap_or_default();
|
||||||
|
dm_key(&me, &target)
|
||||||
|
};
|
||||||
// BETWEEN takes two selectors then the limit; the rest take one + the limit
|
// BETWEEN takes two selectors then the limit; the rest take one + the limit
|
||||||
let (sel, sel2, limit_s) = if sub == "BETWEEN" {
|
let (sel, sel2, limit_s) = if sub == "BETWEEN" {
|
||||||
(
|
(
|
||||||
|
|
@ -161,7 +184,7 @@ impl Command for ChatHistory {
|
||||||
|
|
||||||
let bref = s.next_msgid().replace('-', "");
|
let bref = s.next_msgid().replace('-', "");
|
||||||
let mut lines: Vec<String> = Vec::new();
|
let mut lines: Vec<String> = Vec::new();
|
||||||
if s.is_member(uid, &key) {
|
if !is_channel || s.is_member(uid, &key) {
|
||||||
if let Some(buf) = s.history.get(&key) {
|
if let Some(buf) = s.history.get(&key) {
|
||||||
// Resolve the selector to a reference position. `msgid=` matches an
|
// Resolve the selector to a reference position. `msgid=` matches an
|
||||||
// exact buffer index (so same-second messages aren't lost);
|
// exact buffer index (so same-second messages aren't lost);
|
||||||
|
|
@ -232,11 +255,12 @@ impl Command for ChatHistory {
|
||||||
};
|
};
|
||||||
for m in picked {
|
for m in picked {
|
||||||
lines.push(format!(
|
lines.push(format!(
|
||||||
"@time={};msgid={};batch={bref} :{} {} {target} :{}",
|
"@time={};msgid={};batch={bref} :{} {} {} :{}",
|
||||||
iso_time(m.ts),
|
iso_time(m.ts),
|
||||||
m.msgid,
|
m.msgid,
|
||||||
m.prefix,
|
m.prefix,
|
||||||
m.verb,
|
m.verb,
|
||||||
|
m.target,
|
||||||
m.text
|
m.text
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
@ -440,7 +464,7 @@ fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool) -> CmdResu
|
||||||
let line = format!(":{prefix} {cmd} {target} :{body}");
|
let line = format!(":{prefix} {cmd} {target} :{body}");
|
||||||
let ctags = s.line_ctags.clone();
|
let ctags = s.line_ctags.clone();
|
||||||
let msgid = s.next_msgid(); // one id shared by every recipient of this message
|
let msgid = s.next_msgid(); // one id shared by every recipient of this message
|
||||||
s.store_history(&key, &prefix, cmd, &body, &msgid); // for CHATHISTORY
|
s.store_history(&key, &prefix, cmd, target, &body, &msgid); // for CHATHISTORY
|
||||||
let members: Vec<Uid> = s
|
let members: Vec<Uid> = s
|
||||||
.channels
|
.channels
|
||||||
.get(&key)
|
.get(&key)
|
||||||
|
|
@ -561,6 +585,16 @@ fn deliver(s: &mut Server, uid: Uid, params: &[String], notice: bool) -> CmdResu
|
||||||
let msgid = s.next_msgid();
|
let msgid = s.next_msgid();
|
||||||
if !silenced {
|
if !silenced {
|
||||||
s.send_tagged(tuid, uid, &ctags, &msgid, &pm);
|
s.send_tagged(tuid, uid, &ctags, &msgid, &pm);
|
||||||
|
// store for CHATHISTORY under the canonical pair key (both parties share it)
|
||||||
|
let sender_nick = prefix.split('!').next().unwrap_or_default();
|
||||||
|
s.store_history(
|
||||||
|
&dm_key(sender_nick, target),
|
||||||
|
&prefix,
|
||||||
|
cmd,
|
||||||
|
target,
|
||||||
|
&text,
|
||||||
|
&msgid,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if s.users
|
if s.users
|
||||||
.get(&uid)
|
.get(&uid)
|
||||||
|
|
|
||||||
|
|
@ -87,12 +87,13 @@ pub fn parse_iso(s: &str) -> Option<u64> {
|
||||||
Some((days * 86400 + h * 3600 + mi * 60 + se).max(0) as u64)
|
Some((days * 86400 + h * 3600 + mi * 60 + se).max(0) as u64)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// One stored channel message, replayed by CHATHISTORY.
|
/// One stored message, replayed by CHATHISTORY.
|
||||||
pub struct HistMsg {
|
pub struct HistMsg {
|
||||||
pub ts: u64,
|
pub ts: u64,
|
||||||
pub msgid: String,
|
pub msgid: String,
|
||||||
pub prefix: String, // sender's nick!user@host at send time
|
pub prefix: String, // sender's nick!user@host at send time
|
||||||
pub verb: &'static str, // "PRIVMSG" or "NOTICE"
|
pub verb: &'static str, // "PRIVMSG" or "NOTICE"
|
||||||
|
pub target: String, // original target (channel, or the DM recipient)
|
||||||
pub text: String,
|
pub text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -229,6 +230,7 @@ impl Server {
|
||||||
key: &str,
|
key: &str,
|
||||||
prefix: &str,
|
prefix: &str,
|
||||||
verb: &'static str,
|
verb: &'static str,
|
||||||
|
target: &str,
|
||||||
text: &str,
|
text: &str,
|
||||||
msgid: &str,
|
msgid: &str,
|
||||||
) {
|
) {
|
||||||
|
|
@ -238,6 +240,7 @@ impl Server {
|
||||||
msgid: msgid.to_string(),
|
msgid: msgid.to_string(),
|
||||||
prefix: prefix.to_string(),
|
prefix: prefix.to_string(),
|
||||||
verb,
|
verb,
|
||||||
|
target: target.to_string(),
|
||||||
text: text.to_string(),
|
text: text.to_string(),
|
||||||
});
|
});
|
||||||
while buf.len() > HISTORY_CAP {
|
while buf.len() > HISTORY_CAP {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue