Answer "what wanted me", in one query

Issue #10. The highlight flag is already recorded per message when it
arrives, by the backend that can actually tell - Discord from its own
resolved mentions array, IRC from the nickname because that is all IRC
has - so collecting them is one query rather than a walk of every
buffer. It has to come from here rather than from what a client has
loaded: a mention worth an inbox is usually in a channel nobody has
open, so the client has never seen it.

Channels only, which is the rule Discord's own list follows. A direct
message is addressed to you in its entirety, so "mentioned" adds
nothing there and it already has its own page. Leaving them in also
dragged along every service robot that talks in a query - NickServ says
your nickname in every line it sends, so the page filled with "You are
now identified for" and buried the mentions it exists to collect.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-28 01:35:56 -04:00
co-authored by Claude Opus 5
parent 2f9c2bc7ff
commit e0c66ca0c9
2 changed files with 63 additions and 0 deletions
+28
View File
@@ -822,6 +822,34 @@ pub async fn dispatch(
}
},
// Everything that mentioned this account, across every conversation.
//
// Asked of the daemon rather than assembled by a client from what it
// happens to have loaded: a mention worth an inbox is usually in a
// channel nobody has opened, so the client has never seen it.
"getMentions" => {
// Channels only, which is the same rule Discord's own mentions
// list follows. A direct message is addressed to you in its
// entirety, so "mentioned" adds nothing there - and it already has
// both its own page and its own tile in the column. Leaving them
// in also dragged along every service robot that talks in a query:
// on IRC, NickServ says your nickname in every line it sends, so
// the page filled with "You are now identified for" and buried
// the mentions it exists to collect.
let buffers: Vec<String> = state
.runtime
.list_buffers()
.into_iter()
.filter(|b| b.kind == "channel")
.map(|b| b.id)
.collect();
let limit = p_i64(params, "limit", 100);
match state.store.mentions(&buffers, limit) {
Ok(messages) => (Some(serde_json::to_value(messages).unwrap()), None),
Err(e) => (None, Some(e.to_string())),
}
}
// Where a file can be put so it can be linked to, and doing it.
//
// Listed rather than hard-coded into each client: the choice of where
+35
View File
@@ -359,6 +359,41 @@ impl Store {
}
impl Store {
/// Every message that mentioned this account, newest first, across all of
/// its conversations.
///
/// One query rather than a walk of every buffer: what makes an inbox worth
/// having is that it answers "what wanted me" in one place, and the
/// highlight flag is already recorded per message at the point it arrives
/// - by the backend that knows whether a mention is real, which for
/// Discord is its own resolved mentions array rather than a guess at the
/// nickname.
pub fn mentions(&self, buffer_ids: &[String], limit: i64) -> Result<Vec<Message>> {
if buffer_ids.is_empty() {
return Ok(Vec::new());
}
let conn = self.conn.lock().unwrap();
let limit = if limit > 0 { limit } else { 100 };
let places = std::iter::repeat("?").take(buffer_ids.len()).collect::<Vec<_>>().join(",");
let sql = format!(
"SELECT msg_id, from_nick, body, ts, is_action, is_highlight, kind, reply_to_id, reply_to_from, reply_to_body, edited, reactions, is_own, avatar_url, embeds, sender_id, attachments, buffer_id
FROM messages
WHERE is_highlight = 1 AND is_own = 0 AND buffer_id IN ({places})
ORDER BY ts DESC LIMIT ?"
);
let mut stmt = conn.prepare(&sql)?;
let mut params: Vec<&dyn rusqlite::ToSql> = buffer_ids.iter().map(|b| b as &dyn rusqlite::ToSql).collect();
params.push(&limit);
let rows = stmt.query_map(params.as_slice(), |row| {
// The buffer is read back per row rather than stamped from a
// parameter, since unlike every other query here this one spans
// conversations and each answer belongs to a different one.
let buffer_id: String = row.get(17)?;
Self::row_to_message(&buffer_id, row)
})?;
rows.collect::<rusqlite::Result<_>>().map_err(Into::into)
}
/// Messages in one buffer whose text contains `query`.
///
/// Deliberately a substring match rather than a word index: scrollback