diff --git a/Cargo.lock b/Cargo.lock index 466c07b..f34a480 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43f6cb1bf222025340178f382c426f13757b2960e89779dfcb319c32542a5a41" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -92,6 +101,21 @@ version = "0.21.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "604178f6c5c21f02dc555784810edfb88d34ac2c73b2eae109655649ee73ce3d" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" @@ -343,6 +367,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.28" @@ -487,6 +521,20 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed44880c466736ef9a5c5b5facefb5ed0785676d0c02d612db14e54f0d84286" +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "http" version = "0.2.9" @@ -701,6 +749,38 @@ version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "markup5ever_rcdom" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9521dd6750f8e80ee6c53d65e2e4656d7de37064f3a7a5d2d11d05df93839c2" +dependencies = [ + "html5ever", + "markup5ever", + "tendril", + "xml5ever", +] + [[package]] name = "memchr" version = "2.5.0" @@ -770,6 +850,12 @@ dependencies = [ "tempfile", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + [[package]] name = "num-traits" version = "0.2.15" @@ -804,7 +890,9 @@ version = "1.1.3" dependencies = [ "levenshtein", "poise", + "regex", "reqwest", + "select", "serde", "serde_derive", "serde_json", @@ -901,6 +989,44 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared", + "rand", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -956,6 +1082,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro2" version = "1.0.63" @@ -1015,18 +1147,32 @@ dependencies = [ [[package]] name = "regex" -version = "1.8.4" +version = "1.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d0ab3ca65655bb1e41f2a8c8cd662eb4fb035e67c3f78da1d61dffe89d07300f" +checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39354c10dd07468c2e73926b23bb9c2caca74c5501e38a35da70406f1d923310" +dependencies = [ + "aho-corasick", + "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78" +checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" [[package]] name = "reqwest" @@ -1211,6 +1357,17 @@ dependencies = [ "libc", ] +[[package]] +name = "select" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f9da09dc3f4dfdb6374cbffff7a2cffcec316874d4429899eefdc97b3b94dcd" +dependencies = [ + "bit-set", + "html5ever", + "markup5ever_rcdom", +] + [[package]] name = "serde" version = "1.0.164" @@ -1316,6 +1473,12 @@ dependencies = [ "digest", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.8" @@ -1347,6 +1510,32 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + [[package]] name = "strsim" version = "0.10.0" @@ -1389,6 +1578,17 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "thiserror" version = "1.0.40" @@ -1954,3 +2154,14 @@ checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d" dependencies = [ "winapi", ] + +[[package]] +name = "xml5ever" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4034e1d05af98b51ad7214527730626f019682d797ba38b51689212118d8e650" +dependencies = [ + "log", + "mac", + "markup5ever", +] diff --git a/Cargo.toml b/Cargo.toml index 16d0b43..3064c05 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "obsessed-yanqing" -version = "1.1.3" +version = "1.2.0" edition = "2021" authors = ["Evann Regnault"] license = "MIT" @@ -15,3 +15,5 @@ levenshtein = "1.0.5" reqwest = "0.11.17" serde = "1.0.160" serde_path_to_error = "0.1.11" +select = "0.6.0" +regex = "1.9.1" diff --git a/src/commands/character.rs b/src/commands/character.rs index 31ea446..e33d5d4 100644 --- a/src/commands/character.rs +++ b/src/commands/character.rs @@ -344,7 +344,7 @@ impl CharacterTab { } } -/// Displays your or another user's account creation date +/// Search a character info #[poise::command(slash_command, prefix_command)] pub async fn character( ctx: Context<'_>, diff --git a/src/commands/events.rs b/src/commands/events.rs new file mode 100644 index 0000000..961776d --- /dev/null +++ b/src/commands/events.rs @@ -0,0 +1,166 @@ +use std::cmp::Ordering; +use regex::{Regex}; +use select::document::Document; +use select::predicate::{Class, Name, Predicate}; +use serenity::builder::CreateEmbed; +use serenity::model::channel::Channel; +use crate::data::{Context, Error}; + + +pub async fn get_main_prydwen() -> String { + reqwest::get("https://www.prydwen.gg/star-rail/").await.expect("Cannot get Prydwen").text().await.expect("Cannot get data") +} + +enum EventType { + CharacterBanner, + ConeBanner, + Memory, + Other, +} + +struct BannerData { + five_stars: String, + four_stars: Vec +} + +struct Event { + name: String, + description: Option<(String, String)>, + time: Option, + image: Option, + banner_data: Option, + event_type: EventType +} + +async fn get_events_from_document(doc: String) -> Vec { + let document = Document::from(doc.as_str()); + let event_nodes = document.find(Class("event-tracker")).take(1).next(); + + let mut events : Vec = vec![]; + + if let Some(x) = event_nodes { + for event in x.find(Class("accordion-item")) { + let mut attributes = event.attr("class").expect("").split(' '); + if attributes.clone().count() != 2 {continue;} + + let id = attributes.find(|p| !p.to_owned().eq("accordion-item")).expect("Error while getting other id"); + + let name = event.find(Class("event-name")).next().expect("Cannot find name").text(); + + let image_regex = Regex::new(r".accordion-item\.idofevent button\{background-color:#[0-9a-f]{6};background-image:url\((/static/.*?\.jpg)\)}".replace("idofevent", id).as_str()).expect("Cannot created REGEX"); + let image = image_regex.captures(doc.as_str()).expect("Cannot scan document").get(1).map(|x| format!("https://www.prydwen.gg{}", x.as_str())); + + let description = event.find(Class("description")).next().map(|x| { + let text = x.text(); + let desc = text.split(": ").collect::>(); + (desc.first().expect("").to_string(), desc.get(1).expect("").to_string()) + }); + + let time = event.find(Class("time")).next().map(|x| x.text()); + + let event_type = match &description { + None => { + match event.find(Class("featured-characters")).next() { + None => EventType::ConeBanner, + Some(_) => EventType::CharacterBanner + } + } + Some(desc) => { + match desc.0.as_str() { + "Memory Turbulence" => EventType::Memory, + _ => EventType::Other + } + } + }; + + + let five_stars = event.find(Class("rarity-5").descendant(Name("picture")).descendant(Name("img"))).next().map(|x| x.attr("alt").expect("No alt on five star image").to_string()); + let four_stars = event.find(Class("rarity-4")).take(3).map(|four_stars_node| { + four_stars_node.find(Name("picture").descendant(Name("img"))).next().map(|x| x.attr("alt").expect("No alt on four star image").to_string()).expect("") + }).collect::>(); + + let banner_data = match five_stars { + None => None, + Some(x) => match four_stars.len() { + 3 => Some(BannerData { five_stars: x, four_stars}), + _ => None + } + }; + + events.push(Event {name, description, time, image, banner_data, event_type }) + } + }; + events +} + +pub async fn create_events_embeds() -> Vec { + let doc = get_main_prydwen().await; + let mut events = get_events_from_document(doc).await; + + events.sort_by(|a, b| { + match (&a.event_type,&b.event_type) { + (EventType::CharacterBanner, EventType::ConeBanner) => Ordering::Less, + (EventType::CharacterBanner, EventType::Other) => Ordering::Less, + (EventType::CharacterBanner, EventType::Memory) => Ordering::Less, + (EventType::ConeBanner, EventType::Other) => Ordering::Less, + (EventType::ConeBanner, EventType::Memory) => Ordering::Less, + (EventType::Other, EventType::Memory) => Ordering::Less, + + (EventType::Memory, EventType::Other) => Ordering::Greater, + (EventType::Memory, EventType::ConeBanner) => Ordering::Greater, + (EventType::Memory, EventType::CharacterBanner) => Ordering::Greater, + (EventType::Other, EventType::CharacterBanner) => Ordering::Greater, + (EventType::Other, EventType::ConeBanner) => Ordering::Greater, + (EventType::ConeBanner, EventType::CharacterBanner) => Ordering::Greater, + + (_, _) => {Ordering::Equal} + } + }); + + events.into_iter().map(|event| { + let mut default = CreateEmbed::default(); + let mut embed = default + .title(event.name); + + if let Some(image) = event.image { + embed = embed.image(image); + } + + if let Some(time) = event.time { + embed = embed.description(format!("Time remaining : {}", time)); + } + + if let Some(description) = event.description { + embed = embed.field(description.0, description.1, false); + } + + if let Some(banner_infos) = event.banner_data { + embed = embed + .field("5 Stars", format!("- {}",banner_infos.five_stars), false) + .field("4 Stars", format!("- {}", banner_infos.four_stars.join("\n- ")), false); + } + + embed.to_owned() + }).collect::>() +} + + +/// Create a message that gets updated automatically +#[poise::command(slash_command, prefix_command)] +pub async fn create_event_message( + ctx: Context<'_>, + #[description = "Create Event Tab"] channel: Channel +) -> Result<(), Error> { + let embeds = create_events_embeds().await; + channel.id().send_message(ctx, |f| { + f.set_embeds(embeds) + }).await.unwrap(); + let channel_name = channel.id().name(ctx).await.unwrap(); + ctx.send(|f| { + f.embed(|e|{ + e.title("Success !") + .description(format!("The event message has been created in #{}", channel_name)) + }).ephemeral(true) + }).await.expect("TODO: panic message"); + Ok(()) +} \ No newline at end of file diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 77812b2..cb520fa 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1 +1,4 @@ -pub mod character; \ No newline at end of file +pub mod character; +pub mod events; + + diff --git a/src/main.rs b/src/main.rs index 696d411..beca2e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,8 @@ async fn main() { .intents(GatewayIntents::non_privileged()) .options(poise::FrameworkOptions { commands: vec![ - commands::character::character() + commands::character::character(), + commands::events::create_event_message() ], ..Default::default() })