Initial commit
This commit is contained in:
commit
fd6ff84d18
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