From a2634425eb306725094abe3e3f05d32052877945 Mon Sep 17 00:00:00 2001 From: Rose Hall Date: Thu, 21 Nov 2024 17:34:29 -0500 Subject: [PATCH 1/5] Temp commit --- Cargo.lock | 42 ++++++- src/chat/src/async_list.rs | 27 ++++- src/chat/src/channel.rs | 24 +++- src/chat/src/client.rs | 17 +++ src/chat/src/lib.rs | 1 + src/chat/src/message.rs | 53 +++++++-- src/discord/Cargo.toml | 6 + src/discord/src/channel/mod.rs | 125 ++++++++++++-------- src/discord/src/client.rs | 114 ++++++++++++------ src/discord/src/message/author.rs | 101 ++++++++++++---- src/discord/src/message/content.rs | 22 +++- src/discord/src/message/mod.rs | 181 ++++++++++++++++++++++------- src/discord/src/snowflake.rs | 36 +++++- src/ui/src/app.rs | 16 +-- src/ui/src/channel/message.rs | 31 ++--- src/ui/src/channel/message_list.rs | 19 ++- src/ui/src/channel/mod.rs | 13 ++- 17 files changed, 621 insertions(+), 207 deletions(-) create mode 100644 src/chat/src/client.rs diff --git a/Cargo.lock b/Cargo.lock index ac5bc33..f2da13e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -478,6 +478,12 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" +[[package]] +name = "atomic_refcell" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e67cd8309bbd06cd603a9e693a784ac2e5d1e955f11286e355089fcab3047c" + [[package]] name = "autocfg" version = "1.4.0" @@ -549,7 +555,7 @@ dependencies = [ "bitflags 2.6.0", "cexpr", "clang-sys", - "itertools 0.11.0", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -1372,6 +1378,20 @@ dependencies = [ "serde", ] +[[package]] +name = "dashmap" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5041cc499144891f3790297212f32a74fb938e5136a14943f338ef9e0ae276cf" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + [[package]] name = "data-encoding" version = "2.6.0" @@ -3582,7 +3602,7 @@ checksum = "c325dfab65f261f386debee8b0969da215b3fa0037e74c8a1234db7ba986d803" dependencies = [ "crossbeam-channel", "crossbeam-utils", - "dashmap", + "dashmap 5.5.3", "skeptic", "smallvec", "tagptr", @@ -3860,7 +3880,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" dependencies = [ - "proc-macro-crate 1.3.1", + "proc-macro-crate 3.2.0", "proc-macro2", "quote", "syn 2.0.87", @@ -4626,6 +4646,12 @@ dependencies = [ "bytemuck", ] +[[package]] +name = "querystring" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9318ead08c799aad12a55a3e78b82e0b6167271ffd1f627b758891282f739187" + [[package]] name = "quick-error" version = "2.0.1" @@ -5472,12 +5498,18 @@ dependencies = [ name = "scope-backend-discord" version = "0.1.0" dependencies = [ + "atomic_refcell", + "catty", "chrono", + "dashmap 6.1.0", "gpui", + "querystring", + "rand 0.8.5", "scope-backend-cache", "scope-chat", "serenity", "tokio", + "url", ] [[package]] @@ -5721,7 +5753,7 @@ dependencies = [ "bytes", "chrono", "command_attr", - "dashmap", + "dashmap 5.5.3", "flate2", "futures", "fxhash", @@ -6777,7 +6809,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "549e54551d85ba6718a95333d9bc4367f69793d7aba638de30f8d25a1f554a1d" dependencies = [ "chrono", - "dashmap", + "dashmap 5.5.3", "hashbrown 0.14.5", "mini-moka", "parking_lot", diff --git a/src/chat/src/async_list.rs b/src/chat/src/async_list.rs index c94cb83..3a5a6f4 100644 --- a/src/chat/src/async_list.rs +++ b/src/chat/src/async_list.rs @@ -1,4 +1,4 @@ -use std::{fmt::Debug, future::Future, hash::Hash}; +use std::{fmt::Debug, future::Future, hash::Hash, sync::Arc}; pub trait AsyncList { type Content: AsyncListItem; @@ -12,7 +12,30 @@ pub trait AsyncList { fn bounded_at_bottom_by(&self) -> impl Future::Identifier>>; } -pub trait AsyncListItem: Clone + Debug { +impl AsyncList for Arc { + type Content = L::Content; + + fn bounded_at_bottom_by(&self) -> impl Future::Identifier>> { + (**self).bounded_at_bottom_by() + } + + fn bounded_at_top_by(&self) -> impl Future::Identifier>> { + (**self).bounded_at_top_by() + } + + fn find(&self, identifier: &::Identifier) -> impl Future> { + (**self).find(identifier) + } + + fn get( + &self, + index: AsyncListIndex<::Identifier>, + ) -> impl Future>> + Send { + (**self).get(index) + } +} + +pub trait AsyncListItem: Clone { type Identifier: Eq + Hash + Clone + Send + Debug; fn get_list_identifier(&self) -> Self::Identifier; diff --git a/src/chat/src/channel.rs b/src/chat/src/channel.rs index 12d2a3b..066bb94 100644 --- a/src/chat/src/channel.rs +++ b/src/chat/src/channel.rs @@ -1,11 +1,33 @@ +use std::{fmt::Debug, sync::Arc}; + use tokio::sync::broadcast; use crate::{async_list::AsyncList, message::Message}; pub trait Channel: AsyncList + Send + Sync + Clone { - type Message: Message; + type Message: Message; + type Identifier: Sized + Copy + Clone + Debug + Eq + PartialEq; fn get_receiver(&self) -> broadcast::Receiver; fn send_message(&self, content: String, nonce: String) -> Self::Message; + + fn get_identifier(&self) -> Self::Identifier; +} + +impl Channel for Arc { + type Identifier = C::Identifier; + type Message = C::Message; + + fn get_identifier(&self) -> Self::Identifier { + (**self).get_identifier() + } + + fn get_receiver(&self) -> broadcast::Receiver { + (**self).get_receiver() + } + + fn send_message(&self, content: String, nonce: String) -> Self::Message { + (**self).send_message(content, nonce) + } } diff --git a/src/chat/src/client.rs b/src/chat/src/client.rs new file mode 100644 index 0000000..6bbf400 --- /dev/null +++ b/src/chat/src/client.rs @@ -0,0 +1,17 @@ +use std::{fmt::Debug, future::Future}; + +use crate::channel::Channel; + +pub trait ClientConstructor { + type ConstructorArguments; + type ConstructorFailure; + + fn construct(args: Self::ConstructorArguments) -> impl Future>; +} + +pub trait Client { + type Identifier: Sized + Copy + Clone + Debug + Eq + PartialEq; + type Channel: Channel; + + fn channel(identifier: Self::Identifier) -> impl Future>; +} diff --git a/src/chat/src/lib.rs b/src/chat/src/lib.rs index 29fe6c3..0f66697 100644 --- a/src/chat/src/lib.rs +++ b/src/chat/src/lib.rs @@ -1,3 +1,4 @@ pub mod async_list; pub mod channel; +pub mod client; pub mod message; diff --git a/src/chat/src/message.rs b/src/chat/src/message.rs index 5ffea52..54b3c70 100644 --- a/src/chat/src/message.rs +++ b/src/chat/src/message.rs @@ -1,20 +1,55 @@ +use std::fmt::Debug; + use chrono::{DateTime, Utc}; -use gpui::Element; +use gpui::{IntoElement, Render, View, WindowContext}; use crate::async_list::AsyncListItem; pub trait Message: Clone + AsyncListItem + Send { - fn get_author(&self) -> &impl MessageAuthor; - fn get_content(&self) -> impl Element; - fn get_identifier(&self) -> String; - fn get_nonce(&self) -> Option<&String>; + type Identifier: Sized + Copy + Clone + Debug + Eq + PartialEq; + type Author: MessageAuthor::Identifier>; + type Content: Render; + + fn get_author(&self) -> Self::Author; + fn get_content(&self, cx: &mut WindowContext) -> View; + fn get_identifier(&self) -> Option<::Identifier>; + fn get_nonce(&self) -> impl PartialEq; fn should_group(&self, previous: &Self) -> bool; fn get_timestamp(&self) -> Option>; } +#[derive(Debug, Clone, Copy)] +pub struct IconRenderConfig { + size: usize, +} + +impl Default for IconRenderConfig { + fn default() -> Self { + IconRenderConfig { size: 1024 } + } +} + +impl IconRenderConfig { + pub fn small() -> Self { + IconRenderConfig { size: 32 } + } + + pub fn with_size(mut self, size: usize) -> IconRenderConfig { + self.size = size; + self + } + + pub fn size(&self) -> usize { + self.size + } +} + pub trait MessageAuthor: PartialEq + Eq { - fn get_display_name(&self) -> impl Element; - fn get_icon(&self) -> String; - fn get_small_icon(&self) -> String; - fn get_id(&self) -> String; + type Identifier: Sized + Copy + Clone + Debug + Eq + PartialEq; + type DisplayName: IntoElement + Clone; + type Icon: IntoElement + Clone; + + fn get_display_name(&self) -> Self::DisplayName; + fn get_icon(&self, config: IconRenderConfig) -> Self::Icon; + fn get_identifier(&self) -> Self::Identifier; } diff --git a/src/discord/Cargo.toml b/src/discord/Cargo.toml index a4996d6..64d5b94 100644 --- a/src/discord/Cargo.toml +++ b/src/discord/Cargo.toml @@ -10,3 +10,9 @@ serenity = { git = "https://github.com/scopeclient/serenity", version = "0.12" } tokio = "1.41.1" chrono.workspace = true scope-backend-cache = { version = "0.1.0", path = "../cache" } +url = "2.5.3" +querystring = "1.1.0" +catty = "0.1.5" +atomic_refcell = "0.1.13" +rand = "0.8.5" +dashmap = "6.1.0" diff --git a/src/discord/src/channel/mod.rs b/src/discord/src/channel/mod.rs index eeb6590..4f14d9b 100644 --- a/src/discord/src/channel/mod.rs +++ b/src/discord/src/channel/mod.rs @@ -1,21 +1,23 @@ -use std::sync::Arc; +use std::sync::{Arc, OnceLock}; +use chrono::Utc; use scope_backend_cache::async_list::{refcacheslice::Exists, AsyncListCache}; use scope_chat::{ async_list::{AsyncList, AsyncListIndex, AsyncListItem, AsyncListResult}, channel::Channel, }; -use serenity::all::{GetMessages, MessageId, Timestamp}; +use serenity::all::{ChannelId, GetMessages, MessageId}; use tokio::sync::{broadcast, Mutex, Semaphore}; use crate::{ client::DiscordClient, - message::{content::DiscordMessageContent, DiscordMessage}, + message::{DiscordMessage, DiscordMessageData}, snowflake::Snowflake, }; pub struct DiscordChannel { - channel_id: Snowflake, + channel: Arc, + receiver: broadcast::Receiver, client: Arc, cache: Arc>>, @@ -23,13 +25,15 @@ pub struct DiscordChannel { } impl DiscordChannel { - pub async fn new(client: Arc, channel_id: Snowflake) -> Self { + pub(crate) async fn new(client: Arc, channel_id: ChannelId) -> Self { let (sender, receiver) = broadcast::channel(10); client.add_channel_message_sender(channel_id, sender).await; + let channel = Arc::new(channel_id.to_channel(client.discord()).await.unwrap()); + DiscordChannel { - channel_id, + channel, receiver, client, cache: Arc::new(Mutex::new(AsyncListCache::new())), @@ -40,6 +44,7 @@ impl DiscordChannel { impl Channel for DiscordChannel { type Message = DiscordMessage; + type Identifier = Snowflake; fn get_receiver(&self) -> broadcast::Receiver { self.receiver.resubscribe() @@ -47,7 +52,7 @@ impl Channel for DiscordChannel { fn send_message(&self, content: String, nonce: String) -> DiscordMessage { let client = self.client.clone(); - let channel_id = self.channel_id; + let channel_id = self.channel.id(); let sent_content = content.clone(); let sent_nonce = nonce.clone(); @@ -56,13 +61,21 @@ impl Channel for DiscordChannel { }); DiscordMessage { - content: DiscordMessageContent { content, is_pending: true }, - author: self.client.user().clone(), - id: Snowflake { content: 0 }, - nonce: Some(nonce), - creation_time: Timestamp::now(), + channel: self.channel.clone(), + client: self.client.clone(), + data: DiscordMessageData::Pending { + nonce, + content, + sent_time: Utc::now(), + list_item_id: Snowflake::random(), + }, + content: OnceLock::new(), } } + + fn get_identifier(&self) -> Self::Identifier { + self.channel.id().into() + } } const DISCORD_MESSAGE_BATCH_SIZE: u8 = 50; @@ -76,7 +89,16 @@ impl AsyncList for DiscordChannel { return Some(v); }; - self.client.get_messages(self.channel_id, GetMessages::new().limit(1)).await.first().map(|v| Snowflake { content: v.id.get() }) + match &*self.channel { + serenity::model::channel::Channel::Guild(guild_channel) => guild_channel.messages(self.client.discord(), GetMessages::new().limit(1)).await, + serenity::model::channel::Channel::Private(private_channel) => { + private_channel.messages(self.client.discord(), GetMessages::new().limit(1)).await + } + _ => unimplemented!(), + } + .unwrap() + .first() + .map(|v| Snowflake(v.id.get())) } async fn bounded_at_top_by(&self) -> Option { @@ -91,8 +113,6 @@ impl AsyncList for DiscordChannel { } async fn find(&self, identifier: &Snowflake) -> Option { - let permit = self.blocker.acquire().await; - let lock = self.cache.lock().await; let cache_value = lock.find(identifier); @@ -102,11 +122,13 @@ impl AsyncList for DiscordChannel { return Some(v); } - let result = self.client.get_specific_message(self.channel_id, *identifier).await.map(|v| DiscordMessage::from_serenity(&v)); + let result = self.client.get_specific_message(self.channel.id(), MessageId::new(identifier.0)).await; - drop(permit); + if result.is_none() { + return None; + } - result + Some(DiscordMessage::load_serenity(self.client.clone(), Arc::new(result.unwrap())).await) } async fn get(&self, index: AsyncListIndex) -> Option> { @@ -120,7 +142,7 @@ impl AsyncList for DiscordChannel { return None; } - let result: Option; + let mut result: Option = None; let mut is_top = false; let mut is_bottom = false; @@ -131,25 +153,24 @@ impl AsyncList for DiscordChannel { unimplemented!() } - let v = self.client.get_messages(self.channel_id, GetMessages::new().limit(DISCORD_MESSAGE_BATCH_SIZE)).await; + let v = self.client.get_messages(self.channel.id(), GetMessages::new().limit(DISCORD_MESSAGE_BATCH_SIZE)).await; let is_end = v.len() == DISCORD_MESSAGE_BATCH_SIZE as usize; is_bottom = true; is_top = v.len() == 1; - result = v.first().map(DiscordMessage::from_serenity); - - let mut iter = v.iter(); + let mut iter = v.into_iter(); let v = iter.next(); if let Some(v) = v { - let msg = DiscordMessage::from_serenity(v); + let msg = DiscordMessage::load_serenity(self.client.clone(), Arc::new(v)).await; let mut id = msg.get_list_identifier(); - lock.append_bottom(msg); + lock.append_bottom(msg.clone()); + result = Some(msg); for message in iter { - let msg = DiscordMessage::from_serenity(message); + let msg = DiscordMessage::load_serenity(self.client.clone(), Arc::new(message)).await; let nid = msg.get_list_identifier(); lock.insert(AsyncListIndex::Before(id), msg, false, is_end); @@ -163,34 +184,36 @@ impl AsyncList for DiscordChannel { let v = self .client .get_messages( - self.channel_id, - GetMessages::new().after(MessageId::new(message.content)).limit(DISCORD_MESSAGE_BATCH_SIZE), + self.channel.id(), + GetMessages::new().after(MessageId::new(message.0)).limit(DISCORD_MESSAGE_BATCH_SIZE), ) .await; let mut current_index: Snowflake = message; let is_end = v.len() == DISCORD_MESSAGE_BATCH_SIZE as usize; + let len = v.len(); is_bottom = is_end && v.len() == 1; - result = v.last().map(DiscordMessage::from_serenity); + for (message, index) in v.into_iter().rev().zip(0..) { + let id = Snowflake(message.id.get()); + + let value = DiscordMessage::load_serenity(self.client.clone(), Arc::new(message)).await; + + if index == 0 { + result = Some(value.clone()); + } - for (message, index) in v.iter().rev().zip(0..) { - lock.insert( - AsyncListIndex::After(current_index), - DiscordMessage::from_serenity(message), - false, - is_end && index == (v.len() - 1), - ); + lock.insert(AsyncListIndex::After(current_index), value, false, is_end && index == (len - 1)); - current_index = Snowflake { content: message.id.get() } + current_index = id; } } AsyncListIndex::Before(message) => { let v = self .client .get_messages( - self.channel_id, - GetMessages::new().before(MessageId::new(message.content)).limit(DISCORD_MESSAGE_BATCH_SIZE), + self.channel.id(), + GetMessages::new().before(MessageId::new(message.0)).limit(DISCORD_MESSAGE_BATCH_SIZE), ) .await; let mut current_index: Snowflake = message; @@ -198,19 +221,23 @@ impl AsyncList for DiscordChannel { println!("Discord gave us {:?} messages (out of {:?})", v.len(), DISCORD_MESSAGE_BATCH_SIZE); let is_end = v.len() == DISCORD_MESSAGE_BATCH_SIZE as usize; + let len = v.len(); is_top = is_end && v.len() == 1; - result = v.first().map(DiscordMessage::from_serenity); + result = None; + + for (message, index) in v.into_iter().zip(0..) { + let id = Snowflake(message.id.get()); + + let value = DiscordMessage::load_serenity(self.client.clone(), Arc::new(message)).await; + + if index == 0 { + result = Some(value.clone()); + } - for (message, index) in v.iter().zip(0..) { - lock.insert( - AsyncListIndex::Before(current_index), - DiscordMessage::from_serenity(message), - false, - is_end && index == (v.len() - 1), - ); + lock.insert(AsyncListIndex::Before(current_index), value, false, is_end && index == len); - current_index = Snowflake { content: message.id.get() } + current_index = id; } } }; @@ -231,7 +258,7 @@ impl AsyncList for DiscordChannel { impl Clone for DiscordChannel { fn clone(&self) -> Self { Self { - channel_id: self.channel_id, + channel: self.channel.clone(), receiver: self.receiver.resubscribe(), client: self.client.clone(), cache: self.cache.clone(), diff --git a/src/discord/src/client.rs b/src/discord/src/client.rs index cc33f78..c5b99dd 100644 --- a/src/discord/src/client.rs +++ b/src/discord/src/client.rs @@ -1,47 +1,67 @@ use std::{ + cell::RefCell, collections::HashMap, - sync::{Arc, OnceLock}, + sync::{Arc, OnceLock, Weak}, }; +use atomic_refcell::AtomicRefCell; +use dashmap::DashMap; use serenity::{ - all::{Cache, ChannelId, Context, CreateMessage, EventHandler, GatewayIntents, GetMessages, Http, Message, MessageId, Ready}, + all::{ + Cache, CacheHttp, ChannelId, Context, CreateMessage, CurrentUser, EventHandler, GatewayIntents, GetMessages, GuildId, Http, Member, Message, + MessageId, ModelError, Ready, User, + }, async_trait, }; use tokio::sync::{broadcast, RwLock}; use crate::{ channel::DiscordChannel, - message::{ - author::{DiscordMessageAuthor, DisplayName}, - DiscordMessage, - }, + message::{DiscordMessage, DiscordMessageData}, snowflake::Snowflake, }; #[allow(dead_code)] -struct SerenityClient { +pub struct SerenityClient { // enable this when we enable the serenity[voice] feature // voice_manager: Option> http: Arc, cache: Arc, } +impl CacheHttp for SerenityClient { + fn http(&self) -> &Http { + &self.http + } + + fn cache(&self) -> Option<&Arc> { + Some(&self.cache) + } +} + #[derive(Default)] pub struct DiscordClient { - channel_message_event_handlers: RwLock>>>, + channel_message_event_handlers: RwLock>>>, client: OnceLock, - user: OnceLock, - channels: RwLock>>, + user: OnceLock>, + channels: RwLock>>, + member: DashMap>, + ready_notifier: AtomicRefCell>>, + weak: Weak, } impl DiscordClient { pub async fn new(token: String) -> Arc { - let client = Arc::new(DiscordClient::default()); + let (sender, receiver) = catty::oneshot::<()>(); - let mut discord = serenity::Client::builder(token, GatewayIntents::all()) - .event_handler_arc(client.clone()) - .await - .expect("Error creating client"); + let client = Arc::new_cyclic(|weak| DiscordClient { + ready_notifier: AtomicRefCell::new(Some(sender)), + weak: weak.clone(), + + ..Default::default() + }); + + let mut discord = serenity::Client::builder(token, GatewayIntents::all()).event_handler_arc(client.clone()).await.expect("Error creating client"); let _ = client.client.set(SerenityClient { // voice_manager: discord.voice_manager.clone(), @@ -55,22 +75,32 @@ impl DiscordClient { } }); + println!("Waiting on receiver"); + + receiver.await.expect("The ready notifier was dropped"); + client } - fn discord(&self) -> &SerenityClient { + pub fn discord(&self) -> &SerenityClient { self.client.get().unwrap() } - pub fn user(&self) -> &DiscordMessageAuthor { - self.user.get().unwrap() + pub fn own_user(&self) -> Arc { + self.user.get().unwrap().clone() + } + + pub fn own_member(&self, guild: GuildId) -> Option> { + self.member.get(&guild).map(|v| v.clone()) } - pub async fn add_channel_message_sender(&self, channel: Snowflake, sender: broadcast::Sender) { + pub async fn add_channel_message_sender(&self, channel: ChannelId, sender: broadcast::Sender) { self.channel_message_event_handlers.write().await.entry(channel).or_default().push(sender); } pub async fn channel(self: Arc, channel_id: Snowflake) -> Arc { + let channel_id = ChannelId::new(channel_id.0); + let self_clone = self.clone(); let mut channels = self_clone.channels.write().await; let existing = channels.get(&channel_id); @@ -86,8 +116,8 @@ impl DiscordClient { new } - pub async fn send_message(&self, channel_id: Snowflake, content: String, nonce: String) { - ChannelId::new(channel_id.content) + pub async fn send_message(&self, channel_id: ChannelId, content: String, nonce: String) { + channel_id .send_message( self.discord().http.clone(), CreateMessage::new().content(content).enforce_nonce(true).nonce(serenity::all::Nonce::String(nonce)), @@ -96,37 +126,49 @@ impl DiscordClient { .unwrap(); } - pub async fn get_messages(&self, channel_id: Snowflake, builder: GetMessages) -> Vec { + pub async fn get_messages(&self, channel_id: ChannelId, builder: GetMessages) -> Vec { println!("Discord: get_messages: {:?}", builder); // FIXME: proper error handling - ChannelId::new(channel_id.content).messages(self.discord().http.clone(), builder).await.unwrap() + channel_id.messages(self.discord().http.clone(), builder).await.unwrap() } - pub async fn get_specific_message(&self, channel_id: Snowflake, message_id: Snowflake) -> Option { + pub async fn get_specific_message(&self, channel_id: ChannelId, message_id: MessageId) -> Option { println!("Discord: get_specific_messages"); // FIXME: proper error handling - Some(ChannelId::new(channel_id.content).message(self.discord().http.clone(), MessageId::new(message_id.content)).await.unwrap()) + Some(channel_id.message(self.discord().http.clone(), message_id).await.unwrap()) } } - #[async_trait] impl EventHandler for DiscordClient { async fn ready(&self, _: Context, ready: Ready) { - self.user.get_or_init(|| DiscordMessageAuthor { - display_name: DisplayName(ready.user.name.clone()), - icon: ready.user.face(), - id: ready.user.id.to_string(), - }); + self.user.get_or_init(|| Arc::new((*ready.user).clone())); + + println!("Got ready"); + + if let Some(ready_notifier) = self.ready_notifier.borrow_mut().take() { + ready_notifier.send(()).unwrap(); + } } + async fn message(&self, _: Context, msg: Message) { - let snowflake = Snowflake { - content: msg.channel_id.get(), - }; + if let Some(vec) = self.channel_message_event_handlers.read().await.get(&msg.channel_id) { + let msg = Arc::new(msg); + let channel = Arc::new(msg.channel(self.discord()).await.unwrap()); + let member = match msg.member(self.discord()).await { + Ok(v) => Ok(Some(Arc::new(v))), + Err(serenity::Error::Model(ModelError::ItemMissing)) => Ok(None), + Err(e) => Err(e), + } + .unwrap(); - if let Some(vec) = self.channel_message_event_handlers.read().await.get(&snowflake) { for sender in vec { - let _ = sender.send(DiscordMessage::from_serenity(&msg)); + let _ = sender.send(DiscordMessage::from_serenity( + self.weak.upgrade().unwrap(), + msg.clone(), + channel.clone(), + member.clone(), + )); } } } diff --git a/src/discord/src/message/author.rs b/src/discord/src/message/author.rs index 67f2f34..b44e43b 100644 --- a/src/discord/src/message/author.rs +++ b/src/discord/src/message/author.rs @@ -1,47 +1,106 @@ -use gpui::{div, Element, IntoElement, ParentElement, RenderOnce, Styled, WindowContext}; -use scope_chat::message::MessageAuthor; +use std::sync::Arc; -#[derive(Clone, Debug)] +use gpui::{div, img, IntoElement, ParentElement, RenderOnce, SharedString, Styled, WindowContext}; +use scope_chat::message::{IconRenderConfig, MessageAuthor}; +use url::Url; + +use crate::{client::DiscordClient, snowflake::Snowflake}; + +#[derive(Clone)] +pub enum DiscordMessageAuthorData { + User(Arc), + NonMemberAuthor(Arc), + Member(Arc), +} + +#[derive(Clone)] pub struct DiscordMessageAuthor { - pub display_name: DisplayName, - pub icon: String, - pub id: String, + pub data: DiscordMessageAuthorData, + + pub client: Arc, } impl PartialEq for DiscordMessageAuthor { fn eq(&self, other: &Self) -> bool { - self.id == other.id + match (&self.data, &other.data) { + (DiscordMessageAuthorData::Member(ref left), DiscordMessageAuthorData::Member(ref right)) => { + left.guild_id == right.guild_id && left.user.id == right.user.id + } + (DiscordMessageAuthorData::User(ref left), DiscordMessageAuthorData::User(ref right)) => left.id == right.id, + _ => false, + } } } impl Eq for DiscordMessageAuthor {} impl MessageAuthor for DiscordMessageAuthor { - fn get_display_name(&self) -> impl Element { - self.display_name.clone().into_element() - } + type Identifier = Snowflake; + type DisplayName = DisplayName; + type Icon = DisplayIcon; - fn get_icon(&self) -> String { - self.icon.clone() + fn get_display_name(&self) -> Self::DisplayName { + match &self.data { + DiscordMessageAuthorData::Member(member) => DisplayName(member.display_name().to_owned().into()), + DiscordMessageAuthorData::User(user) => DisplayName(user.display_name().to_owned().into()), + DiscordMessageAuthorData::NonMemberAuthor(message) => DisplayName(message.author.display_name().to_owned().into()), + } } - fn get_small_icon(&self) -> String { - let icon = match self.icon.strip_suffix("?size=1024") { - Some(strip) => strip.to_owned(), - None => self.icon.to_owned(), - }; - icon + "?size=32" + fn get_icon(&self, config: IconRenderConfig) -> Self::Icon { + match &self.data { + DiscordMessageAuthorData::Member(member) => DisplayIcon( + member.avatar_url().or(member.user.avatar_url()).unwrap_or(member.user.default_avatar_url()), + config, + ), + DiscordMessageAuthorData::User(user) => DisplayIcon(user.avatar_url().unwrap_or(user.default_avatar_url()), config), + DiscordMessageAuthorData::NonMemberAuthor(message) => { + DisplayIcon(message.author.avatar_url().unwrap_or(message.author.default_avatar_url()), config) + } + } } - fn get_id(&self) -> String { - self.id.clone() + fn get_identifier(&self) -> Self::Identifier { + match &self.data { + DiscordMessageAuthorData::Member(member) => member.user.id.into(), + DiscordMessageAuthorData::User(user) => user.id.into(), + DiscordMessageAuthorData::NonMemberAuthor(message) => message.author.id.into(), + } } } #[derive(Clone, IntoElement, Debug)] -pub struct DisplayName(pub String); +pub struct DisplayName(pub SharedString); impl RenderOnce for DisplayName { fn render(self, _: &mut WindowContext) -> impl IntoElement { div().text_sm().child(self.0) } } + +#[derive(Clone, IntoElement, Debug)] +pub struct DisplayIcon(pub String, pub IconRenderConfig); + +impl RenderOnce for DisplayIcon { + fn render(self, _: &mut WindowContext) -> impl IntoElement { + let mut url = Url::parse(&self.0).unwrap(); + let mut query_params = querystring::querify(url.query().unwrap_or("")); + + let mut key_found = false; + let size = self.1.size().to_string(); + + for (key, value) in query_params.iter_mut() { + if key == &"size" { + *value = &size; + key_found = true; + } + } + + if !key_found { + query_params.push(("size", &size)); + } + + url.set_query(Some(&querystring::stringify(query_params))); + + img(url.to_string()) + } +} diff --git a/src/discord/src/message/content.rs b/src/discord/src/message/content.rs index 81b6a0e..07df573 100644 --- a/src/discord/src/message/content.rs +++ b/src/discord/src/message/content.rs @@ -1,13 +1,27 @@ -use gpui::{div, IntoElement, ParentElement, RenderOnce, Styled, WindowContext}; +use gpui::{div, IntoElement, ParentElement, Render, Styled, ViewContext}; +use serenity::all::Message; -#[derive(Clone, IntoElement, Debug)] +#[derive(Clone, Debug)] pub struct DiscordMessageContent { pub content: String, pub is_pending: bool, } -impl RenderOnce for DiscordMessageContent { - fn render(self, _: &mut WindowContext) -> impl IntoElement { +impl DiscordMessageContent { + pub fn pending(content: String) -> DiscordMessageContent { + DiscordMessageContent { content, is_pending: true } + } + + pub fn received(message: &Message) -> DiscordMessageContent { + DiscordMessageContent { + content: message.content.clone(), + is_pending: false, + } + } +} + +impl Render for DiscordMessageContent { + fn render(&mut self, _: &mut ViewContext) -> impl IntoElement { div().opacity(if self.is_pending { 0.25 } else { 1.0 }).child(self.content.clone()) } } diff --git a/src/discord/src/message/mod.rs b/src/discord/src/message/mod.rs index 96ec2da..f7e5082 100644 --- a/src/discord/src/message/mod.rs +++ b/src/discord/src/message/mod.rs @@ -1,73 +1,171 @@ -use author::{DiscordMessageAuthor, DisplayName}; +use std::sync::{Arc, OnceLock}; + +use author::DiscordMessageAuthor; use chrono::{DateTime, Utc}; use content::DiscordMessageContent; -use gpui::{Element, IntoElement}; +use gpui::{View, VisualContext, WindowContext}; use scope_chat::{async_list::AsyncListItem, message::Message}; -use serenity::all::Nonce; +use serenity::all::{ModelError, Nonce}; -use crate::snowflake::Snowflake; +use crate::{client::DiscordClient, snowflake::Snowflake}; pub mod author; pub mod content; -#[derive(Clone, Debug)] +#[derive(Clone)] +pub enum DiscordMessageData { + Pending { + nonce: String, + content: String, + sent_time: DateTime, + list_item_id: Snowflake, + }, + Received(Arc, Option>), +} + +#[derive(Clone)] pub struct DiscordMessage { - pub content: DiscordMessageContent, - pub author: DiscordMessageAuthor, - pub id: Snowflake, - pub nonce: Option, - pub creation_time: serenity::model::Timestamp, + pub client: Arc, + pub channel: Arc, + pub data: DiscordMessageData, + pub content: OnceLock>, } impl DiscordMessage { - pub fn from_serenity(msg: &serenity::all::Message) -> Self { - DiscordMessage { - id: Snowflake { content: msg.id.get() }, - author: DiscordMessageAuthor { - display_name: DisplayName(msg.author.name.clone()), - icon: msg.author.face(), - id: msg.author.id.to_string(), - }, - content: DiscordMessageContent { - content: msg.content.clone(), - is_pending: false, - }, - nonce: msg.nonce.clone().map(|n| match n { - Nonce::Number(n) => n.to_string(), - Nonce::String(s) => s, - }), - creation_time: msg.timestamp, + pub async fn load_serenity(client: Arc, msg: Arc) -> Self { + let channel = Arc::new(msg.channel(client.discord()).await.unwrap()); + let member = match msg.member(client.discord()).await { + Ok(v) => Ok(Some(Arc::new(v))), + Err(serenity::Error::Model(ModelError::ItemMissing)) => Ok(None), + Err(e) => Err(e), + } + .unwrap(); + + Self { + client, + channel, + data: DiscordMessageData::Received(msg, member), + + content: OnceLock::new(), + } + } + + pub fn from_serenity( + client: Arc, + msg: Arc, + channel: Arc, + member: Option>, + ) -> Self { + Self { + client, + channel, + data: DiscordMessageData::Received(msg, member), + + content: OnceLock::new(), + } + } +} + +enum NonceState<'r> { + Fixed(&'r String), + Discord(&'r Option), +} + +impl<'r> PartialEq for NonceState<'r> { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + // comparing anything with `None` means they are not equal + (NonceState::Discord(None), _) => false, + (_, NonceState::Discord(None)) => false, + + // two Fixed strings are equal if their contents are + (NonceState::Fixed(left), NonceState::Fixed(right)) => left == right, + + // Fixed strings are only equal to Discord String Nonces + (NonceState::Fixed(left), NonceState::Discord(Some(Nonce::String(right)))) => *left == right, + (NonceState::Discord(Some(Nonce::String(right))), NonceState::Fixed(left)) => *left == right, + + // Discord Nonces are only equal if their types are. + (NonceState::Discord(Some(Nonce::Number(left))), NonceState::Discord(Some(Nonce::Number(right)))) => left == right, + (NonceState::Discord(Some(Nonce::String(left))), NonceState::Discord(Some(Nonce::String(right)))) => left == right, + + _ => false, } } } impl Message for DiscordMessage { - fn get_author(&self) -> &impl scope_chat::message::MessageAuthor { - &self.author + type Identifier = Snowflake; + type Author = DiscordMessageAuthor; + type Content = DiscordMessageContent; + + fn get_author(&self) -> DiscordMessageAuthor { + match &self.data { + DiscordMessageData::Pending { .. } => DiscordMessageAuthor { + client: self.client.clone(), + data: match &*self.channel { + serenity::model::channel::Channel::Private(_) => author::DiscordMessageAuthorData::User(self.client.own_user().clone()), + serenity::model::channel::Channel::Guild(guild_channel) => match self.client.own_member(guild_channel.guild_id) { + Some(member) => author::DiscordMessageAuthorData::Member(member), + None => author::DiscordMessageAuthorData::User(self.client.own_user().clone()), + }, + _ => unimplemented!(), + }, + }, + + DiscordMessageData::Received(message, member) => DiscordMessageAuthor { + client: self.client.clone(), + data: match member { + None => author::DiscordMessageAuthorData::NonMemberAuthor(message.clone()), + Some(member) => author::DiscordMessageAuthorData::Member(member.clone()), + }, + }, + } } - fn get_content(&self) -> impl Element { - self.content.clone().into_element() + // TODO: want reviewer discussion. I'm really stretching the abilities of gpui here and im not sure if this is the right way to do this. + fn get_content(&self, cx: &mut WindowContext) -> View { + self + .content + .get_or_init(|| { + let content = match &self.data { + DiscordMessageData::Pending { content, .. } => DiscordMessageContent::pending(content.clone()), + DiscordMessageData::Received(message, _) => DiscordMessageContent::received(&*message), + }; + + cx.new_view(|cx| content) + }) + .clone() } - fn get_identifier(&self) -> String { - self.id.to_string() + fn get_identifier(&self) -> Option { + match &self.data { + DiscordMessageData::Received(message, _) => Some(message.id.into()), + DiscordMessageData::Pending { .. } => None, + } } - fn get_nonce(&self) -> Option<&String> { - self.nonce.as_ref() + fn get_nonce(&self) -> impl PartialEq { + match &self.data { + DiscordMessageData::Pending { nonce, .. } => NonceState::Fixed(nonce), + DiscordMessageData::Received(message, _) => NonceState::Discord(&message.nonce), + } } fn should_group(&self, previous: &Self) -> bool { const MAX_DISCORD_MESSAGE_GAP_SECS_FOR_GROUP: i64 = 5 * 60; - self.creation_time.signed_duration_since(*previous.creation_time).num_seconds() <= MAX_DISCORD_MESSAGE_GAP_SECS_FOR_GROUP + let left = self.get_timestamp().unwrap(); + let right = previous.get_timestamp().unwrap(); + + left.signed_duration_since(right).num_seconds() <= MAX_DISCORD_MESSAGE_GAP_SECS_FOR_GROUP } fn get_timestamp(&self) -> Option> { - let ts = self.creation_time.timestamp_millis(); - - DateTime::from_timestamp_millis(ts) + match &self.data { + DiscordMessageData::Pending { sent_time, .. } => Some(*sent_time), + DiscordMessageData::Received(message, _) => DateTime::from_timestamp_millis(message.timestamp.timestamp_millis()), + } } } @@ -75,6 +173,9 @@ impl AsyncListItem for DiscordMessage { type Identifier = Snowflake; fn get_list_identifier(&self) -> Self::Identifier { - self.id + match &self.data { + DiscordMessageData::Pending { list_item_id, .. } => *list_item_id, + DiscordMessageData::Received(message, _) => message.id.into(), + } } } diff --git a/src/discord/src/snowflake.rs b/src/discord/src/snowflake.rs index 1d70ebd..10b7d30 100644 --- a/src/discord/src/snowflake.rs +++ b/src/discord/src/snowflake.rs @@ -1,11 +1,41 @@ +use serenity::all::{ChannelId, GuildId, MessageId, UserId}; + #[derive(Clone, Hash, PartialEq, Eq, Copy, Debug)] -pub struct Snowflake { - pub content: u64, +pub struct Snowflake(pub u64); + +impl Snowflake { + pub fn random() -> Snowflake { + Snowflake(rand::random()) + } } #[allow(clippy::to_string_trait_impl)] impl ToString for Snowflake { fn to_string(&self) -> String { - self.content.to_string() + self.0.to_string() + } +} + +impl From for Snowflake { + fn from(value: UserId) -> Self { + Snowflake(value.get()) + } +} + +impl From for Snowflake { + fn from(value: GuildId) -> Self { + Snowflake(value.get()) + } +} + +impl From for Snowflake { + fn from(value: ChannelId) -> Self { + Snowflake(value.get()) + } +} + +impl From for Snowflake { + fn from(value: MessageId) -> Self { + Snowflake(value.get()) } } diff --git a/src/ui/src/app.rs b/src/ui/src/app.rs index 407ab44..5cf3608 100644 --- a/src/ui/src/app.rs +++ b/src/ui/src/app.rs @@ -22,15 +22,15 @@ impl App { ctx .foreground_executor() .spawn(async move { + println!("Trying to make a client..."); + let client = DiscordClient::new(token).await; - let channel = DiscordChannel::new( - client.clone(), - Snowflake { - content: demo_channel_id.parse().unwrap(), - }, - ) - .await; + println!("Made a client 👶"); + + let channel = client.channel(Snowflake(demo_channel_id.parse().unwrap())).await; + + println!("Made a channel 👶"); let view = context.new_view(|cx| ChannelView::::create(cx, channel)).unwrap(); @@ -49,9 +49,11 @@ impl App { impl Render for App { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { + println!("Rendering App 📍"); let mut content = div().w_full().h_full(); if let Some(channel) = self.channel.read(cx).as_ref() { + println!("We have a channel"); content = content.child(channel.clone()); } diff --git a/src/ui/src/channel/message.rs b/src/ui/src/channel/message.rs index 6bb3767..a1550af 100644 --- a/src/ui/src/channel/message.rs +++ b/src/ui/src/channel/message.rs @@ -1,7 +1,6 @@ use chrono::Local; -use gpui::prelude::FluentBuilder; -use gpui::{div, img, rgb, Element, IntoElement, ParentElement, Styled, StyledImage}; -use scope_chat::message::{Message, MessageAuthor}; +use gpui::{div, img, prelude::FluentBuilder, rgb, Element, IntoElement, ParentElement, Render, Styled, StyledImage, View, WindowContext}; +use scope_chat::message::{IconRenderConfig, Message, MessageAuthor}; #[derive(Clone)] pub struct MessageGroup { @@ -13,33 +12,19 @@ impl MessageGroup { MessageGroup { contents: vec![message] } } - pub fn get_author(&self) -> &(impl MessageAuthor + '_) { + pub fn get_author(&self) -> M::Author { self.contents.first().unwrap().get_author() } pub fn add(&mut self, message: M) { // FIXME: This is scuffed, should be using PartialEq trait. - if self.get_author().get_id() != message.get_author().get_id() { + if self.get_author().get_identifier() != message.get_author().get_identifier() { panic!("Authors must match in a message group") } self.contents.push(message); } - pub fn contents(&self) -> impl IntoIterator { - self.contents.iter().map(|v| v.get_content()) - } - - pub fn find_matching(&self, nonce: &String) -> Option { - for haystack in self.contents.iter().zip(0usize..) { - if haystack.0.get_nonce().is_some() && haystack.0.get_nonce().unwrap() == nonce { - return Some(haystack.1); - } - } - - None - } - pub fn size(&self) -> usize { self.contents.len() } @@ -57,14 +42,14 @@ impl MessageGroup { } } -pub fn message(message: MessageGroup) -> impl IntoElement { +pub fn message_group(group: MessageGroup, cx: &mut WindowContext) -> impl IntoElement { div() .flex() .flex_row() .text_color(rgb(0xFFFFFF)) .gap_4() .pb_6() - .child(img(message.get_author().get_small_icon()).flex_shrink_0().object_fit(gpui::ObjectFit::Fill).rounded_full().w_12().h_12()) + .child(div().child(group.get_author().get_icon(IconRenderConfig::small())).flex_shrink_0().rounded_full().w_12().h_12()) .child( div() .flex() @@ -74,10 +59,10 @@ pub fn message(message: MessageGroup) -> impl IntoElement { // enabling this, and thus enabling ellipsis causes a consistent panic!? // .child(div().text_ellipsis().min_w_0().child(message.get_author().get_display_name())) .child( - div().min_w_0().flex().gap_2().child(message.get_author().get_display_name()).when_some(message.last().get_timestamp(), |d, ts| { + div().min_w_0().flex().gap_2().child(group.get_author().get_display_name()).when_some(group.last().get_timestamp(), |d, ts| { d.child(div().min_w_0().text_color(rgb(0xAFBAC7)).text_sm().child(ts.with_timezone(&Local).format("%I:%M %p").to_string())) }), ) - .children(message.contents()), + .children(group.contents.iter().map(|v| v.get_content(cx).clone())), ) } diff --git a/src/ui/src/channel/message_list.rs b/src/ui/src/channel/message_list.rs index 7eef140..4926af4 100644 --- a/src/ui/src/channel/message_list.rs +++ b/src/ui/src/channel/message_list.rs @@ -8,7 +8,7 @@ use scope_chat::{ }; use tokio::sync::RwLock; -use super::message::{message, MessageGroup}; +use super::message::{message_group, MessageGroup}; #[derive(Clone, Copy)] struct ListStateDirtyState { @@ -168,6 +168,8 @@ where let len = groups.len(); + println!("New list state... ✍️"); + let new_list_state = ListState::new( if len == 0 { 1 } else { len + 2 }, ListAlignment::Bottom, @@ -191,7 +193,7 @@ where match &groups[idx - 1] { Element::Unresolved => div().text_color(rgb(0xFFFFFF)).child("Loading..."), Element::Resolved(None) => div(), // we've hit the ends - Element::Resolved(Some(group)) => div().child(message(group.clone())), + Element::Resolved(Some(group)) => div().child(message_group(group.clone(), cx)), } } .into_any_element() @@ -228,6 +230,7 @@ where // update bottom if flags.after { + println!("Updating 🤓☝️"); let cache_model = self.cache.clone(); let list_handle = self.list.clone(); @@ -255,7 +258,10 @@ where let (sender, receiver) = catty::oneshot(); tokio::spawn(async move { - sender.send(list_handle.read().await.get(index).await).unwrap(); + match sender.send(list_handle.read().await.get(index).await) { + Ok(_) => {} + Err(e) => log::error!("Failed to send."), + } }); let v = receiver.await.unwrap(); @@ -304,7 +310,10 @@ where let (sender, receiver) = catty::oneshot(); tokio::spawn(async move { - sender.send(list_handle.read().await.get(index).await).unwrap(); + match sender.send(list_handle.read().await.get(index).await) { + Ok(_) => {} + Err(e) => log::error!("Failed to send."), + } }); let v = receiver.await.unwrap(); @@ -350,6 +359,8 @@ where impl Render for MessageListComponent { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { + println!("Rendering Message List 📍"); + self.update(cx); let ls = if let Some(v) = self.list_state.read(cx).clone() { diff --git a/src/ui/src/channel/mod.rs b/src/ui/src/channel/mod.rs index f61e279..ea320b6 100644 --- a/src/ui/src/channel/mod.rs +++ b/src/ui/src/channel/mod.rs @@ -1,18 +1,20 @@ pub mod message; pub mod message_list; +use std::sync::Arc; + use components::input::{InputEvent, TextInput}; use gpui::{div, ParentElement, Pixels, Render, Styled, View, VisualContext}; use message_list::MessageListComponent; use scope_chat::channel::Channel; pub struct ChannelView { - list_view: View>, + list_view: View>>, message_input: View, } impl ChannelView { - pub fn create(ctx: &mut gpui::ViewContext<'_, ChannelView>, channel: C) -> Self { + pub fn create(ctx: &mut gpui::ViewContext<'_, ChannelView>, channel: Arc) -> Self { let channel_listener = channel.get_receiver(); let c2 = channel.clone(); @@ -31,7 +33,10 @@ impl ChannelView { let mut l = channel_listener.resubscribe(); tokio::spawn(async move { - sender.send(l.recv().await).unwrap(); + match sender.send(l.recv().await) { + Ok(_) => {} + Err(e) => log::error!("Failed to send message data!"), + }; }); let message = receiver.await.unwrap().unwrap(); @@ -97,6 +102,8 @@ impl ChannelView { impl Render for ChannelView { fn render(&mut self, _: &mut gpui::ViewContext) -> impl gpui::IntoElement { + println!("Rendering Channel 📍"); + div() .flex() .flex_col() From 461efdd2b5bc5f4012a641b28887c0ad90b25530 Mon Sep 17 00:00:00 2001 From: Rose Hall Date: Wed, 27 Nov 2024 23:04:54 -0500 Subject: [PATCH 2/5] Refactor complete --- src/discord/src/client.rs | 4 ---- src/discord/src/message/author.rs | 2 +- src/ui/src/app.rs | 10 ---------- src/ui/src/channel/message_list.rs | 9 ++------- src/ui/src/channel/mod.rs | 2 -- 5 files changed, 3 insertions(+), 24 deletions(-) diff --git a/src/discord/src/client.rs b/src/discord/src/client.rs index c5b99dd..71b3aa8 100644 --- a/src/discord/src/client.rs +++ b/src/discord/src/client.rs @@ -75,8 +75,6 @@ impl DiscordClient { } }); - println!("Waiting on receiver"); - receiver.await.expect("The ready notifier was dropped"); client @@ -144,8 +142,6 @@ impl EventHandler for DiscordClient { async fn ready(&self, _: Context, ready: Ready) { self.user.get_or_init(|| Arc::new((*ready.user).clone())); - println!("Got ready"); - if let Some(ready_notifier) = self.ready_notifier.borrow_mut().take() { ready_notifier.send(()).unwrap(); } diff --git a/src/discord/src/message/author.rs b/src/discord/src/message/author.rs index b44e43b..fbf3f2b 100644 --- a/src/discord/src/message/author.rs +++ b/src/discord/src/message/author.rs @@ -101,6 +101,6 @@ impl RenderOnce for DisplayIcon { url.set_query(Some(&querystring::stringify(query_params))); - img(url.to_string()) + img(url.to_string()).w_full().h_full().rounded_full() } } diff --git a/src/ui/src/app.rs b/src/ui/src/app.rs index 5cf3608..1fdff2f 100644 --- a/src/ui/src/app.rs +++ b/src/ui/src/app.rs @@ -22,16 +22,8 @@ impl App { ctx .foreground_executor() .spawn(async move { - println!("Trying to make a client..."); - let client = DiscordClient::new(token).await; - - println!("Made a client 👶"); - let channel = client.channel(Snowflake(demo_channel_id.parse().unwrap())).await; - - println!("Made a channel 👶"); - let view = context.new_view(|cx| ChannelView::::create(cx, channel)).unwrap(); async_channel @@ -49,11 +41,9 @@ impl App { impl Render for App { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { - println!("Rendering App 📍"); let mut content = div().w_full().h_full(); if let Some(channel) = self.channel.read(cx).as_ref() { - println!("We have a channel"); content = content.child(channel.clone()); } diff --git a/src/ui/src/channel/message_list.rs b/src/ui/src/channel/message_list.rs index 4926af4..e4c283c 100644 --- a/src/ui/src/channel/message_list.rs +++ b/src/ui/src/channel/message_list.rs @@ -4,7 +4,7 @@ use gpui::{div, list, rgb, Context, IntoElement, ListAlignment, ListState, Model use scope_chat::{ async_list::{AsyncListIndex, AsyncListItem}, channel::Channel, - message::Message, + message::{Message, MessageAuthor}, }; use tokio::sync::RwLock; @@ -140,7 +140,7 @@ where groups.push(Element::Resolved(Some(MessageGroup::new(m.clone())))); } Some(Element::Resolved(Some(old_group))) => { - if m.get_author() == old_group.last().get_author() && m.should_group(old_group.last()) { + if m.get_author().get_identifier() == old_group.last().get_author().get_identifier() && m.should_group(old_group.last()) { old_group.add(m.clone()); } else { items_added += 1; @@ -168,8 +168,6 @@ where let len = groups.len(); - println!("New list state... ✍️"); - let new_list_state = ListState::new( if len == 0 { 1 } else { len + 2 }, ListAlignment::Bottom, @@ -230,7 +228,6 @@ where // update bottom if flags.after { - println!("Updating 🤓☝️"); let cache_model = self.cache.clone(); let list_handle = self.list.clone(); @@ -359,8 +356,6 @@ where impl Render for MessageListComponent { fn render(&mut self, cx: &mut gpui::ViewContext) -> impl gpui::IntoElement { - println!("Rendering Message List 📍"); - self.update(cx); let ls = if let Some(v) = self.list_state.read(cx).clone() { diff --git a/src/ui/src/channel/mod.rs b/src/ui/src/channel/mod.rs index ea320b6..9e5cd25 100644 --- a/src/ui/src/channel/mod.rs +++ b/src/ui/src/channel/mod.rs @@ -102,8 +102,6 @@ impl ChannelView { impl Render for ChannelView { fn render(&mut self, _: &mut gpui::ViewContext) -> impl gpui::IntoElement { - println!("Rendering Channel 📍"); - div() .flex() .flex_col() From 07a3c4a3bfa432188d23d7ccb422dbfbb49758e0 Mon Sep 17 00:00:00 2001 From: Rose Hall Date: Wed, 27 Nov 2024 23:09:25 -0500 Subject: [PATCH 3/5] make clippy happy --- src/discord/src/channel/mod.rs | 8 ++------ src/discord/src/client.rs | 11 +++-------- src/discord/src/message/mod.rs | 4 ++-- src/ui/src/channel/message.rs | 2 +- src/ui/src/channel/message_list.rs | 4 ++-- src/ui/src/channel/mod.rs | 2 +- 6 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/discord/src/channel/mod.rs b/src/discord/src/channel/mod.rs index 4f14d9b..de4ca25 100644 --- a/src/discord/src/channel/mod.rs +++ b/src/discord/src/channel/mod.rs @@ -122,13 +122,9 @@ impl AsyncList for DiscordChannel { return Some(v); } - let result = self.client.get_specific_message(self.channel.id(), MessageId::new(identifier.0)).await; + let result = self.client.get_specific_message(self.channel.id(), MessageId::new(identifier.0)).await?; - if result.is_none() { - return None; - } - - Some(DiscordMessage::load_serenity(self.client.clone(), Arc::new(result.unwrap())).await) + Some(DiscordMessage::load_serenity(self.client.clone(), Arc::new(result)).await) } async fn get(&self, index: AsyncListIndex) -> Option> { diff --git a/src/discord/src/client.rs b/src/discord/src/client.rs index 71b3aa8..98a0a72 100644 --- a/src/discord/src/client.rs +++ b/src/discord/src/client.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::HashMap, sync::{Arc, OnceLock, Weak}, }; @@ -8,18 +7,14 @@ use atomic_refcell::AtomicRefCell; use dashmap::DashMap; use serenity::{ all::{ - Cache, CacheHttp, ChannelId, Context, CreateMessage, CurrentUser, EventHandler, GatewayIntents, GetMessages, GuildId, Http, Member, Message, - MessageId, ModelError, Ready, User, + Cache, CacheHttp, ChannelId, Context, CreateMessage, EventHandler, GatewayIntents, GetMessages, GuildId, Http, Member, Message, MessageId, + ModelError, Ready, User, }, async_trait, }; use tokio::sync::{broadcast, RwLock}; -use crate::{ - channel::DiscordChannel, - message::{DiscordMessage, DiscordMessageData}, - snowflake::Snowflake, -}; +use crate::{channel::DiscordChannel, message::DiscordMessage, snowflake::Snowflake}; #[allow(dead_code)] pub struct SerenityClient { diff --git a/src/discord/src/message/mod.rs b/src/discord/src/message/mod.rs index f7e5082..35ce8f8 100644 --- a/src/discord/src/message/mod.rs +++ b/src/discord/src/message/mod.rs @@ -130,10 +130,10 @@ impl Message for DiscordMessage { .get_or_init(|| { let content = match &self.data { DiscordMessageData::Pending { content, .. } => DiscordMessageContent::pending(content.clone()), - DiscordMessageData::Received(message, _) => DiscordMessageContent::received(&*message), + DiscordMessageData::Received(message, _) => DiscordMessageContent::received(message), }; - cx.new_view(|cx| content) + cx.new_view(|_cx| content) }) .clone() } diff --git a/src/ui/src/channel/message.rs b/src/ui/src/channel/message.rs index a1550af..76869a6 100644 --- a/src/ui/src/channel/message.rs +++ b/src/ui/src/channel/message.rs @@ -1,5 +1,5 @@ use chrono::Local; -use gpui::{div, img, prelude::FluentBuilder, rgb, Element, IntoElement, ParentElement, Render, Styled, StyledImage, View, WindowContext}; +use gpui::{div, prelude::FluentBuilder, rgb, IntoElement, ParentElement, Styled, WindowContext}; use scope_chat::message::{IconRenderConfig, Message, MessageAuthor}; #[derive(Clone)] diff --git a/src/ui/src/channel/message_list.rs b/src/ui/src/channel/message_list.rs index e4c283c..b8be4aa 100644 --- a/src/ui/src/channel/message_list.rs +++ b/src/ui/src/channel/message_list.rs @@ -257,7 +257,7 @@ where tokio::spawn(async move { match sender.send(list_handle.read().await.get(index).await) { Ok(_) => {} - Err(e) => log::error!("Failed to send."), + Err(_e) => log::error!("Failed to send."), } }); @@ -309,7 +309,7 @@ where tokio::spawn(async move { match sender.send(list_handle.read().await.get(index).await) { Ok(_) => {} - Err(e) => log::error!("Failed to send."), + Err(_e) => log::error!("Failed to send."), } }); diff --git a/src/ui/src/channel/mod.rs b/src/ui/src/channel/mod.rs index 9e5cd25..10b3a89 100644 --- a/src/ui/src/channel/mod.rs +++ b/src/ui/src/channel/mod.rs @@ -35,7 +35,7 @@ impl ChannelView { tokio::spawn(async move { match sender.send(l.recv().await) { Ok(_) => {} - Err(e) => log::error!("Failed to send message data!"), + Err(_e) => log::error!("Failed to send message data!"), }; }); From 7e885c820f52a2b17ed4b215ae87eafd948433bd Mon Sep 17 00:00:00 2001 From: Rose Hall Date: Mon, 21 Apr 2025 12:23:05 -0400 Subject: [PATCH 4/5] bump serenity dep --- Cargo.lock | 4 ++-- src/discord/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2da13e..5139a47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1080,7 +1080,7 @@ dependencies = [ [[package]] name = "command_attr" version = "0.5.3" -source = "git+https://github.com/scopeclient/serenity#a24c41552b3bab4e8fc391efed29fa8c3e1c164d" +source = "git+https://github.com/scopeclient/serenity?branch=dev#c93776fffae61d23b3b0307656cb0db2d05b58af" dependencies = [ "proc-macro2", "quote", @@ -5744,7 +5744,7 @@ dependencies = [ [[package]] name = "serenity" version = "0.12.4" -source = "git+https://github.com/scopeclient/serenity#a24c41552b3bab4e8fc391efed29fa8c3e1c164d" +source = "git+https://github.com/scopeclient/serenity?branch=dev#c93776fffae61d23b3b0307656cb0db2d05b58af" dependencies = [ "arrayvec", "async-trait", diff --git a/src/discord/Cargo.toml b/src/discord/Cargo.toml index 64d5b94..85c96c6 100644 --- a/src/discord/Cargo.toml +++ b/src/discord/Cargo.toml @@ -6,7 +6,7 @@ edition = "2021" [dependencies] gpui.workspace = true scope-chat = { version = "0.1.0", path = "../chat" } -serenity = { git = "https://github.com/scopeclient/serenity", version = "0.12" } +serenity = { git = "https://github.com/scopeclient/serenity", version = "0.12", branch = "dev" } tokio = "1.41.1" chrono.workspace = true scope-backend-cache = { version = "0.1.0", path = "../cache" } From 148e13fc854e6fd27c113f0134b50d6f63eae478 Mon Sep 17 00:00:00 2001 From: circular <50017673+circularsprojects@users.noreply.github.com> Date: Fri, 6 Jun 2025 15:04:15 +1000 Subject: [PATCH 5/5] add a space here --- src/ui/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/src/main.rs b/src/ui/src/main.rs index 4282e3b..52ee9ca 100644 --- a/src/ui/src/main.rs +++ b/src/ui/src/main.rs @@ -24,7 +24,7 @@ impl AssetSource for Assets { fn list(&self, path: &str) -> Result> { Ok(Self::iter().filter_map(|p| if p.starts_with(path) { Some(p.into()) } else { None }).collect()) } -} +} fn init(_: Arc, cx: &mut AppContext) -> Result<()> { components::init(cx);