Merge pull request 'Allow channels to be ignored for XP gain' (#20) from feature/ignore-for-xp into main
Badgey Deployment / build (push) Successful in 6m20s Details
Badgey Deployment / deploy (BADGEY) (push) Successful in 8s Details
Badgey Deployment / deploy (M5_COMPUTER) (push) Successful in 9s Details

Reviewed-on: #20
This commit is contained in:
Xyon 2025-02-17 21:55:09 +00:00
commit fcca9b4ea7
7 changed files with 119 additions and 42 deletions

View File

@ -0,0 +1,18 @@
ALTER TABLE IF EXISTS "channels"
RENAME TO "quarantine_channels";
-- Make the quarantine channels table hold more generic channel info
ALTER TABLE IF EXISTS "quarantine_channels"
RENAME COLUMN "channel_id" TO "qc_channel_id";
-- Channels with this flag should retain old qc behaviour
ALTER TABLE IF EXISTS "quarantine_channels"
DROP COLUMN "is_quarantine_channel";
-- Setting flag to false allows channels to be excluded from XP even if not QC (though QC channels via the above flag imply this flag too)
ALTER TABLE IF EXISTS "quarantine_channels"
DROP COLUMN "is_valid_for_xp";
-- Ensure that the role ID can be null for non-qc channels
ALTER TABLE IF EXISTS "quarantine_channels"
ALTER COLUMN "qc_role_id" SET NOT NULL;

View File

@ -0,0 +1,19 @@
-- Make the quarantine channels table hold more generic channel info
ALTER TABLE IF EXISTS "quarantine_channels"
RENAME COLUMN "qc_channel_id" TO "channel_id";
-- Channels with this flag should retain old qc behaviour
ALTER TABLE IF EXISTS "quarantine_channels"
ADD COLUMN "is_quarantine_channel" BOOL NOT NULL DEFAULT FALSE;
-- Setting flag to false allows channels to be excluded from XP even if not QC (though QC channels via the above flag imply this flag too)
ALTER TABLE IF EXISTS "quarantine_channels"
ADD COLUMN "is_valid_for_xp" BOOL NOT NULL DEFAULT TRUE;
-- Ensure that the role ID can be null for non-qc channels
ALTER TABLE IF EXISTS "quarantine_channels"
ALTER COLUMN "qc_role_id" DROP NOT NULL;
-- Rename table to something more suitable
ALTER TABLE IF EXISTS "quarantine_channels"
RENAME TO "channels";

View File

@ -2,7 +2,7 @@ use built::chrono;
use manifold::error::{ManifoldError, ManifoldResult}; use manifold::error::{ManifoldError, ManifoldResult};
use manifold::{ManifoldContext, ManifoldData}; use manifold::{ManifoldContext, ManifoldData};
use poise::serenity_prelude as serenity; use poise::serenity_prelude as serenity;
use crate::badgey::models::quarantine_channel::QuarantineChannel; use crate::badgey::models::quarantine_channel::Channel;
#[poise::command(slash_command, prefix_command, required_permissions = "MODERATE_MEMBERS")] #[poise::command(slash_command, prefix_command, required_permissions = "MODERATE_MEMBERS")]
async fn void_user(_ctx: ManifoldContext<'_>, _target: serenity::User) -> ManifoldResult<()> { async fn void_user(_ctx: ManifoldContext<'_>, _target: serenity::User) -> ManifoldResult<()> {
@ -38,25 +38,44 @@ async fn security_record(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_GUILD")] #[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_GUILD")]
async fn add_quarantine_channel(ctx: ManifoldContext<'_>, channel: serenity::ChannelId, role: serenity::RoleId) -> ManifoldResult<()> { async fn add_quarantine_channel(ctx: ManifoldContext<'_>, channel: serenity::ChannelId, role: serenity::RoleId) -> ManifoldResult<()> {
let qc = QuarantineChannel::new(role.as_u64().clone() as i64, channel.as_u64().clone() as i64); let qc = Channel::new(Some(role.as_u64().clone() as i64), channel.as_u64().clone() as i64, true, false);
qc.insert(&ctx.data().database)?; qc.insert(&ctx.data().database)?;
ctx.reply(format!("OK: Quarantine channel {channel} defined with quarantine role {role}", channel = channel, role = role)).await?; ctx.reply(format!("OK: Quarantine channel <#{channel}> defined with quarantine role <@{role}>", channel = channel, role = role)).await?;
Ok(()) Ok(())
} }
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_GUILD")] #[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_GUILD")]
async fn remove_quarantine_channel(ctx: ManifoldContext<'_>, channel: serenity::ChannelId) -> ManifoldResult<()> { async fn remove_quarantine_channel(ctx: ManifoldContext<'_>, channel: serenity::ChannelId) -> ManifoldResult<()> {
let db = &ctx.data().database; let db = &ctx.data().database;
QuarantineChannel::get(db, channel.as_u64().clone() as i64)?.remove(db)?; Channel::get_by_channel_id(db, channel.as_u64().clone() as i64)?.remove(db)?;
ctx.reply(format!("OK: Quarantine channel {channel} is no longer defined.", channel = channel)).await?; ctx.reply(format!("OK: Quarantine channel <#{channel}> is no longer defined.", channel = channel)).await?;
Ok(()) Ok(())
} }
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 7] { #[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_GUILD")]
[void_user(), unvoid_user(), airlock_user(), record_chronicle(), security_record(), add_quarantine_channel(), remove_quarantine_channel()] async fn ignore_channel_for_xp(ctx: ManifoldContext<'_>, channel: serenity::ChannelId) -> ManifoldResult<()> {
let qc = Channel::new(None, channel.as_u64().clone() as i64, false, false);
qc.insert(&ctx.data().database)?;
ctx.reply(format!("OK: Channel <#{channel}> will no longer allow users to accrue XP.", channel = channel)).await?;
Ok(())
}
#[poise::command(slash_command, prefix_command, required_permissions = "MANAGE_GUILD")]
async fn unignore_channel_for_xp(ctx: ManifoldContext<'_>, channel: serenity::ChannelId) -> ManifoldResult<()> {
let db = &ctx.data().database;
Channel::get_by_channel_id(db, channel.as_u64().clone() as i64)?.remove(db)?;
ctx.reply(format!("OK: Channel <#{channel}> will now permit users to accrue XP.", channel = channel)).await?;
Ok(())
}
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 9] {
[void_user(), unvoid_user(), airlock_user(), record_chronicle(), security_record(), add_quarantine_channel(), remove_quarantine_channel(), ignore_channel_for_xp(), unignore_channel_for_xp()]
} }

View File

@ -3,7 +3,7 @@ use manifold::ManifoldData;
use poise::FrameworkContext; use poise::FrameworkContext;
use poise::serenity_prelude::{ChannelId, Context, Member, Mentionable}; use poise::serenity_prelude::{ChannelId, Context, Member, Mentionable};
use crate::badgey::models::quarantine_channel::QuarantineChannel; use crate::badgey::models::quarantine_channel::Channel;
pub async fn airlock_handler(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, old: &Option<Member>, new: &Member) -> ManifoldResult<()> { pub async fn airlock_handler(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, old: &Option<Member>, new: &Member) -> ManifoldResult<()> {
@ -11,17 +11,17 @@ pub async fn airlock_handler(ctx: &Context, fctx: &FrameworkContext<'_, Manifold
// Is this user quarantined? // Is this user quarantined?
for role in new.roles.iter() { for role in new.roles.iter() {
if let Ok(channel) = QuarantineChannel::get(db, role.as_u64().clone() as i64) { if let Ok(channel) = Channel::get_by_qc_role_id(db, role.as_u64().clone() as i64) {
// Was this user JUST added to a quarantine channel? // Was this user JUST added to a quarantine channel?
if let Some(m) = old { if let Some(m) = old {
for role in m.roles.iter() { for role in m.roles.iter() {
if let Ok(_) = QuarantineChannel::get(db, role.as_u64().clone() as i64) { if let Ok(_) = Channel::get_by_qc_role_id(db, role.as_u64().clone() as i64) {
// User already had quarantine role, return early // User already had quarantine role, return early
return Ok(()) return Ok(())
} }
} }
let target_channel: ChannelId = ChannelId::from(channel.qc_channel_id as u64); let target_channel: ChannelId = ChannelId::from(channel.channel_id as u64);
// User was just added to a quarantine channel. Post the initial ping message. // User was just added to a quarantine channel. Post the initial ping message.
target_channel.say(ctx, format!("{mention}, please respond to this message", mention = new.mention())).await?; target_channel.say(ctx, format!("{mention}, please respond to this message", mention = new.mention())).await?;

View File

@ -3,45 +3,57 @@ use diesel::dsl::delete;
use diesel::prelude::*; use diesel::prelude::*;
use manifold::Db; use manifold::Db;
use manifold::error::{ManifoldError, ManifoldResult}; use manifold::error::{ManifoldError, ManifoldResult};
use crate::badgey::schema::quarantine_channels as quarantine_table; use crate::badgey::schema::channels as channel_table;
use crate::badgey::schema::*; use crate::badgey::schema::*;
#[derive(Queryable, Selectable, Identifiable, Insertable, Debug, Clone)] #[derive(Queryable, Selectable, Identifiable, Insertable, Debug, Clone)]
#[diesel(table_name = quarantine_channels)] #[diesel(table_name = channels)]
pub struct QuarantineChannel { pub struct Channel {
#[diesel(deserialize_as = i32)] #[diesel(deserialize_as = i32)]
pub id: Option<i32>, pub id: Option<i32>,
pub qc_role_id: i64, pub qc_role_id: Option<i64>,
pub qc_channel_id: i64, pub channel_id: i64,
pub is_quarantine_channel: bool,
pub is_valid_for_xp: bool,
} }
impl QuarantineChannel { impl Channel {
pub fn new(role_id: i64, channel_id: i64) -> Self { pub fn new(qc_role_id: Option<i64>, channel_id: i64, is_quarantine_channel: bool, is_valid_for_xp: bool) -> Self {
QuarantineChannel { Channel {
id: None, id: None,
qc_role_id: role_id, qc_role_id,
qc_channel_id: channel_id channel_id,
is_quarantine_channel,
is_valid_for_xp,
} }
} }
pub fn insert(&self, conn: &Db) -> ManifoldResult<usize> { pub fn insert(&self, conn: &Db) -> ManifoldResult<usize> {
insert_into(quarantine_table::dsl::quarantine_channels) insert_into(channel_table::dsl::channels)
.values(self) .values(self)
.execute(&mut conn.get()?) .execute(&mut conn.get()?)
.map_err(|e| ManifoldError::from(e)) .map_err(|e| ManifoldError::from(e))
} }
pub fn remove(&self, conn: &Db) -> ManifoldResult<usize> { pub fn remove(&self, conn: &Db) -> ManifoldResult<usize> {
Ok(delete(quarantine_table::dsl::quarantine_channels) Ok(delete(channel_table::dsl::channels)
.filter(quarantine_table::qc_role_id.eq(self.qc_role_id)) .filter(channel_table::qc_role_id.eq(self.qc_role_id))
.execute(&mut conn.get()?)?) .execute(&mut conn.get()?)?)
} }
pub fn get(conn: &Db, needle: i64) -> ManifoldResult<Self> { pub fn get_by_channel_id(conn: &Db, needle: i64) -> ManifoldResult<Self> {
Ok(quarantine_table::dsl::quarantine_channels Ok(channel_table::dsl::channels
.filter(quarantine_table::qc_role_id.eq(needle)) .filter(channel_table::channel_id.eq(needle))
.limit(1) .limit(1)
.select(QuarantineChannel::as_select()) .select(Channel::as_select())
.get_result(&mut conn.get()?)?)
}
pub fn get_by_qc_role_id(conn: &Db, needle: i64) -> ManifoldResult<Self> {
Ok(channel_table::dsl::channels
.filter(channel_table::qc_role_id.eq(needle))
.limit(1)
.select(Channel::as_select())
.get_result(&mut conn.get()?)?) .get_result(&mut conn.get()?)?)
} }
} }

View File

@ -11,7 +11,7 @@ use manifold::models::user::UserInfo;
use manifold::schema::userinfo; use manifold::schema::userinfo;
use poise::FrameworkContext; use poise::FrameworkContext;
use poise::serenity_prelude::{Context, Mentionable, Message, RoleId}; use poise::serenity_prelude::{Context, Mentionable, Message, RoleId};
use crate::badgey::models::quarantine_channel::QuarantineChannel; use crate::badgey::models::quarantine_channel::Channel;
use crate::badgey::schema::xp as xp_table; use crate::badgey::schema::xp as xp_table;
use crate::badgey::schema::*; use crate::badgey::schema::*;
@ -171,18 +171,25 @@ impl Xp {
let xp_reward = rng.gen_range(1..30).clone(); let xp_reward = rng.gen_range(1..30).clone();
drop(rng); drop(rng);
let user_id_i64 = msg.author.id.as_u64().clone() as i64; let user_id_i64 = msg.author.id.as_u64().clone() as i64;
let channel_id_i64 = msg.channel_id.as_u64().clone() as i64;
let mut valid_channel = true;
if let Ok(c) = Channel::get_by_channel_id(&fctx.user_data.database, channel_id_i64) {
valid_channel = c.is_valid_for_xp && !c.is_quarantine_channel;
}
let mut xp = match Xp::get(db, &user_id_i64) { let mut xp = match Xp::get(db, &user_id_i64) {
Ok(x) => x, Ok(x) => x,
Err(_) => Xp::new(&user_id_i64) Err(_) => Xp::new(&user_id_i64)
}; };
let valid = match xp.last_given_xp { let valid_user = match xp.last_given_xp {
Some(t) => (chrono::Utc::now().timestamp() - 60) > t && QuarantineChannel::get(&fctx.user_data.database, msg.channel_id.as_u64().clone() as i64).is_err(), Some(t) => (chrono::Utc::now().timestamp() - 60) > t,
None => true None => true
}; };
if valid { if valid_user && valid_channel {
xp.last_given_xp = Some(chrono::Utc::now().timestamp()); xp.last_given_xp = Some(chrono::Utc::now().timestamp());
xp.xp_value += &xp_reward; xp.xp_value += &xp_reward;
let calculated_level = xp.get_level_from_xp(&db, None); let calculated_level = xp.get_level_from_xp(&db, None);

View File

@ -1,5 +1,15 @@
// @generated automatically by Diesel CLI. // @generated automatically by Diesel CLI.
diesel::table! {
channels (id) {
id -> Int4,
qc_role_id -> Nullable<Int8>,
channel_id -> Int8,
is_quarantine_channel -> Bool,
is_valid_for_xp -> Bool,
}
}
diesel::table! { diesel::table! {
custom_responses (id) { custom_responses (id) {
id -> Int4, id -> Int4,
@ -12,14 +22,6 @@ diesel::table! {
} }
} }
diesel::table! {
quarantine_channels (id) {
id -> Int4,
qc_role_id -> Int8,
qc_channel_id -> Int8,
}
}
diesel::table! { diesel::table! {
ranks (role_id) { ranks (role_id) {
role_id -> Int8, role_id -> Int8,
@ -72,8 +74,8 @@ diesel::joinable!(custom_responses -> userinfo (added_for));
diesel::joinable!(xp -> userinfo (user_id)); diesel::joinable!(xp -> userinfo (user_id));
diesel::allow_tables_to_appear_in_same_query!( diesel::allow_tables_to_appear_in_same_query!(
channels,
custom_responses, custom_responses,
quarantine_channels,
ranks, ranks,
tracks, tracks,
userinfo, userinfo,