Add migrations, add event handling, add XP generation and leveling
This commit is contained in:
parent
09276f88b7
commit
2eb61557e1
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="SqlDialectMappings">
|
||||||
|
<file url="file://$PROJECT_DIR$/migrations/2023-09-20-230922_extend userinfo to include XP/up.sql" dialect="GenericSQL" />
|
||||||
|
<file url="PROJECT" dialect="SQLite" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -163,10 +163,13 @@ version = "1.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"built",
|
"built",
|
||||||
"clap",
|
"clap",
|
||||||
|
"diesel",
|
||||||
|
"diesel_migrations",
|
||||||
"env_logger",
|
"env_logger",
|
||||||
"log",
|
"log",
|
||||||
"manifold",
|
"manifold",
|
||||||
"poise",
|
"poise",
|
||||||
|
"rand 0.8.5",
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
@ -1091,8 +1094,8 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "manifold"
|
name = "manifold"
|
||||||
version = "3.3.1"
|
version = "4.0.0"
|
||||||
source = "git+ssh://git@code.orbiter-radio.uk/source/mfold.git#ec8447bc58517b05dd81a6b5da82d7c962a5526d"
|
source = "git+ssh://git@code.orbiter-radio.uk/source/mfold.git#3d712fdcb32f4a6269d12b25b10371e1c307e272"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"built",
|
"built",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|
@ -1886,9 +1889,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rustls-webpki"
|
name = "rustls-webpki"
|
||||||
version = "0.101.5"
|
version = "0.101.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "45a27e3b59326c16e23d30aeb7a36a24cc0d29e71d68ff611cdfb4a01d013bed"
|
checksum = "3c7d5dece342910d9ba34d259310cae3e0154b873b35408b787b59bce53d34fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ring",
|
"ring",
|
||||||
"untrusted",
|
"untrusted",
|
||||||
|
|
@ -1965,9 +1968,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "semver"
|
name = "semver"
|
||||||
version = "1.0.18"
|
version = "1.0.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b0293b4b29daaf487284529cc2f5675b8e57c61f70167ba415a463651fd6a918"
|
checksum = "ad977052201c6de01a8ef2aa3378c4bd23217a056337d1d6da40468d267a4fb0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
@ -2216,9 +2219,9 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time"
|
name = "time"
|
||||||
version = "0.3.28"
|
version = "0.3.29"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48"
|
checksum = "426f806f4089c493dcac0d24c29c01e2c38baf8e30f1b716ee37e83d200b18fe"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"deranged",
|
"deranged",
|
||||||
"itoa",
|
"itoa",
|
||||||
|
|
@ -2229,15 +2232,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-core"
|
name = "time-core"
|
||||||
version = "0.1.1"
|
version = "0.1.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb"
|
checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "time-macros"
|
name = "time-macros"
|
||||||
version = "0.2.14"
|
version = "0.2.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572"
|
checksum = "4ad70d68dba9e1f8aceda7aa6711965dfec1cac869f311a51bd08b3a2ccbce20"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"time-core",
|
"time-core",
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,12 @@ built = { version = "0.6.0", features = ["git2", "semver", "chrono"] }
|
||||||
[dependencies]
|
[dependencies]
|
||||||
built = { version = "0.6.0", features = ["git2", "semver", "chrono"] }
|
built = { version = "0.6.0", features = ["git2", "semver", "chrono"] }
|
||||||
clap = { version = "4.3.23", features = ["cargo"] }
|
clap = { version = "4.3.23", features = ["cargo"] }
|
||||||
|
diesel = { version = "2.1.0", features = ["sqlite", "r2d2", "chrono"] }
|
||||||
|
diesel_migrations = "2.1.0"
|
||||||
env_logger = "0.10.0"
|
env_logger = "0.10.0"
|
||||||
log = "0.4.20"
|
log = "0.4.20"
|
||||||
manifold = { git = "ssh://git@code.orbiter-radio.uk/source/mfold.git" }
|
manifold = { git = "ssh://git@code.orbiter-radio.uk/source/mfold.git" }
|
||||||
# manifold = { path = "/home/xyon/Workspace/manifold/" }
|
# manifold = { path = "/home/xyon/Workspace/manifold/" }
|
||||||
poise = { version = "0.5.*", features = [ "cache" ] }
|
poise = { version = "0.5.*", features = [ "cache" ] }
|
||||||
|
rand = { version = "0.8.5", features = [ "small_rng" ] }
|
||||||
tokio = { version = "1.16.1", features = ["sync", "macros", "rt-multi-thread"] }
|
tokio = { version = "1.16.1", features = ["sync", "macros", "rt-multi-thread"] }
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
# For documentation on how to configure this file,
|
||||||
|
# see https://diesel.rs/guides/configuring-diesel-cli
|
||||||
|
|
||||||
|
[print_schema]
|
||||||
|
file = "src/badgey/schema.rs"
|
||||||
|
custom_type_derives = ["diesel::query_builder::QueryId"]
|
||||||
|
|
||||||
|
[migrations_directory]
|
||||||
|
dir = "migrations"
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
-- This file should undo anything in `up.sql`
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
-- Your SQL goes here
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
DROP TABLE IF EXISTS "xp";
|
||||||
|
DROP TABLE IF EXISTS "levels";
|
||||||
|
DROP TABLE IF EXISTS "ranks";
|
||||||
|
DROP TABLE IF EXISTS "tracks";
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
CREATE TABLE IF NOT EXISTS "xp"
|
||||||
|
(
|
||||||
|
user_id BIGINT NOT NULL REFERENCES userinfo(user_id) PRIMARY KEY,
|
||||||
|
user_current_level INTEGER NOT NULL DEFAULT 0,
|
||||||
|
xp_value BIGINT NOT NULL DEFAULT 0,
|
||||||
|
last_given_xp BIGINT,
|
||||||
|
rank_track INTEGER NOT NULL,
|
||||||
|
rank_track_last_changed BIGINT,
|
||||||
|
freeze_rank BIGINT,
|
||||||
|
freeze_rank_last_changed BIGINT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "tracks"
|
||||||
|
(
|
||||||
|
track_id INTEGER NOT NULL PRIMARY KEY,
|
||||||
|
track_name VARCHAR(128) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS "ranks"
|
||||||
|
(
|
||||||
|
role_id BIGINT NOT NULL PRIMARY KEY,
|
||||||
|
required_level INTEGER NOT NULL DEFAULT 1,
|
||||||
|
rank_track INTEGER NOT NULL DEFAULT 0,
|
||||||
|
rank_name VARCHAR(128) NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO "tracks" (track_id, track_name) VALUES
|
||||||
|
(0, "officer"),
|
||||||
|
(1, "enlisted");
|
||||||
|
|
||||||
|
INSERT INTO "ranks" (role_id, required_level, rank_track, rank_name) VALUES
|
||||||
|
(1154403063960961063, 0, 0, "cadet first year"), -- cadet 1
|
||||||
|
(1154403107313307729, 10, 0, "cadet fourth year"), -- cadet 4
|
||||||
|
(1152963553620393995, 20, 0, "ensign"), -- ensign
|
||||||
|
(1152963552299188224, 30, 0, "lieutenant junior grade"), -- ltjg
|
||||||
|
(1152963550906695762, 40, 0, "lieutenant"), -- lt
|
||||||
|
(1152963549979758712, 50, 0, "lieutenant commander"), -- ltcmdr
|
||||||
|
(1152963548843094126, 60, 0, "commander"), -- cmdr
|
||||||
|
(1152963541255594054, 80, 0, "captain"), -- capt
|
||||||
|
(1152759044893843656, 100, 0, "fleet captain"), -- fleetcapt
|
||||||
|
(1152965025938550926, 0, 1, "crewman third class"), -- crewman 3rd
|
||||||
|
(1152965024550228099, 10, 1, "crewman second class"), -- crewman 2nd
|
||||||
|
(1152965023098994789, 20, 1, "crewman first class"), -- crewman 1st
|
||||||
|
(1152964462844846162, 30, 1, "petty officer third class"), -- petty 3rd
|
||||||
|
(1152964461817253958, 40, 1, "petty officer second class"), -- petty 2nd
|
||||||
|
(1152964460835778631, 50, 1, "petty officer first class"), -- petty 1st
|
||||||
|
(1152963559010087022, 60, 1, "chief petty officer"), -- chief petty
|
||||||
|
(1152963557537886229, 80, 1, "senior chief petty officer"), -- sr ch petty
|
||||||
|
(1152963556527063110, 100, 1, "master chief petty officer"); -- master ch petty
|
||||||
|
|
@ -2,8 +2,12 @@ use manifold::{ManifoldData};
|
||||||
use manifold::error::{ManifoldError};
|
use manifold::error::{ManifoldError};
|
||||||
use poise::Command;
|
use poise::Command;
|
||||||
|
|
||||||
|
pub mod ranks;
|
||||||
|
|
||||||
pub fn collect_commands() -> Vec<Command<ManifoldData, ManifoldError>> {
|
pub fn collect_commands() -> Vec<Command<ManifoldData, ManifoldError>> {
|
||||||
commands().into_iter().collect()
|
commands().into_iter()
|
||||||
|
.chain(ranks::commands())
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn commands() -> [Command<ManifoldData, ManifoldError>; 0] {
|
fn commands() -> [Command<ManifoldData, ManifoldError>; 0] {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
use built::chrono;
|
||||||
|
use manifold::error::{ManifoldError, ManifoldResult};
|
||||||
|
use manifold::{ManifoldContext, ManifoldData};
|
||||||
|
use crate::badgey::models::xp::{Rank, Track, Xp};
|
||||||
|
|
||||||
|
#[poise::command(prefix_command, slash_command)]
|
||||||
|
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;
|
||||||
|
|
||||||
|
let mut xp = match Xp::get(db, &user_id_i64) {
|
||||||
|
Ok(x) => x,
|
||||||
|
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)?;
|
||||||
|
let new_level_rank = Rank::get_rank_for_level(db, &xp.user_current_level, &parsed_track.track_id)?;
|
||||||
|
|
||||||
|
if let Some(mut member) = ctx.author_member().await {
|
||||||
|
member.to_mut().remove_role(ctx, current_level_rank.role_id as u64).await?;
|
||||||
|
member.to_mut().add_role(ctx, new_level_rank.role_id as u64).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
xp.rank_track = parsed_track.track_id;
|
||||||
|
|
||||||
|
ctx.reply(format!("I have switched you onto the {} rank track. You're now a {}. I hope this was worth it.", parsed_track.track_name, new_level_rank.rank_name)).await?;
|
||||||
|
xp.rank_track_last_changed = Some(chrono::Utc::now().timestamp());
|
||||||
|
xp.insert(db)?;
|
||||||
|
} else {
|
||||||
|
ctx.reply(format!("You're already on the {} rank track.", parsed_track.track_name)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(prefix_command, slash_command)]
|
||||||
|
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;
|
||||||
|
|
||||||
|
let mut xp = match Xp::get(db, &user_id_i64) {
|
||||||
|
Ok(x) => x,
|
||||||
|
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,
|
||||||
|
Err(e) => {
|
||||||
|
error!("Rank lookup error: {}", e);
|
||||||
|
ctx.reply(format!("I couldn't find that rank as a rank I can give you. You might need to switch tracks, or it might just not exist.")).await?;
|
||||||
|
Err(e)?
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(fr) = xp.freeze_rank {
|
||||||
|
// Remove any old frozen rank
|
||||||
|
if let Some(mut member) = ctx.author_member().await {
|
||||||
|
member.to_mut().remove_role(ctx, fr as u64).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xp.freeze_rank = Some(frozen_rank.role_id.clone() as i64);
|
||||||
|
|
||||||
|
if let Some(mut member) = ctx.author_member().await {
|
||||||
|
member.to_mut().add_role(ctx, frozen_rank.role_id as u64).await?;
|
||||||
|
ctx.reply(format!("Okay. I've frozen your rank at {}. Enjoy!", frozen_rank.rank_name)).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
if let Some(fr) = xp.freeze_rank {
|
||||||
|
// Remove any old frozen rank
|
||||||
|
if let Some(mut member) = ctx.author_member().await {
|
||||||
|
member.to_mut().remove_role(ctx, fr as u64).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
xp.freeze_rank = None;
|
||||||
|
ctx.reply(format!("Okay. I've unfrozen your rank. Good luck out there.")).await?;
|
||||||
|
} else {
|
||||||
|
ctx.reply(format!("Negative, your rank wasn't frozen.")).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xp.freeze_rank_last_changed = Some(chrono::Utc::now().timestamp());
|
||||||
|
xp.insert(db)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[poise::command(prefix_command, slash_command)]
|
||||||
|
async fn rank(ctx: ManifoldContext<'_>) -> ManifoldResult<()> {
|
||||||
|
let db = &ctx.data().database;
|
||||||
|
let user_id_i64 = ctx.author().id.as_u64().clone() as i64;
|
||||||
|
|
||||||
|
let xp = match Xp::get(db, &user_id_i64) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => Xp::new(&user_id_i64)
|
||||||
|
};
|
||||||
|
|
||||||
|
let current_rank = Rank::get_rank_for_level(db, &xp.user_current_level, &xp.rank_track).unwrap_or(Rank::new());
|
||||||
|
|
||||||
|
let next_level_xp = xp.get_xp_to_next_level();
|
||||||
|
let next_rank_xp = xp.get_xp_to_next_rank(&db).unwrap_or(0);
|
||||||
|
|
||||||
|
let mut response = format!("You're currently {}. You need {} more XP to get to the next level", current_rank.rank_name, next_level_xp);
|
||||||
|
if next_rank_xp == 0 {
|
||||||
|
response = format!("{}. There are no more ranks for you - you can't be promoted any further!", response);
|
||||||
|
} else if next_rank_xp == next_level_xp {
|
||||||
|
response = format!("{} and rank!", response);
|
||||||
|
} else {
|
||||||
|
response = format!("{} and you need {} more to rank up. Good luck!", response, next_rank_xp);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.reply(response).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commands() -> [poise::Command<ManifoldData, ManifoldError>; 3] {
|
||||||
|
[switch_rank_track(), freeze_rank(), rank()]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
use built::chrono;
|
||||||
|
use manifold::error::{ManifoldError, ManifoldResult};
|
||||||
|
use manifold::events::{Handler, EventHandler};
|
||||||
|
use manifold::ManifoldData;
|
||||||
|
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::xp::{Rank, Xp};
|
||||||
|
|
||||||
|
|
||||||
|
pub struct BadgeyHandler {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for BadgeyHandler {
|
||||||
|
async fn listen(ctx: &Context, framework_ctx: FrameworkContext<'_, ManifoldData, ManifoldError>, event: &Event<'_>) -> ManifoldResult<()> {
|
||||||
|
Handler::listen(ctx, framework_ctx, event).await?;
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::Message { new_message } => BadgeyHandler::message(&ctx, &framework_ctx, new_message).await,
|
||||||
|
_ => Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BadgeyHandler {
|
||||||
|
pub async fn message(ctx: &Context, fctx: &FrameworkContext<'_, ManifoldData, ManifoldError>, msg: &Message) -> ManifoldResult<()> {
|
||||||
|
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;
|
||||||
|
let mut rng = SmallRng::from_entropy();
|
||||||
|
let xp_reward = rng.gen_range(1..30).clone();
|
||||||
|
drop(rng);
|
||||||
|
let user_id_i64 = msg.author.id.as_u64().clone() as i64;
|
||||||
|
|
||||||
|
let mut xp = match Xp::get(db, &user_id_i64) {
|
||||||
|
Ok(x) => x,
|
||||||
|
Err(_) => Xp::new(&user_id_i64)
|
||||||
|
};
|
||||||
|
|
||||||
|
let valid = match xp.last_given_xp {
|
||||||
|
Some(t) => {
|
||||||
|
if (chrono::Utc::now().timestamp() - 60) > t {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => true
|
||||||
|
};
|
||||||
|
|
||||||
|
if valid {
|
||||||
|
xp.last_given_xp = Some(chrono::Utc::now().timestamp());
|
||||||
|
xp.xp_value += &xp_reward;
|
||||||
|
let calculated_level = xp.get_level_from_xp();
|
||||||
|
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?;
|
||||||
|
let old_role_id = RoleId::from(Rank::get_rank_for_level(db, &xp.user_current_level, &xp.rank_track).unwrap_or(Rank::new()).role_id as u64);
|
||||||
|
xp.user_current_level = calculated_level;
|
||||||
|
let new_role = RoleId::from(Rank::get_rank_for_level(db, &calculated_level, &xp.rank_track)?.role_id as u64);
|
||||||
|
|
||||||
|
if (new_role != old_role_id || xp.user_current_level == 1) && msg.author.has_role(ctx, guild.id, new_role).await? == false {
|
||||||
|
member.remove_role(ctx, old_role_id).await?;
|
||||||
|
member.add_role(ctx, new_role).await?;
|
||||||
|
|
||||||
|
msg.channel_id.say(ctx, format!("{} is now a {}", msg.author.mention(), new_role.mention())).await?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
error!("Needed to add level to user, but couldn't get the member from the guild!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xp.insert(db)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,23 @@
|
||||||
use clap::ArgMatches;
|
use clap::ArgMatches;
|
||||||
use poise::serenity_prelude::GatewayIntents;
|
use poise::serenity_prelude::GatewayIntents;
|
||||||
use crate::built_info;
|
use crate::built_info;
|
||||||
|
use diesel_migrations::{embed_migrations, EmbeddedMigrations};
|
||||||
|
|
||||||
|
use events::BadgeyHandler;
|
||||||
|
|
||||||
mod commands;
|
mod commands;
|
||||||
|
mod events;
|
||||||
|
mod models;
|
||||||
|
mod schema;
|
||||||
|
|
||||||
|
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations");
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn run(arguments: ArgMatches) {
|
pub async fn run(arguments: ArgMatches) {
|
||||||
let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string();
|
let git_info: String = built_info::GIT_VERSION.unwrap_or("unknown").to_string();
|
||||||
let version_string = format!("Badgey Bot version {ver} built at {time} from revision {rev}", ver=built_info::PKG_VERSION, time=built_info::BUILT_TIME_UTC, rev=git_info);
|
let version_string = format!("Badgey Bot version {ver} built at {time} from revision {rev}", ver=built_info::PKG_VERSION, time=built_info::BUILT_TIME_UTC, rev=git_info);
|
||||||
|
|
||||||
let client = match manifold::prepare_client(arguments, GatewayIntents::all(), commands::collect_commands(), version_string).await {
|
let client = match manifold::prepare_client::<BadgeyHandler>(arguments, GatewayIntents::all(), commands::collect_commands(), version_string, MIGRATIONS).await {
|
||||||
Ok(c) => c,
|
Ok(c) => c,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
error!("Error preparing client; {:?}", e);
|
error!("Error preparing client; {:?}", e);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
pub mod xp;
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
use built::chrono;
|
||||||
|
use diesel::prelude::*;
|
||||||
|
use diesel::insert_into;
|
||||||
|
use manifold::Db;
|
||||||
|
use manifold::error::{ManifoldError, ManifoldResult};
|
||||||
|
use manifold::models::user::UserInfo;
|
||||||
|
use manifold::schema::userinfo;
|
||||||
|
|
||||||
|
use crate::badgey::schema::xp as xp_table;
|
||||||
|
use crate::badgey::schema::*;
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, Identifiable, Insertable, AsChangeset, Associations, Debug, PartialEq, Clone)]
|
||||||
|
#[diesel(belongs_to(UserInfo, foreign_key = user_id))]
|
||||||
|
#[diesel(table_name = xp_table)]
|
||||||
|
#[diesel(primary_key(user_id))]
|
||||||
|
pub struct Xp {
|
||||||
|
pub user_id: i64,
|
||||||
|
pub user_current_level: i32,
|
||||||
|
pub xp_value: i64,
|
||||||
|
pub last_given_xp: Option<i64>,
|
||||||
|
pub rank_track: i32,
|
||||||
|
pub rank_track_last_changed: Option<i64>,
|
||||||
|
pub freeze_rank: Option<i64>,
|
||||||
|
pub freeze_rank_last_changed: Option<i64>
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, Identifiable, Debug, Clone)]
|
||||||
|
#[diesel(primary_key(role_id))]
|
||||||
|
pub struct Rank {
|
||||||
|
pub role_id: i64,
|
||||||
|
pub required_level: i32,
|
||||||
|
pub rank_track: i32,
|
||||||
|
pub rank_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Queryable, Selectable, Identifiable, Debug, Clone)]
|
||||||
|
#[diesel(primary_key(track_id))]
|
||||||
|
pub struct Track {
|
||||||
|
pub track_id: i32,
|
||||||
|
pub track_name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Xp {
|
||||||
|
pub fn new(user_id: &i64) -> Self {
|
||||||
|
Self {
|
||||||
|
user_id: user_id.clone(),
|
||||||
|
user_current_level: 0,
|
||||||
|
xp_value: 0,
|
||||||
|
last_given_xp: None,
|
||||||
|
rank_track: 0,
|
||||||
|
rank_track_last_changed: None,
|
||||||
|
freeze_rank: None,
|
||||||
|
freeze_rank_last_changed: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &Db, needle: &i64) -> ManifoldResult<Self> {
|
||||||
|
let user = userinfo::table
|
||||||
|
.filter(userinfo::user_id.eq(needle))
|
||||||
|
.select(UserInfo::as_select())
|
||||||
|
.get_result(&mut conn.get()?)?;
|
||||||
|
|
||||||
|
Ok(Xp::belonging_to(&user)
|
||||||
|
.select(Xp::as_select())
|
||||||
|
.get_result(&mut conn.get()?)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert(&self, conn: &Db) -> ManifoldResult<usize> {
|
||||||
|
insert_into(xp_table::dsl::xp)
|
||||||
|
.values(self)
|
||||||
|
.on_conflict(xp_table::user_id)
|
||||||
|
.do_update()
|
||||||
|
.set(self)
|
||||||
|
.execute(&mut conn.get()?)
|
||||||
|
.map_err(|e| ManifoldError::from(e))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_level_from_xp(&self) -> i32 {
|
||||||
|
(0.125 * f64::sqrt(self.xp_value.clone() as f64)) as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_xp_to_next_level(&self) -> i64 {
|
||||||
|
let current_level = self.get_level_from_xp();
|
||||||
|
let target_level = current_level + 1;
|
||||||
|
|
||||||
|
(target_level as f32 / 0.125).powi(2) as i64 - &self.xp_value
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_xp_to_next_rank(&self, conn: &Db) -> ManifoldResult<i64> {
|
||||||
|
let current_level = self.get_level_from_xp();
|
||||||
|
let target_level = Rank::get_next_rank_for_level(conn, ¤t_level, &self.rank_track)?.required_level;
|
||||||
|
|
||||||
|
Ok((target_level as f32 / 0.125).powi(2) as i64 - &self.xp_value)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_timeout(value: &Option<i64>, timeout: &i64) -> ManifoldResult<()> {
|
||||||
|
if let Some(v) = value {
|
||||||
|
if v > &(chrono::Utc::now().timestamp() - timeout) {
|
||||||
|
let timeuntil = v - (chrono::Utc::now().timestamp() - timeout);
|
||||||
|
Err(format!("You are on cooldown. Try again in {} seconds.", timeuntil))?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rank {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
role_id: 0,
|
||||||
|
required_level: 0,
|
||||||
|
rank_track: 0,
|
||||||
|
rank_name: "unranked".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rank_for_level(conn: &Db, level: &i32, track: &i32) -> ManifoldResult<Self> {
|
||||||
|
Ok(ranks::table
|
||||||
|
.filter(ranks::required_level.le(level))
|
||||||
|
.filter(ranks::rank_track.eq(track))
|
||||||
|
.order(ranks::required_level.desc())
|
||||||
|
.limit(1)
|
||||||
|
.get_result(&mut conn.get()?)?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_next_rank_for_level(conn: &Db, level: &i32, track: &i32) -> ManifoldResult<Self> {
|
||||||
|
Ok(ranks::table
|
||||||
|
.filter(ranks::required_level.gt(level))
|
||||||
|
.filter(ranks::rank_track.eq(track))
|
||||||
|
.order(ranks::required_level.asc())
|
||||||
|
.limit(1)
|
||||||
|
.get_result(&mut conn.get()?)?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_rank_by_name(conn: &Db, needle: &String, track: &i32) -> ManifoldResult<Rank> {
|
||||||
|
Ok(ranks::table
|
||||||
|
.filter(ranks::rank_name.like(needle.trim().to_lowercase()))
|
||||||
|
.filter(ranks::rank_track.eq(track))
|
||||||
|
.order(ranks::required_level.desc())
|
||||||
|
.limit(1)
|
||||||
|
.get_result(&mut conn.get()?)?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Track {
|
||||||
|
pub fn get_track_by_name(conn: &Db, needle: &String) -> ManifoldResult<Track> {
|
||||||
|
Ok(tracks::table
|
||||||
|
.filter(tracks::track_name.like(needle.trim().to_lowercase()))
|
||||||
|
.select(Track::as_select())
|
||||||
|
.limit(1)
|
||||||
|
.get_result(&mut conn.get()?)?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_track_by_id(conn: &Db, needle: &i32) -> ManifoldResult<Track> {
|
||||||
|
Ok(tracks::table
|
||||||
|
.filter(tracks::track_id.eq(needle))
|
||||||
|
.select(Track::as_select())
|
||||||
|
.limit(1)
|
||||||
|
.get_result(&mut conn.get()?)?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
// @generated automatically by Diesel CLI.
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
ranks (role_id) {
|
||||||
|
role_id -> BigInt,
|
||||||
|
required_level -> Integer,
|
||||||
|
rank_track -> Integer,
|
||||||
|
rank_name -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
tracks (track_id) {
|
||||||
|
track_id -> Integer,
|
||||||
|
track_name -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
userinfo (user_id) {
|
||||||
|
user_id -> BigInt,
|
||||||
|
username -> Text,
|
||||||
|
weather_location -> Nullable<Text>,
|
||||||
|
weather_units -> Nullable<Text>,
|
||||||
|
timezone -> Nullable<Text>,
|
||||||
|
last_seen -> Nullable<BigInt>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::table! {
|
||||||
|
xp (user_id) {
|
||||||
|
user_id -> BigInt,
|
||||||
|
user_current_level -> Integer,
|
||||||
|
xp_value -> BigInt,
|
||||||
|
last_given_xp -> Nullable<BigInt>,
|
||||||
|
rank_track -> Integer,
|
||||||
|
rank_track_last_changed -> Nullable<BigInt>,
|
||||||
|
freeze_rank -> Nullable<BigInt>,
|
||||||
|
freeze_rank_last_changed -> Nullable<BigInt>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diesel::joinable!(xp -> userinfo (user_id));
|
||||||
|
|
||||||
|
diesel::allow_tables_to_appear_in_same_query!(
|
||||||
|
ranks,
|
||||||
|
tracks,
|
||||||
|
userinfo,
|
||||||
|
xp,
|
||||||
|
);
|
||||||
Loading…
Reference in New Issue