atagen

macltok

public
Star0
Watch0
c9effeac326a64a991e3de0049f2ffa1c7a2d18a
3
ssh://brewery@pls.lobotomise.me:33/atagen/macltok.git

Blame: src/search/feed.rs

c9effeac326aatagen2026-10-25 10:32 UTC1
use std::collections::{HashSet, VecDeque};
c9effeac326aatagen2026-10-25 10:32 UTC2
c9effeac326aatagen2026-10-25 10:32 UTC3
use ruma::events::room::message::sanitize::remove_plain_reply_fallback;
c9effeac326aatagen2026-10-25 10:32 UTC4
use serde_json::Value;
c9effeac326aatagen2026-10-25 10:32 UTC5
c9effeac326aatagen2026-10-25 10:32 UTC6
use super::{MessageIndex, index::Doc};
c9effeac326aatagen2026-10-25 10:32 UTC7
use crate::normalise::poll_question;
c9effeac326aatagen2026-10-25 10:32 UTC8
c9effeac326aatagen2026-10-25 10:32 UTC9
const REMEMBERED_REDACTIONS: usize = 10_000;
c9effeac326aatagen2026-10-25 10:32 UTC10
c9effeac326aatagen2026-10-25 10:32 UTC11
#[derive(Clone, Debug, Eq, PartialEq)]
c9effeac326aatagen2026-10-25 10:32 UTC12
pub(crate) struct Edit {
c9effeac326aatagen2026-10-25 10:32 UTC13
    pub(crate) id: String,
c9effeac326aatagen2026-10-25 10:32 UTC14
    pub(crate) target: String,
c9effeac326aatagen2026-10-25 10:32 UTC15
    pub(crate) sender: String,
c9effeac326aatagen2026-10-25 10:32 UTC16
    pub(crate) ts: u64,
c9effeac326aatagen2026-10-25 10:32 UTC17
    pub(crate) text: String,
c9effeac326aatagen2026-10-25 10:32 UTC18
}
c9effeac326aatagen2026-10-25 10:32 UTC19
c9effeac326aatagen2026-10-25 10:32 UTC20
#[derive(Clone, Debug, Eq, PartialEq)]
c9effeac326aatagen2026-10-25 10:32 UTC21
pub(crate) enum Observed {
c9effeac326aatagen2026-10-25 10:32 UTC22
    Message {
c9effeac326aatagen2026-10-25 10:32 UTC23
        key: String,
c9effeac326aatagen2026-10-25 10:32 UTC24
        sender: String,
c9effeac326aatagen2026-10-25 10:32 UTC25
        ts: u64,
c9effeac326aatagen2026-10-25 10:32 UTC26
        text: String,
c9effeac326aatagen2026-10-25 10:32 UTC27
        bundled: Option<Edit>,
c9effeac326aatagen2026-10-25 10:32 UTC28
    },
c9effeac326aatagen2026-10-25 10:32 UTC29
    Edit(Edit),
c9effeac326aatagen2026-10-25 10:32 UTC30
    Redaction {
c9effeac326aatagen2026-10-25 10:32 UTC31
        target: String,
c9effeac326aatagen2026-10-25 10:32 UTC32
    },
c9effeac326aatagen2026-10-25 10:32 UTC33
    Redacted {
c9effeac326aatagen2026-10-25 10:32 UTC34
        key: String,
c9effeac326aatagen2026-10-25 10:32 UTC35
    },
c9effeac326aatagen2026-10-25 10:32 UTC36
    Encrypted {
c9effeac326aatagen2026-10-25 10:32 UTC37
        key: String,
c9effeac326aatagen2026-10-25 10:32 UTC38
        session_id: String,
c9effeac326aatagen2026-10-25 10:32 UTC39
        ts: u64,
c9effeac326aatagen2026-10-25 10:32 UTC40
    },
c9effeac326aatagen2026-10-25 10:32 UTC41
    Other,
c9effeac326aatagen2026-10-25 10:32 UTC42
}
c9effeac326aatagen2026-10-25 10:32 UTC43
c9effeac326aatagen2026-10-25 10:32 UTC44
#[derive(Debug, Eq, PartialEq)]
c9effeac326aatagen2026-10-25 10:32 UTC45
pub(crate) enum Effect {
c9effeac326aatagen2026-10-25 10:32 UTC46
    Indexed(u64),
c9effeac326aatagen2026-10-25 10:32 UTC47
    Removed,
c9effeac326aatagen2026-10-25 10:32 UTC48
    /// an edit was redacted out from under its original, which has to be read again
c9effeac326aatagen2026-10-25 10:32 UTC49
    Refetch(String),
c9effeac326aatagen2026-10-25 10:32 UTC50
    Nothing,
c9effeac326aatagen2026-10-25 10:32 UTC51
}
c9effeac326aatagen2026-10-25 10:32 UTC52
c9effeac326aatagen2026-10-25 10:32 UTC53
/// redactions seen lately, so a stale copy of what they removed can't come back
c9effeac326aatagen2026-10-25 10:32 UTC54
#[derive(Default)]
c9effeac326aatagen2026-10-25 10:32 UTC55
pub(crate) struct Redactions {
c9effeac326aatagen2026-10-25 10:32 UTC56
    order: VecDeque<String>,
c9effeac326aatagen2026-10-25 10:32 UTC57
    seen: HashSet<String>,
c9effeac326aatagen2026-10-25 10:32 UTC58
}
c9effeac326aatagen2026-10-25 10:32 UTC59
c9effeac326aatagen2026-10-25 10:32 UTC60
impl Redactions {
c9effeac326aatagen2026-10-25 10:32 UTC61
    fn insert(&mut self, key: String) {
c9effeac326aatagen2026-10-25 10:32 UTC62
        if self.seen.insert(key.clone()) {
c9effeac326aatagen2026-10-25 10:32 UTC63
            self.order.push_back(key);
c9effeac326aatagen2026-10-25 10:32 UTC64
            if self.order.len() > REMEMBERED_REDACTIONS
c9effeac326aatagen2026-10-25 10:32 UTC65
                && let Some(oldest) = self.order.pop_front()
c9effeac326aatagen2026-10-25 10:32 UTC66
            {
c9effeac326aatagen2026-10-25 10:32 UTC67
                self.seen.remove(&oldest);
c9effeac326aatagen2026-10-25 10:32 UTC68
            }
c9effeac326aatagen2026-10-25 10:32 UTC69
        }
c9effeac326aatagen2026-10-25 10:32 UTC70
    }
c9effeac326aatagen2026-10-25 10:32 UTC71
c9effeac326aatagen2026-10-25 10:32 UTC72
    fn contains(&self, key: &str) -> bool {
c9effeac326aatagen2026-10-25 10:32 UTC73
        self.seen.contains(key)
c9effeac326aatagen2026-10-25 10:32 UTC74
    }
c9effeac326aatagen2026-10-25 10:32 UTC75
}
c9effeac326aatagen2026-10-25 10:32 UTC76
c9effeac326aatagen2026-10-25 10:32 UTC77
fn text_of(event_type: &str, content: &Value) -> Option<String> {
c9effeac326aatagen2026-10-25 10:32 UTC78
    let text = match event_type {
c9effeac326aatagen2026-10-25 10:32 UTC79
        "m.room.message" => {
c9effeac326aatagen2026-10-25 10:32 UTC80
            let msgtype = content.get("msgtype")?.as_str()?;
c9effeac326aatagen2026-10-25 10:32 UTC81
            if msgtype == "m.key.verification.request" {
c9effeac326aatagen2026-10-25 10:32 UTC82
                return None;
c9effeac326aatagen2026-10-25 10:32 UTC83
            }
c9effeac326aatagen2026-10-25 10:32 UTC84
            let mut body = content.get("body")?.as_str()?.to_owned();
c9effeac326aatagen2026-10-25 10:32 UTC85
            if content.pointer("/m.relates_to/m.in_reply_to").is_some() {
c9effeac326aatagen2026-10-25 10:32 UTC86
                body = remove_plain_reply_fallback(&body).to_owned();
c9effeac326aatagen2026-10-25 10:32 UTC87
            }
c9effeac326aatagen2026-10-25 10:32 UTC88
            match content.get("filename").and_then(Value::as_str) {
c9effeac326aatagen2026-10-25 10:32 UTC89
                Some(filename) if filename != body => format!("{body}\n{filename}"),
c9effeac326aatagen2026-10-25 10:32 UTC90
                _ => body,
c9effeac326aatagen2026-10-25 10:32 UTC91
            }
c9effeac326aatagen2026-10-25 10:32 UTC92
        }
c9effeac326aatagen2026-10-25 10:32 UTC93
        "m.sticker" => content.get("body")?.as_str()?.to_owned(),
c9effeac326aatagen2026-10-25 10:32 UTC94
        "m.poll.start" | "org.matrix.msc3381.poll.start" => poll_question(content, event_type)?,
c9effeac326aatagen2026-10-25 10:32 UTC95
        _ => return None,
c9effeac326aatagen2026-10-25 10:32 UTC96
    };
c9effeac326aatagen2026-10-25 10:32 UTC97
    (!text.trim().is_empty()).then_some(text)
c9effeac326aatagen2026-10-25 10:32 UTC98
}
c9effeac326aatagen2026-10-25 10:32 UTC99
c9effeac326aatagen2026-10-25 10:32 UTC100
fn edit_of(value: &Value, event_type: &str) -> Option<Edit> {
c9effeac326aatagen2026-10-25 10:32 UTC101
    let content = value.get("content")?;
c9effeac326aatagen2026-10-25 10:32 UTC102
    if content.pointer("/m.relates_to/rel_type")?.as_str()? != "m.replace" {
c9effeac326aatagen2026-10-25 10:32 UTC103
        return None;
c9effeac326aatagen2026-10-25 10:32 UTC104
    }
c9effeac326aatagen2026-10-25 10:32 UTC105
    Some(Edit {
c9effeac326aatagen2026-10-25 10:32 UTC106
        id: value.get("event_id")?.as_str()?.to_owned(),
c9effeac326aatagen2026-10-25 10:32 UTC107
        target: content
c9effeac326aatagen2026-10-25 10:32 UTC108
            .pointer("/m.relates_to/event_id")?
c9effeac326aatagen2026-10-25 10:32 UTC109
            .as_str()?
c9effeac326aatagen2026-10-25 10:32 UTC110
            .to_owned(),
c9effeac326aatagen2026-10-25 10:32 UTC111
        sender: value.get("sender")?.as_str()?.to_owned(),
c9effeac326aatagen2026-10-25 10:32 UTC112
        ts: value.get("origin_server_ts")?.as_u64()?,
c9effeac326aatagen2026-10-25 10:32 UTC113
        text: text_of(event_type, content.get("m.new_content")?)?,
c9effeac326aatagen2026-10-25 10:32 UTC114
    })
c9effeac326aatagen2026-10-25 10:32 UTC115
}
c9effeac326aatagen2026-10-25 10:32 UTC116
c9effeac326aatagen2026-10-25 10:32 UTC117
pub(crate) fn observe(value: &Value) -> Observed {
c9effeac326aatagen2026-10-25 10:32 UTC118
    observe_inner(value).unwrap_or(Observed::Other)
c9effeac326aatagen2026-10-25 10:32 UTC119
}
c9effeac326aatagen2026-10-25 10:32 UTC120
c9effeac326aatagen2026-10-25 10:32 UTC121
fn observe_inner(value: &Value) -> Option<Observed> {
c9effeac326aatagen2026-10-25 10:32 UTC122
    let event_type = value.get("type")?.as_str()?;
c9effeac326aatagen2026-10-25 10:32 UTC123
    let key = value.get("event_id")?.as_str()?.to_owned();
c9effeac326aatagen2026-10-25 10:32 UTC124
    if event_type == "m.room.redaction" {
c9effeac326aatagen2026-10-25 10:32 UTC125
        let target = value
c9effeac326aatagen2026-10-25 10:32 UTC126
            .get("redacts")
c9effeac326aatagen2026-10-25 10:32 UTC127
            .or_else(|| value.pointer("/content/redacts"))?
c9effeac326aatagen2026-10-25 10:32 UTC128
            .as_str()?
c9effeac326aatagen2026-10-25 10:32 UTC129
            .to_owned();
c9effeac326aatagen2026-10-25 10:32 UTC130
        return Some(Observed::Redaction { target });
c9effeac326aatagen2026-10-25 10:32 UTC131
    }
c9effeac326aatagen2026-10-25 10:32 UTC132
    if value.pointer("/unsigned/redacted_because").is_some() {
c9effeac326aatagen2026-10-25 10:32 UTC133
        return Some(Observed::Redacted { key });
c9effeac326aatagen2026-10-25 10:32 UTC134
    }
c9effeac326aatagen2026-10-25 10:32 UTC135
    let ts = value.get("origin_server_ts")?.as_u64()?;
c9effeac326aatagen2026-10-25 10:32 UTC136
    if event_type == "m.room.encrypted" {
c9effeac326aatagen2026-10-25 10:32 UTC137
        let session_id = value.pointer("/content/session_id")?.as_str()?.to_owned();
c9effeac326aatagen2026-10-25 10:32 UTC138
        return Some(Observed::Encrypted {
c9effeac326aatagen2026-10-25 10:32 UTC139
            key,
c9effeac326aatagen2026-10-25 10:32 UTC140
            session_id,
c9effeac326aatagen2026-10-25 10:32 UTC141
            ts,
c9effeac326aatagen2026-10-25 10:32 UTC142
        });
c9effeac326aatagen2026-10-25 10:32 UTC143
    }
c9effeac326aatagen2026-10-25 10:32 UTC144
    if value
c9effeac326aatagen2026-10-25 10:32 UTC145
        .pointer("/content/m.relates_to/rel_type")
c9effeac326aatagen2026-10-25 10:32 UTC146
        .and_then(Value::as_str)
c9effeac326aatagen2026-10-25 10:32 UTC147
        == Some("m.replace")
c9effeac326aatagen2026-10-25 10:32 UTC148
    {
c9effeac326aatagen2026-10-25 10:32 UTC149
        return edit_of(value, event_type).map(Observed::Edit);
c9effeac326aatagen2026-10-25 10:32 UTC150
    }
c9effeac326aatagen2026-10-25 10:32 UTC151
    let content = value.get("content")?;
c9effeac326aatagen2026-10-25 10:32 UTC152
    let text = text_of(event_type, content)?;
c9effeac326aatagen2026-10-25 10:32 UTC153
    let sender = value.get("sender")?.as_str()?.to_owned();
c9effeac326aatagen2026-10-25 10:32 UTC154
    let bundled = value
c9effeac326aatagen2026-10-25 10:32 UTC155
        .pointer("/unsigned/m.relations/m.replace")
c9effeac326aatagen2026-10-25 10:32 UTC156
        .and_then(|edit| edit_of(edit, event_type))
c9effeac326aatagen2026-10-25 10:32 UTC157
        .filter(|edit| edit.target == key);
c9effeac326aatagen2026-10-25 10:32 UTC158
    Some(Observed::Message {
c9effeac326aatagen2026-10-25 10:32 UTC159
        key,
c9effeac326aatagen2026-10-25 10:32 UTC160
        sender,
c9effeac326aatagen2026-10-25 10:32 UTC161
        ts,
c9effeac326aatagen2026-10-25 10:32 UTC162
        text,
c9effeac326aatagen2026-10-25 10:32 UTC163
        bundled,
c9effeac326aatagen2026-10-25 10:32 UTC164
    })
c9effeac326aatagen2026-10-25 10:32 UTC165
}
c9effeac326aatagen2026-10-25 10:32 UTC166
c9effeac326aatagen2026-10-25 10:32 UTC167
fn newer(ts: u64, id: &str, than: &Doc) -> bool {
c9effeac326aatagen2026-10-25 10:32 UTC168
    (ts, id) > (than.content_ts, than.content.as_str())
c9effeac326aatagen2026-10-25 10:32 UTC169
}
c9effeac326aatagen2026-10-25 10:32 UTC170
c9effeac326aatagen2026-10-25 10:32 UTC171
/// what `observed` does to `room`'s part of the index. edits land on their original under
c9effeac326aatagen2026-10-25 10:32 UTC172
/// its key, the latest by its own sender wins, and one arriving first holds the key
c9effeac326aatagen2026-10-25 10:32 UTC173
/// provisionally until the original turns up
c9effeac326aatagen2026-10-25 10:32 UTC174
pub(crate) fn apply(
c9effeac326aatagen2026-10-25 10:32 UTC175
    index: &mut dyn MessageIndex,
c9effeac326aatagen2026-10-25 10:32 UTC176
    redactions: &mut Redactions,
c9effeac326aatagen2026-10-25 10:32 UTC177
    room: &str,
c9effeac326aatagen2026-10-25 10:32 UTC178
    observed: Observed,
c9effeac326aatagen2026-10-25 10:32 UTC179
) -> Effect {
c9effeac326aatagen2026-10-25 10:32 UTC180
    match observed {
c9effeac326aatagen2026-10-25 10:32 UTC181
        Observed::Message {
c9effeac326aatagen2026-10-25 10:32 UTC182
            key,
c9effeac326aatagen2026-10-25 10:32 UTC183
            sender,
c9effeac326aatagen2026-10-25 10:32 UTC184
            ts,
c9effeac326aatagen2026-10-25 10:32 UTC185
            text,
c9effeac326aatagen2026-10-25 10:32 UTC186
            bundled,
c9effeac326aatagen2026-10-25 10:32 UTC187
        } => {
c9effeac326aatagen2026-10-25 10:32 UTC188
            if redactions.contains(&key) {
c9effeac326aatagen2026-10-25 10:32 UTC189
                return Effect::Nothing;
c9effeac326aatagen2026-10-25 10:32 UTC190
            }
c9effeac326aatagen2026-10-25 10:32 UTC191
            let mut doc = Doc {
c9effeac326aatagen2026-10-25 10:32 UTC192
                key: key.clone(),
c9effeac326aatagen2026-10-25 10:32 UTC193
                room: room.to_owned(),
c9effeac326aatagen2026-10-25 10:32 UTC194
                sender: sender.clone(),
c9effeac326aatagen2026-10-25 10:32 UTC195
                ts,
c9effeac326aatagen2026-10-25 10:32 UTC196
                content: key.clone(),
c9effeac326aatagen2026-10-25 10:32 UTC197
                content_ts: ts,
c9effeac326aatagen2026-10-25 10:32 UTC198
                text,
c9effeac326aatagen2026-10-25 10:32 UTC199
                provisional: false,
c9effeac326aatagen2026-10-25 10:32 UTC200
            };
c9effeac326aatagen2026-10-25 10:32 UTC201
            if let Some(edit) =
c9effeac326aatagen2026-10-25 10:32 UTC202
                bundled.filter(|edit| edit.sender == sender && !redactions.contains(&edit.id))
c9effeac326aatagen2026-10-25 10:32 UTC203
            {
c9effeac326aatagen2026-10-25 10:32 UTC204
                doc.content = edit.id;
c9effeac326aatagen2026-10-25 10:32 UTC205
                doc.content_ts = edit.ts;
c9effeac326aatagen2026-10-25 10:32 UTC206
                doc.text = edit.text;
c9effeac326aatagen2026-10-25 10:32 UTC207
            }
c9effeac326aatagen2026-10-25 10:32 UTC208
            match index.get(&key) {
c9effeac326aatagen2026-10-25 10:32 UTC209
                Some(existing) if !existing.provisional => {
c9effeac326aatagen2026-10-25 10:32 UTC210
                    if !newer(doc.content_ts, &doc.content, &existing) {
c9effeac326aatagen2026-10-25 10:32 UTC211
                        return Effect::Nothing;
c9effeac326aatagen2026-10-25 10:32 UTC212
                    }
c9effeac326aatagen2026-10-25 10:32 UTC213
                }
c9effeac326aatagen2026-10-25 10:32 UTC214
                Some(existing) => {
c9effeac326aatagen2026-10-25 10:32 UTC215
                    if existing.sender == sender && !newer(doc.content_ts, &doc.content, &existing)
c9effeac326aatagen2026-10-25 10:32 UTC216
                    {
c9effeac326aatagen2026-10-25 10:32 UTC217
                        doc.content = existing.content;
c9effeac326aatagen2026-10-25 10:32 UTC218
                        doc.content_ts = existing.content_ts;
c9effeac326aatagen2026-10-25 10:32 UTC219
                        doc.text = existing.text;
c9effeac326aatagen2026-10-25 10:32 UTC220
                    }
c9effeac326aatagen2026-10-25 10:32 UTC221
                }
c9effeac326aatagen2026-10-25 10:32 UTC222
                None => {}
c9effeac326aatagen2026-10-25 10:32 UTC223
            }
c9effeac326aatagen2026-10-25 10:32 UTC224
            index.put(doc);
c9effeac326aatagen2026-10-25 10:32 UTC225
            Effect::Indexed(ts)
c9effeac326aatagen2026-10-25 10:32 UTC226
        }
c9effeac326aatagen2026-10-25 10:32 UTC227
        Observed::Edit(edit) => {
c9effeac326aatagen2026-10-25 10:32 UTC228
            if redactions.contains(&edit.target) || redactions.contains(&edit.id) {
c9effeac326aatagen2026-10-25 10:32 UTC229
                return Effect::Nothing;
c9effeac326aatagen2026-10-25 10:32 UTC230
            }
c9effeac326aatagen2026-10-25 10:32 UTC231
            let doc = match index.get(&edit.target) {
c9effeac326aatagen2026-10-25 10:32 UTC232
                Some(existing) => {
c9effeac326aatagen2026-10-25 10:32 UTC233
                    if existing.sender != edit.sender || !newer(edit.ts, &edit.id, &existing) {
c9effeac326aatagen2026-10-25 10:32 UTC234
                        return Effect::Nothing;
c9effeac326aatagen2026-10-25 10:32 UTC235
                    }
c9effeac326aatagen2026-10-25 10:32 UTC236
                    Doc {
c9effeac326aatagen2026-10-25 10:32 UTC237
                        content: edit.id,
c9effeac326aatagen2026-10-25 10:32 UTC238
                        content_ts: edit.ts,
c9effeac326aatagen2026-10-25 10:32 UTC239
                        text: edit.text,
c9effeac326aatagen2026-10-25 10:32 UTC240
                        ..existing
c9effeac326aatagen2026-10-25 10:32 UTC241
                    }
c9effeac326aatagen2026-10-25 10:32 UTC242
                }
c9effeac326aatagen2026-10-25 10:32 UTC243
                None => Doc {
c9effeac326aatagen2026-10-25 10:32 UTC244
                    key: edit.target,
c9effeac326aatagen2026-10-25 10:32 UTC245
                    room: room.to_owned(),
c9effeac326aatagen2026-10-25 10:32 UTC246
                    sender: edit.sender,
c9effeac326aatagen2026-10-25 10:32 UTC247
                    ts: edit.ts,
c9effeac326aatagen2026-10-25 10:32 UTC248
                    content: edit.id,
c9effeac326aatagen2026-10-25 10:32 UTC249
                    content_ts: edit.ts,
c9effeac326aatagen2026-10-25 10:32 UTC250
                    text: edit.text,
c9effeac326aatagen2026-10-25 10:32 UTC251
                    provisional: true,
c9effeac326aatagen2026-10-25 10:32 UTC252
                },
c9effeac326aatagen2026-10-25 10:32 UTC253
            };
c9effeac326aatagen2026-10-25 10:32 UTC254
            let ts = doc.ts;
c9effeac326aatagen2026-10-25 10:32 UTC255
            index.put(doc);
c9effeac326aatagen2026-10-25 10:32 UTC256
            Effect::Indexed(ts)
c9effeac326aatagen2026-10-25 10:32 UTC257
        }
c9effeac326aatagen2026-10-25 10:32 UTC258
        Observed::Redaction { target } => {
c9effeac326aatagen2026-10-25 10:32 UTC259
            redactions.insert(target.clone());
c9effeac326aatagen2026-10-25 10:32 UTC260
            if index.remove(&target) {
c9effeac326aatagen2026-10-25 10:32 UTC261
                return Effect::Removed;
c9effeac326aatagen2026-10-25 10:32 UTC262
            }
c9effeac326aatagen2026-10-25 10:32 UTC263
            match index.by_content(&target) {
c9effeac326aatagen2026-10-25 10:32 UTC264
                Some(doc) => {
c9effeac326aatagen2026-10-25 10:32 UTC265
                    index.remove(&doc.key);
c9effeac326aatagen2026-10-25 10:32 UTC266
                    if doc.provisional {
c9effeac326aatagen2026-10-25 10:32 UTC267
                        Effect::Removed
c9effeac326aatagen2026-10-25 10:32 UTC268
                    } else {
c9effeac326aatagen2026-10-25 10:32 UTC269
                        Effect::Refetch(doc.key)
c9effeac326aatagen2026-10-25 10:32 UTC270
                    }
c9effeac326aatagen2026-10-25 10:32 UTC271
                }
c9effeac326aatagen2026-10-25 10:32 UTC272
                None => Effect::Nothing,
c9effeac326aatagen2026-10-25 10:32 UTC273
            }
c9effeac326aatagen2026-10-25 10:32 UTC274
        }
c9effeac326aatagen2026-10-25 10:32 UTC275
        Observed::Redacted { key } => {
c9effeac326aatagen2026-10-25 10:32 UTC276
            redactions.insert(key.clone());
c9effeac326aatagen2026-10-25 10:32 UTC277
            if index.remove(&key) {
c9effeac326aatagen2026-10-25 10:32 UTC278
                Effect::Removed
c9effeac326aatagen2026-10-25 10:32 UTC279
            } else {
c9effeac326aatagen2026-10-25 10:32 UTC280
                Effect::Nothing
c9effeac326aatagen2026-10-25 10:32 UTC281
            }
c9effeac326aatagen2026-10-25 10:32 UTC282
        }
c9effeac326aatagen2026-10-25 10:32 UTC283
        Observed::Encrypted { .. } | Observed::Other => Effect::Nothing,
c9effeac326aatagen2026-10-25 10:32 UTC284
    }
c9effeac326aatagen2026-10-25 10:32 UTC285
}
c9effeac326aatagen2026-10-25 10:32 UTC286
c9effeac326aatagen2026-10-25 10:32 UTC287
#[cfg(test)]
c9effeac326aatagen2026-10-25 10:32 UTC288
#[path = "feed_tests.rs"]
c9effeac326aatagen2026-10-25 10:32 UTC289
mod tests;