Initial rework to poise

This commit is contained in:
2025-03-15 17:40:56 -06:00
parent f21f145618
commit 6ac5129114
13 changed files with 210 additions and 407 deletions
+6 -7
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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 -1
View File
@@ -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
View File
@@ -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;
}
+1 -1
View File
@@ -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};
+4 -4
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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)]