redact: draft/message-redaction — author/op can delete a channel message, relayed to capable members + dropped from history
This commit is contained in:
parent
0578b63bd6
commit
e9146167ae
2 changed files with 89 additions and 0 deletions
|
|
@ -124,9 +124,94 @@ pub fn commands() -> Vec<Box<dyn Command>> {
|
||||||
Box::new(Notice),
|
Box::new(Notice),
|
||||||
Box::new(TagMsg),
|
Box::new(TagMsg),
|
||||||
Box::new(ChatHistory),
|
Box::new(ChatHistory),
|
||||||
|
Box::new(Redact),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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,
|
||||||
|
/// and the message is dropped from CHATHISTORY.
|
||||||
|
struct Redact;
|
||||||
|
impl Command for Redact {
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"REDACT"
|
||||||
|
}
|
||||||
|
fn min_params(&self) -> usize {
|
||||||
|
2
|
||||||
|
}
|
||||||
|
fn handle(&self, s: &mut Server, uid: Uid, params: &[String]) -> CmdResult {
|
||||||
|
let target = ¶ms[0];
|
||||||
|
let msgid = ¶ms[1];
|
||||||
|
let reason = params.get(2).cloned().unwrap_or_default();
|
||||||
|
if !target.starts_with('#') {
|
||||||
|
s.fail(
|
||||||
|
uid,
|
||||||
|
"REDACT",
|
||||||
|
"INVALID_TARGET",
|
||||||
|
"REDACT only supports channels",
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
let key = target.to_ascii_lowercase();
|
||||||
|
let Some(author) = s.history.get(&key).and_then(|buf| {
|
||||||
|
buf.iter().find(|m| m.msgid == *msgid).map(|m| {
|
||||||
|
m.prefix
|
||||||
|
.split('!')
|
||||||
|
.next()
|
||||||
|
.unwrap_or("")
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
})
|
||||||
|
}) else {
|
||||||
|
s.fail(
|
||||||
|
uid,
|
||||||
|
"REDACT",
|
||||||
|
"UNKNOWN_MSGID",
|
||||||
|
&format!("No such message id {msgid}"),
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
};
|
||||||
|
let my_nick = s
|
||||||
|
.users
|
||||||
|
.get(&uid)
|
||||||
|
.map(|u| u.nick.to_ascii_lowercase())
|
||||||
|
.unwrap_or_default();
|
||||||
|
if my_nick != author && s.rank(uid, &key) < RANK_HALFOP && !s.is_oper(uid) {
|
||||||
|
s.fail(
|
||||||
|
uid,
|
||||||
|
"REDACT",
|
||||||
|
"REDACT_FORBIDDEN",
|
||||||
|
"You may only redact your own messages",
|
||||||
|
);
|
||||||
|
return CmdResult::Fail;
|
||||||
|
}
|
||||||
|
if let Some(buf) = s.history.get_mut(&key) {
|
||||||
|
buf.retain(|m| m.msgid != *msgid);
|
||||||
|
}
|
||||||
|
let prefix = s.users.get(&uid).map(|u| u.prefix()).unwrap_or_default();
|
||||||
|
let line = if reason.is_empty() {
|
||||||
|
format!(":{prefix} REDACT {target} {msgid}")
|
||||||
|
} else {
|
||||||
|
format!(":{prefix} REDACT {target} {msgid} :{reason}")
|
||||||
|
};
|
||||||
|
let members: Vec<Uid> = s
|
||||||
|
.channels
|
||||||
|
.get(&key)
|
||||||
|
.map(|c| c.members.keys().copied().collect())
|
||||||
|
.unwrap_or_default();
|
||||||
|
for m in members {
|
||||||
|
if s.users
|
||||||
|
.get(&m)
|
||||||
|
.map(|u| u.caps.message_redaction)
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
s.send(m, line.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CmdResult::Ok
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Canonical CHATHISTORY key for a DM between two nicks (order-independent; the
|
/// Canonical CHATHISTORY key for a DM between two nicks (order-independent; the
|
||||||
/// `\0` prefix keeps it from ever colliding with a `#channel` key).
|
/// `\0` prefix keeps it from ever colliding with a `#channel` key).
|
||||||
fn dm_key(a: &str, b: &str) -> String {
|
fn dm_key(a: &str, b: &str) -> String {
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ pub const SUPPORTED_CAPS: &[&str] = &[
|
||||||
"labeled-response",
|
"labeled-response",
|
||||||
"batch",
|
"batch",
|
||||||
"draft/chathistory",
|
"draft/chathistory",
|
||||||
|
"draft/message-redaction",
|
||||||
"cap-notify",
|
"cap-notify",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
@ -124,6 +125,7 @@ pub struct Caps {
|
||||||
pub labeled_response: bool, // tag responses to a labeled command with its label
|
pub labeled_response: bool, // tag responses to a labeled command with its label
|
||||||
pub batch: bool, // understands BATCH framing
|
pub batch: bool, // understands BATCH framing
|
||||||
pub chathistory: bool, // draft/chathistory — can request message history
|
pub chathistory: bool, // draft/chathistory — can request message history
|
||||||
|
pub message_redaction: bool, // draft/message-redaction — understands REDACT
|
||||||
pub cap_notify: bool,
|
pub cap_notify: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -172,6 +174,7 @@ impl Caps {
|
||||||
"labeled-response" => self.labeled_response,
|
"labeled-response" => self.labeled_response,
|
||||||
"batch" => self.batch,
|
"batch" => self.batch,
|
||||||
"draft/chathistory" => self.chathistory,
|
"draft/chathistory" => self.chathistory,
|
||||||
|
"draft/message-redaction" => self.message_redaction,
|
||||||
"cap-notify" => self.cap_notify,
|
"cap-notify" => self.cap_notify,
|
||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
|
|
@ -198,6 +201,7 @@ impl Caps {
|
||||||
"labeled-response" => &mut self.labeled_response,
|
"labeled-response" => &mut self.labeled_response,
|
||||||
"batch" => &mut self.batch,
|
"batch" => &mut self.batch,
|
||||||
"draft/chathistory" => &mut self.chathistory,
|
"draft/chathistory" => &mut self.chathistory,
|
||||||
|
"draft/message-redaction" => &mut self.message_redaction,
|
||||||
"cap-notify" => &mut self.cap_notify,
|
"cap-notify" => &mut self.cap_notify,
|
||||||
_ => return false,
|
_ => return false,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue