summaryrefslogtreecommitdiff
path: root/src/client.rs
blob: 65c221b363b34a2ec5bb0f078ee1285f221d079b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use crate::{commands, events, http, storage::Storage};

use std::{sync::Arc, time::Duration};

use eyre::{bail, Context as _, Result};
use log::{info, trace, warn};
use poise::{
	serenity_prelude::{self as serenity},
	EditTracker, Framework, FrameworkOptions, PrefixFrameworkOptions,
};

pub type Error = eyre::Report;
pub type Context<'a> = poise::Context<'a, Data, Error>;

#[derive(Clone, Debug, Default)]
pub struct Data {
	pub http_client: http::Client,
	pub storage: Option<Storage>,
}

async fn setup(ctx: &serenity::Context) -> Result<Data> {
	let storage = Storage::from_env().ok();

	if let Some(storage) = storage.as_ref() {
		if !storage.clone().is_connected() {
			bail!("You specified a storage backend but there's no connection! Is it running?");
		}
		trace!("Storage backend connected!");

		poise::builtins::register_globally(ctx, &commands::global()).await?;
		info!("Registered global commands!");

		// register "extra" commands in guilds that allow it
		let guilds = storage.get_opted_guilds().await?;

		for guild in guilds {
			poise::builtins::register_in_guild(ctx, &commands::optional(), guild).await?;

			info!("Registered guild commands to {}", guild);
		}
	} else {
		warn!("No storage backend was specified. Features requiring storage cannot be used");
		warn!("Registering optional commands globally since there's no storage backend");
		poise::builtins::register_globally(ctx, &commands::all()).await?;
	}

	let http_client = <http::Client as http::Ext>::default();
	let data = Data {
		http_client,
		storage,
	};

	Ok(data)
}

pub async fn handle_shutdown(shard_manager: Arc<serenity::ShardManager>, reason: &str) {
	warn!("{reason}! Shutting down bot...");
	shard_manager.shutdown_all().await;
	println!("Everything is shutdown. Goodbye!");
}

pub async fn get() -> Result<serenity::Client> {
	let token = std::env::var("TOKEN").wrap_err("Couldn't find bot token in environment!")?;

	let intents =
		serenity::GatewayIntents::non_privileged() | serenity::GatewayIntents::MESSAGE_CONTENT;

	let options = FrameworkOptions {
		commands: commands::all(),
		on_error: |error| Box::pin(events::error::handle(error)),

		command_check: Some(|ctx| {
			Box::pin(async move { Ok(ctx.author().id != ctx.framework().bot_id) })
		}),

		event_handler: |ctx, event, _framework, data| Box::pin(events::handle(ctx, event, data)),

		prefix_options: PrefixFrameworkOptions {
			prefix: Some("!".into()),
			edit_tracker: Some(Arc::new(EditTracker::for_timespan(Duration::from_secs(
				3600,
			)))),
			..Default::default()
		},

		..Default::default()
	};

	let framework = Framework::builder()
		.options(options)
		.setup(|ctx, _ready, _framework| Box::pin(setup(ctx)))
		.build();

	let client = serenity::ClientBuilder::new(token, intents)
		.framework(framework)
		.await?;

	Ok(client)
}