Initial commit
This commit is contained in:
commit
fd6ff84d18
8 changed files with 1547 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
/.idea
|
||||
*.env
|
1339
Cargo.lock
generated
Normal file
1339
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
13
Cargo.toml
Normal file
13
Cargo.toml
Normal file
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "deepdip_bot"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
dotenv = "0.15.0"
|
||||
reqwest = { version = "0.12.4", features = ["json"] }
|
||||
serde = { version = "1.0.201", features = ["derive"]}
|
||||
serde_json = "1.0.117"
|
||||
thiserror = "1.0.60"
|
||||
tokio = { version = "1.37.0", features = ["macros", "rt-multi-thread"] }
|
||||
twitch-irc = "5.0.1"
|
129
src/bot/mod.rs
Normal file
129
src/bot/mod.rs
Normal file
|
@ -0,0 +1,129 @@
|
|||
use std::sync::Arc;
|
||||
use dotenv;
|
||||
use tokio::sync::{mpsc::UnboundedReceiver, Mutex};
|
||||
use twitch_irc::{{ClientConfig, SecureTCPTransport, TwitchIRCClient},
|
||||
login::StaticLoginCredentials,
|
||||
message::ServerMessage};
|
||||
use twitch_irc::message::PrivmsgMessage;
|
||||
use crate::deepdip_api::get_today_run;
|
||||
use crate::deepdip_model::Checkpoint;
|
||||
use crate::error::BotError;
|
||||
|
||||
type IncomingMessages = UnboundedReceiver<ServerMessage>;
|
||||
type Client = TwitchIRCClient<SecureTCPTransport, StaticLoginCredentials>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Payload {
|
||||
last_checkpoint: Checkpoint,
|
||||
check_floor: i32,
|
||||
above_on_last_check: bool
|
||||
}
|
||||
|
||||
impl Payload {
|
||||
pub fn new() -> Payload {
|
||||
Payload {
|
||||
last_checkpoint: Checkpoint::default(),
|
||||
check_floor: 0,
|
||||
above_on_last_check: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn irc_setup() -> (IncomingMessages, Client) {
|
||||
let config = ClientConfig::new_simple(
|
||||
StaticLoginCredentials::new(
|
||||
dotenv::var("BOT_USERNAME").unwrap(),
|
||||
dotenv::var("BOT_OAUTH").ok(),
|
||||
)
|
||||
);
|
||||
|
||||
TwitchIRCClient::<SecureTCPTransport, StaticLoginCredentials>::new(config)
|
||||
}
|
||||
|
||||
pub async fn run_bot() -> Result<(), BotError> {
|
||||
let (mut incoming_msg, client) = irc_setup();
|
||||
|
||||
client.join("gaygebot".into())?;
|
||||
let client_ar: Arc<Mutex<Client>> = Arc::new(Mutex::new(client));
|
||||
|
||||
let payload_ar: Arc<Mutex<Payload>> = Arc::new(Mutex::new(Payload::new()));
|
||||
|
||||
let join_handle = tokio::spawn(async move {
|
||||
while let Some(message) = incoming_msg.recv().await {
|
||||
match message {
|
||||
ServerMessage::Pong(_) => {
|
||||
pong_handler(Arc::clone(&client_ar), Arc::clone(&payload_ar)).await;
|
||||
}
|
||||
ServerMessage::Privmsg(priv_msg) => {
|
||||
privmsg_handler(priv_msg, Arc::clone(&client_ar), Arc::clone(&payload_ar)).await;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
join_handle.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn privmsg_handler(message: PrivmsgMessage, client_ar: Arc<Mutex<Client>>, payload_ar: Arc<Mutex<Payload>>) {
|
||||
let message_parts: Vec<&str> = message.message_text.split(" ").collect();
|
||||
|
||||
if message.message_text == "!ping" {
|
||||
let client = client_ar.lock().await;
|
||||
client.say("gaygebot".to_string(), "Pong!".to_string()).await.unwrap_or_default();
|
||||
return;
|
||||
}
|
||||
|
||||
if message.message_text == "!cp" {
|
||||
let client = client_ar.lock().await;
|
||||
let payload = payload_ar.lock().await;
|
||||
client.say(
|
||||
"gaygebot".to_string(),
|
||||
format!("Height: {} Floor: {}", payload.last_checkpoint.height, payload.last_checkpoint.floor)
|
||||
).await.unwrap_or_default();
|
||||
}
|
||||
|
||||
if message_parts.get(0).unwrap_or(&"") == &"!floor" {
|
||||
if let Ok(floor) = message_parts
|
||||
.get(1)
|
||||
.unwrap_or(&"")
|
||||
.parse::<i32>() {
|
||||
let mut payload = payload_ar.lock().await;
|
||||
payload.check_floor = floor;
|
||||
drop(payload);
|
||||
|
||||
let client = client_ar.lock().await;
|
||||
client.say("gaygebot".to_string(), format!("Set floor to check to {}!", floor)).await.unwrap_or_default();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn pong_handler(client_ar: Arc<Mutex<Client>>, payload_ar: Arc<Mutex<Payload>>) {
|
||||
let run_result = get_today_run().await;
|
||||
|
||||
if run_result.is_err() {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut run = run_result.unwrap();
|
||||
|
||||
if let Some(cp) = run.checkpoints.pop() {
|
||||
let mut payload = payload_ar.lock().await;
|
||||
payload.last_checkpoint = cp;
|
||||
|
||||
if !payload.above_on_last_check && cp.floor >= payload.check_floor {
|
||||
let client = client_ar.lock().await;
|
||||
|
||||
client.say(
|
||||
"gaygebot".to_string(),
|
||||
format!("@mzntori Wirtual just reached floor {}", cp.floor)
|
||||
).await.unwrap();
|
||||
|
||||
payload.above_on_last_check = true;
|
||||
} else if payload.above_on_last_check && cp.floor < payload.check_floor {
|
||||
payload.above_on_last_check = false;
|
||||
}
|
||||
}
|
||||
}
|
21
src/deepdip_api.rs
Normal file
21
src/deepdip_api.rs
Normal file
|
@ -0,0 +1,21 @@
|
|||
use crate::{
|
||||
deepdip_model::{Run, Checkpoint},
|
||||
error::BotError,
|
||||
};
|
||||
|
||||
/// Fetches newest run.
|
||||
pub async fn get_today_run() -> Result<Run, BotError> {
|
||||
let checkpoints: Vec<Checkpoint> = reqwest::get("https://files.deepdip2.com/todayRun.json").await?.json().await?;
|
||||
|
||||
Ok(Run::from(checkpoints))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn test() {
|
||||
dbg!(get_today_run().await.unwrap());
|
||||
}
|
||||
}
|
22
src/deepdip_model.rs
Normal file
22
src/deepdip_model.rs
Normal file
|
@ -0,0 +1,22 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Checkpoint {
|
||||
pub height: i32,
|
||||
pub timestamp: i32,
|
||||
pub floor: i32
|
||||
}
|
||||
|
||||
/// Better representation of a run instead of using `Vec<Checkpoint>`
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Run {
|
||||
pub checkpoints: Vec<Checkpoint>
|
||||
}
|
||||
|
||||
impl From<Vec<Checkpoint>> for Run {
|
||||
fn from(value: Vec<Checkpoint>) -> Self {
|
||||
Run {
|
||||
checkpoints: value
|
||||
}
|
||||
}
|
||||
}
|
11
src/error.rs
Normal file
11
src/error.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use thiserror::Error;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum BotError {
|
||||
#[error("error with requesting data")]
|
||||
RequestError(#[from] reqwest::Error),
|
||||
#[error("error with twitch")]
|
||||
TwitchValidateError(#[from] twitch_irc::validate::Error),
|
||||
#[error("failed to run join handle")]
|
||||
JoinError(#[from] tokio::task::JoinError)
|
||||
}
|
9
src/main.rs
Normal file
9
src/main.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
pub mod bot;
|
||||
pub mod deepdip_model;
|
||||
pub mod deepdip_api;
|
||||
pub mod error;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
bot::run_bot().await.unwrap();
|
||||
}
|
Loading…
Reference in a new issue