custom pyramid
This commit is contained in:
parent
a9da9f4192
commit
6b09aeed69
11 changed files with 337 additions and 24 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -3,3 +3,6 @@
|
|||
|
||||
# env
|
||||
.env
|
||||
|
||||
# temp
|
||||
/tmp
|
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -146,6 +146,7 @@ name = "forcebot-rs-v2"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dotenv",
|
||||
"lazy_static",
|
||||
"tokio",
|
||||
"twitch-irc",
|
||||
]
|
||||
|
@ -214,6 +215,12 @@ version = "0.31.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.169"
|
||||
|
|
|
@ -6,9 +6,11 @@ default-run = "forcebot-rs-v2"
|
|||
|
||||
[dependencies]
|
||||
dotenv = "0.15.0"
|
||||
lazy_static = "1.5.0"
|
||||
tokio = { version = "1.33.0", features = ["full"] }
|
||||
twitch-irc = "5.0.1"
|
||||
|
||||
|
||||
# [[bin]]
|
||||
# name = "simple_bot"
|
||||
# path = "src/simple_bot.rs"
|
||||
|
|
17
readme.md
17
readme.md
|
@ -128,16 +128,19 @@ pub async fn main() {
|
|||
|
||||
|
||||
pub mod custom_mod {
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use forcebot_rs_v2::{asyncfn_box, Badge, Bot, Command, Module};
|
||||
use twitch_irc::message::ServerMessage;
|
||||
|
||||
|
||||
/// Module with a loaded command
|
||||
/// Module definition with a loaded command
|
||||
pub fn new() -> Module {
|
||||
/* 1. Create a new module */
|
||||
let mut custom_mod = Module::new("test".to_string(), "".to_string());
|
||||
let mut custom_mod = Module::new(
|
||||
vec!["test".to_string()],
|
||||
"".to_string());
|
||||
|
||||
/* 2. Load the cmd into a new module */
|
||||
custom_mod.load_command(cmd_test());
|
||||
|
@ -146,9 +149,10 @@ pub mod custom_mod {
|
|||
|
||||
}
|
||||
|
||||
/// Command definition
|
||||
pub fn cmd_test() -> Command {
|
||||
/* 1. Create a new cmd */
|
||||
let mut cmd = Command::new("test".to_string(),"".to_string());
|
||||
let mut cmd = Command::new(vec!["test".to_string()],"".to_string());
|
||||
|
||||
/* 2. Define exec callback */
|
||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||
|
@ -165,7 +169,6 @@ pub mod custom_mod {
|
|||
cmd.set_min_badge(Badge::Moderator);
|
||||
|
||||
cmd
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
@ -290,7 +293,7 @@ pub async fn main() {
|
|||
let mut bot = Bot::new();
|
||||
|
||||
/* 1. Create a new blank cmd */
|
||||
let mut cmd = Command::new("test".to_string(),"".to_string());
|
||||
let mut cmd = Command::new(vec!["test".to_string()],"".to_string());
|
||||
|
||||
/* 2. Define an async fn callback execution */
|
||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||
|
@ -308,8 +311,8 @@ pub async fn main() {
|
|||
cmd.set_admin_only(false);
|
||||
|
||||
/* 5. optionally, set min badge*/
|
||||
cmd.set_min_badge("broadcaster".to_string());
|
||||
//
|
||||
cmd.set_min_badge(Badge::Moderator);
|
||||
|
||||
/* 6. Load the cmd into the bot */
|
||||
bot.load_command(cmd);
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
//! Custom modules that can be managed in chat through `disable` and `enable` commands
|
||||
//! - funbot
|
||||
//! - guests
|
||||
//! - pyramid
|
||||
//!
|
||||
//!
|
||||
//! Be sure the followig is defined in `.env`
|
||||
|
@ -16,7 +17,7 @@
|
|||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||
|
||||
use forcebot_rs_v2::{custom_mods::guest_badge, Bot};
|
||||
use forcebot_rs_v2::{custom_mods::{guest_badge, pyramid}, Bot};
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
|
@ -29,6 +30,7 @@ pub async fn main() {
|
|||
|
||||
/* 2. Load Custom Modules */
|
||||
bot.load_module(guest_badge::create_module());
|
||||
bot.load_module(pyramid::create_module());
|
||||
|
||||
/* 3. Run the bot */
|
||||
bot.run().await;
|
||||
|
|
|
@ -41,7 +41,7 @@ pub mod custom_mod {
|
|||
use twitch_irc::message::ServerMessage;
|
||||
|
||||
|
||||
/// Module with a loaded command
|
||||
/// Module definition with a loaded command
|
||||
pub fn new() -> Module {
|
||||
/* 1. Create a new module */
|
||||
let mut custom_mod = Module::new(
|
||||
|
@ -55,6 +55,7 @@ pub mod custom_mod {
|
|||
|
||||
}
|
||||
|
||||
/// Command definition
|
||||
pub fn cmd_test() -> Command {
|
||||
/* 1. Create a new cmd */
|
||||
let mut cmd = Command::new(vec!["test".to_string()],"".to_string());
|
||||
|
@ -74,5 +75,5 @@ pub mod custom_mod {
|
|||
cmd.set_min_badge(Badge::Moderator);
|
||||
|
||||
cmd
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
|
||||
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
||||
use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient};
|
||||
use twitch_irc::{login::StaticLoginCredentials, message::{PrivmsgMessage, ServerMessage}, SecureTCPTransport, TwitchIRCClient};
|
||||
use dotenv::dotenv;
|
||||
use std::{env, sync::Arc, time::{Duration, Instant}};
|
||||
|
||||
|
@ -32,7 +32,10 @@ pub struct Bot
|
|||
/// channel module status
|
||||
channel_module_status: Mutex<Vec<(String,String,modules::Status)>>,
|
||||
/// chatter guest badges - chatter,channel,Badge,start_time,duration
|
||||
chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>
|
||||
chatter_guest_badges: Mutex<Vec<(String,String,Badge,Instant,Duration)>>,
|
||||
/// Message cache
|
||||
message_cache: Mutex<Vec<PrivmsgMessage>>,
|
||||
// message_cache: Vec<PrivmsgMessage>
|
||||
}
|
||||
|
||||
|
||||
|
@ -112,6 +115,7 @@ impl Bot
|
|||
modules: vec![],
|
||||
channel_module_status: Mutex::new(vec![]),
|
||||
chatter_guest_badges: Mutex::new(vec![]),
|
||||
message_cache : Mutex::new(vec![]),
|
||||
};
|
||||
|
||||
for cmd in built_in_objects::create_commands() {
|
||||
|
@ -135,7 +139,10 @@ impl Bot
|
|||
let a = bot.clone();
|
||||
let mut in_msgs_lock = a.incoming_msgs.lock().await;
|
||||
|
||||
while let Some(message) = in_msgs_lock.recv().await {
|
||||
while let Some(message) = in_msgs_lock.recv().await {
|
||||
|
||||
|
||||
|
||||
for listener in &(*bot).listeners {
|
||||
|
||||
let a = listener.clone();
|
||||
|
@ -147,6 +154,15 @@ impl Bot
|
|||
|
||||
if let ServerMessage::Privmsg(msg) = message.clone() {
|
||||
|
||||
// let mut cache_lock = bot.message_cache.lock().await;
|
||||
let mut cache_lock = bot.message_cache.lock().await;
|
||||
cache_lock.push(msg.clone());
|
||||
// dbg!(cache_lock.clone());
|
||||
drop(cache_lock);
|
||||
|
||||
// let a = bot.clone();
|
||||
// dbg!(bot.get_message_cache());
|
||||
|
||||
for cmd in &(*bot).commands {
|
||||
|
||||
let a = cmd.clone();
|
||||
|
@ -324,6 +340,9 @@ impl Bot
|
|||
|
||||
}
|
||||
|
||||
pub fn get_message_cache(&self) -> &Mutex<Vec<PrivmsgMessage>> {
|
||||
&self.message_cache
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
pub mod guest_badge;
|
||||
pub mod guest_badge;
|
||||
pub mod pyramid;
|
|
@ -14,16 +14,18 @@ use crate::{asyncfn_box, Badge, Bot, Command, Module};
|
|||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
|
||||
const VIP_GIVEN_DUR_MIN:u64 = 15;
|
||||
const MOD_GIVEN_DUR_MIN:u64 = 30;
|
||||
|
||||
|
||||
|
||||
/// Use this function when loading modules into the bot
|
||||
///
|
||||
/// For example
|
||||
/// ```rust
|
||||
/// bot.load_module(guest_badge::create_module());
|
||||
/// ```
|
||||
///
|
||||
pub fn create_module() -> Module {
|
||||
|
||||
let mut custom_mod = Module::new(
|
||||
|
@ -32,7 +34,7 @@ pub fn create_module() -> Module {
|
|||
|
||||
custom_mod.load_command(create_cmd_mod());
|
||||
custom_mod.load_command(create_cmd_vip());
|
||||
|
||||
|
||||
custom_mod
|
||||
|
||||
}
|
||||
|
|
270
src/custom_mods/pyramid.rs
Normal file
270
src/custom_mods/pyramid.rs
Normal file
|
@ -0,0 +1,270 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use twitch_irc::message::{PrivmsgMessage, ServerMessage};
|
||||
|
||||
use crate::{asyncfn_box, Bot, Listener, Module};
|
||||
|
||||
/// pyramid module
|
||||
///
|
||||
/// for detecting & handling pyramids
|
||||
///
|
||||
///
|
||||
///
|
||||
///
|
||||
use lazy_static::lazy_static;
|
||||
|
||||
|
||||
/// Use this function when loading modules into the bot
|
||||
///
|
||||
/// For example
|
||||
/// ```rust
|
||||
/// bot.load_module(pyramid::create_module());
|
||||
/// ```
|
||||
///
|
||||
pub fn create_module() -> Module {
|
||||
|
||||
let mut custom_mod = Module::new(
|
||||
vec!["pyramid".to_string(),
|
||||
"pyramids".to_string()],
|
||||
"o7 I can handle pyramids".to_string());
|
||||
custom_mod.load_listener(create_pyramid_detector());
|
||||
|
||||
custom_mod
|
||||
|
||||
}
|
||||
|
||||
fn create_pyramid_detector() -> Listener {
|
||||
|
||||
/* 1. Create a new blank Listener */
|
||||
let mut listener = Listener::new();
|
||||
|
||||
/* 2. Set a trigger condition function for listener */
|
||||
listener.set_trigger_cond_fn(
|
||||
|_bot:Arc<Bot>,_message:ServerMessage|
|
||||
true
|
||||
);
|
||||
|
||||
/* 3. Define an async fn callback execution */
|
||||
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||
if let ServerMessage::Privmsg(msg) = message {
|
||||
if detect_pyramid_complete_ok(bot.clone(), msg.clone()).await {
|
||||
let _ = bot.client.say_in_reply_to(&msg, "Clap".to_string()).await ;
|
||||
return Result::Ok("Success".to_string()) ;
|
||||
}
|
||||
}
|
||||
Result::Err("Not Valid message type".to_string())
|
||||
}
|
||||
|
||||
/* 4. Set and Store the execution body using `async_box()` */
|
||||
listener.set_exec_fn(asyncfn_box(execbody));
|
||||
|
||||
listener
|
||||
|
||||
}
|
||||
|
||||
|
||||
async fn detect_pyramid_complete_ok(_bot:Arc<Bot>,msg:PrivmsgMessage) -> bool {
|
||||
|
||||
let msgtext = msg.message_text.replace("\u{e0000}","").trim().to_string();
|
||||
let msgchannel = msg.channel_login;
|
||||
|
||||
// 1. Check if Pyramid started in chat > and recognize pyramid started
|
||||
if !is_pyramid_started(msgchannel.clone()) & check_start_pyramid(msgchannel.clone(),msgtext.clone(),) {
|
||||
set_pyramid_started(msgchannel.clone(),true);
|
||||
push_to_compare(msgchannel.clone(),get_start_pattern(msgchannel.clone()));
|
||||
|
||||
}
|
||||
|
||||
if is_pyramid_started(msgchannel.clone()) {
|
||||
push_to_compare(msgchannel.clone(),msgtext.clone());
|
||||
}
|
||||
|
||||
// 2a. If Pyramid Not Started, Assume message is a potential start pattern
|
||||
if !is_pyramid_started(msgchannel.clone()) {
|
||||
set_start_pattern(msgchannel.clone(),msgtext.clone());
|
||||
}
|
||||
|
||||
// 2b. If Pyramid is Started, and the latest message is the pattern, check for
|
||||
// symmetry to determine pyramid
|
||||
if is_pyramid_started(msgchannel.clone()) && msgtext.clone() == get_start_pattern(msgchannel.clone()) {
|
||||
if symmetry_ok(msgchannel.clone()) {
|
||||
return true;
|
||||
} else {
|
||||
return false ;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
lazy_static!{
|
||||
/// Message Compare stack per channel (channel:String,msgstack:Vec<String>)
|
||||
pub static ref COMPARE_MSG_STACK_PER_CHNL: Mutex<Vec<(String,Mutex<Vec<String>>)>> = Mutex::new(vec![]);
|
||||
#[derive(Debug)]
|
||||
/// Pyramid Started per channel (channel:String,started:bool)
|
||||
pub static ref PYRAMID_STARTED_PER_CHNL: Mutex<Vec<(String,Mutex<bool>)>> = Mutex::new(vec![]);
|
||||
/// Start patterns per channel (channel:String,pattern:String)
|
||||
pub static ref START_PATTERNS_PER_CHNL: Mutex<Vec<(String,Mutex<String>)>> = Mutex::new(vec![]);
|
||||
/// temp message stack checker
|
||||
pub static ref TEMP_MSG_STACK: Mutex<Vec<String>> = Mutex::new(vec![]);
|
||||
}
|
||||
|
||||
fn read_top_of_compare(channel:String) -> Option<String> {
|
||||
|
||||
let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap();
|
||||
|
||||
for rec in comp_perchnl.iter() {
|
||||
if rec.0 == channel {
|
||||
let msg_stack = rec.1.lock().unwrap();
|
||||
|
||||
return msg_stack.last().cloned();
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
|
||||
}
|
||||
|
||||
fn pop_top_of_compare(channel:String) -> Option<String> {
|
||||
let comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap();
|
||||
|
||||
for rec in comp_perchnl.iter() {
|
||||
if rec.0 == channel {
|
||||
let mut msg_stack = rec.1.lock().unwrap();
|
||||
|
||||
let popped = msg_stack.pop();
|
||||
return popped;
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn set_pyramid_started(channel:String,started:bool) {
|
||||
let mut start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap();
|
||||
let mut found = false;
|
||||
for rec in start_perchnl.iter() {
|
||||
if rec.0 == channel {
|
||||
found = true;
|
||||
let mut rec_started = rec.1.lock().unwrap();
|
||||
*rec_started = started;
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
start_perchnl.push((channel,Mutex::new(started)));
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pyramid_started(channel:String) -> bool {
|
||||
let start_perchnl = PYRAMID_STARTED_PER_CHNL.lock().unwrap();
|
||||
for rec in start_perchnl.iter() {
|
||||
if rec.0 == channel {
|
||||
let rec_started = rec.1.lock().unwrap();
|
||||
return *rec_started;
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
fn set_start_pattern(channel:String,pattern:String) {
|
||||
let mut start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap();
|
||||
|
||||
let mut found = false;
|
||||
for rec in start_patterns.iter() {
|
||||
|
||||
if rec.0 == channel {
|
||||
found = true;
|
||||
let mut patternlock = rec.1.lock().unwrap();
|
||||
*patternlock = pattern.clone();
|
||||
}
|
||||
|
||||
}
|
||||
if !found {
|
||||
start_patterns.push((channel.clone(),Mutex::new(pattern.clone())));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fn get_start_pattern(channel:String) -> String {
|
||||
let start_patterns = START_PATTERNS_PER_CHNL.lock().unwrap();
|
||||
|
||||
for rec in start_patterns.iter() {
|
||||
|
||||
if rec.0 == channel {
|
||||
let patternlock = rec.1.lock().unwrap();
|
||||
return patternlock.clone();
|
||||
}
|
||||
}
|
||||
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
|
||||
/// pushes message to compare stack
|
||||
fn push_to_compare(channel:String,message:String) {
|
||||
let mut comp_perchnl = COMPARE_MSG_STACK_PER_CHNL.lock().unwrap();
|
||||
|
||||
let mut found = false;
|
||||
for rec in comp_perchnl.iter() {
|
||||
if rec.0 == channel {
|
||||
found = true;
|
||||
let mut msg_stack = rec.1.lock().unwrap();
|
||||
msg_stack.push(message.clone());
|
||||
// dbg!("Push message to cmp stack ; result last cmp_pchnl - ",comp_perchnl.last());
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
comp_perchnl.push((channel,Mutex::new(vec![message])));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// checks latest and next latest messages for potential start
|
||||
fn check_start_pyramid(channel:String,msgtext: String) -> bool {
|
||||
msgtext == format!("{} {}",get_start_pattern(channel.clone()),get_start_pattern(channel.clone()))
|
||||
}
|
||||
|
||||
|
||||
/// pops the compare stack to determine symmetry
|
||||
fn symmetry_ok(channel:String) -> bool {
|
||||
let mut temp_stack = TEMP_MSG_STACK.lock().unwrap();
|
||||
let mut checking_started = false;
|
||||
if !(read_top_of_compare(channel.clone()).unwrap_or("".to_string()) == get_start_pattern(channel.clone())) {
|
||||
return false;
|
||||
}
|
||||
loop {
|
||||
|
||||
if !checking_started && read_top_of_compare(channel.clone()).unwrap_or("".to_string()) == get_start_pattern(channel.clone()) {
|
||||
checking_started = true;
|
||||
}
|
||||
if temp_stack.last().is_none() || read_top_of_compare(channel.clone()).unwrap_or("".to_string()).len() > temp_stack.last().unwrap_or(&"".to_string()).len() {
|
||||
temp_stack.push(pop_top_of_compare(channel.clone()).unwrap_or("".to_string()));
|
||||
|
||||
} else if temp_stack.last().is_some() && read_top_of_compare(channel.clone()).unwrap_or("".to_string()).len() < temp_stack.last().unwrap_or(&"".to_string()).len() {
|
||||
|
||||
temp_stack.pop();
|
||||
if temp_stack.last().unwrap_or(&"".to_string()).clone() == read_top_of_compare(channel.clone()).unwrap_or("".to_string()) {
|
||||
temp_stack.pop();
|
||||
|
||||
continue;
|
||||
} else {
|
||||
|
||||
temp_stack.clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
} else { /* dbg!("failed catchall"); */ return false; }
|
||||
if checking_started && read_top_of_compare(channel.clone()).unwrap() == get_start_pattern(channel.clone()) {
|
||||
|
||||
temp_stack.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
11
src/lib.rs
11
src/lib.rs
|
@ -59,10 +59,12 @@
|
|||
//! use twitch_irc::message::ServerMessage;
|
||||
//!
|
||||
//!
|
||||
//! /// Module with a loaded command
|
||||
//! /// Module definition with a loaded command
|
||||
//! pub fn new() -> Module {
|
||||
//! /* 1. Create a new module */
|
||||
//! let mut custom_mod = Module::new("test".to_string(), "".to_string());
|
||||
//! let mut custom_mod = Module::new(
|
||||
//! vec!["test".to_string()],
|
||||
//! "".to_string());
|
||||
//!
|
||||
//! /* 2. Load the cmd into a new module */
|
||||
//! custom_mod.load_command(cmd_test());
|
||||
|
@ -71,9 +73,10 @@
|
|||
//!
|
||||
//! }
|
||||
//!
|
||||
//! /// Command definition
|
||||
//! pub fn cmd_test() -> Command {
|
||||
//! /* 1. Create a new cmd */
|
||||
//! let mut cmd = Command::new("test".to_string(),"".to_string());
|
||||
//! let mut cmd = Command::new(vec!["test".to_string()],"".to_string());
|
||||
//!
|
||||
//! /* 2. Define exec callback */
|
||||
//! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||
|
@ -83,7 +86,7 @@
|
|||
//! }
|
||||
//! Result::Err("Not Valid message type".to_string())
|
||||
//! }
|
||||
//!
|
||||
//! z
|
||||
//! /* 3. Set Command flags */
|
||||
//! cmd.set_exec_fn(asyncfn_box(execbody));
|
||||
//! cmd.set_admin_only(false);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue