Better time in events message + README.md and LICENSE.md
This commit is contained in:
parent
d8fe8edf04
commit
0e43b286ef
7 changed files with 196 additions and 36 deletions
31
Cargo.lock
generated
31
Cargo.lock
generated
|
@ -171,7 +171,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_json",
|
||||
"time",
|
||||
"time 0.3.22",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
|
@ -213,8 +213,11 @@ checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
|
|||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"serde",
|
||||
"time 0.1.45",
|
||||
"wasm-bindgen",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
|
@ -612,7 +615,7 @@ checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427"
|
|||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1075,7 +1078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "927a765cd3fc26206e66b296465fa9d3e5ab003e651c1b3c060e7956d96b19d2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
|
@ -1180,8 +1183,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "obsessed-yanqing"
|
||||
version = "1.2.0"
|
||||
version = "1.3.1"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"levenshtein",
|
||||
"mongodb",
|
||||
"poise",
|
||||
|
@ -1863,7 +1867,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde-value",
|
||||
"serde_json",
|
||||
"time",
|
||||
"time 0.3.22",
|
||||
"tokio",
|
||||
"tracing",
|
||||
"typemap_rev",
|
||||
|
@ -2075,6 +2079,17 @@ dependencies = [
|
|||
"syn 2.0.27",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.1.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b797afad3f312d1c66a56d11d0316f916356d11bd158fbc6ca6389ff6bf805a"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"wasi 0.10.0+wasi-snapshot-preview1",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "time"
|
||||
version = "0.3.22"
|
||||
|
@ -2412,6 +2427,12 @@ dependencies = [
|
|||
"try-lock",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "obsessed-yanqing"
|
||||
version = "1.3.0"
|
||||
version = "1.3.1"
|
||||
edition = "2021"
|
||||
authors = ["Evann Regnault"]
|
||||
license = "MIT"
|
||||
|
@ -19,3 +19,4 @@ select = "0.6.0"
|
|||
regex = "1.9.1"
|
||||
rand = "0.8.5"
|
||||
mongodb = "2.6.0"
|
||||
chrono = "0.4.26"
|
||||
|
|
21
LICENSE.md
Normal file
21
LICENSE.md
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2023 Evann REGNAULT
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
49
README.md
Normal file
49
README.md
Normal file
|
@ -0,0 +1,49 @@
|
|||
# Obsessed Yanqing
|
||||
|
||||
This is a bot whose main purpose is to give as much infomrations as possible about Honkai Star Rail.
|
||||
|
||||
> ⚠️ Disclaimer : All Data is fetched from [Prydwen.gg](https://prydwen.gg)
|
||||
|
||||
## Features
|
||||
|
||||
### Event messages
|
||||
The command `/create_event_message <channel>` creates, as its name indicate, an event message in the channel of your
|
||||
choice.</br>This message will contain:
|
||||
- All the currently running banners
|
||||
- The currently going events
|
||||
- The upcoming banners and events
|
||||
- The currently known redeemable codes
|
||||
|
||||
> In order to update this message every hour, three information are stored in a database : Guild id, Channel id, Message id.
|
||||
>
|
||||
> ❗ Those are the only information stored by the bot !
|
||||
|
||||
|
||||
### Character Data
|
||||
|
||||
By using the command `/character <character>`, you get access to loads of data about the selected character.
|
||||
|
||||
Such as its rarity, role, description, pros and cons, its different builds, and the recommended teams.
|
||||
|
||||
All the navigation is done by click and touch on the buttons bellow the message.
|
||||
|
||||
If a character has multiple builds/gears, you need to click on th gear button again to navigate to the next one.
|
||||
|
||||
## Setup
|
||||
|
||||
If you want to setup this bot by yourself, a docker image is available on [my docker registry](https://registry.evannregnault.dev/#!/taglist/obsessed-yanqing).
|
||||
|
||||
You'll also need a mongoDB server alongside it.
|
||||
|
||||
### ENV
|
||||
```env
|
||||
TOKEN : The discord bot's token
|
||||
MONGO_HOST : The hostname of the mongodb instance
|
||||
MONGO_PORT : The port of the mongodb instance
|
||||
```
|
||||
|
||||
## APIs / Libraries
|
||||
|
||||
- [Prydwen.gg](https://www.prydwen.gg/star-rail/)
|
||||
- [Serenity](https://github.com/serenity-rs/serenity)
|
||||
- [Poise](https://github.com/serenity-rs/poise)
|
|
@ -202,7 +202,7 @@ fn create_character_tabs_button<'a>(f: &'a mut CreateComponents, char: &Characte
|
|||
gear_button.disabled(false);
|
||||
match current_tab {
|
||||
CharacterTab::Gear(n) => {
|
||||
gear_button.label(format!("Gear {}/{}", n+1, char.build_data.as_ref().expect("").len()));
|
||||
gear_button.label(format!("Gear {}/{}", n+1, char.build_data.as_ref().expect("Cannot find gear").len()));
|
||||
}
|
||||
_ => {gear_button.label("Gear");}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::cmp::Ordering;
|
||||
use std::i32;
|
||||
use std::str::Split;
|
||||
use std::time::{SystemTime, UNIX_EPOCH};
|
||||
use chrono::{NaiveDateTime};
|
||||
use regex::{Regex};
|
||||
use select::document::Document;
|
||||
use select::node::Node;
|
||||
|
@ -32,11 +34,17 @@ struct BannerData {
|
|||
four_stars: Vec<String>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct EventTime {
|
||||
start: Option<i64>,
|
||||
end: Option<i64>
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
struct Event {
|
||||
name: String,
|
||||
description: Option<(String, String)>,
|
||||
time: Option<String>,
|
||||
time: EventTime,
|
||||
image: Option<String>,
|
||||
banner_data: Option<BannerData>,
|
||||
color: Option<Color>,
|
||||
|
@ -49,31 +57,62 @@ struct Code {
|
|||
}
|
||||
|
||||
fn get_current_events_from_document(doc: &str) -> Vec<Event> {
|
||||
let events = get_all_events(doc);
|
||||
|
||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time no longer works").as_secs() as i64;
|
||||
events.into_iter().filter(|p| match p.time.start {
|
||||
None => true,
|
||||
Some(start) => {
|
||||
start <= current_time && match p.time.end {
|
||||
None => true,
|
||||
Some(end) => current_time < end
|
||||
}
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn get_upcoming_events_from_document(doc: &str) -> Vec<Event> {
|
||||
let events = get_all_events(doc);
|
||||
|
||||
let current_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time no longer works").as_secs() as i64;
|
||||
events.into_iter().filter(|p| match p.time.start {
|
||||
None => false,
|
||||
Some(start) => {
|
||||
start > current_time && match p.time.end {
|
||||
None => true,
|
||||
Some(end) => current_time < end
|
||||
}
|
||||
}
|
||||
}).collect()
|
||||
}
|
||||
|
||||
fn get_all_events(doc: &str) -> Vec<Event> {
|
||||
let document = Document::from(doc);
|
||||
let nodes = document.find(Class("event-tracker")).collect::<Vec<Node>>();
|
||||
let event_nodes = nodes.get(0);
|
||||
|
||||
let mut events : Vec<Event> = vec![];
|
||||
|
||||
parse_events(&doc, event_nodes, &mut events);
|
||||
let mut events: Vec<Event> = vec![];
|
||||
|
||||
let current_event_nodes = nodes.get(0);
|
||||
parse_events(&doc, current_event_nodes, &mut events);
|
||||
let upcoming_event_nodes = nodes.get(1);
|
||||
parse_events(&doc, upcoming_event_nodes, &mut events);
|
||||
events
|
||||
}
|
||||
|
||||
|
||||
fn parse_events(doc: &&str, event_nodes: Option<&Node>, events: &mut Vec<Event>) {
|
||||
if let Some(x) = event_nodes {
|
||||
for event in x.find(Class("accordion-item")) {
|
||||
let mut attributes = event.attr("class").expect("").split(' ');
|
||||
let mut attributes = event.attr("class").expect("Cant find class attribute").split(' ');
|
||||
if attributes.clone().count() != 2 { continue; }
|
||||
|
||||
|
||||
let name = event.find(Class("event-name")).next().expect("Cannot find name").text();
|
||||
|
||||
let (image, color) = get_image_color(&doc, &mut attributes);
|
||||
let (image, color) = get_image_color(doc, &mut attributes);
|
||||
|
||||
let description = get_description(event);
|
||||
|
||||
let time = event.find(Class("time")).next().map(|x| x.text());
|
||||
let time = get_time(event);
|
||||
|
||||
let event_type = get_event_type(event, &description);
|
||||
|
||||
|
@ -84,16 +123,30 @@ fn parse_events(doc: &&str, event_nodes: Option<&Node>, events: &mut Vec<Event>)
|
|||
};
|
||||
}
|
||||
|
||||
fn get_upcoming_events_from_document(doc: &str) -> Vec<Event> {
|
||||
let document = Document::from(doc);
|
||||
let nodes = document.find(Class("event-tracker")).collect::<Vec<Node>>();
|
||||
let event_nodes = nodes.get(1);
|
||||
let mut events : Vec<Event> = vec![];
|
||||
|
||||
parse_events(&doc, event_nodes, &mut events);
|
||||
events
|
||||
fn get_date(date_string: &str) -> Option<String> {
|
||||
let date_regex = Regex::new(r"(?m)[0-9]{4}/(?:1[0-9]|0[1-9])/(?:0[1-9]|[1-2][0-9]|3[0-1]) (?:(?:[01][0-9]|2[0-3])|[0-9]):[0-5][0-9]").expect("Cannot compile time regex");
|
||||
date_regex.captures(date_string).map(|x| x.get(0).expect("No captures found").as_str().to_string())
|
||||
}
|
||||
|
||||
fn get_time(event: Node) -> EventTime {
|
||||
event.find(Class("duration")).next().map(|x| {
|
||||
let text = x.text();
|
||||
let dates = Regex::new(r" [-–] ").expect("Can't create split time regex").split(text.as_str()).collect::<Vec<&str>>();
|
||||
let start_date_text = dates.first().expect("Cannot get start date string").to_owned();
|
||||
let end_date_text = dates.get(1).expect("Cannot get end date string").to_owned();
|
||||
let start = get_date(start_date_text).map(|x| {
|
||||
NaiveDateTime::parse_from_str(x.as_str(), "%Y/%m/%d %H:%M").expect("Date").timestamp()
|
||||
});
|
||||
let end = get_date(end_date_text).map(|x| {
|
||||
NaiveDateTime::parse_from_str(x.as_str(), "%Y/%m/%d %H:%M").expect("Date").timestamp()
|
||||
});
|
||||
|
||||
EventTime {start, end}
|
||||
}).expect("No time found")
|
||||
}
|
||||
|
||||
|
||||
fn get_codes_from_document(doc: &str) -> Option<Vec<Code>> {
|
||||
let document = Document::from(doc);
|
||||
document.find(Class("codes")).next().map( |codes | {
|
||||
|
@ -125,7 +178,7 @@ fn get_description(event: Node) -> Option<(String, String)> {
|
|||
let description = event.find(Class("description")).next().map(|x| {
|
||||
let text = x.text();
|
||||
let desc = text.split(": ").collect::<Vec<&str>>();
|
||||
(desc.first().expect("").to_string(), desc.get(1).expect("").to_string())
|
||||
(desc.first().expect("Cannot get description title").to_string(), desc.get(1).expect("Cannot get description text").to_string())
|
||||
});
|
||||
description
|
||||
}
|
||||
|
@ -163,7 +216,7 @@ fn get_event_type(event: Node, description: &Option<(String, String)>) -> EventT
|
|||
fn get_banner_data(event: Node) -> Option<BannerData> {
|
||||
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("")
|
||||
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("Cannot get four stars names")
|
||||
}).collect::<Vec<String>>();
|
||||
|
||||
match five_stars {
|
||||
|
@ -176,7 +229,7 @@ fn get_banner_data(event: Node) -> Option<BannerData> {
|
|||
}
|
||||
|
||||
fn create_current_events_embeds(doc : &str) -> Vec<CreateEmbed> {
|
||||
let mut events = get_current_events_from_document(doc);
|
||||
let mut events = get_current_events_from_document(doc).into_iter().take(8).collect::<Vec<Event>>();
|
||||
|
||||
events.sort_by(sort_events());
|
||||
|
||||
|
@ -193,8 +246,8 @@ fn create_current_events_embeds(doc : &str) -> Vec<CreateEmbed> {
|
|||
embed = embed.color(color);
|
||||
}
|
||||
|
||||
if let Some(time) = event.time {
|
||||
embed = embed.description(format!("Time remaining : {}", time));
|
||||
if let Some(time) = event.time.end {
|
||||
embed = embed.description(format!("Ends <t:{}:R>", time));
|
||||
}
|
||||
|
||||
if let Some(description) = event.description {
|
||||
|
@ -217,17 +270,16 @@ fn create_upcoming_embed(doc : &str) -> Option<CreateEmbed> {
|
|||
events.sort_by(sort_events());
|
||||
if events.is_empty() {return None;}
|
||||
|
||||
let banner_events : Vec<Event> = events.to_vec().into_iter().filter(|p| matches!(p.event_type, EventType::ConeBanner | EventType::CharacterBanner)).collect();
|
||||
|
||||
let other_events: Vec<Event> = events.iter().cloned().filter(|p| !matches!(p.event_type, EventType::ConeBanner | EventType::CharacterBanner)).collect();
|
||||
let banner_events : Vec<Event> = events.iter().cloned().filter(|p| matches!(p.event_type, EventType::ConeBanner | EventType::CharacterBanner)).collect();
|
||||
let other_events: Vec<Event> = events.iter().cloned().filter(|p| !matches!(p.event_type, EventType::ConeBanner | EventType::CharacterBanner)).rev().collect();
|
||||
|
||||
Some(CreateEmbed::default()
|
||||
.title("Upcoming Events")
|
||||
.field("Banners", banner_events.iter().map(|banner| {
|
||||
format!("{} - **{}** : Starts in {}", banner.name, banner.banner_data.as_ref().expect("").five_stars, banner.time.as_ref().expect(""))
|
||||
format!("{} - **{}** : Starts <t:{}:R>", banner.name, banner.banner_data.as_ref().expect("No Banner Data on Banner").five_stars, banner.time.start.expect("No start time for Upcomming Event"))
|
||||
}).collect::<Vec<String>>().join("\n"),false)
|
||||
.field("Events", other_events.iter().map(|event| {
|
||||
format!("{} : Starts in {}", event.name, event.time.as_ref().expect(""))
|
||||
format!("{} : Starts <t:{}:R>", event.name, event.time.start.expect("No start time for Upcomming Event"))
|
||||
}).collect::<Vec<String>>().join("\n"),false)
|
||||
.color(Color::from_rgb(rand::random(), rand::random(), rand::random()))
|
||||
.footer(|f| f.text("Data from https://www.prydwen.gg"))
|
||||
|
@ -245,6 +297,7 @@ fn create_codes_embed(doc : &str) -> Option<CreateEmbed> {
|
|||
false => format!("- {}", code.code)
|
||||
}
|
||||
}).collect::<Vec<String>>().join("\n"))
|
||||
.url("https://hsr.hoyoverse.com/gift")
|
||||
.color(Color::from_rgb(rand::random(), rand::random(), rand::random()))
|
||||
.footer(|f| f.text("Data from https://www.prydwen.gg"))
|
||||
.to_owned())
|
||||
|
@ -253,6 +306,7 @@ fn create_codes_embed(doc : &str) -> Option<CreateEmbed> {
|
|||
|
||||
pub async fn create_event_embeds() -> Vec<CreateEmbed> {
|
||||
let doc = get_main_prydwen().await;
|
||||
|
||||
let mut embeds: Vec<CreateEmbed> = vec![];
|
||||
embeds.append(create_current_events_embeds(&doc).as_mut());
|
||||
if let Some(embed) = create_upcoming_embed(&doc) {
|
||||
|
@ -281,7 +335,17 @@ fn sort_events() -> fn(&Event, &Event) -> Ordering {
|
|||
(EventType::Other, EventType::ConeBanner) => Ordering::Greater,
|
||||
(EventType::ConeBanner, EventType::CharacterBanner) => Ordering::Greater,
|
||||
|
||||
(_, _) => { Ordering::Equal }
|
||||
(_, _) => {
|
||||
match (a.time.end, b.time.end) {
|
||||
(Some(a_end), Some(b_end)) => {
|
||||
match a_end > b_end {
|
||||
true => Ordering::Greater,
|
||||
false => Ordering::Less
|
||||
}
|
||||
}
|
||||
_ => Ordering::Equal
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -294,7 +358,7 @@ pub async fn create_event_message(
|
|||
#[description = "Create Event Tab"] channel: Channel
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let message = get_discord_status_message(ctx.guild().expect("").id.0).await;
|
||||
let message = get_discord_status_message(ctx.guild().expect("Message not sent in guild").id.0).await;
|
||||
if let Some(e) = message {
|
||||
let rm = ChannelId::from(e.channel_id as u64)
|
||||
.message(&ctx.http(), MessageId::from(e.message_id as u64))
|
||||
|
|
|
@ -3,13 +3,17 @@ mod data;
|
|||
mod utils;
|
||||
mod mongo;
|
||||
|
||||
use std::any::Any;
|
||||
use std::fmt::Display;
|
||||
use std::hash::Hash;
|
||||
use std::time::Duration;
|
||||
use poise::FrameworkError;
|
||||
use poise::serenity_prelude::GatewayIntents;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::id::ChannelId;
|
||||
use serenity::model::prelude::Activity;
|
||||
use crate::commands::events::create_event_embeds;
|
||||
use crate::data::{Data};
|
||||
use crate::data::{Data, Error};
|
||||
use crate::mongo::core::get_all_status_messages;
|
||||
|
||||
fn update_daily(ctx: Context) {
|
||||
|
|
Loading…
Reference in a new issue