Started working on event system
+ Only used for "bad bot" for now but the framework is there
This commit is contained in:
parent
bf43d63c8a
commit
6aa8a44b3e
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1123,6 +1123,7 @@ dependencies = [
|
|||||||
"thiserror 2.0.12",
|
"thiserror 2.0.12",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tonic",
|
"tonic",
|
||||||
|
"tracing-core",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -28,10 +28,11 @@ tonic = "0.12.3"
|
|||||||
prost = "0.13.5"
|
prost = "0.13.5"
|
||||||
emojis = "0.6.2"
|
emojis = "0.6.2"
|
||||||
poise = "0.6.1"
|
poise = "0.6.1"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = { version = "0.3.19", features = ["env-filter"] }
|
||||||
log = "0.4.26"
|
log = "0.4.26"
|
||||||
cta-api = { version = "0.5.0", registry = "ahines"}
|
cta-api = { version = "0.5.0", registry = "ahines"}
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
|
tracing-core = "0.1.33"
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
version = "1.35.1"
|
version = "1.35.1"
|
||||||
|
|||||||
@ -1,10 +1,13 @@
|
|||||||
use crate::album_manager::AlbumManager;
|
use crate::album_manager::AlbumManager;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::event_listener::{Action, Expiration, Listener, TriggerType};
|
||||||
use crate::migrations::{CURRENT_DB_VERSION, do_migration};
|
use crate::migrations::{CURRENT_DB_VERSION, do_migration};
|
||||||
use config::{Config, File};
|
use config::{Config, File};
|
||||||
use cta_api::CTAClient;
|
use cta_api::CTAClient;
|
||||||
use j_db::database::{DB_METADATA_ID, Database};
|
use j_db::database::{DB_METADATA_ID, Database};
|
||||||
use j_db::metadata::DBMetadata;
|
use j_db::metadata::DBMetadata;
|
||||||
|
use j_db::model::JdbModel;
|
||||||
|
use log::info;
|
||||||
use poise::serenity_prelude::model::id::ChannelId;
|
use poise::serenity_prelude::model::id::ChannelId;
|
||||||
use poise::serenity_prelude::model::prelude::{GuildId, UserId};
|
use poise::serenity_prelude::model::prelude::{GuildId, UserId};
|
||||||
use poise::serenity_prelude::prelude::TypeMapKey;
|
use poise::serenity_prelude::prelude::TypeMapKey;
|
||||||
@ -83,6 +86,32 @@ pub struct GlobalData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl GlobalData {
|
impl GlobalData {
|
||||||
|
fn setup_system_listeners(db: &Database) -> Result<(), Error> {
|
||||||
|
db.filter(|_, listener: &Listener| listener.system_listener)?
|
||||||
|
.for_each(|listener: Listener| {
|
||||||
|
let _ = db.remove::<Listener>(listener.id().unwrap()).is_ok();
|
||||||
|
});
|
||||||
|
|
||||||
|
info!("Adding system listeners...");
|
||||||
|
Listener::add_event(
|
||||||
|
db,
|
||||||
|
Listener::new(
|
||||||
|
TriggerType::OnMessage {
|
||||||
|
channel_id: None,
|
||||||
|
content: "bad bot".to_string(),
|
||||||
|
},
|
||||||
|
vec![Action::React {
|
||||||
|
emoji: "😭".to_string(),
|
||||||
|
}],
|
||||||
|
1.0,
|
||||||
|
Expiration::Never,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn new(args: Args, cfg: BotConfig) -> Result<Self, Error> {
|
pub async fn new(args: Args, cfg: BotConfig) -> Result<Self, Error> {
|
||||||
let db = Database::new(&cfg.db_path)?;
|
let db = Database::new(&cfg.db_path)?;
|
||||||
|
|
||||||
@ -97,6 +126,8 @@ impl GlobalData {
|
|||||||
|
|
||||||
do_migration(&db);
|
do_migration(&db);
|
||||||
|
|
||||||
|
Self::setup_system_listeners(&db)?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
args,
|
args,
|
||||||
bot_state: Mutex::new(BotState::new().await?),
|
bot_state: Mutex::new(BotState::new().await?),
|
||||||
|
|||||||
@ -18,11 +18,12 @@ use crate::config::GlobalData;
|
|||||||
use crate::discord::fren_coin::give_coin;
|
use crate::discord::fren_coin::give_coin;
|
||||||
use crate::discord::joke::random;
|
use crate::discord::joke::random;
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
|
use crate::event_listener::{Listener, TriggerEvent, TriggerType};
|
||||||
use crate::models::lil_fren::lil_fren_task;
|
use crate::models::lil_fren::lil_fren_task;
|
||||||
use crate::models::social_credit::SocialCreditPhrase;
|
use crate::models::social_credit::SocialCreditPhrase;
|
||||||
use crate::models::task::Task;
|
use crate::models::task::Task;
|
||||||
use crate::user::User;
|
use crate::user::User;
|
||||||
use log::{error, info};
|
use log::{debug, error, info};
|
||||||
use poise::serenity_prelude::{GuildId, Http, Message, MessageBuilder, ReactionType, RoleId};
|
use poise::serenity_prelude::{GuildId, Http, Message, MessageBuilder, ReactionType, RoleId};
|
||||||
use poise::{FrameworkOptions, find_command, serenity_prelude as serenity};
|
use poise::{FrameworkOptions, find_command, serenity_prelude as serenity};
|
||||||
use rand::prelude::IteratorRandom;
|
use rand::prelude::IteratorRandom;
|
||||||
@ -122,10 +123,6 @@ async fn handle_message(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if new_message.content.to_lowercase().contains("bad bot") {
|
|
||||||
new_message.react(&ctx.http, '😭').await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
give_coin(&data.db, new_message.author.id, 0.05, 10).await?;
|
give_coin(&data.db, new_message.author.id, 0.05, 10).await?;
|
||||||
|
|
||||||
if let Some(phrase) = SocialCreditPhrase::check_if_match(&data.db, &new_message.content)? {
|
if let Some(phrase) = SocialCreditPhrase::check_if_match(&data.db, &new_message.content)? {
|
||||||
@ -140,6 +137,25 @@ async fn handle_message(
|
|||||||
User::update_social_credit(&data.db, new_message.author.id, social_credit_change)?;
|
User::update_social_credit(&data.db, new_message.author.id, social_credit_change)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let trigger_event = TriggerEvent::new(
|
||||||
|
new_message.author.id,
|
||||||
|
TriggerType::OnMessage {
|
||||||
|
channel_id: Some(new_message.channel_id),
|
||||||
|
content: new_message.content.clone(),
|
||||||
|
},
|
||||||
|
new_message.id,
|
||||||
|
new_message.channel_id,
|
||||||
|
);
|
||||||
|
|
||||||
|
match Listener::process_trigger(ctx, data, trigger_event).await {
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("Processed message trigger successfully")
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Failed to process message trigger: {err}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
203
src/event_listener/mod.rs
Normal file
203
src/event_listener/mod.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
use crate::config::GlobalData;
|
||||||
|
use crate::error::Error;
|
||||||
|
use crate::inventory::{ItemData, ItemType};
|
||||||
|
use chrono::Utc;
|
||||||
|
use j_db::database::Database;
|
||||||
|
use log::{debug, error};
|
||||||
|
use poise::serenity_prelude::{ArgumentConvert, ChannelId, Context, Emoji, MessageId, UserId};
|
||||||
|
use rand::{Rng, rng};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::fmt::Debug;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum TriggerType {
|
||||||
|
OnMessage {
|
||||||
|
channel_id: Option<ChannelId>,
|
||||||
|
content: String,
|
||||||
|
},
|
||||||
|
UseItem {
|
||||||
|
item: ItemType,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriggerType {
|
||||||
|
pub fn match_event(&self, trigger_event: &Self) -> bool {
|
||||||
|
match (self, trigger_event) {
|
||||||
|
(
|
||||||
|
TriggerType::OnMessage {
|
||||||
|
content: trigger,
|
||||||
|
channel_id: channel_id_trigger,
|
||||||
|
},
|
||||||
|
TriggerType::OnMessage {
|
||||||
|
content: event_msg,
|
||||||
|
channel_id: channel_id_event,
|
||||||
|
},
|
||||||
|
) => {
|
||||||
|
let match_channel = if channel_id_trigger.is_some() {
|
||||||
|
channel_id_trigger == channel_id_event
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
};
|
||||||
|
event_msg
|
||||||
|
.to_ascii_lowercase()
|
||||||
|
.contains(&trigger.to_ascii_lowercase())
|
||||||
|
&& match_channel
|
||||||
|
}
|
||||||
|
(TriggerType::UseItem { item: trigger }, TriggerType::UseItem { item: event_item }) => {
|
||||||
|
trigger == event_item
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TriggerEvent {
|
||||||
|
triggerer: UserId,
|
||||||
|
trigger_type: TriggerType,
|
||||||
|
message_id: MessageId,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TriggerEvent {
|
||||||
|
pub fn new(
|
||||||
|
triggerer: UserId,
|
||||||
|
trigger_type: TriggerType,
|
||||||
|
message_id: MessageId,
|
||||||
|
channel_id: ChannelId,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
triggerer,
|
||||||
|
trigger_type,
|
||||||
|
message_id,
|
||||||
|
channel_id,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Action {
|
||||||
|
Kill { hours: u16 },
|
||||||
|
Cancel { hours: u16 },
|
||||||
|
UpdateSocialCredit { score_diff: i64 },
|
||||||
|
UpdateFrenCoins { fren_coin_diff: i64 },
|
||||||
|
GiveItem { item: ItemType, data: ItemData },
|
||||||
|
TakeItem { item: ItemType },
|
||||||
|
Speak { msg: String },
|
||||||
|
React { emoji: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type")]
|
||||||
|
pub enum Expiration {
|
||||||
|
NumberOfTriggers(u16),
|
||||||
|
Time(chrono::DateTime<Utc>),
|
||||||
|
Never,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
pub struct Listener {
|
||||||
|
id: Option<u64>,
|
||||||
|
pub trigger: TriggerType,
|
||||||
|
pub actions: Vec<Action>,
|
||||||
|
pub trigger_chance: f64,
|
||||||
|
pub expiration: Expiration,
|
||||||
|
pub system_listener: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener {
|
||||||
|
pub fn new(
|
||||||
|
trigger: TriggerType,
|
||||||
|
actions: Vec<Action>,
|
||||||
|
trigger_chance: f64,
|
||||||
|
expiration: Expiration,
|
||||||
|
system_listener: bool,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
id: None,
|
||||||
|
trigger,
|
||||||
|
actions,
|
||||||
|
trigger_chance,
|
||||||
|
expiration,
|
||||||
|
system_listener,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check_if_triggers(&self, trigger_type: &TriggerType) -> bool {
|
||||||
|
self.trigger.match_event(trigger_type) && rng().random_bool(self.trigger_chance)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn handle_action(
|
||||||
|
&self,
|
||||||
|
ctx: &Context,
|
||||||
|
data: &Arc<GlobalData>,
|
||||||
|
trigger_event: &TriggerEvent,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
for action in &self.actions {
|
||||||
|
if let Action::React { emoji } = action {
|
||||||
|
let msg = ctx
|
||||||
|
.http
|
||||||
|
.get_message(trigger_event.channel_id, trigger_event.message_id)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
if emoji.chars().count() == 1 {
|
||||||
|
msg.react(&ctx.http, emoji.chars().next().unwrap()).await?;
|
||||||
|
} else {
|
||||||
|
let emoji =
|
||||||
|
Emoji::convert(&ctx.http, None, Some(msg.channel_id), emoji.as_str())
|
||||||
|
.await
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
msg.react(&ctx.http, emoji).await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_event(db: &Database, new_event: Self) -> Result<Self, Error> {
|
||||||
|
debug!("Adding listener: {new_event:?}");
|
||||||
|
Ok(db.insert(new_event)?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn process_trigger(
|
||||||
|
ctx: &Context,
|
||||||
|
data: &Arc<GlobalData>,
|
||||||
|
trigger_event: TriggerEvent,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let triggered_listeners = data.db.filter(|_, listener: &Listener| {
|
||||||
|
listener.check_if_triggers(&trigger_event.trigger_type)
|
||||||
|
})?;
|
||||||
|
|
||||||
|
for listener in triggered_listeners {
|
||||||
|
match listener.handle_action(ctx, data, &trigger_event).await {
|
||||||
|
Ok(_) => {
|
||||||
|
debug!("Processed event: {listener:?}");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
error!("Got error processing event '{listener:?}': {err}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl j_db::model::JdbModel for Listener {
|
||||||
|
fn id(&self) -> Option<u64> {
|
||||||
|
self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_id(&mut self, id: u64) {
|
||||||
|
self.id = Some(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tree() -> String {
|
||||||
|
"Events".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -29,7 +29,16 @@ pub enum ItemData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug, Clone, Serialize, Deserialize, Hash, Eq, PartialEq, Copy, poise::ChoiceParameter,
|
Debug,
|
||||||
|
Clone,
|
||||||
|
Serialize,
|
||||||
|
Deserialize,
|
||||||
|
Hash,
|
||||||
|
Eq,
|
||||||
|
PartialEq,
|
||||||
|
Copy,
|
||||||
|
poise::ChoiceParameter,
|
||||||
|
PartialOrd,
|
||||||
)]
|
)]
|
||||||
#[allow(clippy::upper_case_acronyms)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
pub enum ItemType {
|
pub enum ItemType {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ mod album_manager;
|
|||||||
mod config;
|
mod config;
|
||||||
mod discord;
|
mod discord;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod event_listener;
|
||||||
mod image_manipulation;
|
mod image_manipulation;
|
||||||
mod inventory;
|
mod inventory;
|
||||||
mod migrations;
|
mod migrations;
|
||||||
@ -14,6 +15,7 @@ use log::{error, info};
|
|||||||
use magick_rust::magick_wand_genesis;
|
use magick_rust::magick_wand_genesis;
|
||||||
use std::sync::Once;
|
use std::sync::Once;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
const BAD_APPLE: &str = include_str!("assets/bad_apple.txt");
|
const BAD_APPLE: &str = include_str!("assets/bad_apple.txt");
|
||||||
|
|
||||||
@ -22,7 +24,10 @@ static START: Once = Once::new();
|
|||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
let args: Args = Args::from_args();
|
let args: Args = Args::from_args();
|
||||||
tracing_subscriber::fmt::init();
|
tracing_subscriber::fmt()
|
||||||
|
.with_max_level(tracing_core::metadata::Level::INFO)
|
||||||
|
.with_env_filter(EnvFilter::from_default_env())
|
||||||
|
.init();
|
||||||
|
|
||||||
let cfg = match BotConfig::new(&args.cfg_path) {
|
let cfg = match BotConfig::new(&args.cfg_path) {
|
||||||
Ok(cfg) => cfg,
|
Ok(cfg) => cfg,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user