Reviewed-on: #2 Co-authored-by: Xyon <xyon@orbiter-radio.uk> Co-committed-by: Xyon <xyon@orbiter-radio.uk>
This commit is contained in:
parent
d4a5cb08e6
commit
a07f721a80
|
|
@ -1,2 +1,3 @@
|
|||
/target
|
||||
*.db
|
||||
.env
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
<project version="4">
|
||||
<component name="SqlDialectMappings">
|
||||
<file url="file://$PROJECT_DIR$/migrations/2023-09-20-230922_extend userinfo to include XP/up.sql" dialect="PostgreSQL" />
|
||||
<file url="file://$PROJECT_DIR$/migrations/2023-09-26-215927_add custom responses table/up.sql" dialect="PostgreSQL" />
|
||||
<file url="PROJECT" dialect="SQLite" />
|
||||
</component>
|
||||
</project>
|
||||
|
|
@ -159,7 +159,7 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "badgey"
|
||||
version = "3.0.1"
|
||||
version = "3.1.0"
|
||||
dependencies = [
|
||||
"built",
|
||||
"clap",
|
||||
|
|
@ -170,6 +170,7 @@ dependencies = [
|
|||
"manifold",
|
||||
"poise",
|
||||
"rand 0.8.5",
|
||||
"regex 1.9.5",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "badgey"
|
||||
version = "3.0.2"
|
||||
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"] }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,73 @@
|
|||
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(_) = 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<ManifoldData, ManifoldError>; 4] {
|
||||
[add_custom_response(), remove_custom_response(), undelete_custom_response(), list_custom_responses_for_user()]
|
||||
}
|
||||
|
|
@ -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<Command<ManifoldData, ManifoldError>> {
|
||||
commands().into_iter()
|
||||
.chain(custom_responses::commands())
|
||||
.chain(ranks::commands())
|
||||
.collect()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String>) -> 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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -63,6 +68,13 @@ impl BadgeyHandler {
|
|||
xp.last_given_xp = Some(chrono::Utc::now().timestamp());
|
||||
xp.xp_value += &xp_reward;
|
||||
let calculated_level = xp.get_level_from_xp();
|
||||
|
||||
if xp.freeze_rank.is_some() {
|
||||
xp.user_current_level = calculated_level.clone();
|
||||
xp.insert(db)?;
|
||||
return Ok(())
|
||||
}
|
||||
|
||||
if (xp.user_current_level != calculated_level) || xp.user_current_level == 1 {
|
||||
if let Some(guild) = msg.guild(&ctx.cache) {
|
||||
let mut member = guild.member(&ctx, msg.author.id).await?;
|
||||
|
|
|
|||
|
|
@ -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<bool>,
|
||||
}
|
||||
|
||||
#[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<bool>,
|
||||
}
|
||||
|
||||
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<usize> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
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<Vec<Self>> {
|
||||
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::<CustomResponse>(&mut conn.get()?)?)
|
||||
}
|
||||
|
||||
pub fn insert(&self, conn: &Db) -> ManifoldResult<usize> {
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1,2 @@
|
|||
pub mod custom_response;
|
||||
pub mod xp;
|
||||
|
|
|
|||
|
|
@ -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<Bool>,
|
||||
}
|
||||
}
|
||||
|
||||
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<Text>,
|
||||
weather_units -> Nullable<Text>,
|
||||
timezone -> Nullable<Text>,
|
||||
last_seen -> Nullable<BigInt>,
|
||||
#[max_length = 36]
|
||||
weather_location -> Nullable<Varchar>,
|
||||
#[max_length = 1]
|
||||
weather_units -> Nullable<Varchar>,
|
||||
#[max_length = 8]
|
||||
timezone -> Nullable<Varchar>,
|
||||
last_seen -> Nullable<Int8>,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
xp (user_id) {
|
||||
user_id -> BigInt,
|
||||
user_current_level -> BigInt,
|
||||
xp_value -> BigInt,
|
||||
last_given_xp -> Nullable<BigInt>,
|
||||
rank_track -> BigInt,
|
||||
rank_track_last_changed -> Nullable<BigInt>,
|
||||
freeze_rank -> Nullable<BigInt>,
|
||||
freeze_rank_last_changed -> Nullable<BigInt>,
|
||||
user_id -> Int8,
|
||||
user_current_level -> Int8,
|
||||
xp_value -> Int8,
|
||||
last_given_xp -> Nullable<Int8>,
|
||||
rank_track -> Int8,
|
||||
rank_track_last_changed -> Nullable<Int8>,
|
||||
freeze_rank -> Nullable<Int8>,
|
||||
freeze_rank_last_changed -> Nullable<Int8>,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
|
|||
Loading…
Reference in New Issue