diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/handlers/mod.rs | 226 | ||||
-rw-r--r-- | src/handlers/presence_setter.rs | 17 | ||||
-rw-r--r-- | src/handlers/x500_mapper.rs | 22 | ||||
-rw-r--r-- | src/main.rs | 67 |
4 files changed, 319 insertions, 13 deletions
diff --git a/src/handlers/mod.rs b/src/handlers/mod.rs new file mode 100644 index 0000000..f2b8a4f --- /dev/null +++ b/src/handlers/mod.rs @@ -0,0 +1,226 @@ +use futures::stream::{self, StreamExt}; +use serenity::{ + all::{ + ActionExecution, ApplicationId, AuditLogEntry, ChannelId, ChannelPinsUpdateEvent, + CommandPermissions, CurrentUser, Emoji, EmojiId, Guild, GuildChannel, GuildId, + GuildMemberUpdateEvent, GuildMembersChunkEvent, GuildScheduledEventUserAddEvent, + GuildScheduledEventUserRemoveEvent, Integration, IntegrationId, Interaction, + InviteCreateEvent, InviteDeleteEvent, Member, Message, MessageId, MessageUpdateEvent, + PartialGuild, PartialGuildChannel, Presence, Reaction, Ready, ResumedEvent, Role, RoleId, + Rule, ScheduledEvent, StageInstance, Sticker, StickerId, ThreadListSyncEvent, ThreadMember, + ThreadMembersUpdateEvent, TypingStartEvent, UnavailableGuild, User, VoiceServerUpdateEvent, + VoiceState, + }, + async_trait, + client::{Context, EventHandler}, + gateway::ShardStageUpdateEvent, + http::RatelimitInfo, +}; +use std::collections::HashMap; + +mod presence_setter; +mod x500_mapper; + +pub use self::{presence_setter::PresenceSetter, x500_mapper::X500Mapper}; + +/// An EventHandler that proxies events to each of its contained handlers concurrently (but not in +/// parallel). +pub struct MultiHandler(pub Vec<Box<dyn EventHandler>>); + +macro_rules! define_multihandler_impl { + ( + $(async fn $name:ident(&self, $($arg:ident : $argty:ty),* $(,)?);)* + ) => { + #[async_trait] + impl EventHandler for MultiHandler { + $(async fn $name( + &self, + $($arg: $argty),* + ) { + stream::iter(self.0.iter()) + .for_each_concurrent(None, move |handler| { + handler.$name($($arg.clone()),*) + }) + .await + })* + } + }; +} + +define_multihandler_impl! { + async fn command_permissions_update(&self, ctx: Context, permission: CommandPermissions); + async fn auto_moderation_rule_create(&self, ctx: Context, rule: Rule); + async fn auto_moderation_rule_update(&self, ctx: Context, rule: Rule); + async fn auto_moderation_rule_delete(&self, ctx: Context, rule: Rule); + async fn auto_moderation_action_execution(&self, ctx: Context, execution: ActionExecution); + async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>); + async fn shards_ready(&self, ctx: Context, total_shards: u32); + async fn channel_create(&self, ctx: Context, channel: GuildChannel); + async fn category_create(&self, ctx: Context, category: GuildChannel); + async fn category_delete(&self, ctx: Context, category: GuildChannel); + async fn channel_delete( + &self, + ctx: Context, + channel: GuildChannel, + messages: Option<Vec<Message>>, + ); + async fn channel_pins_update(&self, ctx: Context, pin: ChannelPinsUpdateEvent); + async fn channel_update(&self, ctx: Context, old: Option<GuildChannel>, new: GuildChannel); + async fn guild_audit_log_entry_create( + &self, + ctx: Context, + entry: AuditLogEntry, + guild_id: GuildId, + ); + async fn guild_ban_addition(&self, ctx: Context, guild_id: GuildId, banned_user: User); + async fn guild_ban_removal(&self, ctx: Context, guild_id: GuildId, unbanned_user: User); + async fn guild_create(&self, ctx: Context, guild: Guild, is_new: Option<bool>); + async fn guild_delete(&self, ctx: Context, incomplete: UnavailableGuild, full: Option<Guild>); + async fn guild_emojis_update( + &self, + ctx: Context, + guild_id: GuildId, + current_state: HashMap<EmojiId, Emoji>, + ); + async fn guild_integrations_update(&self, ctx: Context, guild_id: GuildId); + async fn guild_member_addition(&self, ctx: Context, new_member: Member); + async fn guild_member_removal( + &self, + ctx: Context, + guild_id: GuildId, + user: User, + member_data_if_available: Option<Member>, + ); + async fn guild_member_update( + &self, + ctx: Context, + old_if_available: Option<Member>, + new: Option<Member>, + event: GuildMemberUpdateEvent, + ); + async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent); + async fn guild_role_create(&self, ctx: Context, new: Role); + async fn guild_role_delete( + &self, + ctx: Context, + guild_id: GuildId, + removed_role_id: RoleId, + removed_role_data_if_available: Option<Role>, + ); + async fn guild_role_update( + &self, + ctx: Context, + old_data_if_available: Option<Role>, + new: Role, + ); + async fn guild_stickers_update( + &self, + ctx: Context, + guild_id: GuildId, + current_state: HashMap<StickerId, Sticker>, + ); + async fn guild_update( + &self, + ctx: Context, + old_data_if_available: Option<Guild>, + new_data: PartialGuild, + ); + async fn invite_create(&self, ctx: Context, data: InviteCreateEvent); + async fn invite_delete(&self, ctx: Context, data: InviteDeleteEvent); + async fn message(&self, ctx: Context, new_message: Message); + async fn message_delete( + &self, + ctx: Context, + channel_id: ChannelId, + deleted_message_id: MessageId, + guild_id: Option<GuildId>, + ); + async fn message_delete_bulk( + &self, + ctx: Context, + channel_id: ChannelId, + multiple_deleted_messages_ids: Vec<MessageId>, + guild_id: Option<GuildId>, + ); + async fn message_update( + &self, + ctx: Context, + old_if_available: Option<Message>, + new: Option<Message>, + event: MessageUpdateEvent, + ); + async fn reaction_add(&self, ctx: Context, add_reaction: Reaction); + async fn reaction_remove(&self, ctx: Context, removed_reaction: Reaction); + async fn reaction_remove_all( + &self, + ctx: Context, + channel_id: ChannelId, + removed_from_message_id: MessageId, + ); + async fn reaction_remove_emoji(&self, ctx: Context, removed_reactions: Reaction); + async fn presence_replace(&self, ctx: Context, presences: Vec<Presence>); + async fn presence_update(&self, ctx: Context, new_data: Presence); + async fn ready(&self, ctx: Context, data_about_bot: Ready); + async fn resume(&self, ctx: Context, event: ResumedEvent); + async fn shard_stage_update(&self, ctx: Context, event: ShardStageUpdateEvent); + async fn typing_start(&self, ctx: Context, event: TypingStartEvent); + async fn user_update(&self, ctx: Context, old_data: Option<CurrentUser>, new: CurrentUser); + async fn voice_server_update(&self, ctx: Context, event: VoiceServerUpdateEvent); + async fn voice_state_update(&self, ctx: Context, old: Option<VoiceState>, new: VoiceState); + async fn voice_channel_status_update( + &self, + ctx: Context, + old: Option<String>, + status: Option<String>, + id: ChannelId, + guild_id: GuildId, + ); + async fn webhook_update( + &self, + ctx: Context, + guild_id: GuildId, + belongs_to_channel_id: ChannelId, + ); + async fn interaction_create(&self, ctx: Context, interaction: Interaction); + async fn integration_create(&self, ctx: Context, integration: Integration); + async fn integration_update(&self, ctx: Context, integration: Integration); + async fn integration_delete( + &self, + ctx: Context, + integration_id: IntegrationId, + guild_id: GuildId, + application_id: Option<ApplicationId>, + ); + async fn stage_instance_create(&self, ctx: Context, stage_instance: StageInstance); + async fn stage_instance_update(&self, ctx: Context, stage_instance: StageInstance); + async fn stage_instance_delete(&self, ctx: Context, stage_instance: StageInstance); + async fn thread_create(&self, ctx: Context, thread: GuildChannel); + async fn thread_update(&self, ctx: Context, old: Option<GuildChannel>, new: GuildChannel); + async fn thread_delete( + &self, + ctx: Context, + thread: PartialGuildChannel, + full_thread_data: Option<GuildChannel>, + ); + async fn thread_list_sync(&self, ctx: Context, thread_list_sync: ThreadListSyncEvent); + async fn thread_member_update(&self, ctx: Context, thread_member: ThreadMember); + async fn thread_members_update( + &self, + ctx: Context, + thread_members_update: ThreadMembersUpdateEvent, + ); + async fn guild_scheduled_event_create(&self, ctx: Context, event: ScheduledEvent); + async fn guild_scheduled_event_update(&self, ctx: Context, event: ScheduledEvent); + async fn guild_scheduled_event_delete(&self, ctx: Context, event: ScheduledEvent); + async fn guild_scheduled_event_user_add( + &self, + ctx: Context, + subscribed: GuildScheduledEventUserAddEvent, + ); + async fn guild_scheduled_event_user_remove( + &self, + ctx: Context, + unsubscribed: GuildScheduledEventUserRemoveEvent, + ); + async fn ratelimit(&self, data: RatelimitInfo); +} diff --git a/src/handlers/presence_setter.rs b/src/handlers/presence_setter.rs new file mode 100644 index 0000000..4e56dfd --- /dev/null +++ b/src/handlers/presence_setter.rs @@ -0,0 +1,17 @@ +use serenity::{ + all::{ActivityData, Ready}, + async_trait, + client::{Context, EventHandler}, +}; + +/// A handler that sets a fun presence. +pub struct PresenceSetter; + +#[async_trait] +impl EventHandler for PresenceSetter { + async fn ready(&self, ctx: Context, _data_about_bot: Ready) { + ctx.set_activity(Some(ActivityData::custom( + "evaluating (λx → x x)(λx → x x)".to_string(), + ))); + } +} diff --git a/src/handlers/x500_mapper.rs b/src/handlers/x500_mapper.rs new file mode 100644 index 0000000..f477e97 --- /dev/null +++ b/src/handlers/x500_mapper.rs @@ -0,0 +1,22 @@ +use serenity::{ + all::{GuildMemberUpdateEvent, Member}, + async_trait, + client::{Context, EventHandler}, +}; + +/// A handler that notices people with an X.500 in their nicknames that matches a student's, and +/// records it in the database. +pub struct X500Mapper; + +#[async_trait] +impl EventHandler for X500Mapper { + async fn guild_member_update( + &self, + _ctx: Context, + old_if_available: Option<Member>, + new: Option<Member>, + event: GuildMemberUpdateEvent, + ) { + dbg!((old_if_available, new, event)); + } +} diff --git a/src/main.rs b/src/main.rs index 0d602af..c9dbd7a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,17 @@ -use anyhow::{Context, Result}; -use clap::Parser; +mod handlers; + +use crate::handlers::*; +use anyhow::{Context as _, Result}; +use clap::{value_parser, ArgAction, Parser}; use serde::Deserialize; use serenity::{ - all::{ActivityData, ActivityType, GatewayIntents, OnlineStatus}, + all::{ActivityData, GatewayIntents, GuildMemberUpdateEvent, Member, Ready}, + async_trait, + client::{Context, EventHandler}, Client, }; use std::{fs, path::PathBuf}; +use stderrlog::StdErrLog; #[derive(Debug, Deserialize)] struct Config { @@ -14,28 +20,63 @@ struct Config { #[derive(Debug, Parser)] struct Args { + /// The path to the configuration file. config_path: PathBuf, + + /// Decreases the log level. + #[clap( + short, + long, + conflicts_with("verbose"), + action = ArgAction::Count, + value_parser = value_parser!(u8).range(..=2) + )] + quiet: u8, + + /// Increases the log level. + #[clap( + short, + long, + conflicts_with("quiet"), + action = ArgAction::Count, + value_parser = value_parser!(u8).range(..=3) + )] + verbose: u8, } #[tokio::main] async fn main() -> Result<()> { let args = Args::parse(); + + // Set up logging. + { + let mut logger = StdErrLog::new(); + match args.quiet { + 0 => logger.verbosity(1 + args.verbose as usize), + 1 => logger.verbosity(0), + 2 => logger.quiet(true), + // UNREACHABLE: A maximum of two occurrences of quiet are allowed. + _ => unreachable!(), + }; + // UNWRAP: No other logger should be set up. + logger.show_module_names(true).init().unwrap() + } + let config_str = fs::read_to_string(&args.config_path) .with_context(|| format!("failed to read {}", args.config_path.display()))?; let config: Config = toml::from_str(&config_str) .with_context(|| format!("failed to parse {}", args.config_path.display()))?; drop(config_str); - let mut client = Client::builder(&config.discord_token, GatewayIntents::default()) - .activity(ActivityData { - name: "(λx → x x)(λx → x x)".to_string(), - kind: ActivityType::Custom, - state: Some("pondering".to_string()), - url: None, - }) - .status(OnlineStatus::Online) - .await - .context("failed to create Discord client")?; + let handler = MultiHandler(vec![Box::new(PresenceSetter), Box::new(X500Mapper)]); + + let mut client = Client::builder( + &config.discord_token, + GatewayIntents::default() | GatewayIntents::GUILD_MEMBERS, + ) + .event_handler(handler) + .await + .context("failed to create Discord client")?; client .start() |