feat: add last message cache to gateway

This commit is contained in:
alyssa 2025-04-01 10:48:20 +00:00
parent 15c992c572
commit a8664665a6
10 changed files with 172 additions and 18 deletions

View file

@ -8,7 +8,7 @@ use axum::{
use libpk::runtime_config::RuntimeConfig;
use serde_json::{json, to_string};
use tracing::{error, info};
use twilight_model::id::Id;
use twilight_model::id::{marker::ChannelMarker, Id};
use crate::{
discord::{
@ -136,7 +136,10 @@ pub async fn run_server(cache: Arc<DiscordCache>, runtime_config: Arc<RuntimeCon
)
.route(
"/guilds/:guild_id/channels/:channel_id/last_message",
get(|| async { status_code(StatusCode::NOT_IMPLEMENTED, "".to_string()) }),
get(|State(cache): State<Arc<DiscordCache>>, Path((_guild_id, channel_id)): Path<(u64, Id<ChannelMarker>)>| async move {
let lm = cache.get_last_message(channel_id).await;
status_code(StatusCode::FOUND, to_string(&lm).unwrap())
}),
)
.route(

View file

@ -1,6 +1,7 @@
use anyhow::format_err;
use lazy_static::lazy_static;
use std::sync::Arc;
use serde::Serialize;
use std::{collections::HashMap, sync::Arc};
use tokio::sync::RwLock;
use twilight_cache_inmemory::{
model::CachedMember,
@ -8,11 +9,12 @@ use twilight_cache_inmemory::{
traits::CacheableChannel,
InMemoryCache, ResourceType,
};
use twilight_gateway::Event;
use twilight_model::{
channel::{Channel, ChannelType},
guild::{Guild, Member, Permissions},
id::{
marker::{ChannelMarker, GuildMarker, UserMarker},
marker::{ChannelMarker, GuildMarker, MessageMarker, UserMarker},
Id,
},
};
@ -123,16 +125,134 @@ pub fn new() -> DiscordCache {
.build(),
);
DiscordCache(cache, client, RwLock::new(Vec::new()))
DiscordCache(
cache,
client,
RwLock::new(Vec::new()),
RwLock::new(HashMap::new()),
)
}
#[derive(Clone, Serialize)]
pub struct CachedMessage {
id: Id<MessageMarker>,
referenced_message: Option<Id<MessageMarker>>,
author_username: String,
}
#[derive(Clone, Serialize)]
pub struct LastMessageCacheEntry {
pub current: CachedMessage,
pub previous: Option<CachedMessage>,
}
pub struct DiscordCache(
pub Arc<InMemoryCache>,
pub Arc<twilight_http::Client>,
pub RwLock<Vec<u32>>,
pub RwLock<HashMap<Id<ChannelMarker>, LastMessageCacheEntry>>,
);
impl DiscordCache {
pub async fn get_last_message(
&self,
channel: Id<ChannelMarker>,
) -> Option<LastMessageCacheEntry> {
self.3.read().await.get(&channel).cloned()
}
pub async fn update(&self, event: &twilight_gateway::Event) {
self.0.update(event);
match event {
Event::MessageCreate(m) => match self.3.write().await.entry(m.channel_id) {
std::collections::hash_map::Entry::Occupied(mut e) => {
let cur = e.get();
e.insert(LastMessageCacheEntry {
current: CachedMessage {
id: m.id,
referenced_message: m.referenced_message.as_ref().map(|v| v.id),
author_username: m.author.name.clone(),
},
previous: Some(cur.current.clone()),
});
}
std::collections::hash_map::Entry::Vacant(e) => {
e.insert(LastMessageCacheEntry {
current: CachedMessage {
id: m.id,
referenced_message: m.referenced_message.as_ref().map(|v| v.id),
author_username: m.author.name.clone(),
},
previous: None,
});
}
},
Event::MessageDelete(m) => {
self.handle_message_deletion(m.channel_id, vec![m.id]).await;
}
Event::MessageDeleteBulk(m) => {
self.handle_message_deletion(m.channel_id, m.ids.clone())
.await;
}
_ => {}
};
}
async fn handle_message_deletion(
&self,
channel_id: Id<ChannelMarker>,
mids: Vec<Id<MessageMarker>>,
) {
let mut lm = self.3.write().await;
let Some(entry) = lm.get(&channel_id) else {
return;
};
let mut entry = entry.clone();
// if none of the deleted messages are relevant, just return
if !mids.contains(&entry.current.id)
&& entry
.previous
.clone()
.map(|v| !mids.contains(&v.id))
.unwrap_or(false)
{
return;
}
// remove "previous" entry if it was deleted
if let Some(prev) = entry.previous.clone()
&& mids.contains(&prev.id)
{
entry.previous = None;
}
// set "current" entry to "previous" if current entry was deleted
// (if the "previous" entry still exists, it was not deleted)
if let Some(prev) = entry.previous.clone()
&& mids.contains(&entry.current.id)
{
entry.current = prev;
entry.previous = None;
}
// if the current entry was already deleted, but previous wasn't,
// we would've set current to previous
// so if current is deleted this means both current and previous have
// been deleted
// so just drop the cache entry here
if mids.contains(&entry.current.id) && entry.previous.is_none() {
lm.remove(&channel_id);
return;
}
// ok, update the entry
lm.insert(channel_id, entry.clone());
}
pub async fn guild_permissions(
&self,
guild_id: Id<GuildMarker>,

View file

@ -173,7 +173,7 @@ pub async fn runner(
cache.2.write().await.push(shard_id);
}
}
cache.0.update(&event);
cache.update(&event).await;
// okay, we've handled the event internally, let's send it to consumers

View file

@ -115,7 +115,7 @@ impl EventAwaiter {
.remove(&(message.channel_id, message.author.id))
.map(|(timeout, target, options)| {
if let Some(options) = options
&& !options.contains(&message.content)
&& !options.contains(&message.content.to_lowercase())
{
messages.insert(
(message.channel_id, message.author.id),