aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock156
-rw-r--r--Cargo.toml3
-rw-r--r--README.md46
-rw-r--r--ezau-sample.toml18
-rw-r--r--src/categories.rs101
-rw-r--r--src/config.rs7
-rw-r--r--src/discord.rs14
-rw-r--r--src/logbag.rs278
-rw-r--r--src/main.rs80
-rw-r--r--src/matrix.rs11
10 files changed, 392 insertions, 322 deletions
diff --git a/Cargo.lock b/Cargo.lock
index b0e261e..2a79aca 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -304,7 +304,7 @@ dependencies = [
"arrayvec",
"cc",
"cfg-if 1.0.0",
- "constant_time_eq 0.3.0",
+ "constant_time_eq",
]
[[package]]
@@ -408,10 +408,6 @@ name = "cc"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "324c74f2155653c90b04f25b2a47a8a631360cb908f92a772695f430c7e31052"
-dependencies = [
- "jobserver",
- "libc",
-]
[[package]]
name = "cfg-if"
@@ -549,12 +545,6 @@ checksum = "6051f239ecec86fde3410901ab7860d458d160371533842974fc61f96d15879b"
[[package]]
name = "constant_time_eq"
-version = "0.1.5"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
-
-[[package]]
-name = "constant_time_eq"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2"
@@ -759,15 +749,6 @@ dependencies = [
]
[[package]]
-name = "deranged"
-version = "0.3.11"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4"
-dependencies = [
- "powerfmt",
-]
-
-[[package]]
name = "digest"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -924,15 +905,14 @@ dependencies = [
[[package]]
name = "evtclib"
version = "0.7.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d5e15d67f82f9d739ec853cc3022a5d4eaf749dace4020e820ff8463f01d4ccb"
+source = "git+https://gitlab.com/dunj3/evtclib?rev=b80279d4fb8a8a106ceb3290886c4a98b20aa42c#b80279d4fb8a8a106ceb3290886c4a98b20aa42c"
dependencies = [
"byteorder",
"getset",
"num-derive",
"num-traits",
"thiserror",
- "zip 0.5.13",
+ "zip",
]
[[package]]
@@ -983,7 +963,6 @@ dependencies = [
"tokio",
"toml 0.5.11",
"url",
- "zip 0.6.6",
]
[[package]]
@@ -1656,15 +1635,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
-name = "jobserver"
-version = "0.1.31"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e"
-dependencies = [
- "libc",
-]
-
-[[package]]
name = "js-sys"
version = "0.3.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1962,7 +1932,7 @@ dependencies = [
"hmac",
"itertools 0.12.1",
"matrix-sdk-common",
- "pbkdf2 0.12.2",
+ "pbkdf2",
"rand 0.8.5",
"rmp-serde",
"ruma",
@@ -2040,7 +2010,7 @@ dependencies = [
"displaydoc",
"getrandom 0.2.15",
"hmac",
- "pbkdf2 0.12.2",
+ "pbkdf2",
"rand 0.8.5",
"rmp-serde",
"serde",
@@ -2198,12 +2168,6 @@ dependencies = [
]
[[package]]
-name = "num-conv"
-version = "0.1.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
-
-[[package]]
name = "num-derive"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2323,17 +2287,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
[[package]]
-name = "password-hash"
-version = "0.4.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7676374caaee8a325c9e7a2ae557f216c5563a171d6997b0ef8a65af35147700"
-dependencies = [
- "base64ct",
- "rand_core 0.6.4",
- "subtle",
-]
-
-[[package]]
name = "paste"
version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2341,18 +2294,6 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
[[package]]
name = "pbkdf2"
-version = "0.11.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917"
-dependencies = [
- "digest 0.10.7",
- "hmac",
- "password-hash",
- "sha2",
-]
-
-[[package]]
-name = "pbkdf2"
version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
@@ -2438,12 +2379,6 @@ dependencies = [
]
[[package]]
-name = "powerfmt"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
-
-[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3215,17 +3150,6 @@ dependencies = [
]
[[package]]
-name = "sha1"
-version = "0.10.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba"
-dependencies = [
- "cfg-if 1.0.0",
- "cpufeatures",
- "digest 0.10.7",
-]
-
-[[package]]
name = "sha2"
version = "0.10.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -3431,25 +3355,6 @@ dependencies = [
]
[[package]]
-name = "time"
-version = "0.3.36"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885"
-dependencies = [
- "deranged",
- "num-conv",
- "powerfmt",
- "serde",
- "time-core",
-]
-
-[[package]]
-name = "time-core"
-version = "0.1.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3"
-
-[[package]]
name = "tinyvec"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -4303,54 +4208,5 @@ dependencies = [
"crc32fast",
"flate2",
"thiserror",
- "time 0.1.45",
-]
-
-[[package]]
-name = "zip"
-version = "0.6.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "760394e246e4c28189f19d488c058bf16f564016aefac5d32bb1f3b51d5e9261"
-dependencies = [
- "aes",
- "byteorder",
- "bzip2",
- "constant_time_eq 0.1.5",
- "crc32fast",
- "crossbeam-utils",
- "flate2",
- "hmac",
- "pbkdf2 0.11.0",
- "sha1",
- "time 0.3.36",
- "zstd",
-]
-
-[[package]]
-name = "zstd"
-version = "0.11.2+zstd.1.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4"
-dependencies = [
- "zstd-safe",
-]
-
-[[package]]
-name = "zstd-safe"
-version = "5.0.2+zstd.1.5.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db"
-dependencies = [
- "libc",
- "zstd-sys",
-]
-
-[[package]]
-name = "zstd-sys"
-version = "2.0.12+zstd.1.5.6"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0a4e40c320c3cb459d9a9ff6de98cff88f4751ee9275d140e2be94a2b74e4c13"
-dependencies = [
- "cc",
- "pkg-config",
+ "time",
]
diff --git a/Cargo.toml b/Cargo.toml
index 31894d1..2eab6c2 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -16,14 +16,13 @@ clap = { version = "3.1.8", features = ["derive"] }
log = "0.4.16"
pretty_env_logger = "0.4.0"
anyhow = "1.0.56"
-evtclib = "0.7.3"
+evtclib = { git = "https://gitlab.com/dunj3/evtclib", rev = "b80279d4fb8a8a106ceb3290886c4a98b20aa42c" }
reqwest = { version = "0.11.10", features = ["json", "blocking", "multipart"] }
url = { version = "2.2.2", features = ["serde"] }
serde = { version = "1.0.136", features = ["derive"] }
chrono = "0.4.19"
notify = "4.0.17"
regex = "1.5.5"
-zip = "0.6.1"
toml = "0.5.8"
itertools = "0.10.3"
# Optional features for IM integration
diff --git a/README.md b/README.md
index e1fe4d7..bf3ca93 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,19 @@
-EVTC Zipper And Uploader
-========================
+EVTC (Zipper And) Uploader
+==========================
![MIT license](https://img.shields.io/badge/license-MIT-green) ![Rustlang](https://img.shields.io/badge/language-Rust-green)
-`ezau` is a tool which watches a directory for new EVTC log files and can zip them, upload them to [dps.report](https://dps.report) and post a message to a Discord channel.
+`ezau` is a tool which watches a directory for new EVTC log files, uploads them to [dps.report](https://dps.report) and posts the link to a Discord or Matrix channel.
Motivation
----------
-EVTC logs generated by arcpds can get quite big, so having them zipped is a huge space saving (80-90%).
-Though while arcdps itself works fine on Linux/Wine, the built-in zip functionality relies on PowerShell and is not available outside of Windows 10.
-As such, `ezau` can be used to automatically zip newly created log files in order to save disk space.
-In addition to that, `ezau` can also optionally upload new logs to dps.report, and post a link to the uploaded report to a Discord channel.
+The zipping functionality of arcdps used to be limited to Windows.
+As a result, Linux users ended up with huge unzipped evtc log files.
+`ezau` zipped those for you, and optionally also uploaded them to dps.report.
+
+However, with new versions of arcdps, the logs are always zipped and this functionality works even on Linux.
+As such, `ezau` is simply a log uploader now (but `eu` is even worse of a name than `ezau`).
`ezau` was inspired by [`evtc-watch`](https://gitlab.com/networkjanitor/evtc-watch/-/blob/master/evtc-watch) from Xyoz, with the difference that the file watching logic is included in the Rust part.
This makes it cross-platform and fixes some bugs that were present in the `inotifywait` version.
@@ -57,9 +59,24 @@ A full example configuration is provided here or alternatively as `ezau-sample.t
```toml
# Whether logs should be uploaded.
-# If this is false, ezau will only zip new logs and do no further processing.
+# For legacy reasons, uploading can be disabled, but you probably want to set
+# it to true (otherwise, ezau has no purpose).
# (mandatory)
-upload = false
+upload = true
+
+# Whether logs should be sorted when posting to Discord/Matrix.
+# If disabled, logs will be posted on a first-come-first-posted basis within
+# their group.
+# If enabled, categories will be sorted and bosses will be grouped.
+# (optional)
+sort_logs = false
+
+# Where to upload the logs.
+# By default, ezau will attempt to upload to https://dps.report/uploadContent,
+# but depending on service availability you might want to use a different domain,
+# like b.dps.report instead.
+# (optional)
+dps_report_upload_url = "https://dps.report/uploadContent"
# Whether logs with an unknown boss should be uploaded.
# By default, ezau only uploads logs with bosses known to evtclib.
@@ -77,12 +94,6 @@ minimum_duration = 0
# (optional)
retries = 0
-# Zip freshly created (unzipped) logs.
-# Deactivate this if you use arcDPS's built-in zip functionality to prevent any
-# weird interactions.
-# (optional)
-zip = true
-
# Discord messaging section.
# If this section is missing, ezau will not do Discord notifications for log uploads.
# Mandatory keys in this section are only mandatory if the section is present, as the whole Discord functionality is optional.
@@ -121,9 +132,10 @@ room_id = ["!room123456:homeserver.org"]
Usage
-----
-`ezau watch <dirname>`: Watch the given directory for new log files, zip them and optionally upload them.
+`ezau watch <dirname>`: Watch the given directory for new log files and upload them, posting a link to Discord/Matrix.
-`ezau upload <filename>`: Upload a single log and do a Discord notification.
+`ezau upload <filename>`: Upload a single log.
+Prints the URL to the console, and (if configured) posts it to Discord/Matrix.
Note that this bypasses the `upload`/`upload_unknown` settings.
See `ezau help` and `ezau help <subcommand>` for more information.
diff --git a/ezau-sample.toml b/ezau-sample.toml
index 512511c..99348cf 100644
--- a/ezau-sample.toml
+++ b/ezau-sample.toml
@@ -1,7 +1,15 @@
# Whether logs should be uploaded.
-# If this is false, ezau will only zip new logs and do no further processing.
+# For legacy reasons, uploading can be disabled, but you probably want to set
+# it to true (otherwise, ezau has no purpose).
# (mandatory)
-upload = false
+upload = true
+
+# Whether logs should be sorted when posting to Discord/Matrix.
+# If disabled, logs will be posted on a first-come-first-posted basis within
+# their group.
+# If enabled, categories will be sorted and bosses will be grouped.
+# (optional)
+sort_logs = false
# Where to upload the logs.
# By default, ezau will attempt to upload to https://dps.report/uploadContent,
@@ -26,12 +34,6 @@ minimum_duration = 0
# (optional)
retries = 0
-# Zip freshly created (unzipped) logs.
-# Deactivate this if you use arcDPS's built-in zip functionality to prevent any
-# weird interactions.
-# (optional)
-zip = true
-
# Discord messaging section.
# If this section is missing, ezau will not do Discord notifications for log uploads.
# Mandatory keys in this section are only mandatory if the section is present, as the whole Discord functionality is optional.
diff --git a/src/categories.rs b/src/categories.rs
index de4f5e6..1713d87 100644
--- a/src/categories.rs
+++ b/src/categories.rs
@@ -1,48 +1,115 @@
use evtclib::{Encounter, GameMode, Log};
+// There is no canonical order of "categories", so we'll do
+// * WvW
+// * Raid wings ascending
+// * Strike
+// * Fractals descending
+// * Training area
+// * Unknown
+// Subject to change, it's only used to sort the message (if enabled).
+#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)]
+pub enum Category {
+ WvW,
+ Wing1,
+ Wing2,
+ Wing3,
+ Wing4,
+ Wing5,
+ Wing6,
+ Wing7,
+ Strike,
+ SunquaPeak,
+ ShatteredObservatory,
+ Nightmare,
+ SpecialForcesTrainingArea,
+ #[default]
+ Unknown,
+}
+
+macro_rules! category_strings {
+ ($(($item:path, $str:literal),)*) => {
+ impl std::fmt::Display for Category {
+ fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+ let repr = match *self {
+ $($item => $str),*
+ };
+ write!(f, "{}", repr)
+ }
+ }
+
+ impl std::str::FromStr for Category {
+ type Err = ();
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ $($str => Ok($item),)*
+ _ => Err(()),
+ }
+ }
+ }
+ }
+}
+
+category_strings! {
+ (Category::WvW, "World versus World"),
+ (Category::Wing1, "Wing 1 (Spirit Vale)"),
+ (Category::Wing2, "Wing 2 (Salvation Pass)"),
+ (Category::Wing3, "Wing 3 (Stronghold of the Faithful)"),
+ (Category::Wing4, "Wing 4 (Bastion of the Penitent)"),
+ (Category::Wing5, "Wing 5 (Hall of Chains)"),
+ (Category::Wing6, "Wing 6 (Mythwright Gambit)"),
+ (Category::Wing7, "Wing 7 (Key of Ahdashim)"),
+ (Category::SunquaPeak, "100 CM (Sunqua Peak)"),
+ (Category::ShatteredObservatory, "99 CM (Shattered Observatory)"),
+ (Category::Nightmare, "98 CM (Nightmare)"),
+ (Category::Strike, "Strike Mission"),
+ (Category::SpecialForcesTrainingArea, "Special Forces Training Area"),
+ (Category::Unknown, "Unknown"),
+}
+
pub trait Categorizable {
- fn category(&self) -> &'static str;
+ fn category(&self) -> Category;
}
impl Categorizable for Log {
- fn category(&self) -> &'static str {
+ fn category(&self) -> Category {
if self.game_mode() == Some(GameMode::WvW) {
- return "World versus World";
+ return Category::WvW;
}
if let Some(encounter) = self.encounter() {
match encounter {
Encounter::ValeGuardian | Encounter::Gorseval | Encounter::Sabetha => {
- "Wing 1 (Spirit Vale)"
+ Category::Wing1
}
Encounter::Slothasor | Encounter::BanditTrio | Encounter::Matthias => {
- "Wing 2 (Salvation Pass)"
+ Category::Wing2
}
Encounter::KeepConstruct | Encounter::TwistedCastle | Encounter::Xera => {
- "Wing 3 (Stronghold of the Faithful)"
+ Category::Wing3
}
Encounter::Cairn
| Encounter::MursaatOverseer
| Encounter::Samarog
- | Encounter::Deimos => "Wing 4 (Bastion of the Penitent)",
+ | Encounter::Deimos => Category::Wing4,
Encounter::SoullessHorror
| Encounter::RiverOfSouls
| Encounter::BrokenKing
| Encounter::EaterOfSouls
| Encounter::StatueOfDarkness
- | Encounter::VoiceInTheVoid => "Wing 5 (Hall of Chains)",
+ | Encounter::VoiceInTheVoid => Category::Wing5,
Encounter::ConjuredAmalgamate | Encounter::TwinLargos | Encounter::Qadim => {
- "Wing 6 (Mythwright Gambit)"
+ Category::Wing6
}
Encounter::CardinalAdina
| Encounter::CardinalSabir
- | Encounter::QadimThePeerless => "Wing 7 (Key of Ahdashim)",
+ | Encounter::QadimThePeerless => Category::Wing7,
- Encounter::Ai => "100 CM (Sunqua Peak)",
+ Encounter::Ai => Category::SunquaPeak,
Encounter::Skorvald | Encounter::Artsariiv | Encounter::Arkk => {
- "99 CM (Shattered Observatory)"
+ Category::ShatteredObservatory
}
- Encounter::MAMA | Encounter::Siax | Encounter::Ensolyss => "98 CM (Nightmare)",
+ Encounter::MAMA | Encounter::Siax | Encounter::Ensolyss => Category::Nightmare,
Encounter::IcebroodConstruct
| Encounter::SuperKodanBrothers
@@ -52,16 +119,16 @@ impl Categorizable for Log {
| Encounter::CaptainMaiTrin
| Encounter::Ankka
| Encounter::MinisterLi
- | Encounter::Dragonvoid => "Strike Mission",
+ | Encounter::Dragonvoid => Category::Strike,
Encounter::StandardKittyGolem
| Encounter::MediumKittyGolem
- | Encounter::LargeKittyGolem => "Special Forces Training Area",
+ | Encounter::LargeKittyGolem => Category::SpecialForcesTrainingArea,
- _ => "Unknown",
+ _ => Category::Unknown,
}
} else {
- "Unknown"
+ Category::Unknown
}
}
}
diff --git a/src/config.rs b/src/config.rs
index ad2ee22..6c2a035 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -9,6 +9,9 @@ use serde_with::{serde_as, OneOrMany};
pub struct Config {
/// Flag indicating whether logs should be uploaded or not.
pub upload: bool,
+ /// Whether to sort the logs when posting them.
+ #[serde(default)]
+ pub sort_logs: bool,
/// Where to upload the logs.
#[serde(default = "default_dps_report_upload_url")]
pub dps_report_upload_url: String,
@@ -23,6 +26,8 @@ pub struct Config {
pub retries: u32,
/// Whether ezau should zip non-zipped logs.
#[serde(default = "default_zip")]
+ // We don't use this anymore, but we keep it so old configs can be parsed and we can properly
+ // inform the user.
pub zip: bool,
/// Option Discord information for bot postings.
pub discord: Option<Discord>,
@@ -63,7 +68,7 @@ pub fn load<P: AsRef<Path>>(path: P) -> Result<Config> {
}
fn default_zip() -> bool {
- true
+ false
}
fn default_dps_report_upload_url() -> String {
diff --git a/src/discord.rs b/src/discord.rs
index 3ab849c..eeb4210 100644
--- a/src/discord.rs
+++ b/src/discord.rs
@@ -10,6 +10,7 @@ use tokio::runtime::Runtime;
use log::info;
+use super::config::Config;
use super::categories::Categorizable;
use super::logbag::{state_emoji, LogBag};
@@ -33,6 +34,7 @@ struct Handler {
channel_id: u64,
log: Log,
link: String,
+ sort_logs: bool,
}
impl Handler {
@@ -59,14 +61,14 @@ impl Handler {
}
if let Some(mut m) = messages.pop() {
- let new_text = insert_link(&m.content, &self.log, &self.link);
+ let new_text = insert_link(&m.content, &self.log, &self.link, self.sort_logs);
if new_text.len() <= MAX_MESSAGE_LENGTH {
m.edit(ctx, |m| m.content(new_text)).await?;
return Ok(());
}
}
- let new_text = insert_link("", &self.log, &self.link);
+ let new_text = insert_link("", &self.log, &self.link, false);
ChannelId(self.channel_id).say(ctx, new_text).await?;
Ok(())
@@ -88,7 +90,7 @@ impl EventHandler for Handler {
}
}
-pub fn post_link(discord_token: &str, channel_id: u64, log: &Log, link: &str) -> Result<()> {
+pub fn post_link(config: &Config, discord_token: &str, channel_id: u64, log: &Log, link: &str) -> Result<()> {
let link = link.to_owned();
let log = log.clone();
let rt = Runtime::new()?;
@@ -99,6 +101,7 @@ pub fn post_link(discord_token: &str, channel_id: u64, log: &Log, link: &str) ->
channel_id,
log,
link,
+ sort_logs: config.sort_logs,
})
.await?;
{
@@ -112,9 +115,12 @@ pub fn post_link(discord_token: &str, channel_id: u64, log: &Log, link: &str) ->
})
}
-fn insert_link(text: &str, log: &Log, link: &str) -> String {
+fn insert_link(text: &str, log: &Log, link: &str, sort_logs: bool) -> String {
let mut logbag = LogBag::parse_markdown(text).unwrap();
let line = format!("{} {}", state_emoji(log), link);
logbag.insert(log.category(), line);
+ if sort_logs {
+ logbag.sort();
+ }
logbag.render_markdown()
}
diff --git a/src/logbag.rs b/src/logbag.rs
index 362bee5..f173d6e 100644
--- a/src/logbag.rs
+++ b/src/logbag.rs
@@ -1,8 +1,10 @@
-use std::iter;
-use std::str::FromStr;
+use super::categories::Category;
-use evtclib::{Log, Outcome};
+use std::{cmp::Ordering, sync::OnceLock, str::FromStr};
+
+use evtclib::{Encounter, Log, Outcome};
use itertools::Itertools;
+use regex::Regex;
/// A [`LogBag`] is a struct that holds multiple logs in their categories.
///
@@ -11,7 +13,7 @@ use itertools::Itertools;
/// them and we can just handle arbitrary data.
#[derive(Default, Debug, Clone, PartialEq, Eq, Hash)]
pub struct LogBag {
- data: Vec<(String, Vec<String>)>,
+ data: Vec<(Category, Vec<String>)>,
}
// Conditional compilation makes it hard to really use all the code, so we just allow dead code
@@ -24,30 +26,54 @@ impl LogBag {
}
/// Return an iterator over all available categories.
- pub fn categories(&self) -> impl Iterator<Item = &str> {
- self.data.iter().map(|x| &x.0 as &str)
+ pub fn categories(&self) -> impl Iterator<Item = Category> + '_ {
+ self.data.iter().map(|x| x.0)
}
/// Return an iterator over (category, items).
- pub fn iter(&self) -> impl Iterator<Item = (&str, impl Iterator<Item = &str>)> {
+ pub fn iter(&self) -> impl Iterator<Item = (Category, impl Iterator<Item = &str>)> {
self.data
.iter()
- .map(|(cat, lines)| (cat as &str, lines.iter().map(|l| l as &str)))
+ .map(|(cat, lines)| (*cat, lines.iter().map(|l| l as &str)))
}
/// Insert an item into the given category.
///
/// If the category does not exist yet, it will be appended at the bottom.
- pub fn insert(&mut self, category: &str, line: String) {
+ pub fn insert(&mut self, category: Category, line: String) {
for (cat, lines) in self.data.iter_mut() {
- if cat == category {
+ if *cat == category {
lines.push(line);
return;
}
}
// When we reach here, we don't have the category yet, so we gotta insert it.
- self.data.push((String::from(category), vec![line]));
+ self.data.push((category, vec![line]));
+ }
+
+ /// Sort the categories and logs.
+ pub fn sort(&mut self) {
+ // First sort the categories, the Ord impl of `Category` takes care here
+ self.data.sort();
+ // Then sort the lines within the categories
+ for (_, ref mut lines) in self.data.iter_mut() {
+ lines.sort_by(|a, b| {
+ let (encounter_a, date_a, url_a) = info_from_line(&a);
+ let (encounter_b, date_b, url_b) = info_from_line(&b);
+ match (encounter_a, encounter_b) {
+ (None, None) => date_a.cmp(&date_b),
+ (None, Some(_)) => Ordering::Greater,
+ (Some(_), None) => Ordering::Less,
+ (Some(encounter_a), Some(encounter_b)) => encounter_a.partial_cmp(&encounter_b)
+ .or_else(|| order_strikes(encounter_a, encounter_b))
+ // at this point, just give us a stable order
+ .unwrap_or((encounter_a as u16).cmp(&(encounter_b as u16)))
+ .then(date_a.cmp(&date_b))
+ .then(url_a.cmp(&url_b)),
+ }
+ });
+ }
}
/// Tries to parse the given text as a plain [`LogBag`].
@@ -68,7 +94,7 @@ impl LogBag {
/// The output of this can be fed back into [`LogBag::parse_plain`] to round-trip.
pub fn render_plain(&self) -> String {
self.iter()
- .map(|(category, lines)| iter::once(category).chain(lines).join("\n"))
+ .map(|(category, mut lines)| category.to_string() + "\n" + &lines.join("\n"))
.join("\n\n")
}
@@ -104,18 +130,18 @@ impl FromStr for LogBag {
let mut lines = chunk.split('\n');
let category = lines.next().unwrap();
(
- category.to_string(),
+ category.parse::<Category>().unwrap_or_default(),
lines.map(ToString::to_string).collect::<Vec<_>>(),
)
})
- .filter(|(cat, lines)| !cat.is_empty() && !lines.is_empty())
+ .filter(|(_, lines)| !lines.is_empty())
.collect();
Ok(LogBag { data })
}
}
-impl From<Vec<(String, Vec<String>)>> for LogBag {
- fn from(data: Vec<(String, Vec<String>)>) -> Self {
+impl From<Vec<(Category, Vec<String>)>> for LogBag {
+ fn from(data: Vec<(Category, Vec<String>)>) -> Self {
LogBag { data }
}
}
@@ -130,6 +156,100 @@ pub fn state_emoji(log: &Log) -> &'static str {
}
}
+// I don't want to pull in something like `chrono` just to represent a local datetime. Therefore,
+// we simply use two integers: The date in the format YYYYMMDD (which automatically sorts
+// correctly), and the time in HHMMSS (which does as well).
+fn info_from_line(line: &str) -> (Option<Encounter>, Option<(u32, u32)>, &str) {
+ static RE: OnceLock<Regex> = OnceLock::new();
+ let url_re = RE.get_or_init(|| {
+ Regex::new("http(s?)://[^ ]+(?P<date>\\d{8})-(?P<time>\\d{6})_(?P<slug>[a-z]+)").unwrap()
+ });
+ let Some(caps) = url_re.captures(line) else { return (None, None, line); };
+ let date = caps
+ .name("date")
+ .expect("date group must be present")
+ .as_str()
+ .parse()
+ .expect("matched only digits, parsing should succeeed");
+ let time = caps
+ .name("time")
+ .expect("time group must be present")
+ .as_str()
+ .parse()
+ .expect("matched only digits, parsing should succeeed");
+
+ let encounter = match caps.name("slug").expect("slug group must be present").as_str() {
+ "vg" => Some(Encounter::ValeGuardian),
+ "gors" => Some(Encounter::Gorseval),
+ "sab" => Some(Encounter::Sabetha),
+ "sloth" => Some(Encounter::Slothasor),
+ "trio" => Some(Encounter::BanditTrio),
+ "matt" => Some(Encounter::Matthias),
+ "kc" => Some(Encounter::KeepConstruct),
+ "tc" => Some(Encounter::TwistedCastle),
+ "xera" => Some(Encounter::Xera),
+ "cairn" => Some(Encounter::Cairn),
+ "mo" => Some(Encounter::MursaatOverseer),
+ "sam" => Some(Encounter::Samarog),
+ "dei" => Some(Encounter::Deimos),
+ "sh" => Some(Encounter::SoullessHorror),
+ "rr" => Some(Encounter::RiverOfSouls),
+ "bk" => Some(Encounter::BrokenKing),
+ "se" => Some(Encounter::EaterOfSouls),
+ "eyes" => Some(Encounter::StatueOfDarkness),
+ "dhuum" => Some(Encounter::VoiceInTheVoid),
+ "ca" => Some(Encounter::ConjuredAmalgamate),
+ "twins" => Some(Encounter::TwinLargos),
+ "qadim" => Some(Encounter::Qadim),
+ "adina" => Some(Encounter::CardinalAdina),
+ "sabir" => Some(Encounter::CardinalSabir),
+ "qpeer" => Some(Encounter::QadimThePeerless),
+ // ambiguous, but it doesn't matter because we map them all to
+ // Category::SpecialForcesTrainingArea
+ "golem" => Some(Encounter::LargeKittyGolem),
+ "ai" => Some(Encounter::Ai),
+ "skor" => Some(Encounter::Skorvald),
+ "arriv" => Some(Encounter::Artsariiv),
+ "arkk" => Some(Encounter::Arkk),
+ "mama" => Some(Encounter::MAMA),
+ "siax" => Some(Encounter::Siax),
+ "enso" => Some(Encounter::Ensolyss),
+ "ice" => Some(Encounter::IcebroodConstruct),
+ "frae" => Some(Encounter::FraenirOfJormag),
+ "falln" => Some(Encounter::SuperKodanBrothers),
+ "whisp" => Some(Encounter::WhisperOfJormag),
+ "bone" => Some(Encounter::Boneskinner),
+ "trin" => Some(Encounter::CaptainMaiTrin),
+ "ankka" => Some(Encounter::Ankka),
+ "li" => Some(Encounter::MinisterLi),
+ "void" => Some(Encounter::Dragonvoid),
+ _ => None,
+ };
+
+ (encounter, Some((date, time)), caps.get(0).unwrap().as_str())
+}
+
+fn order_strikes(left: Encounter, right: Encounter) -> Option<Ordering> {
+ // Order according to the wiki at https://wiki.guildwars2.com/wiki/Strike_Mission
+ let strikes = &[
+ Encounter::IcebroodConstruct,
+ Encounter::SuperKodanBrothers,
+ Encounter::FraenirOfJormag,
+ Encounter::Boneskinner,
+ Encounter::WhisperOfJormag,
+ Encounter::CaptainMaiTrin,
+ Encounter::Ankka,
+ Encounter::MinisterLi,
+ Encounter::Dragonvoid,
+ ];
+ if let Some(pos_a) = strikes.iter().position(|x| *x == left) {
+ if let Some(pos_b) = strikes.iter().position(|x| *x == right) {
+ return Some(pos_a.cmp(&pos_b));
+ }
+ }
+ None
+}
+
#[cfg(test)]
mod test {
use super::*;
@@ -137,16 +257,16 @@ mod test {
#[test]
fn insert() {
let mut logbag = LogBag::new();
- logbag.insert("cat 1", "line 1".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
assert_eq!(logbag.categories().count(), 1);
- logbag.insert("cat 1", "line 2".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
assert_eq!(logbag.categories().count(), 1);
- logbag.insert("cat 2", "line 1".to_string());
+ logbag.insert(Category::Strike, "line 1".to_string());
assert_eq!(logbag.categories().count(), 2);
assert_eq!(
logbag.categories().collect::<Vec<_>>(),
- vec!["cat 1", "cat 2"]
+ vec![Category::WvW, Category::Strike]
);
}
@@ -158,13 +278,13 @@ mod test {
#[test]
fn parse_single() {
let mut logbag = LogBag::new();
- logbag.insert("cat 1", "line 1".to_string());
- logbag.insert("cat 1", "line 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
assert_eq!(
LogBag::parse_plain(
"\
-cat 1
+World versus World
line 1
line 2"
),
@@ -175,19 +295,19 @@ line 2"
#[test]
fn parse_multi() {
let mut logbag = LogBag::new();
- logbag.insert("cat 1", "line 1".to_string());
- logbag.insert("cat 1", "line 2".to_string());
- logbag.insert("cat 2", "line 1".to_string());
- logbag.insert("cat 2", "line 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
+ logbag.insert(Category::Strike, "line 1".to_string());
+ logbag.insert(Category::Strike, "line 2".to_string());
assert_eq!(
LogBag::parse_plain(
"\
-cat 1
+World versus World
line 1
line 2
-cat 2
+Strike Mission
line 1
line 2"
),
@@ -198,19 +318,19 @@ line 2"
#[test]
fn parse_markdown() {
let mut logbag = LogBag::new();
- logbag.insert("cat 1", "line 1".to_string());
- logbag.insert("cat 1", "line 2".to_string());
- logbag.insert("cat 2", "line 1".to_string());
- logbag.insert("cat 2", "line 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
+ logbag.insert(Category::Strike, "line 1".to_string());
+ logbag.insert(Category::Strike, "line 2".to_string());
assert_eq!(
LogBag::parse_markdown(
"\
-**cat 1**
+**World versus World**
line 1
line 2
-**cat 2**
+**Strike Mission**
line 1
line 2"
),
@@ -221,13 +341,13 @@ line 2"
#[test]
fn render_plain_single() {
let mut logbag = LogBag::new();
- logbag.insert("category", "line 1".to_string());
- logbag.insert("category", "line 2".to_string());
+ logbag.insert(Category::Unknown, "line 1".to_string());
+ logbag.insert(Category::Unknown, "line 2".to_string());
assert_eq!(
logbag.render_plain(),
"\
-category
+Unknown
line 1
line 2"
);
@@ -236,19 +356,19 @@ line 2"
#[test]
fn render_plain_multi() {
let mut logbag = LogBag::new();
- logbag.insert("category 1", "line 1".to_string());
- logbag.insert("category 1", "line 2".to_string());
- logbag.insert("category 2", "enil 1".to_string());
- logbag.insert("category 2", "enil 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
+ logbag.insert(Category::SpecialForcesTrainingArea, "enil 1".to_string());
+ logbag.insert(Category::SpecialForcesTrainingArea, "enil 2".to_string());
assert_eq!(
logbag.render_plain(),
"\
-category 1
+World versus World
line 1
line 2
-category 2
+Special Forces Training Area
enil 1
enil 2"
);
@@ -257,13 +377,13 @@ enil 2"
#[test]
fn render_html_single() {
let mut logbag = LogBag::new();
- logbag.insert("category", "line 1".to_string());
- logbag.insert("category", "line 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
assert_eq!(
logbag.render_html(),
"\
-<b>category</b><br>
+<b>World versus World</b><br>
line 1<br>
line 2"
);
@@ -272,19 +392,19 @@ line 2"
#[test]
fn render_html_multi() {
let mut logbag = LogBag::new();
- logbag.insert("category 1", "line 1".to_string());
- logbag.insert("category 1", "line 2".to_string());
- logbag.insert("category 2", "enil 1".to_string());
- logbag.insert("category 2", "enil 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
+ logbag.insert(Category::Unknown, "enil 1".to_string());
+ logbag.insert(Category::Unknown, "enil 2".to_string());
assert_eq!(
logbag.render_html(),
"\
-<b>category 1</b><br>
+<b>World versus World</b><br>
line 1<br>
line 2<br>
<br>
-<b>category 2</b><br>
+<b>Unknown</b><br>
enil 1<br>
enil 2"
);
@@ -293,13 +413,13 @@ enil 2"
#[test]
fn render_markdown_single() {
let mut logbag = LogBag::new();
- logbag.insert("category", "line 1".to_string());
- logbag.insert("category", "line 2".to_string());
+ logbag.insert(Category::WvW, "line 1".to_string());
+ logbag.insert(Category::WvW, "line 2".to_string());
assert_eq!(
logbag.render_markdown(),
"\
-**category**
+**World versus World**
line 1
line 2"
);
@@ -308,21 +428,55 @@ line 2"
#[test]
fn render_markdown_multi() {
let mut logbag = LogBag::new();
- logbag.insert("category 1", "line 1".to_string());
- logbag.insert("category 1", "line 2".to_string());
- logbag.insert("category 2", "enil 1".to_string());
- logbag.insert("category 2", "enil 2".to_string());
+ logbag.insert(Category::Strike, "line 1".to_string());
+ logbag.insert(Category::Strike, "line 2".to_string());
+ logbag.insert(Category::SpecialForcesTrainingArea, "enil 1".to_string());
+ logbag.insert(Category::SpecialForcesTrainingArea, "enil 2".to_string());
assert_eq!(
logbag.render_markdown(),
"\
-**category 1**
+**Strike Mission**
line 1
line 2
-**category 2**
+**Special Forces Training Area**
enil 1
enil 2"
);
}
+
+ #[test]
+ fn test_info_from_line() {
+ assert_eq!(
+ info_from_line("✅ https://dps.report/O514-20240827-214630_dhuum"),
+ (Some(Encounter::VoiceInTheVoid), Some((20240827, 214630)), "https://dps.report/O514-20240827-214630_dhuum")
+ );
+ }
+
+ #[test]
+ fn test_sort_logbag() {
+ let mut logbag = LogBag::new();
+ logbag.insert(Category::Wing2, String::from("https://dps.report/abcd-20240101-120000_matt"));
+ logbag.insert(Category::Wing2, String::from("https://dps.report/abcd-20240101-115500_matt"));
+ logbag.insert(Category::Wing2, String::from("https://dps.report/abcd-20240101-130000_sloth"));
+ logbag.insert(Category::Wing1, String::from("https://dps.report/abcd-20240101-120000_gors"));
+ logbag.insert(Category::Wing1, String::from("https://dps.report/abcd-20240101-120000_vg"));
+ logbag.insert(Category::Wing1, String::from("https://dps.report/abcd-20240101-120000_sab"));
+
+ logbag.sort();
+
+ assert_eq!(logbag.data, vec![
+ (Category::Wing1, vec![
+ String::from("https://dps.report/abcd-20240101-120000_vg"),
+ String::from("https://dps.report/abcd-20240101-120000_gors"),
+ String::from("https://dps.report/abcd-20240101-120000_sab"),
+ ]),
+ (Category::Wing2, vec![
+ String::from("https://dps.report/abcd-20240101-130000_sloth"),
+ String::from("https://dps.report/abcd-20240101-115500_matt"),
+ String::from("https://dps.report/abcd-20240101-120000_matt"),
+ ]),
+ ]);
+ }
}
diff --git a/src/main.rs b/src/main.rs
index 3e84e2d..482ec53 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,20 +1,17 @@
use std::{
- fs::{self, File},
- io::{BufReader, BufWriter, Read, Write},
path::{Path, PathBuf},
sync::mpsc::channel,
thread,
time::Duration,
};
-use anyhow::{anyhow, bail, Context, Result};
+use anyhow::{bail, Context, Result};
use clap::Parser;
use evtclib::{Compression, Encounter, Log};
use log::{debug, error, info, warn};
use notify::{self, DebouncedEvent, RecursiveMode, Watcher};
use regex::Regex;
use serde::Deserialize;
-use zip::{CompressionMethod, ZipArchive, ZipWriter};
mod categories;
use categories::Categorizable;
@@ -48,7 +45,7 @@ enum SubCommand {
/// Use the watch mode to automatically handle new logs.
///
-/// This watches the given directory for new files and then zips and uploads them.
+/// This watches the given directory for new files and then uploads them.
#[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)]
struct Watch {
/// The directory to watch.
@@ -87,13 +84,13 @@ fn inner_main(opts: &Opts) -> Result<()> {
let log = load_log(&u.path)?;
if let Some(d) = &config.discord {
- discord::post_link(&d.auth_token, d.channel_id, &log, &permalink)
+ discord::post_link(&config, &d.auth_token, d.channel_id, &log, &permalink)
.context("Could not post link to Discord")?;
}
if let Some(m) = &config.matrix {
for room_id in &m.room_id {
- matrix::post_link(m.clone().into(), room_id, &log, &permalink).context(
+ matrix::post_link(&config, m.clone().into(), room_id, &log, &permalink).context(
format!("Could not post link to Matrix (room_id: {})", &room_id),
)?;
}
@@ -104,8 +101,7 @@ fn inner_main(opts: &Opts) -> Result<()> {
}
fn watch(watch: &Watch, config: &Config) -> Result<()> {
- let raw_evtc_re = Regex::new(r"\d{8}-\d{6}(\.evtc)?$").unwrap();
- let zip_evtc_re = Regex::new(r"(\.zip|\.zevtc)$").unwrap();
+ let zip_evtc_re = Regex::new(r"(\.evtc\.zip|\.zevtc)$").unwrap();
let (tx, rx) = channel();
let mut watcher = notify::watcher(tx, Duration::from_secs(WATCH_DELAY_SECONDS))?;
@@ -118,54 +114,13 @@ fn watch(watch: &Watch, config: &Config) -> Result<()> {
debug!("Event: {:?}", event);
if let DebouncedEvent::Create(path) = event {
let path_str = path.to_str().unwrap();
- // Check if we need to zip it first.
- if config.zip && raw_evtc_re.is_match(path_str) {
- info!("Zipping up {}", path_str);
- zip_file(&path)?;
- } else if zip_evtc_re.is_match(path_str) {
+ if zip_evtc_re.is_match(path_str) {
handle_file(config, &path)?;
}
}
}
}
-fn zip_file(filepath: &Path) -> Result<()> {
- let evtc_content = fs::read(filepath)?;
-
- let filename = filepath
- .file_name()
- .ok_or_else(|| anyhow!("Path does not have a file name"))?
- .to_str()
- .ok_or_else(|| anyhow!("Filename is invalid utf-8"))?;
- let outname = filepath.with_extension("zevtc");
- let outfile = BufWriter::new(File::create(&outname)?);
- let mut zip = ZipWriter::new(outfile);
- let options =
- zip::write::FileOptions::default().compression_method(CompressionMethod::Deflated);
-
- zip.start_file(filename, options)?;
- zip.write_all(&evtc_content)?;
- zip.finish()?.flush()?;
-
- if !verify_zip(filepath, &outname)? {
- warn!("ZIP content mismatch, keeping original file");
- return Ok(());
- }
-
- fs::remove_file(filepath)?;
- Ok(())
-}
-
-fn verify_zip(original: &Path, zip_path: &Path) -> Result<bool> {
- let expected_content = fs::read(original)?;
- let mut archive = ZipArchive::new(BufReader::new(File::open(zip_path)?))?;
- let mut inner = archive.by_index(0)?;
- let mut actual_content = Vec::new();
- inner.read_to_end(&mut actual_content)?;
-
- Ok(expected_content == actual_content)
-}
-
fn handle_file(config: &Config, filename: &Path) -> Result<()> {
if !config.upload {
return Ok(());
@@ -199,14 +154,14 @@ fn handle_file(config: &Config, filename: &Path) -> Result<()> {
info!("Uploaded log, available at {}", permalink);
if let Some(d) = &config.discord {
- discord::post_link(&d.auth_token, d.channel_id, &log, &permalink)
+ discord::post_link(config, &d.auth_token, d.channel_id, &log, &permalink)
.context("Could not post link to Discord")?;
info!("Posted link to Discord");
}
if let Some(m) = &config.matrix {
for room_id in &m.room_id {
- matrix::post_link(m.clone().into(), room_id, &log, &permalink).context(format!(
+ matrix::post_link(config, m.clone().into(), room_id, &log, &permalink).context(format!(
"Could not post link to Matrix (room_id: {})",
&room_id
))?;
@@ -259,6 +214,17 @@ fn upload_log(file: &Path, url: &str) -> Result<String> {
}
fn sanity_check(config: &Config, opts: &Opts) -> Result<()> {
+ if config.zip {
+ warn!(
+ "You have zipping enabled, but zipping is no longer part of ezau. \
+ Arcdps automatically zips logs now."
+ );
+ }
+ if matches!(opts.subcmd, SubCommand::Watch(_))
+ && !config.upload
+ {
+ bail!("Watching but not uploading. What am I even doing here?");
+ }
if matches!(opts.subcmd, SubCommand::Watch(_))
&& config.discord.is_none()
&& config.matrix.is_none()
@@ -289,18 +255,18 @@ fn sanity_check(config: &Config, opts: &Opts) -> Result<()> {
// Dummy modules for when the features are disabled
#[cfg(not(feature = "im-discord"))]
mod discord {
- use super::{Log, Result};
+ use super::{config::Config, Log, Result};
use anyhow::bail;
/// Stub, enable the `im-discord` feature to use this function.
- pub fn post_link(_: &str, _: u64, _: &Log, _: &str) -> Result<()> {
+ pub fn post_link(_: &Config, _: &str, _: u64, _: &Log, _: &str) -> Result<()> {
bail!("Discord feature is disabled in this build!")
}
}
#[cfg(not(feature = "im-matrix"))]
mod matrix {
- use super::{Log, Result};
+ use super::{config::Config, Log, Result};
use anyhow::bail;
pub struct MatrixUser;
impl From<super::config::Matrix> for MatrixUser {
@@ -309,7 +275,7 @@ mod matrix {
}
}
/// Stub, enable the `im-matrix` feature to use this function.
- pub fn post_link(_: MatrixUser, _: &str, _: &Log, _: &str) -> Result<()> {
+ pub fn post_link(_: &Config, _: MatrixUser, _: &str, _: &Log, _: &str) -> Result<()> {
bail!("Matrix feature is disabled in this build!")
}
}
diff --git a/src/matrix.rs b/src/matrix.rs
index 8be0e38..d75381d 100644
--- a/src/matrix.rs
+++ b/src/matrix.rs
@@ -95,7 +95,7 @@ const MESSAGE_CHUNK_COUNT: u16 = 3;
///
/// This function blocks until all API calls have been made, that is until the message has reached
/// the homeserver.
-pub fn post_link(user: MatrixUser, room_id: &str, log: &Log, link: &str) -> Result<()> {
+pub fn post_link(config: &config::Config, user: MatrixUser, room_id: &str, log: &Log, link: &str) -> Result<()> {
let rt = Runtime::new()?;
let room_id = RoomId::parse(room_id)?;
@@ -112,7 +112,7 @@ pub fn post_link(user: MatrixUser, room_id: &str, log: &Log, link: &str) -> Resu
};
sync(&client, sync_token, &session_file).await?;
-
+
info!("Matrix connected as {:?}", client.user_id());
let old_msg = find_message(&client, &client.user_id().unwrap(), &room_id).await?;
@@ -125,7 +125,10 @@ pub fn post_link(user: MatrixUser, room_id: &str, log: &Log, link: &str) -> Resu
Some((old_id, old_text)) => {
debug!("Updating message {:?}", old_id);
debug!("Updating message body {:?}", old_text);
- let logbag = insert_log(&old_text, log, link);
+ let mut logbag = insert_log(&old_text, log, link);
+ if config.sort_logs {
+ logbag.sort();
+ }
let new_text = logbag.render_plain();
debug!("New message body {:?}", new_text);
let new_html = logbag.render_html();
@@ -377,7 +380,7 @@ async fn find_message(
/// Post a new message to the given Matrix channel.
async fn post_new(client: &Client, room_id: OwnedRoomId, log: &Log, link: &str) -> Result<()> {
let line = format!("{} {}", state_emoji(log), link);
- let logbag: LogBag = vec![(log.category().to_string(), vec![line])].into();
+ let logbag: LogBag = vec![(log.category(), vec![line])].into();
let body = logbag.render_plain();
let html = logbag.render_html();