summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
authorashelyn ghost <git@ashen.earth>2024-07-08 20:36:06 -0600
committerAshelyn Rose <git@ashen.earth>2024-07-08 20:36:06 -0600
commit2bacf887c0f3aff01d60dd1255b2d0e673f550ea (patch)
treeff9ae462a57ed3e3ea4f40a4e1829cc045d66b8a /src
parentf7058336fbad943b2bcc03716c998abe3fd912ec (diff)
Separate out listener and event handling
Diffstat (limited to 'src')
-rw-r--r--src/listener.rs99
-rw-r--r--src/main.rs4
-rw-r--r--src/system.rs130
3 files changed, 144 insertions, 89 deletions
diff --git a/src/listener.rs b/src/listener.rs
new file mode 100644
index 0000000..1db480f
--- /dev/null
+++ b/src/listener.rs
@@ -0,0 +1,99 @@
+use tokio::sync::mpsc::Sender;
+use twilight_model::{id::{Id, marker::{MessageMarker, UserMarker}}, user::User};
+use twilight_gateway::{error::ReceiveMessageError, Intents, Shard, ShardId};
+use twilight_model::util::Timestamp;
+
+pub struct Listener {
+    pub config: crate::config::Member,
+    pub reference_user_id: Id<UserMarker>,
+}
+
+impl Listener {
+    pub fn new(config: crate::config::Member, reference_user_id: Id<UserMarker>) -> Self {
+        Listener {
+            config,
+            reference_user_id,
+        }
+    }
+
+    pub async fn start_listening(&mut self, channel : Sender<ClientEvent>) {
+        println!("Starting client with token: {}", self.config.discord_token);
+
+        let intents = Intents::GUILD_MEMBERS | Intents::GUILD_PRESENCES | Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT;
+
+        let mut shard = Shard::new(ShardId::ONE, self.config.discord_token.clone(), intents);
+        
+        loop {
+            match shard.next_event().await {
+                Ok(event) => {
+                    match event {
+                        twilight_gateway::Event::Ready(client) => {
+                            println!("Bot started for {}#{}", client.user.name, client.user.discriminator);
+                        },
+
+                        twilight_gateway::Event::MessageCreate(message) => {
+                            if message.author.id != self.reference_user_id {
+                                continue
+                            }
+
+                            if let Err(_) = channel.send(ClientEvent::Message {
+                                event_time: message.timestamp,
+                                message_id: message.id,
+                                author: message.author.clone(),
+                                content: message.content.clone()
+                            }).await {
+                                println!("Client listener error: System context has already closed");
+                                return
+                            }
+                        },
+
+                        twilight_gateway::Event::MessageUpdate(message) => {
+                            if message.author.is_none() || message.author.as_ref().unwrap().id != self.reference_user_id {
+                                continue
+                            }
+
+                            if message.edited_timestamp.is_none() {
+                                println!("Message update but no edit timestamp");
+                                continue;
+                            }
+
+
+                            if let Err(_) = channel.send(ClientEvent::Message {
+                                event_time: message.edited_timestamp.unwrap(),
+                                message_id: message.id,
+                                author: message.author.unwrap(),
+                                content: message.content.unwrap()
+                            }).await {
+                                println!("Client listener error: System context has already closed");
+                                return
+                            }
+                        },
+
+                        _ => (),
+                    }
+                },
+                Err(source) => {
+                    if !source.is_fatal() {
+                        continue;
+                    }
+
+                    if let Err(_) = channel.send(ClientEvent::Error(source)).await {
+                        println!("Client listener error: System context has already closed");
+                        break;
+                    }
+                    break;
+                }
+            };
+        }
+    }
+}
+
+pub enum ClientEvent {
+    Message {
+        event_time: Timestamp,
+        message_id: Id<MessageMarker>,
+        author: User,
+        content: String,
+    },
+    Error(ReceiveMessageError)
+}
diff --git a/src/main.rs b/src/main.rs
index a63c4bd..e29f691 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,5 +1,6 @@
 mod config;
 mod system;
+mod listener;
 
 use std::thread::{self, JoinHandle};
 use tokio::runtime;
@@ -11,11 +12,10 @@ fn main() {
     let config = config::Config::load(config_str.to_string());
 
     let handles : Vec<_> = config.systems.into_iter().map(|(system_name, system_config)| -> JoinHandle<()>  {
-        println!("Starting thread for system {}", system_name);
         thread::spawn(move || {
             let runtime = runtime::Builder::new_current_thread().enable_all().build().expect("Could not construct Tokio runtime");
             runtime.block_on(async {
-                let mut system = System::new(system_config);
+                let mut system = System::new(system_name, system_config);
                 system.start_clients().await;
             })
         })
diff --git a/src/system.rs b/src/system.rs
index 77df36a..9a8b327 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -1,126 +1,82 @@
-use std::{num::NonZeroUsize, str::FromStr};
+use std::{collections::HashMap, num::NonZeroUsize, str::FromStr};
 
-use std::sync::Arc;
-use tokio::task::JoinSet;
-use tokio::sync::Mutex;
+use tokio::sync::mpsc::channel;
 use twilight_http::Client;
 use twilight_model::id::{Id, marker::{MessageMarker, UserMarker}};
-use twilight_gateway::{Intents, Shard, ShardId};
 use twilight_model::util::Timestamp;
 
-#[derive(Clone)]
+use crate::listener::{Listener, ClientEvent};
+
 pub struct System {
+    pub name: String,
     pub config: crate::config::System,
-    pub message_dedup_cache: Arc<Mutex<lru::LruCache<Id<MessageMarker>, Timestamp>>>,
+    pub message_dedup_cache: lru::LruCache<Id<MessageMarker>, Timestamp>,
+    pub clients: HashMap<String, Client>,
 }
 
 impl System {
-    pub fn new(system_config: crate::config::System) -> Self {
+    pub fn new(system_name: String, system_config: crate::config::System) -> Self {
         System {
+            name: system_name,
             config: system_config,
-            message_dedup_cache: Arc::new(Mutex::new(lru::LruCache::new(NonZeroUsize::new(100).unwrap())))
+            message_dedup_cache: lru::LruCache::new(NonZeroUsize::new(100).unwrap()),
+            clients: HashMap::new(),
         }
     }
 
     pub async fn start_clients(&mut self) {
-        println!("Starting clients for system");
+        println!("Starting clients for system {}", self.name);
+
+        let (tx, mut rx) = channel::<ClientEvent>(100);
 
         let reference_user_id : Id<UserMarker> = Id::from_str(self.config.reference_user_id.as_str())
             .expect(format!("Invalid user ID: {}", self.config.reference_user_id).as_str());
 
-        let mut set = JoinSet::new();
-
         for member in self.config.members.iter() {
-            let token = member.discord_token.clone();
-            let self_clone = self.clone();
-            set.spawn(async move {
-                println!("Starting client with token: {}", token);
-
-                let intents = Intents::GUILD_MEMBERS | Intents::GUILD_PRESENCES | Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT;
-
-                let mut shard = Shard::new(ShardId::ONE, token.clone(), intents);
-                let mut client = Client::new(token.clone());
-
-                loop {
-                    let event = match shard.next_event().await {
-                        Ok(event) => event,
-                        Err(source) => {
-                            println!("error receiving event");
-
-                            if source.is_fatal() {
-                                break;
-                            }
-
-                            continue;
-                        }
-                    };
-
-                    match event {
-                        twilight_gateway::Event::Ready(client) => {
-                            println!("Bot started for {}#{}", client.user.name, client.user.discriminator);
-                        },
-
-                        twilight_gateway::Event::MessageCreate(message) => {
-                            if message.author.id != reference_user_id {
-                                continue
-                            }
-
-                            if self_clone.is_new_message(message.id, message.timestamp).await {
-                                self_clone.handle_message_create(message, &mut client).await;
-                            }
-                        },
-
-                        twilight_gateway::Event::MessageUpdate(message) => {
-                            if message.author.is_none() || message.author.as_ref().unwrap().id != reference_user_id {
-                                continue
-                            }
-
-                            if self_clone.is_new_message(message.id, message.edited_timestamp.unwrap_or(message.timestamp.expect("No message creation timestamp"))).await {
-                                self_clone.handle_message_update(message, &mut client).await;
-                            }
-                        },
-
-                        _ => (),
-                    }
-                }
+            let client = twilight_http::Client::new(member.discord_token.clone());
+            self.clients.insert(member.name.clone(), client);
+
+            let tx = tx.clone();
+            let member = member.clone();
+            tokio::spawn(async move {
+                let mut listener = Listener::new(member, reference_user_id);
+                listener.start_listening(tx).await;
             });
         }
 
-        while let Some(join_result) = set.join_next().await {
-            if let Err(join_error) = join_result {
-                println!("Task encountered error: {}", join_error);
-            } else {
-                println!("Task joined cleanly");
+        loop {
+            match rx.recv().await {
+                Some(event) => match event {
+                    ClientEvent::Message { event_time, message_id, content, author: _ } => {
+                        if self.is_new_message(message_id, event_time) {
+                            self.handle_message(content).await;
+                        }
+                    }
+                    ClientEvent::Error(_err) => {
+                        println!("Client ran into an error for system {}", self.name);
+                        return
+                    },
+                },
+                None => {
+                    return
+                },
             }
         }
     }
 
-    async fn is_new_message(&self, message_id: Id<MessageMarker>, timestamp: Timestamp) -> bool {
-        let mut message_cache = self.message_dedup_cache.lock().await;
-
-        let last_seen_timestamp = message_cache.get(&message_id);
+    fn is_new_message(&mut self, message_id: Id<MessageMarker>, timestamp: Timestamp) -> bool {
+        let last_seen_timestamp = self.message_dedup_cache.get(&message_id);
         let current_timestamp = timestamp;
 
         if last_seen_timestamp.is_none() || last_seen_timestamp.unwrap().as_micros() < current_timestamp.as_micros() {
-            message_cache.put(message_id, timestamp);
+            self.message_dedup_cache.put(message_id, timestamp);
             true
         } else {
             false
         }
     }
 
-    async fn handle_message_create(&self, message: Box<twilight_model::gateway::payload::incoming::MessageCreate>, client: &mut Client) {
-        println!("Message created: {}", message.content);
-
-        if let Err(err) = client.create_message(message.channel_id)
-            .reply(message.id)
-            .content(&format!("Recognized message from authorized user {}", message.author.name)).expect("Error: reply too long")
-            .await {
-            println!("Error: {}", err);
-        }
-    }
-
-    async fn handle_message_update(&self, message: Box<twilight_model::gateway::payload::incoming::MessageUpdate>, _client: &mut Client) {
-        println!("Message updated: {}", message.content.unwrap_or("".to_string()))
+    async fn handle_message(&mut self, content: String) {
+        println!("Message: {}", content);
     }
 }