mirror of
https://github.com/natsukagami/youmubot.git
synced 2025-04-16 07:18:54 +00:00
Parsing and printing the basic info with old links
This commit is contained in:
parent
0cc9b318ad
commit
e37ae361fd
13 changed files with 422 additions and 54 deletions
25
Cargo.lock
generated
25
Cargo.lock
generated
|
@ -5,6 +5,14 @@ name = "adler32"
|
|||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "anyhow"
|
||||
version = "1.0.25"
|
||||
|
@ -1000,7 +1008,10 @@ name = "regex"
|
|||
version = "1.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex-syntax 0.6.12 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1323,6 +1334,14 @@ dependencies = [
|
|||
"winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thread_local"
|
||||
version = "0.3.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
dependencies = [
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "threadpool"
|
||||
version = "1.7.1"
|
||||
|
@ -1802,12 +1821,15 @@ version = "0.1.0"
|
|||
dependencies = [
|
||||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"regex 1.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rustbreak 2.0.0-rc3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serenity 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"static_assertions 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"youmubot-osu 0.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1817,11 +1839,13 @@ dependencies = [
|
|||
"chrono 0.4.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.24 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.103 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.44 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serenity 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[metadata]
|
||||
"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
|
||||
"checksum aho-corasick 0.7.6 (registry+https://github.com/rust-lang/crates.io-index)" = "58fb5e95d83b38284460a5fda7d6470aa0b8844d283a0b614b8535e880800d2d"
|
||||
"checksum anyhow 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "9267dff192e68f3399525901e709a48c1d3982c9c072fa32f2127a0cb0babf14"
|
||||
"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
|
||||
"checksum backtrace 0.3.40 (registry+https://github.com/rust-lang/crates.io-index)" = "924c76597f0d9ca25d762c25a4d369d51267536465dc5064bdf0eb073ed477ea"
|
||||
|
@ -1972,6 +1996,7 @@ dependencies = [
|
|||
"checksum syn 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)" = "dff0acdb207ae2fe6d5976617f887eb1e35a2ba52c13c7234c790960cdad9238"
|
||||
"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
|
||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
|
||||
"checksum thread_local 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c6b53e329000edc2b34dbe8545fd20e55a333362d0a321909685a19bd28c3f1b"
|
||||
"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
|
||||
"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
|
||||
"checksum tokio 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)" = "5a09c0b5bb588872ab2f09afa13ee6e9dac11e10a0ec9e8e3ba39a5a5d530af6"
|
||||
|
|
|
@ -11,3 +11,6 @@ serenity = "0.7"
|
|||
chrono = "0.4.10"
|
||||
reqwest = "0.9.24"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "1"
|
||||
|
|
|
@ -2,6 +2,9 @@ pub mod models;
|
|||
|
||||
pub mod request;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test;
|
||||
|
||||
use models::*;
|
||||
use request::builders::*;
|
||||
use request::*;
|
||||
|
@ -25,13 +28,11 @@ impl Client {
|
|||
&self,
|
||||
client: &HTTPClient,
|
||||
kind: BeatmapRequestKind,
|
||||
f: impl FnOnce(BeatmapRequestBuilder) -> BeatmapRequestBuilder,
|
||||
f: impl FnOnce(&mut BeatmapRequestBuilder) -> &mut BeatmapRequestBuilder,
|
||||
) -> Result<Vec<Beatmap>, Error> {
|
||||
let res = f(BeatmapRequestBuilder::new(kind))
|
||||
.build(client)
|
||||
.query(&[("k", &self.key)])
|
||||
.send()?
|
||||
.json()?;
|
||||
let mut r = BeatmapRequestBuilder::new(kind);
|
||||
f(&mut r);
|
||||
let res = r.build(client).query(&[("k", &self.key)]).send()?.json()?;
|
||||
Ok(res)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,15 @@ impl<'de> Deserialize<'de> for Beatmap {
|
|||
bpm: parse_from_str(&raw.bpm)?,
|
||||
creator: raw.creator,
|
||||
creator_id: parse_from_str(&raw.creator_id)?,
|
||||
source: raw.source,
|
||||
source: raw.source.filter(|v| !v.is_empty()),
|
||||
genre: parse_genre(&raw.genre_id)?,
|
||||
language: parse_language(&raw.language_id)?,
|
||||
tags: raw.tags.split_whitespace().map(|v| v.to_owned()).collect(),
|
||||
difficulty_name: raw.version,
|
||||
difficulty: Difficulty {
|
||||
stars: parse_from_str(&raw.difficultyrating)?,
|
||||
aim: parse_from_str(&raw.diff_aim)?,
|
||||
speed: parse_from_str(&raw.diff_speed)?,
|
||||
aim: raw.diff_aim.map(parse_from_str).transpose()?,
|
||||
speed: raw.diff_speed.map(parse_from_str).transpose()?,
|
||||
cs: parse_from_str(&raw.diff_size)?,
|
||||
od: parse_from_str(&raw.diff_overall)?,
|
||||
ar: parse_from_str(&raw.diff_approach)?,
|
||||
|
@ -41,7 +41,7 @@ impl<'de> Deserialize<'de> for Beatmap {
|
|||
count_normal: parse_from_str(&raw.count_normal)?,
|
||||
count_slider: parse_from_str(&raw.count_slider)?,
|
||||
count_spinner: parse_from_str(&raw.count_spinner)?,
|
||||
max_combo: parse_from_str(&raw.max_combo)?,
|
||||
max_combo: raw.max_combo.map(parse_from_str).transpose()?,
|
||||
},
|
||||
drain_length: parse_duration(&raw.hit_length)?,
|
||||
total_length: parse_duration(&raw.total_length)?,
|
||||
|
@ -61,8 +61,8 @@ fn parse_mode<E: de::Error>(s: impl AsRef<str>) -> Result<Mode, E> {
|
|||
Ok(match t {
|
||||
0 => Std,
|
||||
1 => Taiko,
|
||||
2 => Mania,
|
||||
3 => Catch,
|
||||
2 => Catch,
|
||||
3 => Mania,
|
||||
_ => return Err(E::custom(format!("invalid value {} for mode", t))),
|
||||
})
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ fn parse_language<E: de::Error>(s: impl AsRef<str>) -> Result<Language, E> {
|
|||
9 => Swedish,
|
||||
10 => Spanish,
|
||||
11 => Italian,
|
||||
_ => return Err(E::custom(format!("Invalid value {} for language", t))),
|
||||
_ => return Err(E::custom(format!("invalid value {} for language", t))),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ fn parse_genre<E: de::Error>(s: impl AsRef<str>) -> Result<Genre, E> {
|
|||
7 => Novelty,
|
||||
9 => HipHop,
|
||||
10 => Electronic,
|
||||
_ => return Err(E::custom(format!("Invalid value {} for genre", t))),
|
||||
_ => return Err(E::custom(format!("invalid value {} for genre", t))),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -110,14 +110,14 @@ fn parse_duration<E: de::Error>(s: impl AsRef<str>) -> Result<Duration, E> {
|
|||
}
|
||||
|
||||
fn parse_from_str<T: FromStr, E: de::Error>(s: impl AsRef<str>) -> Result<T, E> {
|
||||
T::from_str(s.as_ref()).map_err(|_| E::custom(format!("Invalid value {}", s.as_ref())))
|
||||
T::from_str(s.as_ref()).map_err(|_| E::custom(format!("invalid value {}", s.as_ref())))
|
||||
}
|
||||
|
||||
fn parse_bool<E: de::Error>(b: impl AsRef<str>) -> Result<bool, E> {
|
||||
match b.as_ref() {
|
||||
"1" => Ok(true),
|
||||
"0" => Ok(false),
|
||||
_ => Err(E::custom("Invalid value for bool")),
|
||||
_ => Err(E::custom("invalid value for bool")),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,11 +127,15 @@ fn parse_approval_status<E: de::Error>(b: &raw::Beatmap) -> Result<ApprovalStatu
|
|||
"4" => Loved,
|
||||
"3" => Qualified,
|
||||
"2" => Approved,
|
||||
"1" => Ranked(parse_date(&b.approved_date)?),
|
||||
"1" => Ranked(parse_date(
|
||||
b.approved_date
|
||||
.as_ref()
|
||||
.ok_or(E::custom("expected approved date got none"))?,
|
||||
)?),
|
||||
"0" => Pending,
|
||||
"-1" => WIP,
|
||||
"-2" => Graveyarded,
|
||||
_ => return Err(E::custom("Invalid value for approval status")),
|
||||
_ => return Err(E::custom("invalid value for approval status")),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -142,15 +146,15 @@ fn parse_date<E: de::Error>(date: impl AsRef<str>) -> Result<DateTime<Utc>, E> {
|
|||
date.as_ref(),
|
||||
(&[
|
||||
Item::Numeric(Numeric::Year, Pad::Zero),
|
||||
Item::Literal("/"),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Numeric::Month, Pad::Zero),
|
||||
Item::Literal("/"),
|
||||
Item::Literal("-"),
|
||||
Item::Numeric(Numeric::Day, Pad::Zero),
|
||||
Item::Space(""),
|
||||
Item::Numeric(Numeric::Hour, Pad::Zero),
|
||||
Item::Literal("-"),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Numeric::Minute, Pad::Zero),
|
||||
Item::Literal("-"),
|
||||
Item::Literal(":"),
|
||||
Item::Numeric(Numeric::Second, Pad::Zero),
|
||||
])
|
||||
.iter(),
|
||||
|
|
|
@ -18,8 +18,8 @@ pub enum ApprovalStatus {
|
|||
#[derive(Debug)]
|
||||
pub struct Difficulty {
|
||||
pub stars: f64,
|
||||
pub aim: f64,
|
||||
pub speed: f64,
|
||||
pub aim: Option<f64>,
|
||||
pub speed: Option<f64>,
|
||||
|
||||
pub cs: f64,
|
||||
pub od: f64,
|
||||
|
@ -29,7 +29,7 @@ pub struct Difficulty {
|
|||
pub count_normal: u64,
|
||||
pub count_slider: u64,
|
||||
pub count_spinner: u64,
|
||||
pub max_combo: u64,
|
||||
pub max_combo: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -61,17 +61,27 @@ pub enum Language {
|
|||
Spanish,
|
||||
Italian,
|
||||
}
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Std,
|
||||
Taiko,
|
||||
Mania,
|
||||
Catch,
|
||||
Mania,
|
||||
}
|
||||
|
||||
impl ToString for Mode {
|
||||
fn to_string(&self) -> String {
|
||||
(*self as u64).to_string()
|
||||
impl std::fmt::Display for Mode {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use Mode::*;
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
match self {
|
||||
Std => "osu!",
|
||||
Taiko => "osu!taiko",
|
||||
Mania => "osu!mania",
|
||||
Catch => "osu!catch",
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub(crate) struct Beatmap {
|
||||
pub approved: String,
|
||||
pub submit_date: String,
|
||||
pub approved_date: String,
|
||||
pub approved_date: Option<String>,
|
||||
pub last_update: String,
|
||||
pub artist: String,
|
||||
pub beatmap_id: String,
|
||||
|
@ -13,8 +13,8 @@ pub(crate) struct Beatmap {
|
|||
pub creator: String,
|
||||
pub creator_id: String,
|
||||
pub difficultyrating: String,
|
||||
pub diff_aim: String,
|
||||
pub diff_speed: String,
|
||||
pub diff_aim: Option<String>,
|
||||
pub diff_speed: Option<String>,
|
||||
pub diff_size: String,
|
||||
pub diff_overall: String,
|
||||
pub diff_approach: String,
|
||||
|
@ -36,7 +36,7 @@ pub(crate) struct Beatmap {
|
|||
pub count_normal: String,
|
||||
pub count_slider: String,
|
||||
pub count_spinner: String,
|
||||
pub max_combo: String,
|
||||
pub max_combo: Option<String>,
|
||||
pub download_unavailable: String,
|
||||
pub audio_unavailable: String,
|
||||
}
|
||||
|
|
|
@ -15,9 +15,12 @@ impl<T: ToQuery> ToQuery for Option<T> {
|
|||
}
|
||||
}
|
||||
|
||||
impl ToQuery for Mode {
|
||||
impl ToQuery for (Mode, bool) {
|
||||
fn to_query(&self) -> Vec<(&'static str, String)> {
|
||||
vec![("m", (*self as u8).to_string())]
|
||||
vec![
|
||||
("m", (self.0 as u8).to_string()),
|
||||
("a", (self.1 as u8).to_string()),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,27 +77,24 @@ pub mod builders {
|
|||
pub struct BeatmapRequestBuilder {
|
||||
kind: BeatmapRequestKind,
|
||||
since: Option<DateTime<Utc>>,
|
||||
mode: Mode,
|
||||
converted: bool,
|
||||
mode: Option<(Mode, /* Converted */ bool)>,
|
||||
}
|
||||
impl BeatmapRequestBuilder {
|
||||
pub(crate) fn new(kind: BeatmapRequestKind) -> Self {
|
||||
BeatmapRequestBuilder {
|
||||
kind,
|
||||
since: None,
|
||||
mode: Mode::Std,
|
||||
converted: false,
|
||||
mode: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn since(&mut self, since: DateTime<Utc>) -> &Self {
|
||||
pub fn since(&mut self, since: DateTime<Utc>) -> &mut Self {
|
||||
self.since = Some(since);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn mode(&mut self, mode: Mode, converted: bool) -> &Self {
|
||||
self.mode = mode;
|
||||
self.converted = converted;
|
||||
pub fn mode(&mut self, mode: Mode, converted: bool) -> &mut Self {
|
||||
self.mode = Some((mode, converted));
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -104,14 +104,6 @@ pub mod builders {
|
|||
.query(&self.kind.to_query())
|
||||
.query(&self.since.map(|v| ("since", v)).to_query())
|
||||
.query(&self.mode.to_query())
|
||||
.query(
|
||||
&(if self.converted {
|
||||
Some(("a", "1".to_owned()))
|
||||
} else {
|
||||
None
|
||||
})
|
||||
.to_query(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
9
youmubot-osu/src/test.rs
Normal file
9
youmubot-osu/src/test.rs
Normal file
File diff suppressed because one or more lines are too long
|
@ -14,6 +14,9 @@ chrono = "0.4.9"
|
|||
rand = "0.7.2"
|
||||
static_assertions = "1.1.0"
|
||||
reqwest = "0.9.24"
|
||||
regex = "1"
|
||||
lazy_static = "1"
|
||||
youmubot-osu = { path = "../youmubot-osu" }
|
||||
|
||||
[dependencies.rustbreak]
|
||||
version = "2.0.0-rc3"
|
||||
|
|
|
@ -12,6 +12,7 @@ mod args;
|
|||
pub mod admin;
|
||||
pub mod community;
|
||||
pub mod fun;
|
||||
pub mod osu;
|
||||
|
||||
pub use admin::ADMIN_GROUP;
|
||||
pub use community::COMMUNITY_GROUP;
|
||||
|
|
303
youmubot/src/commands/osu/mod.rs
Normal file
303
youmubot/src/commands/osu/mod.rs
Normal file
|
@ -0,0 +1,303 @@
|
|||
use crate::commands::args::Duration;
|
||||
use crate::http;
|
||||
use lazy_static::lazy_static;
|
||||
use regex::Regex;
|
||||
use serenity::{
|
||||
builder::CreateEmbed,
|
||||
framework::standard::{macros::group, CommandResult},
|
||||
model::{channel::Message, id::ChannelId},
|
||||
prelude::*,
|
||||
utils::MessageBuilder,
|
||||
};
|
||||
use youmubot_osu::{
|
||||
models::{Beatmap, Mode},
|
||||
request::BeatmapRequestKind,
|
||||
};
|
||||
|
||||
group!({
|
||||
name: "osu",
|
||||
options: {
|
||||
prefix: "osu",
|
||||
description: "osu! related commands.",
|
||||
},
|
||||
commands: [],
|
||||
});
|
||||
|
||||
lazy_static! {
|
||||
static ref OLD_LINK_REGEX: Regex = Regex::new(
|
||||
r"https?://osu\.ppy\.sh/(?P<link_type>s|b)/(?P<id>\d+)(?:[\&\?]m=(?P<mode>\d))?(?:\+(?P<mods>[A-Z]+))?"
|
||||
).unwrap();
|
||||
static ref NEW_LINK_REGEX: Regex = Regex::new(
|
||||
r"https?://osu\.ppy\.sh/beatmapsets/(?P<set_id>\d+)/?(?:\#(?P<mode>osu|taiko|fruits|mania)(?:/(?P<beatmap_id>\d+)|/?))?(?:\+(?P<mods>[A-Z]+))?"
|
||||
).unwrap();
|
||||
}
|
||||
|
||||
pub fn hook(ctx: &mut Context, msg: &Message) -> () {
|
||||
Some(msg)
|
||||
.filter(|&m| !m.author.bot) // Don't react to bot messages
|
||||
.map(|m| {
|
||||
if let Err(v) = handle_old_links(ctx, &m.content, m.channel_id) {
|
||||
println!("Error on old link handling: {:?}", v);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
fn handle_old_links(ctx: &mut Context, content: impl AsRef<str>, ch: ChannelId) -> CommandResult {
|
||||
let data = ctx.data.write();
|
||||
let reqwest = data.get::<http::HTTP>().unwrap();
|
||||
let osu = data.get::<http::Osu>().unwrap();
|
||||
for capture in OLD_LINK_REGEX.captures_iter(content.as_ref()) {
|
||||
let req_type = capture.name("link_type").unwrap().as_str();
|
||||
let req = match req_type {
|
||||
"b" => BeatmapRequestKind::Beatmap(capture["id"].parse()?),
|
||||
"s" => BeatmapRequestKind::Beatmapset(capture["id"].parse()?),
|
||||
_ => continue,
|
||||
};
|
||||
let mode = capture
|
||||
.name("mode")
|
||||
.map(|v| v.as_str().parse())
|
||||
.transpose()?
|
||||
.and_then(|v| {
|
||||
Some(match v {
|
||||
0 => Mode::Std,
|
||||
1 => Mode::Taiko,
|
||||
2 => Mode::Catch,
|
||||
3 => Mode::Mania,
|
||||
_ => return None,
|
||||
})
|
||||
});
|
||||
let mut beatmaps = osu.beatmaps(reqwest, req, |v| match mode {
|
||||
Some(m) => v.mode(m, true),
|
||||
None => v,
|
||||
})?;
|
||||
match req_type {
|
||||
"b" => {
|
||||
for beatmap in beatmaps.iter() {
|
||||
if let Err(v) = ch.send_message(&ctx, |m| {
|
||||
m.content(
|
||||
MessageBuilder::new()
|
||||
.push("Beatmap information for ")
|
||||
.push_mono_safe(&capture[0])
|
||||
.build(),
|
||||
)
|
||||
.embed(|b| beatmap_embed(beatmap, mode.unwrap_or(beatmap.mode), b))
|
||||
}) {
|
||||
println!("Error on printing beatmap {}: {:?}", beatmap.beatmap_id, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
"s" => {
|
||||
beatmaps.sort_by(|a, b| {
|
||||
(mode.unwrap_or(a.mode) as u8, a.difficulty.stars)
|
||||
.partial_cmp(&(mode.unwrap_or(b.mode) as u8, b.difficulty.stars))
|
||||
.unwrap()
|
||||
});
|
||||
ch.send_message(&ctx, |m| {
|
||||
m.content(
|
||||
MessageBuilder::new()
|
||||
.push("Beatmapset information for ")
|
||||
.push_mono_safe(&capture[0])
|
||||
.build(),
|
||||
)
|
||||
.embed(|b| beatmapset_embed(&beatmaps, mode, b))
|
||||
})?;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
const NEW_MODE_NAMES: [&'static str; 4] = ["osu", "taiko", "fruits", "mania"];
|
||||
|
||||
fn format_mode(actual: Mode, original: Mode) -> String {
|
||||
if actual == original {
|
||||
format!("{}", actual)
|
||||
} else {
|
||||
format!("{} (converted)", actual)
|
||||
}
|
||||
}
|
||||
|
||||
fn beatmap_embed<'a>(b: &'_ Beatmap, m: Mode, c: &'a mut CreateEmbed) -> &'a mut CreateEmbed {
|
||||
c.title(
|
||||
MessageBuilder::new()
|
||||
.push_bold_safe(&b.artist)
|
||||
.push(" - ")
|
||||
.push_bold_safe(&b.title)
|
||||
.push(" [")
|
||||
.push_bold_safe(&b.difficulty_name)
|
||||
.push("]")
|
||||
.build(),
|
||||
)
|
||||
.author(|a| {
|
||||
a.name(&b.creator)
|
||||
.url(format!("https://osu.ppy.sh/users/{}", b.creator_id))
|
||||
.icon_url(format!("https://a.ppy.sh/{}", b.creator_id))
|
||||
})
|
||||
.url(format!(
|
||||
"https://osu.ppy.sh/beatmapsets/{}/#{}/{}",
|
||||
b.beatmapset_id, NEW_MODE_NAMES[b.mode as usize], b.beatmap_id
|
||||
))
|
||||
.thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id))
|
||||
.image(format!(
|
||||
"https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg",
|
||||
b.beatmapset_id
|
||||
))
|
||||
.color(0xffb6c1)
|
||||
.field(
|
||||
"Star Difficulty",
|
||||
format!("{:.2}⭐", b.difficulty.stars),
|
||||
false,
|
||||
)
|
||||
.field(
|
||||
"Length",
|
||||
MessageBuilder::new()
|
||||
.push_bold_safe(Duration(b.total_length))
|
||||
.push(" (")
|
||||
.push_bold_safe(Duration(b.drain_length))
|
||||
.push(" drain)")
|
||||
.build(),
|
||||
false,
|
||||
)
|
||||
.field("Circle Size", format!("{:.1}", b.difficulty.cs), true)
|
||||
.field("Approach Rate", format!("{:.1}", b.difficulty.ar), true)
|
||||
.field(
|
||||
"Overall Difficulty",
|
||||
format!("{:.1}", b.difficulty.od),
|
||||
true,
|
||||
)
|
||||
.field("HP Drain", format!("{:.1}", b.difficulty.hp), true)
|
||||
.field("BPM", b.bpm.round(), true)
|
||||
.fields(b.difficulty.max_combo.map(|v| ("Max combo", v, true)))
|
||||
.field("Mode", format_mode(m, b.mode), true)
|
||||
.fields(b.source.as_ref().map(|v| ("Source", v, true)))
|
||||
.field(
|
||||
"Tags",
|
||||
b.tags
|
||||
.iter()
|
||||
.map(|v| MessageBuilder::new().push_mono_safe(v).build())
|
||||
.take(10)
|
||||
.chain(std::iter::once("...".to_owned()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
false,
|
||||
)
|
||||
.description(
|
||||
MessageBuilder::new()
|
||||
.push_line({
|
||||
let link = format!("https://osu.ppy.sh/beatmapsets/{}/download", b.beatmap_id);
|
||||
format!(
|
||||
"Download: [[Link]]({}) [[No Video]]({}?noVideo=1)",
|
||||
link, link
|
||||
)
|
||||
})
|
||||
.push_line(format!(
|
||||
"Beatmapset: https://osu.ppy.sh/beatmapsets/{}/#{}",
|
||||
b.beatmapset_id, NEW_MODE_NAMES[b.mode as usize],
|
||||
))
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
const MAX_DIFFS: usize = 25 - 4;
|
||||
|
||||
fn beatmapset_embed<'a>(
|
||||
bs: &'_ [Beatmap],
|
||||
m: Option<Mode>,
|
||||
c: &'a mut CreateEmbed,
|
||||
) -> &'a mut CreateEmbed {
|
||||
let too_many_diffs = bs.len() > MAX_DIFFS;
|
||||
let b: &Beatmap = &bs[0];
|
||||
c.title(
|
||||
MessageBuilder::new()
|
||||
.push_bold_safe(&b.artist)
|
||||
.push(" - ")
|
||||
.push_bold_safe(&b.title)
|
||||
.build(),
|
||||
)
|
||||
.author(|a| {
|
||||
a.name(&b.creator)
|
||||
.url(format!("https://osu.ppy.sh/users/{}", b.creator_id))
|
||||
.icon_url(format!("https://a.ppy.sh/{}", b.creator_id))
|
||||
})
|
||||
.url(format!(
|
||||
"https://osu.ppy.sh/beatmapsets/{}",
|
||||
b.beatmapset_id,
|
||||
))
|
||||
// .thumbnail(format!("https://b.ppy.sh/thumb/{}l.jpg", b.beatmapset_id))
|
||||
.image(format!(
|
||||
"https://assets.ppy.sh/beatmaps/{}/covers/cover.jpg",
|
||||
b.beatmapset_id
|
||||
))
|
||||
.color(0xffb6c1)
|
||||
.description(
|
||||
MessageBuilder::new()
|
||||
.push_line({
|
||||
let link = format!("https://osu.ppy.sh/beatmapsets/{}/download", b.beatmap_id);
|
||||
format!(
|
||||
"Download: [[Link]]({}) [[No Video]]({}?noVideo=1)",
|
||||
link, link
|
||||
)
|
||||
})
|
||||
.build(),
|
||||
)
|
||||
.field(
|
||||
"Length",
|
||||
MessageBuilder::new()
|
||||
.push_bold_safe(Duration(b.total_length))
|
||||
.build(),
|
||||
true,
|
||||
)
|
||||
.field("BPM", b.bpm.round(), true)
|
||||
.fields(b.source.as_ref().map(|v| ("Source", v, false)))
|
||||
.field(
|
||||
"Tags",
|
||||
b.tags
|
||||
.iter()
|
||||
.map(|v| MessageBuilder::new().push_mono_safe(v).build())
|
||||
.take(10)
|
||||
.chain(std::iter::once("...".to_owned()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" "),
|
||||
false,
|
||||
)
|
||||
.footer(|f| {
|
||||
if too_many_diffs {
|
||||
f.text(format!(
|
||||
"This map has {} diffs, we are showing the first {}.",
|
||||
bs.len(),
|
||||
MAX_DIFFS
|
||||
))
|
||||
} else {
|
||||
f
|
||||
}
|
||||
})
|
||||
.fields(bs.iter().take(MAX_DIFFS).map(|b: &Beatmap| {
|
||||
(
|
||||
format!("[{}]", b.difficulty_name),
|
||||
MessageBuilder::new()
|
||||
.push(format!(
|
||||
"[[Link]](https://osu.ppy.sh/beatmapsets/{}/#{}/{})",
|
||||
b.beatmapset_id,
|
||||
NEW_MODE_NAMES[m.unwrap_or(b.mode) as usize],
|
||||
b.beatmap_id
|
||||
))
|
||||
.push(", ")
|
||||
.push_bold(format!("{:.2}⭐", b.difficulty.stars))
|
||||
.push(", ")
|
||||
.push_bold_line(format_mode(m.unwrap_or(b.mode), b.mode))
|
||||
.push("CS")
|
||||
.push_bold(format!("{:.1}", b.difficulty.cs))
|
||||
.push(", AR")
|
||||
.push_bold(format!("{:.1}", b.difficulty.ar))
|
||||
.push(", OD")
|
||||
.push_bold(format!("{:.1}", b.difficulty.od))
|
||||
.push(", HP")
|
||||
.push_bold(format!("{:.1}", b.difficulty.hp))
|
||||
.push(", ⌛ ")
|
||||
.push_bold(format!("{}", Duration(b.drain_length)))
|
||||
.build(),
|
||||
false,
|
||||
)
|
||||
}))
|
||||
}
|
|
@ -1,7 +1,14 @@
|
|||
use serenity::prelude::TypeMapKey;
|
||||
use youmubot_osu::Client as OsuClient;
|
||||
|
||||
pub(crate) struct HTTP;
|
||||
|
||||
impl TypeMapKey for HTTP {
|
||||
type Value = reqwest::Client;
|
||||
}
|
||||
|
||||
pub(crate) struct Osu;
|
||||
|
||||
impl TypeMapKey for Osu {
|
||||
type Value = OsuClient;
|
||||
}
|
||||
|
|
|
@ -3,20 +3,27 @@ use dotenv::var;
|
|||
use reqwest;
|
||||
use serenity::{
|
||||
framework::standard::{DispatchError, StandardFramework},
|
||||
model::gateway,
|
||||
model::{channel::Message, gateway},
|
||||
prelude::*,
|
||||
};
|
||||
use youmubot_osu::Client as OsuClient;
|
||||
|
||||
mod commands;
|
||||
mod db;
|
||||
mod http;
|
||||
|
||||
const MESSAGE_HOOKS: [fn(&mut Context, &Message) -> (); 1] = [commands::osu::hook];
|
||||
|
||||
struct Handler;
|
||||
|
||||
impl EventHandler for Handler {
|
||||
fn ready(&self, _: Context, ready: gateway::Ready) {
|
||||
println!("{} is connected!", ready.user.name);
|
||||
}
|
||||
|
||||
fn message(&self, mut ctx: Context, message: Message) {
|
||||
MESSAGE_HOOKS.iter().for_each(|f| f(&mut ctx, &message));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
|
@ -39,6 +46,9 @@ fn main() {
|
|||
{
|
||||
let mut data = client.data.write();
|
||||
data.insert::<http::HTTP>(reqwest::Client::new());
|
||||
data.insert::<http::Osu>(OsuClient::new(
|
||||
var("OSU_API_KEY").expect("Please set OSU_API_KEY as osu! api key."),
|
||||
));
|
||||
}
|
||||
|
||||
// Create handler threads
|
||||
|
|
Loading…
Add table
Reference in a new issue