From 0d3c1329c4a8cc1e744a312c8e9f914c309eb281 Mon Sep 17 00:00:00 2001 From: Stefano Pigozzi Date: Fri, 24 Mar 2023 00:51:18 +0100 Subject: [PATCH] Use imgproxy to convert images on the fly --- Cargo.lock | 37 ++++++++++++ Cargo.toml | 8 ++- src/data/setbundle/art.rs | 124 ++++++++++++++++++++++++++------------ 3 files changed, 128 insertions(+), 41 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e2331d..873f31d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -347,6 +347,7 @@ checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" dependencies = [ "block-buffer", "crypto-common", + "subtle", ] [[package]] @@ -652,6 +653,21 @@ dependencies = [ "libc", ] +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest", +] + [[package]] name = "htmlescape" version = "0.3.1" @@ -1088,8 +1104,11 @@ name = "patched_porobot" version = "0.9.2" dependencies = [ "anyhow", + "base64 0.21.0", "data-encoding", "glob", + "hex", + "hmac", "itertools 0.10.5", "lazy_static", "log", @@ -1101,6 +1120,7 @@ dependencies = [ "serde", "serde_json", "serenity", + "sha2", "tantivy", "teloxide", "tokio", @@ -1563,6 +1583,17 @@ dependencies = [ "digest", ] +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + [[package]] name = "sharded-slab" version = "0.1.4" @@ -1624,6 +1655,12 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +[[package]] +name = "subtle" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" + [[package]] name = "syn" version = "1.0.107" diff --git a/Cargo.toml b/Cargo.toml index 7b53834..3ede801 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,6 +26,11 @@ data-encoding = { version = "2.3.2" } varint-rs = { version = "2.2.0" } glob = { version = "0.3.0" } reqwest = { version = "0.11.11", features = ["rustls-tls", "json"], default-features = false } +# jpg +hex = { version = "0.4.3", optional = true } +base64 = { version = "0.21.0", optional = true } +hmac = { version = "0.12.1", optional = true } +sha2 = { version = "0.10.6", optional = true } # exec pretty_env_logger = { version = "0.4.0", optional = true } # data @@ -45,9 +50,10 @@ anyhow = { version = "^1.0.68", optional = true } [features] +jpg = ["hmac", "sha2", "base64", "hex"] exec = ["pretty_env_logger"] search = ["tantivy"] -telegram = ["exec", "search", "teloxide", "tokio", "md5", "rand"] +telegram = ["exec", "search", "jpg", "teloxide", "tokio", "md5", "rand"] discord = ["exec", "search", "serenity", "tokio", "anyhow"] matrix = ["exec", "search"] diff --git a/src/data/setbundle/art.rs b/src/data/setbundle/art.rs index 2994078..dc4da1b 100644 --- a/src/data/setbundle/art.rs +++ b/src/data/setbundle/art.rs @@ -1,7 +1,8 @@ //! Module defining [CardArt]. -use lazy_static::lazy_static; -use regex::Regex; +use base64::Engine; +use hmac::Mac; +use std::env; /// The illustration of a [Card](super::card::Card), also referred to as an *art asset*. #[derive(Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)] @@ -30,52 +31,95 @@ pub struct CardArt { } impl CardArt { - /// URL to the `.jpg` image of the `en_us` locale of the rendered card, via my custom S3 mirror. - /// - /// # Example - /// - /// ```text - /// https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/set1-en_us/en_us/img/cards/01DE001.jpg - /// ``` - /// - pub fn card_jpg(&self) -> String { - lazy_static! { - static ref GET_JPG: Regex = Regex::new( - r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P[^/]+)/(?P[^/]+)/img/cards/(?P.+)[.]png$"# - ) - .unwrap(); - } - GET_JPG - .replace_all( - &self.card_png, - "https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/$bundle-$locale/$locale/img/cards/$code.jpg", - ) - .to_string() + /// Get the URL to convert the image at the given URL into JPG using imgproxy. + #[cfg(feature = "jpg")] + fn imgproxy_convert_to_jpg(url: &str) -> String { + let url = base64::prelude::BASE64_URL_SAFE.encode(url); + let url = format!("/{url}.jpg"); + + log::trace!("Created JPG conversion URL: {url}"); + + url } - /// URL to the `.jpg` image of the `en_us` locale of the full card art, via my custom S3 mirror. + /// Add the HMAC required by imgproxy to authenticate the source of the image requester to the URL. /// - /// # Example + /// # Panics /// - /// ```text - /// https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/set1-en_us/en_us/img/cards/01DE001-full.jpg - /// ``` + /// If the `POROXY_KEY` or `POROXY_SALT` variables are not defined. /// - pub fn full_jpg(&self) -> String { - lazy_static! { - static ref GET_JPG: Regex = Regex::new( - r#"https?://dd[.]b[.]pvp[.]net/[^/]+/(?P[^/]+)/(?P[^/]+)/img/cards/(?P.+)[.]png$"# - ) - .unwrap(); - } + #[cfg(feature = "jpg")] + fn imgproxy_authenticate_url(url: &str) -> String { + let key = env::var("POROXY_KEY") + .expect("POROXY_KEY to be set"); + let key = hex::decode(key) + .expect("POROXY_KEY to be a valid hex code"); - GET_JPG - .replace_all( - &self.full_png, - "https://objectstorage.eu-milan-1.oraclecloud.com/n/axxdmk4y92aq/b/porobot-storage/o/$bundle-$locale/$locale/img/cards/$code.jpg", + let salt = env::var("POROXY_SALT") + .expect("POROXY_SALT to be set"); + let salt = hex::decode(salt) + .expect("POROXY_SALT to be a valid hex code"); + let salt: String = String::from_utf8(salt) + .expect("salt to be a valid UTF-8 string"); + + let mut hmac = hmac::Hmac::::new_from_slice(key.as_slice()) + .expect("HMAC to be initialized successfully"); + hmac.update(&format!("{salt}{url}").into_bytes()); + let hmac = hmac.finalize().into_bytes(); + let hmac = base64::prelude::BASE64_URL_SAFE_NO_PAD.encode(hmac); + + let url = format!("/{hmac}{url}"); + + log::trace!("Created authenticated URL: {url}"); + + url + } + + /// URL to the `.jpg` image of the rendered card, via imgproxy. + /// + /// # Panics + /// + /// If the `POROXY_HOST`, `POROXY_KEY` and `POROXY_SALT` variables are not defined. + /// + #[cfg(feature = "jpg")] + pub fn card_jpg(&self) -> String { + let host = env::var("POROXY_HOST") + .expect("POROXY_HOST to be set"); + + let url = Self::imgproxy_authenticate_url( + &Self::imgproxy_convert_to_jpg( + &self.card_png ) - .to_string() + ); + + let url = format!("{host}{url}"); + log::trace!("Accessed card_jpg: {url}"); + + url + } + + /// URL to the `.jpg` image of the rendered card, via imgproxy. + /// + /// # Panics + /// + /// If the `POROXY_HOST`, `POROXY_KEY` and `POROXY_SALT` variables are not defined. + /// + #[cfg(feature = "jpg")] + pub fn full_jpg(&self) -> String { + let host = env::var("POROXY_HOST") + .expect("POROXY_HOST to be set"); + + let url = Self::imgproxy_authenticate_url( + &Self::imgproxy_convert_to_jpg( + &self.full_png + ) + ); + + let url = format!("{host}{url}"); + log::trace!("Accessed full_jpg: {url}"); + + url } }