chanserv: SET TOPICLOCK and KEEPTOPIC (topic subsystem)
Adds topic tracking: a TopicChange event (parsed from InspIRCd FTOPIC, our own changes filtered by SID like FMODE) and a stored topic on the channel, folded through the event log. TOPICLOCK reverts a topic change by a user without op-level access back to the stored topic; KEEPTOPIC remembers the topic and restores it when a registered channel is recreated. Both via the typed ChanSettings path; shown in INFO. Parse + engine tests cover both.
This commit is contained in:
parent
d2c6448c6b
commit
4600ee062c
7 changed files with 135 additions and 7 deletions
|
|
@ -165,6 +165,17 @@ impl Protocol for InspIrcd {
|
|||
_ => vec![],
|
||||
}
|
||||
}
|
||||
// :<src> FTOPIC <chan> <chants> <topicts> [setby] :<topic> — a topic
|
||||
// change. Skip changes we made ourselves so enforcement can't loop.
|
||||
"FTOPIC" => {
|
||||
let a: Vec<&str> = tokens.collect();
|
||||
match (source.as_deref(), a.first()) {
|
||||
(Some(src), Some(chan)) if !src.starts_with(self.sid.as_str()) && chan.starts_with('#') => {
|
||||
vec![NetEvent::TopicChange { channel: chan.to_string(), setter: src.to_string(), topic: trailing(rest) }]
|
||||
}
|
||||
_ => vec![],
|
||||
}
|
||||
}
|
||||
"QUIT" => vec![NetEvent::Quit { uid: source.unwrap_or_default() }],
|
||||
// account-registration relay from an ircd:
|
||||
// ACCTREGISTER <reqid> <origin> <kind> <account> <p2> :<p3>
|
||||
|
|
@ -412,6 +423,21 @@ mod tests {
|
|||
assert!(!ev.iter().any(|e| matches!(e, NetEvent::ChannelModeChange { .. })), "own change filtered: {ev:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parses_ftopic_and_filters_own() {
|
||||
let ev = proto().parse(":0IRAAAAAB FTOPIC #chan 1783845132 1783845140 :Welcome all");
|
||||
assert!(
|
||||
matches!(ev.as_slice(), [NetEvent::TopicChange { channel, setter, topic }] if channel == "#chan" && setter == "0IRAAAAAB" && topic == "Welcome all"),
|
||||
"{ev:?}"
|
||||
);
|
||||
// The burst/RESYNC form carries a setby field before the topic.
|
||||
let ev = proto().parse(":0IRAAAAAB FTOPIC #chan 1783845132 1783845140 someone!u@h :Hi there");
|
||||
assert!(matches!(ev.as_slice(), [NetEvent::TopicChange { topic, .. }] if topic == "Hi there"), "{ev:?}");
|
||||
// Our own topic changes are filtered so enforcement can't loop.
|
||||
let ev = proto().parse(":42S FTOPIC #chan 1 1 :nope");
|
||||
assert!(!ev.iter().any(|e| matches!(e, NetEvent::TopicChange { .. })), "own change filtered: {ev:?}");
|
||||
}
|
||||
|
||||
// FJOIN members and IJOIN both surface as Join events.
|
||||
#[test]
|
||||
fn parses_joins() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue