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(TagMsg),
|
||||
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
|
||||
/// `\0` prefix keeps it from ever colliding with a `#channel` key).
|
||||
fn dm_key(a: &str, b: &str) -> String {
|
||||
|
|
|
|||
|
|
@ -98,6 +98,7 @@ pub const SUPPORTED_CAPS: &[&str] = &[
|
|||
"labeled-response",
|
||||
"batch",
|
||||
"draft/chathistory",
|
||||
"draft/message-redaction",
|
||||
"cap-notify",
|
||||
];
|
||||
|
||||
|
|
@ -124,6 +125,7 @@ pub struct Caps {
|
|||
pub labeled_response: bool, // tag responses to a labeled command with its label
|
||||
pub batch: bool, // understands BATCH framing
|
||||
pub chathistory: bool, // draft/chathistory — can request message history
|
||||
pub message_redaction: bool, // draft/message-redaction — understands REDACT
|
||||
pub cap_notify: bool,
|
||||
}
|
||||
|
||||
|
|
@ -172,6 +174,7 @@ impl Caps {
|
|||
"labeled-response" => self.labeled_response,
|
||||
"batch" => self.batch,
|
||||
"draft/chathistory" => self.chathistory,
|
||||
"draft/message-redaction" => self.message_redaction,
|
||||
"cap-notify" => self.cap_notify,
|
||||
_ => false,
|
||||
}
|
||||
|
|
@ -198,6 +201,7 @@ impl Caps {
|
|||
"labeled-response" => &mut self.labeled_response,
|
||||
"batch" => &mut self.batch,
|
||||
"draft/chathistory" => &mut self.chathistory,
|
||||
"draft/message-redaction" => &mut self.message_redaction,
|
||||
"cap-notify" => &mut self.cap_notify,
|
||||
_ => return false,
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue