diff --git a/.gitignore b/.gitignore
index 2c4918c..e09f1ce 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
*.db
+.env
diff --git a/.idea/sqldialects.xml b/.idea/sqldialects.xml
index 5a52f7b..c2adfc3 100644
--- a/.idea/sqldialects.xml
+++ b/.idea/sqldialects.xml
@@ -2,6 +2,7 @@
+
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 101a98e..a2fdb01 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -159,7 +159,7 @@ dependencies = [
[[package]]
name = "badgey"
-version = "3.0.2"
+version = "3.0.3"
dependencies = [
"built",
"clap",
@@ -170,6 +170,7 @@ dependencies = [
"manifold",
"poise",
"rand 0.8.5",
+ "regex 1.9.5",
"tokio",
]
diff --git a/Cargo.toml b/Cargo.toml
index 0cf535a..af1641d 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "badgey"
-version = "3.0.3"
+version = "3.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -19,4 +19,5 @@ manifold = { git = "https://code.orbiter-radio.uk/discord/manifold.git" }
# manifold = { path = "/home/xyon/Workspace/manifold/" }
poise = { version = "0.5.*", features = [ "cache" ] }
rand = { version = "0.8.5", features = [ "small_rng" ] }
+regex = "1.9.5"
tokio = { version = "1.16.1", features = ["sync", "macros", "rt-multi-thread"] }
diff --git a/src/badgey/commands/custom_responses.rs b/src/badgey/commands/custom_responses.rs
new file mode 100644
index 0000000..6edbe97
--- /dev/null
+++ b/src/badgey/commands/custom_responses.rs
@@ -0,0 +1,74 @@
+use clap::error::ContextKind::Custom;
+use poise::serenity_prelude as serenity;
+
+use manifold::error::{ManifoldError, ManifoldResult};
+use manifold::{ManifoldContext, ManifoldData};
+use poise::serenity_prelude::Mentionable;
+use crate::badgey::models::custom_response::{CustomResponse, CustomResponseInserter};
+
+
+#[poise::command(slash_command, prefix_command, required_permissions = "MODERATE_MEMBERS" )]
+async fn add_custom_response(ctx: ManifoldContext<'_>, target: serenity::User, trigger: String, response: String) -> ManifoldResult<()> {
+ let db = &ctx.data().database;
+
+ match CustomResponseInserter::new(trigger, response, target, ctx.author().clone()).insert(db) {
+ Ok(_) => ctx.reply("Custom response successfully added").await?,
+ Err(e) => ctx.reply(format!("Custom response NOT added, an error happened. You should probably complain to Xyon about that. The error was {}", e)).await?,
+ };
+
+ Ok(())
+}
+
+#[poise::command(slash_command, prefix_command, required_permissions = "MODERATE_MEMBERS")]
+async fn remove_custom_response(ctx: ManifoldContext<'_>, id: i32) -> ManifoldResult<()> {
+ let db = &ctx.data().database;
+
+ let mut response = CustomResponse::find_by_id(db, &id)?;
+
+ response.deleted = Some(true);
+ response.insert(db)?;
+
+ ctx.reply(format!("Successfully marked custom response with ID {} as deleted.", id)).await?;
+
+ Ok(())
+}
+
+#[poise::command(slash_command, prefix_command, required_permissions = "MODERATE_MEMBERS")]
+async fn undelete_custom_response(ctx: ManifoldContext<'_>, id: i32) -> ManifoldResult<()> {
+ let db = &ctx.data().database;
+
+ let mut response = CustomResponse::find_by_id(db, &id)?;
+
+ if let Some(d) = response.deleted {
+ response.deleted = None;
+ response.insert(db)?;
+ ctx.reply(format!("Undeleted custom response with ID: {}", &id)).await?;
+ } else {
+ ctx.reply(format!("Can't undelete custom response with ID {} because it was not deleted in the first place.", &id)).await?;
+ }
+
+ Ok(())
+}
+
+#[poise::command(slash_command, prefix_command, required_permissions = "MODERATE_MEMBERS")]
+async fn list_custom_responses_for_user(ctx: ManifoldContext<'_>, target: serenity::User) -> ManifoldResult<()> {
+ let db = &ctx.data().database;
+
+ let mut answer: String = "".to_string();
+
+ CustomResponse::find_by_user(db, &target)?.iter().for_each(|f| {
+ answer.push_str(format!("{}{}", "\n", f.to_string()).as_str());
+ });
+
+ if answer.len() == 0 {
+ answer.push_str("\nNone found");
+ }
+
+ ctx.reply(format!("I found the following custom responses for {}: {}", &target.mention(), answer)).await?;
+
+ Ok(())
+}
+
+pub fn commands() -> [poise::Command; 4] {
+ [add_custom_response(), remove_custom_response(), undelete_custom_response(), list_custom_responses_for_user()]
+}
diff --git a/src/badgey/commands/mod.rs b/src/badgey/commands/mod.rs
index c915187..a5c50a2 100644
--- a/src/badgey/commands/mod.rs
+++ b/src/badgey/commands/mod.rs
@@ -2,10 +2,12 @@ use manifold::{ManifoldData};
use manifold::error::{ManifoldError};
use poise::Command;
+pub mod custom_responses;
pub mod ranks;
pub fn collect_commands() -> Vec> {
commands().into_iter()
+ .chain(custom_responses::commands())
.chain(ranks::commands())
.collect()
}
diff --git a/src/badgey/commands/ranks.rs b/src/badgey/commands/ranks.rs
index 4062b67..27e7ded 100644
--- a/src/badgey/commands/ranks.rs
+++ b/src/badgey/commands/ranks.rs
@@ -4,7 +4,7 @@ use manifold::{ManifoldContext, ManifoldData};
use poise::serenity_prelude::{Mentionable, RoleId};
use crate::badgey::models::xp::{Rank, Track, Xp};
-#[poise::command(prefix_command, slash_command)]
+#[poise::command(prefix_command, slash_command, user_cooldown = 86400)]
async fn switch_rank_track(ctx: ManifoldContext<'_>, #[description = "Track to switch to: officer or enlisted"] track: String) -> ManifoldResult<()> {
let db = &ctx.data().database;
let user_id_i64 = ctx.author().id.as_u64().clone() as i64;
@@ -14,8 +14,6 @@ async fn switch_rank_track(ctx: ManifoldContext<'_>, #[description = "Track to s
Err(_) => Xp::new(&user_id_i64)
};
- Xp::check_timeout(&xp.rank_track_last_changed, &86400)?;
-
let parsed_track = Track::get_track_by_name(db, &track)?;
if xp.rank_track != parsed_track.track_id {
let current_level_rank = Rank::get_rank_for_level(db, &xp.user_current_level, &xp.rank_track)?;
@@ -38,7 +36,7 @@ async fn switch_rank_track(ctx: ManifoldContext<'_>, #[description = "Track to s
Ok(())
}
-#[poise::command(prefix_command, slash_command)]
+#[poise::command(prefix_command, slash_command, user_cooldown = 86400)]
async fn freeze_rank(ctx: ManifoldContext<'_>, #[rest] #[description = "Rank to lock yourself at. Omit to unfreeze your progression."] rank: Option) -> ManifoldResult<()> {
let db = &ctx.data().database;
let user_id_i64 = ctx.author().id.as_u64().clone() as i64;
@@ -48,8 +46,6 @@ async fn freeze_rank(ctx: ManifoldContext<'_>, #[rest] #[description = "Rank to
Err(_) => Xp::new(&user_id_i64)
};
- Xp::check_timeout(&xp.freeze_rank_last_changed, &86400)?;
-
if let Some(r) = rank {
let frozen_rank = match Rank::get_rank_by_name(db, &r, &xp.rank_track) {
Ok(r) => r,
diff --git a/src/badgey/events.rs b/src/badgey/events.rs
index a339003..ca9a02c 100644
--- a/src/badgey/events.rs
+++ b/src/badgey/events.rs
@@ -6,6 +6,7 @@ use poise::{async_trait, FrameworkContext, Event};
use poise::serenity_prelude::{Context, Mentionable, Message, RoleId};
use rand::rngs::SmallRng;
use rand::{Rng, SeedableRng};
+use crate::badgey::models::custom_response::CustomResponse;
use crate::badgey::models::xp::{Rank, Xp};
@@ -31,13 +32,17 @@ impl BadgeyHandler {
if msg.author.id == ctx.http.get_current_user().await?.id || msg.content.starts_with(&fctx.user_data().await.bot_config.prefix) {
return Ok(())
}
+ let db = &fctx.user_data().await.database;
+
+ if let Ok(r) = CustomResponse::test_content(db, &msg.content_safe(&ctx.cache), &"!".to_string()) {
+ msg.channel_id.say(ctx, r.response).await?;
+ }
if fctx.user_data().await.user_info.lock().await.get_mut(&msg.author.id.as_u64()).is_none() {
debug!("Tried to add XP to a user we don't know about, aborting.");
return Ok(())
}
- let db = &fctx.user_data().await.database;
let mut rng = SmallRng::from_entropy();
let xp_reward = rng.gen_range(1..30).clone();
drop(rng);
diff --git a/src/badgey/models/custom_response.rs b/src/badgey/models/custom_response.rs
new file mode 100644
index 0000000..f24c06c
--- /dev/null
+++ b/src/badgey/models/custom_response.rs
@@ -0,0 +1,114 @@
+use std::fmt::{Display, Formatter};
+use built::chrono::{NaiveDateTime, Utc};
+use diesel::prelude::*;
+use diesel::insert_into;
+use regex::Regex;
+use poise::serenity_prelude as serenity;
+
+use manifold::Db;
+use manifold::error::{ManifoldError, ManifoldResult};
+use manifold::models::user::UserInfo;
+use manifold::schema::userinfo;
+
+use crate::badgey::schema::*;
+
+#[derive(Queryable, Selectable, Identifiable, Insertable, AsChangeset, Associations, Debug, PartialEq, Clone)]
+#[diesel(belongs_to(UserInfo, foreign_key = added_for))]
+#[diesel(table_name = custom_responses)]
+#[diesel(treat_none_as_null = true)]
+pub struct CustomResponse {
+ pub id: i32,
+ pub trigger: String,
+ pub response: String,
+ pub added_by: i64,
+ pub added_on: i64,
+ pub added_for: i64,
+ pub deleted: Option,
+}
+
+#[derive(Insertable, Debug, PartialEq, Clone)]
+#[diesel(table_name = custom_responses)]
+pub struct CustomResponseInserter {
+ pub trigger: String,
+ pub response: String,
+ pub added_by: i64,
+ pub added_on: i64,
+ pub added_for: i64,
+ pub deleted: Option,
+}
+
+impl CustomResponseInserter {
+ pub fn new(trigger: String, response: String, target: serenity::User, caller: serenity::User) -> Self {
+ Self {
+ trigger,
+ response,
+ added_by: caller.id.as_u64().clone() as i64,
+ added_on: Utc::now().timestamp(),
+ added_for: target.id.as_u64().clone() as i64,
+ deleted: None,
+ }
+ }
+
+ pub fn insert(&self, conn: &Db) -> ManifoldResult {
+ insert_into(custom_responses::table)
+ .values(self)
+ .execute(&mut conn.get()?)
+ .map_err(|e| ManifoldError::from(e))
+ }
+}
+
+
+impl CustomResponse {
+
+ pub fn test_content(conn: &Db, needle: &String, prefix: &String) -> ManifoldResult {
+ let pattern = Regex::new(format!("^{}(\\S*)", prefix).as_str())?;
+ match pattern.captures(needle) {
+ Some(hit) => CustomResponse::find_by_trigger(conn, hit.get(1).unwrap().as_str()),
+ None => Err("")?
+ }
+ }
+
+ pub fn find_by_id(conn: &Db, needle: &i32) -> ManifoldResult {
+ Ok(custom_responses::table
+ .filter(custom_responses::id.eq(needle))
+ .limit(1)
+ .select(CustomResponse::as_select())
+ .get_result(&mut conn.get()?)?)
+ }
+
+ pub fn find_by_trigger(conn: &Db, needle: &str) -> ManifoldResult {
+ Ok(custom_responses::table
+ .filter(custom_responses::trigger.ilike(needle))
+ .filter(custom_responses::deleted.is_null())
+ .limit(1)
+ .select(CustomResponse::as_select())
+ .get_result(&mut conn.get()?)?)
+ }
+
+ pub fn find_by_user(conn: &Db, needle: &serenity::User) -> ManifoldResult> {
+ let user = userinfo::table
+ .filter(userinfo::user_id.eq(needle.id.as_u64().clone() as i64))
+ .select(UserInfo::as_select())
+ .get_result(&mut conn.get()?)?;
+
+ Ok(CustomResponse::belonging_to(&user)
+ .select(CustomResponse::as_select())
+ .load::(&mut conn.get()?)?)
+ }
+
+ pub fn insert(&self, conn: &Db) -> ManifoldResult {
+ insert_into(custom_responses::table)
+ .values(self)
+ .on_conflict(custom_responses::id)
+ .do_update()
+ .set(self)
+ .execute(&mut conn.get()?)
+ .map_err(|e| ManifoldError::from(e))
+ }
+}
+
+impl Display for CustomResponse {
+ fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+ write!(f, "ID: {}, trigger: {}, response: {}, added by: {}, added on: {}, deleted: {}", self.id, self.trigger, self.response, serenity::UserId::from(self.added_by as u64), NaiveDateTime::from_timestamp_opt(self.added_on, 0).unwrap(), self.deleted.unwrap_or(false))
+ }
+}
\ No newline at end of file
diff --git a/src/badgey/models/mod.rs b/src/badgey/models/mod.rs
index 06e0b45..bdc39b0 100644
--- a/src/badgey/models/mod.rs
+++ b/src/badgey/models/mod.rs
@@ -1 +1,2 @@
+pub mod custom_response;
pub mod xp;
diff --git a/src/badgey/schema.rs b/src/badgey/schema.rs
index a37b0bb..21ba434 100644
--- a/src/badgey/schema.rs
+++ b/src/badgey/schema.rs
@@ -1,48 +1,67 @@
// @generated automatically by Diesel CLI.
+diesel::table! {
+ custom_responses (id) {
+ id -> Int4,
+ trigger -> Text,
+ response -> Text,
+ added_by -> Int8,
+ added_on -> Int8,
+ added_for -> Int8,
+ deleted -> Nullable,
+ }
+}
+
diesel::table! {
ranks (role_id) {
- role_id -> BigInt,
- required_level -> BigInt,
- rank_track -> BigInt,
- rank_name -> Text,
+ role_id -> Int8,
+ required_level -> Int8,
+ rank_track -> Int8,
+ #[max_length = 128]
+ rank_name -> Varchar,
}
}
diesel::table! {
tracks (track_id) {
- track_id -> BigInt,
- track_name -> Text,
+ track_id -> Int8,
+ #[max_length = 128]
+ track_name -> Varchar,
}
}
diesel::table! {
userinfo (user_id) {
- user_id -> BigInt,
+ user_id -> Int8,
username -> Text,
- weather_location -> Nullable,
- weather_units -> Nullable,
- timezone -> Nullable,
- last_seen -> Nullable,
+ #[max_length = 36]
+ weather_location -> Nullable,
+ #[max_length = 1]
+ weather_units -> Nullable,
+ #[max_length = 8]
+ timezone -> Nullable,
+ last_seen -> Nullable,
}
}
diesel::table! {
xp (user_id) {
- user_id -> BigInt,
- user_current_level -> BigInt,
- xp_value -> BigInt,
- last_given_xp -> Nullable,
- rank_track -> BigInt,
- rank_track_last_changed -> Nullable,
- freeze_rank -> Nullable,
- freeze_rank_last_changed -> Nullable,
+ user_id -> Int8,
+ user_current_level -> Int8,
+ xp_value -> Int8,
+ last_given_xp -> Nullable,
+ rank_track -> Int8,
+ rank_track_last_changed -> Nullable,
+ freeze_rank -> Nullable,
+ freeze_rank_last_changed -> Nullable,
}
}
+diesel::joinable!(custom_responses -> userinfo (added_for));
diesel::joinable!(xp -> userinfo (user_id));
diesel::allow_tables_to_appear_in_same_query!(
+ custom_responses,
ranks,
tracks,
userinfo,