Skip to content

Commit aa0b8b4

Browse files
feat: use both serenity and local state, load live reactions
1 parent 00a9c43 commit aa0b8b4

File tree

12 files changed

+377
-83
lines changed

12 files changed

+377
-83
lines changed

Diff for: Cargo.lock

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Diff for: src/chat/src/channel.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use tokio::sync::broadcast;
22

33
use crate::{async_list::AsyncList, message::Message};
4+
use crate::reaction::ReactionOperation;
45

56
pub trait Channel: AsyncList<Content = Self::Message> + Send + Sync + Clone {
67
type Message: Message;
78

8-
fn get_receiver(&self) -> broadcast::Receiver<Self::Message>;
9+
fn get_message_receiver(&self) -> broadcast::Receiver<Self::Message>;
10+
fn get_reaction_receiver(&self) -> broadcast::Receiver<(String, ReactionOperation)>;
911

1012
fn send_message(&self, content: String, nonce: String) -> Self::Message;
1113
}

Diff for: src/chat/src/message.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use chrono::{DateTime, Utc};
22
use gpui::Element;
33

44
use crate::async_list::AsyncListItem;
5-
use crate::reaction::MessageReaction;
5+
use crate::reaction::ReactionList;
66

77
pub trait Message: Clone + AsyncListItem + Send {
88
fn get_author(&self) -> &impl MessageAuthor;
@@ -11,7 +11,7 @@ pub trait Message: Clone + AsyncListItem + Send {
1111
fn get_nonce(&self) -> Option<&String>;
1212
fn should_group(&self, previous: &Self) -> bool;
1313
fn get_timestamp(&self) -> Option<DateTime<Utc>>;
14-
fn get_reactions(&self) -> Vec<impl MessageReaction>;
14+
fn get_reactions(&mut self) -> &mut impl ReactionList;
1515
}
1616

1717
pub trait MessageAuthor: PartialEq + Eq {

Diff for: src/chat/src/reaction.rs

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use std::fmt::Debug;
12
use gpui::Rgba;
23

34
#[derive(Copy, Clone, Debug)]
@@ -6,15 +7,33 @@ pub enum MessageReactionType {
67
Burst,
78
}
89

9-
#[derive(Clone, Debug)]
10+
#[derive(Clone, Debug, PartialEq)]
1011
pub enum ReactionEmoji {
1112
Simple(String),
1213
Custom { url: String, animated: bool, name: Option<String> },
1314
}
1415

15-
pub trait MessageReaction {
16+
pub trait MessageReaction: Debug {
1617
fn get_count(&self, kind: Option<MessageReactionType>) -> u64;
1718
fn get_self_reaction(&self) -> Option<MessageReactionType>;
1819
fn get_emoji(&self) -> ReactionEmoji;
1920
fn get_burst_colors(&self) -> Vec<Rgba>;
21+
fn increment(&mut self, kind: MessageReactionType, user_is_self: bool, by: isize);
22+
}
23+
24+
#[derive(Clone, Debug)]
25+
pub enum ReactionOperation {
26+
Add(ReactionEmoji, MessageReactionType),
27+
AddSelf(ReactionEmoji, MessageReactionType),
28+
Remove(ReactionEmoji),
29+
RemoveSelf(ReactionEmoji),
30+
RemoveAll,
31+
RemoveEmoji(ReactionEmoji),
32+
}
33+
34+
pub trait ReactionList {
35+
fn get_reactions(&self) -> &Vec<impl MessageReaction>;
36+
fn get_reaction(&self, emoji: &ReactionEmoji) -> Option<&impl MessageReaction>;
37+
fn increment(&mut self, emoji: &ReactionEmoji, kind: MessageReactionType, user_is_self: bool, by: isize);
38+
fn apply(&mut self, operation: ReactionOperation);
2039
}

Diff for: src/discord/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ serenity = { git = "https://github.com/scopeclient/serenity", version = "0.12" }
1010
tokio = "1.41.1"
1111
chrono.workspace = true
1212
scope-backend-cache = { version = "0.1.0", path = "../cache" }
13+
log = "0.4.22"

Diff for: src/discord/src/channel/mod.rs

+23-14
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,40 @@
11
use std::sync::Arc;
22

3+
use crate::{
4+
client::DiscordClient,
5+
message::{content::DiscordMessageContent, DiscordMessage},
6+
snowflake::Snowflake,
7+
};
38
use scope_backend_cache::async_list::{refcacheslice::Exists, AsyncListCache};
9+
use scope_chat::reaction::ReactionOperation;
410
use scope_chat::{
511
async_list::{AsyncList, AsyncListIndex, AsyncListItem, AsyncListResult},
612
channel::Channel,
713
};
814
use serenity::all::{GetMessages, MessageId, Timestamp};
915
use tokio::sync::{broadcast, Mutex, Semaphore};
1016

11-
use crate::{
12-
client::DiscordClient,
13-
message::{content::DiscordMessageContent, DiscordMessage},
14-
snowflake::Snowflake,
15-
};
16-
1717
pub struct DiscordChannel {
1818
channel_id: Snowflake,
19-
receiver: broadcast::Receiver<DiscordMessage>,
19+
message_receiver: broadcast::Receiver<DiscordMessage>,
20+
reaction_receiver: broadcast::Receiver<(String, ReactionOperation)>,
2021
client: Arc<DiscordClient>,
2122
cache: Arc<Mutex<AsyncListCache<DiscordMessage>>>,
2223
blocker: Semaphore,
2324
}
2425

2526
impl DiscordChannel {
2627
pub async fn new(client: Arc<DiscordClient>, channel_id: Snowflake) -> Self {
27-
let (sender, receiver) = broadcast::channel(10);
28+
let (message_sender, message_receiver) = broadcast::channel(10);
29+
client.add_channel_message_sender(channel_id, message_sender).await;
2830

29-
client.add_channel_message_sender(channel_id, sender).await;
31+
let (reaction_sender, reaction_receiver) = broadcast::channel(10);
32+
client.add_channel_reaction_sender(channel_id, reaction_sender).await;
3033

3134
DiscordChannel {
3235
channel_id,
33-
receiver,
36+
message_receiver,
37+
reaction_receiver,
3438
client,
3539
cache: Arc::new(Mutex::new(AsyncListCache::new())),
3640
blocker: Semaphore::new(1),
@@ -41,8 +45,12 @@ impl DiscordChannel {
4145
impl Channel for DiscordChannel {
4246
type Message = DiscordMessage;
4347

44-
fn get_receiver(&self) -> broadcast::Receiver<Self::Message> {
45-
self.receiver.resubscribe()
48+
fn get_message_receiver(&self) -> broadcast::Receiver<Self::Message> {
49+
self.message_receiver.resubscribe()
50+
}
51+
52+
fn get_reaction_receiver(&self) -> broadcast::Receiver<(String, ReactionOperation)> {
53+
self.reaction_receiver.resubscribe()
4654
}
4755

4856
fn send_message(&self, content: String, nonce: String) -> DiscordMessage {
@@ -61,7 +69,7 @@ impl Channel for DiscordChannel {
6169
id: Snowflake { content: 0 },
6270
nonce: Some(nonce),
6371
creation_time: Timestamp::now(),
64-
reactions: vec![],
72+
reactions: Default::default(),
6573
}
6674
}
6775
}
@@ -233,7 +241,8 @@ impl Clone for DiscordChannel {
233241
fn clone(&self) -> Self {
234242
Self {
235243
channel_id: self.channel_id,
236-
receiver: self.receiver.resubscribe(),
244+
message_receiver: self.message_receiver.resubscribe(),
245+
reaction_receiver: self.reaction_receiver.resubscribe(),
237246
client: self.client.clone(),
238247
cache: self.cache.clone(),
239248
blocker: Semaphore::new(1),

Diff for: src/discord/src/client.rs

+67-11
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,6 @@ use std::{
33
sync::{Arc, OnceLock},
44
};
55

6-
use serenity::{
7-
all::{Cache, ChannelId, Context, CreateMessage, EventHandler, GatewayIntents, GetMessages, Http, Message, MessageId, Ready},
8-
async_trait,
9-
};
10-
use tokio::sync::{broadcast, RwLock};
11-
126
use crate::{
137
channel::DiscordChannel,
148
message::{
@@ -17,6 +11,14 @@ use crate::{
1711
},
1812
snowflake::Snowflake,
1913
};
14+
use scope_chat::reaction::{MessageReactionType, ReactionOperation};
15+
use serenity::all::Reaction;
16+
use serenity::{
17+
all::{Cache, ChannelId, Context, CreateMessage, EventHandler, GatewayIntents, GetMessages, Http, Message, MessageId, Ready},
18+
async_trait,
19+
};
20+
use tokio::sync::{broadcast, RwLock};
21+
use crate::message::reaction::discord_reaction_to_emoji;
2022

2123
#[allow(dead_code)]
2224
struct SerenityClient {
@@ -29,6 +31,7 @@ struct SerenityClient {
2931
#[derive(Default)]
3032
pub struct DiscordClient {
3133
channel_message_event_handlers: RwLock<HashMap<Snowflake, Vec<broadcast::Sender<DiscordMessage>>>>,
34+
channel_reaction_event_handlers: RwLock<HashMap<Snowflake, Vec<broadcast::Sender<(String, ReactionOperation)>>>>,
3235
client: OnceLock<SerenityClient>,
3336
user: OnceLock<DiscordMessageAuthor>,
3437
channels: RwLock<HashMap<Snowflake, Arc<DiscordChannel>>>,
@@ -38,10 +41,7 @@ impl DiscordClient {
3841
pub async fn new(token: String) -> Arc<DiscordClient> {
3942
let client = Arc::new(DiscordClient::default());
4043

41-
let mut discord = serenity::Client::builder(token, GatewayIntents::all())
42-
.event_handler_arc(client.clone())
43-
.await
44-
.expect("Error creating client");
44+
let mut discord = serenity::Client::builder(token, GatewayIntents::all()).event_handler_arc(client.clone()).await.expect("Error creating client");
4545

4646
let _ = client.client.set(SerenityClient {
4747
// voice_manager: discord.voice_manager.clone(),
@@ -70,6 +70,10 @@ impl DiscordClient {
7070
self.channel_message_event_handlers.write().await.entry(channel).or_default().push(sender);
7171
}
7272

73+
pub async fn add_channel_reaction_sender(&self, channel: Snowflake, sender: broadcast::Sender<(String, ReactionOperation)>) {
74+
self.channel_reaction_event_handlers.write().await.entry(channel).or_default().push(sender);
75+
}
76+
7377
pub async fn channel(self: Arc<Self>, channel_id: Snowflake) -> Arc<DiscordChannel> {
7478
let self_clone = self.clone();
7579
let mut channels = self_clone.channels.write().await;
@@ -107,8 +111,15 @@ impl DiscordClient {
107111
// FIXME: proper error handling
108112
Some(ChannelId::new(channel_id.content).message(self.discord().http.clone(), MessageId::new(message_id.content)).await.unwrap())
109113
}
110-
}
111114

115+
async fn send_reaction_operation(&self, channel_id: Snowflake, message_id: MessageId, operation: ReactionOperation) {
116+
if let Some(vec) = self.channel_reaction_event_handlers.read().await.get(&channel_id) {
117+
for sender in vec {
118+
let _ = sender.send((message_id.to_string(), operation.clone()));
119+
}
120+
}
121+
}
122+
}
112123

113124
#[async_trait]
114125
impl EventHandler for DiscordClient {
@@ -130,4 +141,49 @@ impl EventHandler for DiscordClient {
130141
}
131142
}
132143
}
144+
145+
async fn reaction_add(&self, _: Context, reaction: Reaction) {
146+
let channel_snowflake = reaction.channel_id.into();
147+
148+
let ty = if reaction.burst {
149+
MessageReactionType::Burst
150+
} else {
151+
MessageReactionType::Normal
152+
};
153+
154+
let emoji = discord_reaction_to_emoji(&reaction.emoji);
155+
156+
let operation = if reaction.member.is_none_or(|member| member.user.id.get() == self.user().id.parse::<u64>().unwrap()) {
157+
ReactionOperation::AddSelf(emoji, ty)
158+
} else {
159+
ReactionOperation::Add(emoji, ty)
160+
};
161+
162+
self.send_reaction_operation(channel_snowflake, reaction.message_id, operation).await;
163+
}
164+
165+
async fn reaction_remove(&self, _: Context, reaction: Reaction) {
166+
let channel_snowflake = reaction.channel_id.into();
167+
168+
let emoji = discord_reaction_to_emoji(&reaction.emoji);
169+
170+
let operation = if reaction.member.is_none_or(|member| member.user.id.get() == self.user().id.parse::<u64>().unwrap()) {
171+
ReactionOperation::RemoveSelf(emoji)
172+
} else {
173+
ReactionOperation::Remove(emoji)
174+
};
175+
176+
self.send_reaction_operation(channel_snowflake, reaction.message_id, operation).await;
177+
}
178+
179+
async fn reaction_remove_all(&self, _: Context, channel_id: ChannelId, removed_from_message_id: MessageId) {
180+
let channel_snowflake = channel_id.into();
181+
self.send_reaction_operation(channel_snowflake, removed_from_message_id, ReactionOperation::RemoveAll).await;
182+
}
183+
184+
async fn reaction_remove_emoji(&self, _: Context, removed_reactions: Reaction) {
185+
let channel_snowflake = removed_reactions.channel_id.into();
186+
let emoji = discord_reaction_to_emoji(&removed_reactions.emoji);
187+
self.send_reaction_operation(channel_snowflake, removed_reactions.message_id, ReactionOperation::RemoveEmoji(emoji)).await;
188+
}
133189
}

Diff for: src/discord/src/message/mod.rs

+8-7
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1+
use crate::message::reaction::DiscordReactionList;
12
use crate::snowflake::Snowflake;
23
use author::{DiscordMessageAuthor, DisplayName};
34
use chrono::{DateTime, Utc};
45
use content::DiscordMessageContent;
56
use gpui::{Element, IntoElement};
67
use scope_chat::message::MessageAuthor;
7-
use scope_chat::reaction::MessageReaction;
8+
use scope_chat::reaction::ReactionList;
89
use scope_chat::{async_list::AsyncListItem, message::Message};
910
use serenity::all::Nonce;
1011

1112
pub mod author;
1213
pub mod content;
13-
mod reaction;
14+
pub mod reaction;
1415

1516
#[derive(Clone, Debug)]
1617
pub struct DiscordMessage {
@@ -19,12 +20,12 @@ pub struct DiscordMessage {
1920
pub id: Snowflake,
2021
pub nonce: Option<String>,
2122
pub creation_time: serenity::model::Timestamp,
22-
pub reactions: Vec<reaction::DiscordMessageReaction>,
23+
pub reactions: DiscordReactionList,
2324
}
2425

2526
impl DiscordMessage {
2627
pub fn from_serenity(msg: &serenity::all::Message) -> Self {
27-
let reactions = &msg.reactions.iter().map(|r| reaction::DiscordMessageReaction::from_serenity(r)).collect::<Vec<_>>();
28+
let reactions = msg.reactions.iter().map(|r| reaction::DiscordMessageReaction::from_message(r)).collect::<Vec<_>>();
2829
if !reactions.is_empty() {
2930
println!("Reactions: {:?}", reactions);
3031
}
@@ -44,7 +45,7 @@ impl DiscordMessage {
4445
Nonce::String(s) => s,
4546
}),
4647
creation_time: msg.timestamp,
47-
reactions: reactions.clone(),
48+
reactions: DiscordReactionList::new(reactions),
4849
}
4950
}
5051
}
@@ -78,8 +79,8 @@ impl Message for DiscordMessage {
7879
DateTime::from_timestamp_millis(ts)
7980
}
8081

81-
fn get_reactions(&self) -> Vec<impl MessageReaction> {
82-
self.reactions.clone()
82+
fn get_reactions(&mut self) -> &mut impl ReactionList {
83+
&mut self.reactions
8384
}
8485
}
8586

0 commit comments

Comments
 (0)