atagen

macltok

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