Initial rework to poise
This commit is contained in:
+6
-7
@@ -4,17 +4,17 @@ use crate::migrations::{do_migration, CURRENT_DB_VERSION};
|
||||
use config::{Config, File};
|
||||
use j_db::database::Database;
|
||||
use j_db::metadata::DBMetadata;
|
||||
use poise::serenity_prelude::model::id::ChannelId;
|
||||
use poise::serenity_prelude::model::prelude::{GuildId, UserId};
|
||||
use poise::serenity_prelude::prelude::TypeMapKey;
|
||||
use reqwest::Url;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::id::ChannelId;
|
||||
use serenity::model::prelude::{GuildId, UserId};
|
||||
use serenity::prelude::TypeMapKey;
|
||||
use std::net::SocketAddr;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::Arc;
|
||||
use structopt::StructOpt;
|
||||
use tokio::sync::mpsc::{channel, Receiver, Sender};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
|
||||
#[derive(Debug, StructOpt)]
|
||||
#[structopt(name = "fren", about = "Friend Bot")]
|
||||
@@ -75,7 +75,7 @@ impl BotState {
|
||||
pub struct GlobalData {
|
||||
pub args: Args,
|
||||
pub cfg: BotConfig,
|
||||
pub bot_state: BotState,
|
||||
pub bot_state: Mutex<BotState>,
|
||||
pub db: Database,
|
||||
pub picox: AlbumManager,
|
||||
}
|
||||
@@ -98,7 +98,7 @@ impl GlobalData {
|
||||
|
||||
Ok(Self {
|
||||
args,
|
||||
bot_state: BotState::new().await?,
|
||||
bot_state: Mutex::new(BotState::new().await?),
|
||||
db,
|
||||
cfg: cfg.clone(),
|
||||
picox: AlbumManager::new(cfg.picox.api_base_url, &cfg.picox.token),
|
||||
@@ -109,7 +109,6 @@ impl GlobalData {
|
||||
let cfg = BotConfig::new(&self.args.cfg_path)?;
|
||||
|
||||
self.cfg = cfg;
|
||||
self.bot_state = BotState::new().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+22
-36
@@ -1,49 +1,35 @@
|
||||
use crate::{command, group, GlobalData};
|
||||
use serenity::client::Context;
|
||||
use serenity::framework::standard::{Args, CommandResult};
|
||||
use serenity::model::channel::Message;
|
||||
use crate::config::GlobalData;
|
||||
use crate::discord::Context;
|
||||
use crate::error::Error;
|
||||
use std::net::ToSocketAddrs;
|
||||
|
||||
#[group]
|
||||
#[commands(nudetayne, tayne, celeryman)]
|
||||
pub struct CeleryMan;
|
||||
|
||||
#[command]
|
||||
#[aliases("NUDETAYNE")]
|
||||
#[only_in(guilds)]
|
||||
async fn nudetayne(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||
if msg.content.starts_with("!nudetayne") {
|
||||
msg.reply(&ctx.http, "Not computing, please repeat.")
|
||||
.await?;
|
||||
} else if msg.content.starts_with("!NUDETAYNE") {
|
||||
let mut data = ctx.data.write().await;
|
||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||
msg.reply(&ctx.http, "This is not suitable for work are you sure?")
|
||||
#[poise::command(prefix_command)]
|
||||
pub async fn nudetayne(ctx: Context<'_>) -> Result<(), Error> {
|
||||
if ctx.invoked_command_name() == "nudetayne".to_string() {
|
||||
ctx.reply("Not computing, please repeat.").await?;
|
||||
} else if ctx.invoked_command_name() == "NUDETAYNE".to_string() {
|
||||
ctx.reply("This is not suitable for work are you sure?")
|
||||
.await?;
|
||||
|
||||
global_data.bot_state.accepted_nsfw = Some(msg.author.id);
|
||||
let mut global_data = ctx.data().bot_state.lock().await;
|
||||
|
||||
global_data.accepted_nsfw = Some(ctx.author().id)
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
async fn tayne(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
"https://media.tenor.com/115eUl2XUaAAAAAM/flarhgunnstow-paul-rudd.gif",
|
||||
)
|
||||
.await?;
|
||||
#[poise::command(prefix_command)]
|
||||
pub async fn tayne(ctx: Context<'_>) -> Result<(), Error> {
|
||||
ctx.reply("https://media.tenor.com/115eUl2XUaAAAAAM/flarhgunnstow-paul-rudd.gif")
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[command]
|
||||
#[only_in(guilds)]
|
||||
async fn celeryman(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
"https://media.tenor.com/1iOUXZFLpBgAAAAM/dance-dancing.gif",
|
||||
)
|
||||
.await?;
|
||||
#[poise::command(prefix_command)]
|
||||
pub async fn celeryman(ctx: Context<'_>) -> Result<(), Error> {
|
||||
ctx.reply("https://media.tenor.com/1iOUXZFLpBgAAAAM/dance-dancing.gif")
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
+39
-216
@@ -1,227 +1,50 @@
|
||||
pub mod admin;
|
||||
pub mod album;
|
||||
pub mod birthday;
|
||||
pub mod celeryman;
|
||||
pub mod color;
|
||||
pub mod emoji_race;
|
||||
pub mod fren_coin;
|
||||
pub mod joke;
|
||||
pub mod little_fren;
|
||||
pub mod motivate;
|
||||
pub mod shop;
|
||||
pub mod story;
|
||||
pub mod voices;
|
||||
mod celeryman;
|
||||
|
||||
use crate::api::web_server;
|
||||
use crate::discord::fren_coin::give_coin;
|
||||
use crate::discord::joke::random;
|
||||
use crate::models::lil_fren::lil_fren_task;
|
||||
use crate::models::task::Task;
|
||||
use crate::{help, hook, GlobalData};
|
||||
use rand::prelude::IteratorRandom;
|
||||
use rand::thread_rng;
|
||||
use serenity::all::{ActivityData, Http, RoleId};
|
||||
use serenity::async_trait;
|
||||
use serenity::client::Context;
|
||||
use serenity::framework::standard::{
|
||||
help_commands, Args, CommandGroup, CommandResult, HelpOptions,
|
||||
};
|
||||
use serenity::model::channel::{Message, ReactionType};
|
||||
use serenity::model::id::UserId;
|
||||
use serenity::model::prelude::{GuildId, OnlineStatus, Ready};
|
||||
use serenity::prelude::EventHandler;
|
||||
use std::collections::HashSet;
|
||||
use std::time::Duration;
|
||||
use tokio::time::sleep;
|
||||
use crate::config::GlobalData;
|
||||
use crate::error::Error;
|
||||
|
||||
pub struct Handler;
|
||||
use poise::{serenity_prelude as serenity, FrameworkOptions};
|
||||
|
||||
static ERROR_MSG: &str =
|
||||
"OOPSIE WOOPSIE!! Uwu We made a fucky wucky!! A wittle fucko boingo! The admins at our headquarters are working VEWY HAWD to fix this!";
|
||||
type Context<'a> = poise::Context<'a, GlobalData, Error>;
|
||||
|
||||
#[async_trait]
|
||||
impl EventHandler for Handler {
|
||||
async fn cache_ready(&self, ctx: Context, _guilds: Vec<GuildId>) {
|
||||
let ctx1 = ctx.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
lil_fren_task(&ctx1).await;
|
||||
}
|
||||
});
|
||||
|
||||
tokio::spawn(async move {
|
||||
Task::create_reoccurring_tasks(&ctx).await.unwrap();
|
||||
loop {
|
||||
Task::run_tasks(&ctx).await.unwrap();
|
||||
sleep(Duration::from_secs(5)).await;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async fn message(&self, ctx: Context, new_message: Message) {
|
||||
if new_message.author.bot {
|
||||
return;
|
||||
}
|
||||
|
||||
if new_message.guild_id.is_none() {
|
||||
return;
|
||||
}
|
||||
|
||||
if new_message.content.eq_ignore_ascii_case("yes")
|
||||
|| new_message.content.eq_ignore_ascii_case("mhmm")
|
||||
{
|
||||
let mut data = ctx.data.write().await;
|
||||
let global_data = data.get_mut::<GlobalData>().unwrap();
|
||||
|
||||
if let Some(u) = global_data.bot_state.accepted_nsfw {
|
||||
if new_message.author.id == u {
|
||||
new_message.reply(&ctx.http, "||https://cdn.discordapp.com/attachments/614891432079130625/1041545254362423368/unknown.png||").await.unwrap();
|
||||
global_data.bot_state.accepted_nsfw = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_message.content.to_lowercase().contains("good bot") {
|
||||
let recv_coin = give_coin(&ctx, new_message.author.id, 0.50, 25)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
if recv_coin {
|
||||
let emojis = &new_message
|
||||
.guild_id
|
||||
.unwrap()
|
||||
.emojis(&ctx.http)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let emoji = {
|
||||
let mut rng = thread_rng();
|
||||
emojis.iter().choose(&mut rng)
|
||||
};
|
||||
|
||||
if let Some(emoji) = emoji {
|
||||
new_message
|
||||
.react(
|
||||
&ctx.http,
|
||||
ReactionType::Custom {
|
||||
animated: emoji.animated,
|
||||
id: emoji.id,
|
||||
name: Some(emoji.name.clone()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if new_message.content.to_lowercase().contains("bad bot") {
|
||||
new_message.react(&ctx.http, '😭').await.unwrap();
|
||||
}
|
||||
|
||||
give_coin(&ctx, new_message.author.id, 0.05, 10)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
async fn ready(&self, ctx: Context, ready: Ready) {
|
||||
println!("Connected as {}", ready.user.name);
|
||||
|
||||
ctx.set_presence(
|
||||
Some(
|
||||
ActivityData::streaming(
|
||||
"Security Cameras inside your Home",
|
||||
"https://www.youtube.com/watch?v=6n3pFFPSlW4",
|
||||
)
|
||||
.unwrap(),
|
||||
),
|
||||
OnlineStatus::Online,
|
||||
);
|
||||
|
||||
tokio::spawn(async move { web_server(ctx).await });
|
||||
}
|
||||
}
|
||||
|
||||
#[hook]
|
||||
pub async fn after(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
command_name: &str,
|
||||
command_result: CommandResult,
|
||||
) {
|
||||
match command_result {
|
||||
Ok(()) => {
|
||||
println!("Processed command '{}'", command_name);
|
||||
|
||||
let data = ctx.data.read().await;
|
||||
let global_data = data.get::<GlobalData>().unwrap();
|
||||
|
||||
global_data.db.db.flush_async().await.unwrap();
|
||||
}
|
||||
Err(why) => {
|
||||
println!("Command '{}' returned error {:?}", command_name, why);
|
||||
msg.reply(
|
||||
&ctx.http,
|
||||
format!("{} (Error in command {})", ERROR_MSG, command_name),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hook]
|
||||
pub async fn unrecognised_command_hook(
|
||||
ctx: &Context,
|
||||
msg: &Message,
|
||||
unrecognised_command_name: &str,
|
||||
) {
|
||||
let contents_split: Vec<&str> = msg.content.split(' ').collect();
|
||||
|
||||
let tags = if contents_split.len() > 1 {
|
||||
contents_split[1..].to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
pub async fn run_bot(global_data: GlobalData) {
|
||||
let framework_options: FrameworkOptions<GlobalData, Error> = poise::FrameworkOptions {
|
||||
prefix_options: poise::PrefixFrameworkOptions {
|
||||
prefix: Some("!".into()),
|
||||
ignore_bots: true,
|
||||
ignore_thread_creation: false,
|
||||
case_insensitive_commands: true,
|
||||
..Default::default()
|
||||
},
|
||||
commands: vec![
|
||||
celeryman::nudetayne(),
|
||||
celeryman::celeryman(),
|
||||
celeryman::tayne(),
|
||||
],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let parsed_album = match album::parse_album(ctx, msg, unrecognised_command_name, tags).await {
|
||||
Ok(parsed) => parsed,
|
||||
Err(e) => {
|
||||
println!("Error processing album command: {}", e);
|
||||
true
|
||||
}
|
||||
};
|
||||
let token = global_data.cfg.bot_token.clone();
|
||||
|
||||
if !parsed_album {
|
||||
match random(ctx, msg, unrecognised_command_name).await {
|
||||
Ok(_) => {}
|
||||
Err(e) => println!("Error processing random command: {}", e),
|
||||
}
|
||||
}
|
||||
let framework = poise::framework::Framework::builder()
|
||||
.options(framework_options)
|
||||
.setup(move |ctx, _ready, framework| {
|
||||
Box::pin(async move {
|
||||
poise::builtins::register_globally(ctx, &framework.options().commands).await?;
|
||||
Ok(global_data)
|
||||
})
|
||||
})
|
||||
.build();
|
||||
|
||||
give_coin(ctx, msg.author.id, 0.5, 10).await.unwrap();
|
||||
}
|
||||
let intents = serenity::GatewayIntents::non_privileged()
|
||||
| serenity::GatewayIntents::DIRECT_MESSAGES
|
||||
| serenity::GatewayIntents::GUILDS
|
||||
| serenity::GatewayIntents::MESSAGE_CONTENT;
|
||||
|
||||
#[help]
|
||||
pub async fn my_help(
|
||||
context: &Context,
|
||||
msg: &Message,
|
||||
args: Args,
|
||||
help_options: &'static HelpOptions,
|
||||
groups: &[&'static CommandGroup],
|
||||
owners: HashSet<UserId>,
|
||||
) -> CommandResult {
|
||||
let _ = help_commands::with_embeds(context, msg, args, help_options, groups, owners).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_role(http: &Http, guild_id: GuildId, role_name: &str) -> Option<RoleId> {
|
||||
guild_id
|
||||
.roles(http)
|
||||
let mut client = serenity::ClientBuilder::new(token, intents)
|
||||
.framework(framework)
|
||||
.await
|
||||
.unwrap()
|
||||
.iter()
|
||||
.find(|(_, role)| role.name == role_name)
|
||||
.map(|(role_id, _)| role_id)
|
||||
.copied()
|
||||
.unwrap();
|
||||
|
||||
client.start().await.unwrap();
|
||||
}
|
||||
|
||||
+3
-3
@@ -7,7 +7,7 @@ use std::fmt::{Display, Formatter};
|
||||
#[allow(dead_code)]
|
||||
pub enum Error {
|
||||
ConfigError(config::ConfigError),
|
||||
SerenityError(serenity::Error),
|
||||
SerenityError(poise::serenity_prelude::Error),
|
||||
TeraError(tera::Error),
|
||||
NoAlbumFound,
|
||||
UserError(user::UserError),
|
||||
@@ -23,8 +23,8 @@ impl From<config::ConfigError> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<serenity::Error> for Error {
|
||||
fn from(err: serenity::Error) -> Self {
|
||||
impl From<poise::serenity_prelude::Error> for Error {
|
||||
fn from(err: poise::serenity_prelude::Error) -> Self {
|
||||
Self::SerenityError(err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
use poise::serenity_prelude::utils::MessageBuilder;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::utils::MessageBuilder;
|
||||
use std::collections::hash_map::DefaultHasher;
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::hash::{Hash, Hasher};
|
||||
|
||||
+10
-53
@@ -1,5 +1,4 @@
|
||||
mod album_manager;
|
||||
mod api;
|
||||
mod config;
|
||||
mod discord;
|
||||
mod error;
|
||||
@@ -9,13 +8,11 @@ mod models;
|
||||
mod user;
|
||||
|
||||
use crate::config::{Args, BotConfig, Channel, GlobalData};
|
||||
use crate::discord::emoji_race::RaceMessage;
|
||||
use crate::discord::unrecognised_command_hook;
|
||||
//use crate::discord::emoji_race::RaceMessage;
|
||||
//use crate::discord::{unrecognised_command_hook, Handler};
|
||||
use crate::discord::run_bot;
|
||||
use log::{error, info, LevelFilter};
|
||||
use magick_rust::magick_wand_genesis;
|
||||
use serenity::all::standard::BucketBuilder;
|
||||
use serenity::framework::standard::macros::{command, group, help, hook};
|
||||
use serenity::framework::standard::{Configuration, StandardFramework};
|
||||
use serenity::prelude::*;
|
||||
use songbird::SerenityInit;
|
||||
use std::sync::Once;
|
||||
use structopt::StructOpt;
|
||||
@@ -31,11 +28,13 @@ async fn main() {
|
||||
let cfg = match BotConfig::new(&args.cfg_path) {
|
||||
Ok(cfg) => cfg,
|
||||
Err(err) => {
|
||||
println!("Unable to open config: {}", err);
|
||||
error!("Unable to open config: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
START.call_once(|| {
|
||||
magick_wand_genesis();
|
||||
});
|
||||
@@ -43,53 +42,11 @@ async fn main() {
|
||||
let global_data = match GlobalData::new(args, cfg).await {
|
||||
Ok(global_data) => global_data,
|
||||
Err(err) => {
|
||||
println!("Error parsing config: {}", err);
|
||||
error!("Error parsing config: {}", err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let framework = StandardFramework::new()
|
||||
.group(&discord::color::COLOR_GROUP)
|
||||
.group(&discord::album::ALBUMCMD_GROUP)
|
||||
.group(&discord::celeryman::CELERYMAN_GROUP)
|
||||
.group(&discord::joke::JOKE_GROUP)
|
||||
.group(&discord::admin::ADMIN_GROUP)
|
||||
.group(&discord::story::STORY_GROUP)
|
||||
.group(&discord::fren_coin::FRENCOIN_GROUP)
|
||||
.group(&discord::emoji_race::EMOJIRACE_GROUP)
|
||||
.group(&discord::motivate::MOTIVATE_GROUP)
|
||||
.group(&discord::voices::VOICES_GROUP)
|
||||
.group(&discord::shop::SHOP_GROUP)
|
||||
.group(&discord::birthday::BIRTHDAY_GROUP)
|
||||
.group(&discord::little_fren::BUDDY_GROUP)
|
||||
.unrecognised_command(unrecognised_command_hook)
|
||||
.bucket("bad_apple", BucketBuilder::default().delay(60 * 10))
|
||||
.await
|
||||
.help(&discord::MY_HELP)
|
||||
.after(discord::after);
|
||||
|
||||
framework.configure(
|
||||
Configuration::new()
|
||||
.with_whitespace(true)
|
||||
.prefix("!")
|
||||
.ignore_bots(true),
|
||||
);
|
||||
|
||||
let story_channel = Channel::<String>::new();
|
||||
let race_channel = Channel::<RaceMessage>::new();
|
||||
|
||||
let intents = GatewayIntents::all();
|
||||
let mut client = Client::builder(&global_data.cfg.bot_token, intents)
|
||||
.framework(framework)
|
||||
.register_songbird()
|
||||
.type_map_insert::<GlobalData>(global_data)
|
||||
.type_map_insert::<Channel<String>>(story_channel)
|
||||
.type_map_insert::<Channel<RaceMessage>>(race_channel)
|
||||
.event_handler(discord::Handler)
|
||||
.await
|
||||
.expect("Unable to create client.");
|
||||
|
||||
if let Err(err) = client.start().await {
|
||||
println!("Client error: {:?}", err);
|
||||
}
|
||||
info!("Fren bot starting...");
|
||||
run_bot(global_data).await;
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ use crate::error::Error;
|
||||
use base64::{engine::general_purpose, Engine as _};
|
||||
use j_db::database::Database;
|
||||
use j_db::model::JdbModel;
|
||||
use poise::serenity_prelude::model::id::UserId;
|
||||
use rand::distr::Alphanumeric;
|
||||
use rand::{rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::id::UserId;
|
||||
use sha3::digest::FixedOutput;
|
||||
use sha3::{Digest, Sha3_256};
|
||||
|
||||
|
||||
@@ -2,13 +2,13 @@ use crate::config::GlobalData;
|
||||
use crate::error::Error;
|
||||
use j_db::database::Database;
|
||||
use j_db::model::JdbModel;
|
||||
use poise::serenity_prelude::model::guild::Emoji;
|
||||
use poise::serenity_prelude::model::id::{EmojiId, GuildId};
|
||||
use poise::serenity_prelude::prelude::Context;
|
||||
use poise::serenity_prelude::utils::MessageBuilder;
|
||||
use rand::distr::{Distribution, StandardUniform};
|
||||
use rand::{rng, Rng};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::guild::Emoji;
|
||||
use serenity::model::id::{EmojiId, GuildId};
|
||||
use serenity::prelude::Context;
|
||||
use serenity::utils::MessageBuilder;
|
||||
use std::time::Duration;
|
||||
|
||||
pub fn draw_standing(emoji: &Emoji) -> String {
|
||||
|
||||
+1
-1
@@ -4,4 +4,4 @@ pub mod insult_compliment;
|
||||
pub mod lil_fren;
|
||||
pub mod motivation;
|
||||
pub mod random;
|
||||
pub mod task;
|
||||
//pub mod task;
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
use crate::config::GlobalData;
|
||||
use crate::discord::shop::restock_shop;
|
||||
//use crate::discord::shop::restock_shop;
|
||||
use crate::error::Error;
|
||||
use crate::models::birthday::BirthdayEntry;
|
||||
use crate::models::insult_compliment::{RandomResponseTemplate, ResponseType};
|
||||
@@ -7,8 +7,8 @@ use chrono::{Days, Duration, TimeZone, Timelike, Utc};
|
||||
use j_db::database::Database;
|
||||
use j_db::model::JdbModel;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::all::Mentionable;
|
||||
use serenity::prelude::Context;
|
||||
use poise::serenity_prelude::all::Mentionable;
|
||||
use poise::
|
||||
|
||||
#[derive(Debug, Deserialize, Serialize, Clone, Copy, PartialEq)]
|
||||
pub enum TaskType {
|
||||
|
||||
+1
-1
@@ -2,8 +2,8 @@ use crate::error::Error;
|
||||
use crate::inventory::{InventoryError, InventoryManager, ItemData, ItemType, Operation};
|
||||
use j_db::database::Database;
|
||||
use j_db::model::JdbModel;
|
||||
use poise::serenity_prelude::model::id::UserId;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serenity::model::id::UserId;
|
||||
use std::fmt::{Display, Formatter};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
||||
Reference in New Issue
Block a user