mod aggregator; mod types; mod plugin; mod util; mod log; use aggregator::MemberEvent; use log::Logger; use twilight_gateway::{Intents, Shard, ShardId}; use twilight_http::Client; pub use types::SystemThreadCommand; use crate::SystemUiEvent; use std::{collections::HashMap, num::NonZeroU64, sync::Arc}; use tokio::sync::Mutex; use plugin::get_plugins; use std::sync::mpsc::Sender as ThreadSender; use types::{Member, Response, System}; use std::iter::once; pub struct Manager; impl Manager { pub async fn start(system_name: String, system_config: crate::config::System, ui_sender : ThreadSender<(String, SystemUiEvent)>) { let gateway_intents = Intents::GUILD_MEMBERS | Intents::GUILD_PRESENCES | Intents::GUILD_MESSAGES | Intents::MESSAGE_CONTENT; let system = System { followed_user: NonZeroU64::try_from(system_config.reference_user_id.parse::().unwrap()).unwrap().into(), command_prefix: "!".to_string(), members: system_config.members.iter().map(|member| Member { discord_token: member.discord_token.clone(), user_id: Arc::new(Mutex::new(None)), message_pattern: member.message_pattern.clone(), shard: Arc::new(Mutex::new(Shard::new( ShardId::ONE, member.discord_token.clone(), gateway_intents.clone(), ))), client: Arc::new(Mutex::new(Client::new(member.discord_token.clone()))) }).collect(), message_cache: Arc::new(Mutex::new(HashMap::new())), }; let mut message_receiver = aggregator::MessageAggregator::start(&system); let logger = Logger::new(system_name.clone(), system_config.clone(), ui_sender.clone()); let (all_plugins, by_command) = get_plugins(); 'member_event: loop { match message_receiver.recv().await { None => (), Some(MemberEvent::GatewayConnect(member_token, user_id)) => { for member in &system.members { if member.discord_token == member_token { {*member.user_id.lock().await = Some(user_id)}; } } logger.log_connect(member_token).await; }, Some(MemberEvent::GatewayError(member_token)) => { logger.log_err(Some(member_token), "Non-fatal gateway error".to_string()).await; }, Some(MemberEvent::GatewayDisconnect(member_token)) => { logger.log_disconnect(member_token).await; }, Some(MemberEvent::Message(message, seen_by)) => { if let Some(command_string) = message.content.strip_prefix(&system.command_prefix) { let mut words = command_string.split_whitespace(); if let Some(first_word) = words.next() { if let Some((command, plugin)) = by_command .get(first_word) .map(|command| Some(command)) .unwrap_or_else(|| by_command.get(first_word.get(0..1).unwrap())) { logger.log_line(None, format!("Handling command: {command:?}")).await; let args : Vec<_> = match command { plugin::PluginCommand::Word(_) => words.collect(), plugin::PluginCommand::Char(_) => once(first_word).chain(words).collect(), }; plugin.handle_command(&logger, &system, &message, *command, args).await; continue 'member_event; } else { logger.log_line(None, format!("Unknown command: {first_word}")).await; } } } // Handle as message let mut message_response = Response::Noop { delete_source: false }; for plugin in &all_plugins { plugin.handle_message(&logger, &system, &message, &mut message_response).await; } match message_response.clone() { Response::Noop { delete_source } => { if delete_source { let client = system.members.iter().find(|m| m.discord_token == seen_by).map(|m| m.client.clone()) .expect("No such client"); let _ = client.lock().await.delete_message(message.channel_id, message.id) .await; } }, Response::Proxy { member, content } => { if let Ok(new_message) = util::duplicate_message(&member.client, &message, content.as_str()).await { if let Err(err) = {member.client.lock().await.delete_message(message.channel_id, message.id).await.map(|_| ()).map_err(|err| err.to_string()).clone() } { logger.log_err(Some(member.discord_token), format!("Could not proxy message: {err}")).await; {let _ = member.client.lock().await.delete_message(new_message.channel_id, new_message.id).await;} } else { system.cache_most_recent_message(new_message.channel_id, new_message.clone(), member.clone()).await; } for plugin in &all_plugins { plugin.post_response(&logger, &system, &new_message, message.channel_id, &message_response).await; } } }, } }, } } } }