aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNathan Ringo <nathan@remexre.com>2024-01-15 23:36:04 -0600
committerNathan Ringo <nathan@remexre.com>2024-01-15 23:36:04 -0600
commit54f497163f57dacd8d621a2a3c89e1f06ac370d0 (patch)
treebc58027e740cef37c902d1f67cd97115fd5127be
parentea90b5ce89b9babb4ebc86de523efc7fa9631281 (diff)
Start splitting up handlers.
-rw-r--r--Cargo.lock71
-rw-r--r--Cargo.toml3
-rw-r--r--src/handlers/mod.rs226
-rw-r--r--src/handlers/presence_setter.rs17
-rw-r--r--src/handlers/x500_mapper.rs22
-rw-r--r--src/main.rs67
6 files changed, 392 insertions, 14 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 3f41ae2..740a581 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -110,6 +110,17 @@ dependencies = [
]
[[package]]
+name = "atty"
+version = "0.2.14"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
+dependencies = [
+ "hermit-abi 0.1.19",
+ "libc",
+ "winapi",
+]
+
+[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -235,8 +246,10 @@ checksum = "7f2c685bad3eb3d45a01354cedb7d5faa66194d1d58ba6e267a8de788f79db38"
dependencies = [
"android-tzdata",
"iana-time-zone",
+ "js-sys",
"num-traits",
"serde",
+ "wasm-bindgen",
"windows-targets 0.48.5",
]
@@ -469,6 +482,7 @@ checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0"
dependencies = [
"futures-channel",
"futures-core",
+ "futures-executor",
"futures-io",
"futures-sink",
"futures-task",
@@ -492,6 +506,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d"
[[package]]
+name = "futures-executor"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d"
+dependencies = [
+ "futures-core",
+ "futures-task",
+ "futures-util",
+]
+
+[[package]]
name = "futures-io"
version = "0.3.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -613,6 +638,15 @@ checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
+version = "0.1.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "hermit-abi"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7"
@@ -759,8 +793,11 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap",
+ "futures",
+ "log",
"serde",
"serenity",
+ "stderrlog",
"tokio",
"toml",
]
@@ -871,7 +908,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
- "hermit-abi",
+ "hermit-abi 0.3.3",
"libc",
]
@@ -1320,6 +1357,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
+name = "stderrlog"
+version = "0.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "69a26bbf6de627d389164afa9783739b56746c6c72c4ed16539f4ff54170327b"
+dependencies = [
+ "atty",
+ "chrono",
+ "log",
+ "termcolor",
+ "thread_local",
+]
+
+[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1388,6 +1438,15 @@ dependencies = [
]
[[package]]
+name = "termcolor"
+version = "1.1.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
name = "thiserror"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1408,6 +1467,16 @@ dependencies = [
]
[[package]]
+name = "thread_local"
+version = "1.1.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+]
+
+[[package]]
name = "time"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index ccc799d..dd2c38e 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -6,7 +6,10 @@ edition = "2021"
[dependencies]
anyhow = { version = "1.0.79", features = ["backtrace"] }
clap = { version = "4.4.17", features = ["derive"] }
+futures = "0.3.30"
+log = "0.4.20"
serde = { version = "1.0.195", features = ["derive"] }
serenity = "0.12.0"
+stderrlog = "0.5.4"
tokio = { version = "1.35.1", features = ["rt-multi-thread"] }
toml = "0.8.8"
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()