aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorDaniel Schadt <kingdread@gmx.de>2020-06-07 17:33:12 +0200
committerDaniel Schadt <kingdread@gmx.de>2020-06-07 17:33:12 +0200
commitccca613d4bd1454fa57d818c2b2a7c3629cf7ec2 (patch)
tree13344588d8b93029f74e74ce26c716b29954d76d /src
downloadezau-ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2.tar.gz
ezau-ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2.tar.bz2
ezau-ccca613d4bd1454fa57d818c2b2a7c3629cf7ec2.zip
Repository::new()
Diffstat (limited to 'src')
-rw-r--r--src/categories.rs38
-rw-r--r--src/discord.rs98
-rw-r--r--src/main.rs91
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)
+}