feat: add a channel as a logging channel

Closes #1, fixes #10
This commit is contained in:
Lucien Cartier-Tilet 2023-11-23 02:09:45 +01:00
parent 47dd2b02c4
commit c7bf7ec9e6
Signed by: phundrak
GPG Key ID: BD7789E705CB8DCA
7 changed files with 114 additions and 73 deletions

View File

@ -1,6 +1,13 @@
-- Add migration script here -- Add migration script here
-- Discord IDs are kept as INTEGERs and not unsigned INTEGERs despite
-- their Rust type being u64. In order to properly manage them, you'll
-- need to cast any u64 to i64 with `as i64` before writing them to
-- the database, and cast any i64 to u64 with `as u64` when reading
-- them from the database. This operation is noop in Rust and should
-- therefore not cost a single CPU cycle.
CREATE TABLE IF NOT EXISTS guild_log_channels ( CREATE TABLE IF NOT EXISTS guild_log_channels (
guild_id TEXT PRIMARY KEY, guild_id INTEGER PRIMARY KEY,
channel_id TEXT NOT NULL, channel_id INTEGER NOT NULL,
UNIQUE(guild_id, channel_id) UNIQUE(guild_id, channel_id)
); );

View File

@ -1,59 +0,0 @@
use std::env;
use sqlx::SqlitePool;
pub struct Database {
pool: SqlitePool,
}
impl Database {
pub async fn new() -> color_eyre::Result<Self> {
Ok(Self {
pool: SqlitePool::connect(&env::var("DATABASE_URL")?).await?,
})
}
pub async fn get_logging_channel(
&self,
guild_id: u64,
) -> color_eyre::Result<Vec<u64>> {
let guild_str = guild_id.to_string();
let channels = sqlx::query!(
r#"
SELECT channel_id
FROM guild_log_channels
WHERE guild_id = ?1
"#,
guild_str
)
.fetch_all(&self.pool)
.await?;
Ok(channels
.iter()
.map(|id| id.channel_id.parse::<u64>().unwrap())
.collect())
}
pub async fn set_logging_channel(
&self,
guild_id: u64,
channel_id: u64,
) -> color_eyre::Result<()> {
let guild_str = guild_id.to_string();
let channel_str = channel_id.to_string();
let mut conn = self.pool.acquire().await?;
sqlx::query!(
r#"
INSERT INTO guild_log_channels (guild_id, channel_id)
VALUES ( ?1, ?2 )
"#,
guild_str,
channel_str
)
.execute(&mut *conn)
.await?
.last_insert_rowid();
Ok(())
}
}

62
src/db/mod.rs Normal file
View File

@ -0,0 +1,62 @@
use std::env;
use poise::serenity_prelude::{ChannelId, GuildId};
use sqlx::SqlitePool;
pub type Result<T> = ::std::result::Result<T, sqlx::Error>;
pub struct Database {
pool: SqlitePool,
}
impl Database {
pub async fn new() -> Result<Self> {
Ok(Self {
pool: SqlitePool::connect(
&env::var("DATABASE_URL")
.expect("Missing enviroment variable DATABASE_URL"),
)
.await?,
})
}
pub async fn get_logging_channel(
&self,
guild_id: u64,
) -> Result<Vec<u64>> {
let guild_id = guild_id as i64;
let channels = sqlx::query!(
r#"
SELECT channel_id
FROM guild_log_channels
WHERE guild_id = ?1
"#,
guild_id
)
.fetch_all(&self.pool)
.await?;
Ok(channels.iter().map(|id| id.channel_id as u64).collect())
}
pub async fn set_logging_channel(
&self,
guild_id: GuildId,
channel_id: ChannelId,
) -> Result<()> {
let guild_id = guild_id.0 as i64;
let channel_id = channel_id.0 as i64;
let mut conn = self.pool.acquire().await?;
sqlx::query!(
r#"
INSERT INTO guild_log_channels (guild_id, channel_id)
VALUES ( ?1, ?2 )
"#,
guild_id,
channel_id
)
.execute(&mut *conn)
.await
.map(|_| ())
}
}

View File

@ -2,16 +2,47 @@ use super::{Context, Error};
use super::utils::serenity; use super::utils::serenity;
#[poise::command(slash_command)] #[poise::command(
pub async fn add_logging_channel( slash_command,
subcommands("add_channel"),
required_permissions = "ADMINISTRATOR"
)]
pub async fn logging(_ctx: Context<'_>, _arg: String) -> Result<(), Error> {
Ok(())
}
#[poise::command(slash_command, aliases("add-channel"))]
pub async fn add_channel(
ctx: Context<'_>, ctx: Context<'_>,
#[description = "Selected channel"] channel: Option<serenity::Channel>, #[description = "New logging channel"] channel: serenity::Channel,
) -> Result<(), Error> { ) -> Result<(), Error> {
let response = match channel { let channel_id = channel.id();
None => "No channel selected. Please select one.".to_owned(), let response = match ctx.guild_id() {
Some(chan) => { None => "Error: Could not determine the guild's ID.".to_owned(),
let channel_id = chan.id(); Some(guild_id) => {
format!("Selected channel <#{channel_id}>") match ctx
.data()
.database
.set_logging_channel(guild_id, channel_id)
.await
{
Ok(_) => format!(
"Added channel <#{channel_id}> as a logging channel"
),
Err(e) => {
if let Some(db_error) = e.as_database_error() {
if db_error.is_unique_violation() {
format!("Channel <#{channel_id}> is already a logging channel")
} else {
format!("Error: {:?}", e)
}
} else {
format!(
"Something bad happened with the database: {e:?}"
)
}
}
}
} }
}; };
ctx.say(response).await?; ctx.say(response).await?;

View File

@ -5,14 +5,14 @@ pub mod utils;
use poise::FrameworkBuilder; use poise::FrameworkBuilder;
use utils::serenity; use utils::serenity;
use commands::add_logging_channel; use commands::logging;
use utils::{BotData, Context, Error}; use utils::{BotData, Context, Error};
pub async fn make_bot() -> color_eyre::Result<FrameworkBuilder<BotData, Error>> pub async fn make_bot() -> color_eyre::Result<FrameworkBuilder<BotData, Error>>
{ {
let framework = poise::Framework::builder() let framework = poise::Framework::builder()
.options(poise::FrameworkOptions { .options(poise::FrameworkOptions {
commands: vec![add_logging_channel()], commands: vec![logging()],
..Default::default() ..Default::default()
}) })
.token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN")) .token(std::env::var("DISCORD_TOKEN").expect("missing DISCORD_TOKEN"))

View File

@ -2,7 +2,7 @@ use crate::db::Database;
pub use poise::serenity_prelude as serenity; pub use poise::serenity_prelude as serenity;
pub struct BotData { pub struct BotData {
database: Database, pub database: Database,
} }
impl BotData { impl BotData {

View File

@ -3,7 +3,7 @@ use tracing_subscriber::FmtSubscriber;
pub fn setup_logging() { pub fn setup_logging() {
let subscriber = FmtSubscriber::builder() let subscriber = FmtSubscriber::builder()
.with_max_level(Level::DEBUG) .with_max_level(Level::INFO)
.finish(); .finish();
tracing::subscriber::set_global_default(subscriber) tracing::subscriber::set_global_default(subscriber)
.expect("Setting default subscriber failed"); .expect("Setting default subscriber failed");