diff options
author | Daniel Schadt <kingdread@gmx.de> | 2020-06-07 17:33:12 +0200 |
---|---|---|
committer | Daniel Schadt <kingdread@gmx.de> | 2020-06-07 17:33:12 +0200 |
commit | ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2 (patch) | |
tree | 13344588d8b93029f74e74ce26c716b29954d76d /src | |
download | ezau-ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2.tar.gz ezau-ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2.tar.bz2 ezau-ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2.zip |
Repository::new()
Diffstat (limited to 'src')
-rw-r--r-- | src/categories.rs | 38 | ||||
-rw-r--r-- | src/discord.rs | 98 | ||||
-rw-r--r-- | src/main.rs | 91 |
3 files changed, 227 insertions, 0 deletions
diff --git a/src/categories.rs b/src/categories.rs new file mode 100644 index 0000000..24ffcb1 --- /dev/null +++ b/src/categories.rs @@ -0,0 +1,38 @@ +use evtclib::{Boss, Log}; + +pub trait Categorizable { + fn category(&self) -> &'static str; +} + +impl Categorizable for Log { + fn category(&self) -> &'static str { + if let Some(encounter) = self.encounter() { + match encounter { + Boss::ValeGuardian | Boss::Gorseval | Boss::Sabetha => "Wing 1 (Spirit Vale)", + Boss::Slothasor | Boss::Matthias => "Wing 2 (Salvation Pass)", + Boss::KeepConstruct | Boss::Xera => "Wing 3 (Stronghold of the Faithful)", + Boss::Cairn | Boss::MursaatOverseer | Boss::Samarog | Boss::Deimos => { + "Wing 4 (Bastion of the Penitent)" + } + Boss::SoullessHorror | Boss::Dhuum => "Wing 5 (Hall of Chains)", + Boss::ConjuredAmalgamate | Boss::LargosTwins | Boss::Qadim => { + "Wing 6 (Mythwright Gambit)" + } + Boss::CardinalAdina | Boss::CardinalSabir | Boss::QadimThePeerless => { + "Wing 7 (Key of Ahdashim)" + } + + Boss::Skorvald | Boss::Artsariiv | Boss::Arkk => "100 CM (Shattered Observatory)", + Boss::MAMA | Boss::Siax | Boss::Ensolyss => "99 CM (Nightmare)", + + Boss::IcebroodConstruct + | Boss::VoiceOfTheFallen + | Boss::FraenirOfJormag + | Boss::Boneskinner + | Boss::WhisperOfJormag => "Strike Mission", + } + } else { + "Unknown" + } + } +} diff --git a/src/discord.rs b/src/discord.rs new file mode 100644 index 0000000..c7aea78 --- /dev/null +++ b/src/discord.rs @@ -0,0 +1,98 @@ +use std::sync::Arc; + +use anyhow::Result; +use chrono::prelude::*; +use evtclib::Log; +use serenity::client::bridge::gateway::ShardManager; +use serenity::model::id::*; +use serenity::prelude::*; + +use log::info; + +use super::categories::Categorizable; + +const MAX_HOURS: i64 = 5; + +struct ShardManagerContainer; + +impl TypeMapKey for ShardManagerContainer { + type Value = Arc<Mutex<ShardManager>>; +} + +#[derive(Debug, Clone)] +struct Handler { + channel_id: u64, + log: Log, + link: String, +} + +impl EventHandler for Handler { + fn ready(&self, ctx: Context, _ready: serenity::model::gateway::Ready) { + info!("Discord client is ready"); + let mut messages = ChannelId(self.channel_id) + .messages(&ctx, |r| r.limit(25)) + .unwrap(); + messages.sort_by_key(|m| m.timestamp); + messages.retain(|m| { + m.is_own(&ctx) + && Utc::now().signed_duration_since(m.timestamp) + < chrono::Duration::hours(MAX_HOURS) + }); + + if let Some(mut m) = messages.pop() { + let new_text = insert_link(&m.content, &self.log, &self.link); + m.edit(&ctx, |m| m.content(new_text)).unwrap(); + } else { + let new_text = insert_link("", &self.log, &self.link); + ChannelId(self.channel_id).say(&ctx, new_text).unwrap(); + } + + let data = ctx.data.read(); + if let Some(manager) = data.get::<ShardManagerContainer>() { + manager.lock().shutdown_all(); + } + } +} + +pub fn post_link(discord_token: &str, channel_id: u64, log: Log, link: String) -> Result<()> { + let mut client = Client::new( + discord_token, + Handler { + channel_id, + log, + link, + }, + )?; + { + let mut data = client.data.write(); + data.insert::<ShardManagerContainer>(Arc::clone(&client.shard_manager)); + } + client.start()?; + Ok(()) +} + +fn find_insertion(text: &str, category: &str) -> Option<usize> { + let cat_pos = text.find(&format!("**{}**", category))?; + let empty_line = text[cat_pos..].find("\n\n")?; + Some(cat_pos + empty_line + 1) +} + +fn insert_link(text: &str, log: &Log, link: &str) -> String { + let mut text = format!("\n\n{}\n\n", text); + let point = find_insertion(&text, log.category()); + let link_line = format!("{} {}\n", state_emoji(log), link); + if let Some(i) = point { + text.insert_str(i, &link_line); + } else { + text.push_str(&format!("**{}**\n{}", log.category(), link_line)); + } + text.trim().into() +} + +fn state_emoji(log: &Log) -> &'static str { + if log.was_rewarded() { + "✔️" + } else { + "❌" + } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..627a5c8 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,91 @@ +use std::path::{Path, PathBuf}; + +use anyhow::Result; +use clap::Clap; +use evtclib::{Boss, Compression, Log}; +use log::{error, info}; +use serde::Deserialize; + +mod categories; +use categories::Categorizable; + +mod discord; + +const DPS_REPORT_API: &str = "https://dps.report/uploadContent"; + +#[derive(Clap, Debug, Clone, PartialEq, Eq, Hash)] +#[clap(version = "0.1")] +struct Opts { + /// The filename to upload. + filename: PathBuf, + /// The configuration file to use. + #[clap(short, long, default_value = "ezau.toml")] + config: PathBuf, + /// The Discord auth token for the bot account. + #[clap(short, long)] + discord_token: String, + /// The channel ID where the log message should be posted. + #[clap(short, long)] + channel_id: u64, +} + +fn main() { + pretty_env_logger::init(); + + let opts = Opts::parse(); + if let Err(e) = inner_main(&opts) { + error!("{}", e); + } +} + +fn inner_main(opts: &Opts) -> Result<()> { + let log = load_log(&opts.filename)?; + info!("Loaded log from category {}", log.category()); + + if !should_upload(&log) { + info!("Skipping log, not uploading"); + return Ok(()); + } + + let permalink = upload_log(&opts.filename)?; + info!("Uploaded log, available at {}", permalink); + + discord::post_link(&opts.discord_token, opts.channel_id, log, permalink)?; + + info!("We done bois, wrapping it up!"); + Ok(()) +} + +fn should_upload(log: &Log) -> bool { + // Only upload known logs + if log.encounter().is_none() { + return false; + } + // Only upload Skorvald if it actually was in 100 CM (and not in in lower-tier or non-CM). + if log.encounter() == Some(Boss::Skorvald) && !log.is_cm() { + return false; + } + true +} + +fn load_log(path: &Path) -> Result<Log> { + evtclib::process_file(path, Compression::Zip).map_err(Into::into) +} + +fn upload_log(file: &Path) -> Result<String> { + #[derive(Debug, Deserialize)] + struct ApiResponse { + permalink: String, + } + + let client = reqwest::blocking::Client::new(); + + let form = reqwest::blocking::multipart::Form::new().file("file", file)?; + let resp: ApiResponse = client + .post(DPS_REPORT_API) + .query(&[("json", 1)]) + .multipart(form) + .send()? + .json()?; + Ok(resp.permalink) +} |