listener obj
This commit is contained in:
parent
2532cc5a3e
commit
ef344402ab
10 changed files with 390 additions and 17 deletions
15
Cargo.toml
15
Cargo.toml
|
@ -2,8 +2,21 @@
|
||||||
name = "forcebot-rs-v2"
|
name = "forcebot-rs-v2"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
default-run = "forcebot-rs-v2"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
tokio = { version = "1.33.0", features = ["full"] }
|
tokio = { version = "1.33.0", features = ["full"] }
|
||||||
twitch-irc = "5.0.1"
|
twitch-irc = "5.0.1"
|
||||||
|
|
||||||
|
# [[bin]]
|
||||||
|
# name = "simple_bot"
|
||||||
|
# path = "src/simple_bot.rs"
|
||||||
|
|
||||||
|
# [[bin]]
|
||||||
|
# name = "simple_bot_listener"
|
||||||
|
# path = "src/simple_bot_listener.rs"
|
||||||
|
|
||||||
|
# [lib]
|
||||||
|
# name = "botlib"
|
||||||
|
# path = "src/lib.rs"
|
88
readme.md
88
readme.md
|
@ -2,7 +2,7 @@ Twitch chat bot written in rust
|
||||||
|
|
||||||
# Quick Start
|
# Quick Start
|
||||||
|
|
||||||
Runs the bot's binary crate
|
Run a Simple bot with Built in functionality
|
||||||
|
|
||||||
1. Generate a twitch access token
|
1. Generate a twitch access token
|
||||||
|
|
||||||
|
@ -25,16 +25,32 @@ bot_admins=ADMIN
|
||||||
cargo run
|
cargo run
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Binary Crates
|
||||||
|
|
||||||
|
## Simple Bot
|
||||||
|
Run a simple bot that logs into chat based on env
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --bin simple_bot
|
||||||
|
```
|
||||||
|
|
||||||
|
## Simple Bot with Example Custom Listener
|
||||||
|
Run a bot with some custom listeners
|
||||||
|
|
||||||
|
```
|
||||||
|
cargo run --bin simple_bot_listener
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Example Code
|
# Example Code
|
||||||
|
|
||||||
**Quick Start Main**
|
## Simple Bot
|
||||||
|
|
||||||
Uses Env defined variables to create and run the bot
|
Uses Env defined variables to create and run the bot
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
use botcore::bot::Bot;
|
use forcebot_rs_v2::botcore::bot::Bot;
|
||||||
|
|
||||||
mod botcore;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
@ -46,10 +62,72 @@ pub async fn main() {
|
||||||
bot.run().await;
|
bot.run().await;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Custom Bot with listener
|
||||||
|
Bot with a simple listener
|
||||||
|
|
||||||
|
Example listener listens for a moderator badge and reply in chat
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
|
||||||
|
use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() {
|
||||||
|
|
||||||
|
/* Create the bot using env */
|
||||||
|
let mut bot = Bot::new();
|
||||||
|
|
||||||
|
/* 1. Create a new blank Listener */
|
||||||
|
let mut listener = Listener::new();
|
||||||
|
|
||||||
|
/* 2. Set a trigger condition callback */
|
||||||
|
listener.set_trigger_cond_fn(
|
||||||
|
|_:Arc<Bot>,message:ServerMessage|
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
for badge in msg.badges {
|
||||||
|
if matches!(badge, x if x.name == "moderator") {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else { false }
|
||||||
|
);
|
||||||
|
|
||||||
|
/* 3. Define an async fn callback execution */
|
||||||
|
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
match bot.client.say_in_reply_to(&msg, String::from("test")).await {
|
||||||
|
Ok(_) => return Result::Ok("Success".to_string()) ,
|
||||||
|
Err(_) => return Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Result::Err("Not Valid message type".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 4. Set the execution body using `async_box()` */
|
||||||
|
listener.set_exec_fn(asyncfn_box(execbody));
|
||||||
|
|
||||||
|
/* 5. Load the Listener into the bot */
|
||||||
|
bot.load_listener(listener);
|
||||||
|
|
||||||
|
/* Run the bot */
|
||||||
|
bot.run().await;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Crate Rust Documentation
|
# Crate Rust Documentation
|
||||||
|
|
||||||
|
Create Crate documentation
|
||||||
|
|
||||||
Clean Build Documentation
|
Clean Build Documentation
|
||||||
```
|
```
|
||||||
cargo clean && cargo doc
|
cargo clean && cargo doc
|
||||||
|
|
24
src/bin/simple_bot.rs
Normal file
24
src/bin/simple_bot.rs
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
//! Example simple Binary crate that creates & runs bot based on `.env`
|
||||||
|
//! Be sure the followig is defined in `.env`
|
||||||
|
//! - login_name
|
||||||
|
//! - access_token
|
||||||
|
//! - bot_channels
|
||||||
|
//! - prefix
|
||||||
|
//! - bot_admins
|
||||||
|
//!
|
||||||
|
//! Bot access tokens be generated here -
|
||||||
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
|
use forcebot_rs_v2::botcore::bot::Bot;
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() {
|
||||||
|
|
||||||
|
/* 1. Create the bot using env */
|
||||||
|
let bot = Bot::new();
|
||||||
|
|
||||||
|
/* 2. Run the bot */
|
||||||
|
bot.run().await;
|
||||||
|
|
||||||
|
}
|
66
src/bin/simple_bot_listener.rs
Normal file
66
src/bin/simple_bot_listener.rs
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
//! Simple bot with a custom listeners that listens for moderators and respond to the moderator
|
||||||
|
//!
|
||||||
|
//! Be sure the followig is defined in `.env`
|
||||||
|
//! - login_name
|
||||||
|
//! - access_token
|
||||||
|
//! - bot_channels
|
||||||
|
//! - prefix
|
||||||
|
//! - bot_admins
|
||||||
|
//!
|
||||||
|
//! Bot access tokens be generated here -
|
||||||
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
|
||||||
|
use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
pub async fn main() {
|
||||||
|
|
||||||
|
/* Create the bot using env */
|
||||||
|
let mut bot = Bot::new();
|
||||||
|
|
||||||
|
/* 1. Create a new blank Listener */
|
||||||
|
let mut listener = Listener::new();
|
||||||
|
|
||||||
|
/* 2. Set a trigger condition function for listener */
|
||||||
|
|
||||||
|
listener.set_trigger_cond_fn(
|
||||||
|
|_:Arc<Bot>,message:ServerMessage|
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
// dbg!(msg.clone());
|
||||||
|
for badge in msg.badges {
|
||||||
|
if matches!(badge, x if x.name == "moderator") {
|
||||||
|
// dbg!("moderator found");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
} else { false }
|
||||||
|
);
|
||||||
|
|
||||||
|
/* 3. Define an async fn callback execution */
|
||||||
|
async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
|
if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
match bot.client.say_in_reply_to(&msg, String::from("test")).await {
|
||||||
|
Ok(_) => return Result::Ok("Success".to_string()) ,
|
||||||
|
Err(_) => return Result::Err("Not Valid message type".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));
|
||||||
|
|
||||||
|
/* 5. Load the listener into the bot */
|
||||||
|
bot.load_listener(listener);
|
||||||
|
|
||||||
|
/* Run the bot */
|
||||||
|
bot.run().await;
|
||||||
|
|
||||||
|
}
|
|
@ -1 +1,2 @@
|
||||||
pub mod bot;
|
pub mod bot;
|
||||||
|
pub mod bot_objects;
|
|
@ -3,23 +3,30 @@
|
||||||
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
||||||
use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient};
|
use twitch_irc::{login::StaticLoginCredentials, message::ServerMessage, SecureTCPTransport, TwitchIRCClient};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use std::env;
|
use std::{env, sync::Arc};
|
||||||
|
|
||||||
|
use super::bot_objects::listener::Listener;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/// Twitch chat bot
|
/// Twitch chat bot
|
||||||
pub struct Bot {
|
pub struct Bot
|
||||||
|
{
|
||||||
/// Prefix for commands
|
/// Prefix for commands
|
||||||
_prefix: char,
|
_prefix: char,
|
||||||
/// inbound chat msg stream
|
/// inbound chat msg stream
|
||||||
incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
|
incoming_msgs: Mutex<UnboundedReceiver<ServerMessage>>,
|
||||||
/// outbound chat client msg stream
|
/// outbound chat client msg stream
|
||||||
client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
|
pub client: TwitchIRCClient<SecureTCPTransport,StaticLoginCredentials>,
|
||||||
/// joined channels
|
/// joined channels
|
||||||
botchannels: Vec<String>,
|
botchannels: Vec<String>,
|
||||||
|
/// listeners
|
||||||
|
listeners: Vec<Listener>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Bot {
|
impl Bot
|
||||||
|
{
|
||||||
/// Creates a new `Bot` using env variables
|
/// Creates a new `Bot` using env variables
|
||||||
///
|
///
|
||||||
/// Be sure the following is defined in an `.env` file
|
/// Be sure the following is defined in an `.env` file
|
||||||
|
@ -79,6 +86,7 @@ impl Bot {
|
||||||
incoming_msgs : Mutex::new(incoming_messages),
|
incoming_msgs : Mutex::new(incoming_messages),
|
||||||
client,
|
client,
|
||||||
botchannels : botchannels_all,
|
botchannels : botchannels_all,
|
||||||
|
listeners : vec![],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,17 +96,35 @@ impl Bot {
|
||||||
for chnl in &self.botchannels {
|
for chnl in &self.botchannels {
|
||||||
self.client.join(chnl.to_owned()).unwrap();
|
self.client.join(chnl.to_owned()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let bot = Arc::new(self);
|
||||||
|
|
||||||
let join_handle = tokio::spawn(async move {
|
let join_handle = tokio::spawn(async move {
|
||||||
let mut in_msgs_lock = self.incoming_msgs.lock().await;
|
|
||||||
|
let mut in_msgs_lock = bot.incoming_msgs.lock().await;
|
||||||
|
|
||||||
while let Some(message) = in_msgs_lock.recv().await {
|
while let Some(message) = in_msgs_lock.recv().await {
|
||||||
//sprintln!("Received message: {:?}", message);
|
// dbg!("Received message: {:?}", message.clone());
|
||||||
dbg!("Received message: {:?}", message);
|
|
||||||
|
for listener in &(*bot).listeners {
|
||||||
|
|
||||||
|
let a = listener.clone();
|
||||||
|
if a.cond_triggered(bot.clone(),message.clone()) {
|
||||||
|
|
||||||
|
let _ = listener.execute_fn(bot.clone(),message.clone()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
drop(in_msgs_lock);
|
drop(in_msgs_lock);
|
||||||
});
|
});
|
||||||
|
|
||||||
join_handle.await.unwrap();
|
join_handle.await.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a `Listener` into the bot
|
||||||
|
pub fn load_listener(&mut self,l : Listener) {
|
||||||
|
self.listeners.push(l);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
1
src/botcore/bot_objects.rs
Normal file
1
src/botcore/bot_objects.rs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
pub mod listener;
|
83
src/botcore/bot_objects/listener.rs
Normal file
83
src/botcore/bot_objects/listener.rs
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use twitch_irc::message::ServerMessage;
|
||||||
|
|
||||||
|
use crate::botcore::bot::Bot;
|
||||||
|
|
||||||
|
/// Bot `Listener`` that stores trigger condition callback and a execution functon
|
||||||
|
///
|
||||||
|
/// Use `asyncfn_box()` on custom async execution bodies
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Listener
|
||||||
|
{
|
||||||
|
trigger_cond_fn : fn(Arc<Bot>,ServerMessage) -> bool,
|
||||||
|
exec_fn : Arc<ExecBody>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Listener
|
||||||
|
{
|
||||||
|
|
||||||
|
/// Creates a new empty `Listener` .
|
||||||
|
/// Call `set_trigger_cond_fn()` and `set_exec_fn()` to trigger & execution function callbacks
|
||||||
|
pub fn new() -> Listener {
|
||||||
|
|
||||||
|
async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) }
|
||||||
|
|
||||||
|
Listener {
|
||||||
|
trigger_cond_fn : |_:Arc<Bot>,_:ServerMessage| false,
|
||||||
|
exec_fn : Arc::new(asyncfn_box(execbody))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// set a trigger conditin callback that returns true if the listener shoud trigger
|
||||||
|
pub fn set_trigger_cond_fn(&mut self,cond_fn: fn(Arc<Bot>,ServerMessage) -> bool) {
|
||||||
|
self.trigger_cond_fn = cond_fn;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// sets the execution body of the listener for when it triggers
|
||||||
|
///
|
||||||
|
/// Use`asyncfn_box()` on the async fn when storing
|
||||||
|
///
|
||||||
|
/// Example -
|
||||||
|
/// ```rust
|
||||||
|
/// /* 1. Create a new blank Listener */
|
||||||
|
/// let mut listener = Listener::new();
|
||||||
|
///
|
||||||
|
/// /* 2. define an async function */
|
||||||
|
/// async fn execbody(_:Arc<Bot>,_:ServerMessage) -> Result<String,String> {Result::Ok("success".to_string()) }
|
||||||
|
///
|
||||||
|
/// /* 3. Set and Store the execution body using `async_box()` */
|
||||||
|
/// listener.set_exec_fn(asyncfn_box(execbody));
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
pub fn set_exec_fn(&mut self,exec_fn:ExecBody ) {
|
||||||
|
self.exec_fn = Arc::new(exec_fn);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// checks if the trigger condition is met
|
||||||
|
pub fn cond_triggered(&self,bot:Arc<Bot>,msg:ServerMessage) -> bool {
|
||||||
|
(self.trigger_cond_fn)(bot,msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// executes the listeners executon body
|
||||||
|
pub async fn execute_fn(&self,bot:Arc<Bot>,msg:ServerMessage) -> Result<String, String> {
|
||||||
|
// (self.exec_fn)(bot,msg)
|
||||||
|
(self.exec_fn)(bot,msg).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
use std::boxed::Box;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
pub type ExecBody = Box<
|
||||||
|
dyn Fn(Arc<Bot>,ServerMessage) -> Pin<Box<dyn Future<Output = Result<String,String>> + Send>> + Send + Sync,
|
||||||
|
>;
|
||||||
|
|
||||||
|
pub fn asyncfn_box<T>(f: fn(Arc<Bot>,ServerMessage) -> T) -> ExecBody
|
||||||
|
where
|
||||||
|
T: Future<Output = Result<String,String>> + Send + 'static,
|
||||||
|
{
|
||||||
|
Box::new(move |a,b| Box::pin(f(a,b)))
|
||||||
|
}
|
82
src/lib.rs
Normal file
82
src/lib.rs
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
//! `forcebot-rs-v2` : Twitch chat bot written in rust
|
||||||
|
//! Customize by adding additional bot objects
|
||||||
|
//!
|
||||||
|
//! # Example Simple Bot
|
||||||
|
//! ```
|
||||||
|
//! use forcebot_rs_v2::botcore::bot::Bot;
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//!pub async fn main() {
|
||||||
|
//!
|
||||||
|
//! /* 1. Create the bot using env */
|
||||||
|
//! let bot = Bot::new();
|
||||||
|
//!
|
||||||
|
//! /* 2. Run the bot */
|
||||||
|
//! bot.run().await;
|
||||||
|
//!
|
||||||
|
//!}
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! # Example Code Add Listener
|
||||||
|
//!
|
||||||
|
//! Bot with a simple listener
|
||||||
|
//!
|
||||||
|
//! Example listener listens for a moderator badge and reply in chat
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! use std::sync::Arc;
|
||||||
|
//!
|
||||||
|
//! use forcebot_rs_v2::botcore::{bot::Bot, bot_objects::listener::asyncfn_box};
|
||||||
|
//! use forcebot_rs_v2::botcore::bot_objects::listener::Listener;
|
||||||
|
//! use twitch_irc::message::ServerMessage;
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! pub async fn main() {
|
||||||
|
//!
|
||||||
|
//! /* Create the bot using env */
|
||||||
|
//! let mut bot = Bot::new();
|
||||||
|
//!
|
||||||
|
//! /* 1. Create a new blank Listener */
|
||||||
|
//! let mut listener = Listener::new();
|
||||||
|
//!
|
||||||
|
//! /* 2. Set a trigger condition callback */
|
||||||
|
//! listener.set_trigger_cond_fn(
|
||||||
|
//! |_:Arc<Bot>,message:ServerMessage|
|
||||||
|
//! if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
//! for badge in msg.badges {
|
||||||
|
//! if matches!(badge, x if x.name == "moderator") {
|
||||||
|
//! return true;
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! false
|
||||||
|
//! } else { false }
|
||||||
|
//! );
|
||||||
|
//!
|
||||||
|
//! /* 3. Define an async fn callback execution */
|
||||||
|
//! async fn execbody(bot:Arc<Bot>,message:ServerMessage) -> Result<String,String> {
|
||||||
|
//! if let ServerMessage::Privmsg(msg) = message {
|
||||||
|
//! match bot.client.say_in_reply_to(&msg, String::from("test")).await {
|
||||||
|
//! Ok(_) => return Result::Ok("Success".to_string()) ,
|
||||||
|
//! Err(_) => return Result::Err("Not Valid message type".to_string())
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! Result::Err("Not Valid message type".to_string())
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! /* 4. Set the execution body using `async_box()` */
|
||||||
|
//! listener.set_exec_fn(asyncfn_box(execbody));
|
||||||
|
//!
|
||||||
|
//! /* 5. Load the Listener into the bot */
|
||||||
|
//! bot.load_listener(listener);
|
||||||
|
//!
|
||||||
|
//! /* Run the bot */
|
||||||
|
//! bot.run().await;
|
||||||
|
//!
|
||||||
|
//! }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
pub mod botcore;
|
|
@ -10,9 +10,8 @@
|
||||||
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
//! - Get a Bot Chat Token here - <https://twitchtokengenerator.com>
|
||||||
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
//! - More Info - <https://dev.twitch.tv/docs/authentication>
|
||||||
|
|
||||||
pub use botcore::bot::Bot;
|
use forcebot_rs_v2::botcore::bot::Bot;
|
||||||
|
|
||||||
mod botcore;
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn main() {
|
pub async fn main() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue