Pull info from an attached .osu/.osz file (#18)

This commit is contained in:
Natsu Kagami 2022-04-29 19:31:04 -04:00 committed by GitHub
parent acc0e339a0
commit 2e3c6f61be
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 631 additions and 125 deletions

315
Cargo.lock generated
View file

@ -14,9 +14,21 @@ dependencies = [
[[package]]
name = "adler"
version = "0.2.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aes"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
"opaque-debug",
]
[[package]]
name = "ahash"
@ -121,6 +133,12 @@ version = "0.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
[[package]]
name = "base64ct"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a32fd6af2b5827bce66c29053ba0e7c42b9dcab01835835058558c10851a46b"
[[package]]
name = "bincode"
version = "1.3.1"
@ -158,6 +176,15 @@ dependencies = [
"generic-array",
]
[[package]]
name = "block-buffer"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
dependencies = [
"generic-array",
]
[[package]]
name = "build_const"
version = "0.2.1"
@ -172,9 +199,9 @@ checksum = "099e596ef14349721d9016f6b80dd3419ea1bf289ab9b44df8e4dfd3a005d5d9"
[[package]]
name = "byteorder"
version = "1.4.2"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
@ -188,11 +215,35 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040"
[[package]]
name = "bzip2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
dependencies = [
"bzip2-sys",
"libc",
]
[[package]]
name = "bzip2-sys"
version = "0.1.11+1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc"
dependencies = [
"cc",
"libc",
"pkg-config",
]
[[package]]
name = "cc"
version = "1.0.66"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
dependencies = [
"jobserver",
]
[[package]]
name = "cfg-if"
@ -210,10 +261,19 @@ dependencies = [
"num-integer",
"num-traits",
"serde",
"time",
"time 0.1.43",
"winapi",
]
[[package]]
name = "cipher"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ee52072ec15386f770805afd189a01c8841be8696bed250fa2f13c4c0d6dfb7"
dependencies = [
"generic-array",
]
[[package]]
name = "codeforces"
version = "0.3.1"
@ -239,6 +299,12 @@ dependencies = [
"syn",
]
[[package]]
name = "constant_time_eq"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation"
version = "0.9.1"
@ -255,6 +321,15 @@ version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]]
name = "cpufeatures"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b"
dependencies = [
"libc",
]
[[package]]
name = "cpuid-bool"
version = "0.1.2"
@ -272,9 +347,9 @@ dependencies = [
[[package]]
name = "crc32fast"
version = "1.2.1"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a"
checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d"
dependencies = [
"cfg-if",
]
@ -301,15 +376,24 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.3"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38"
dependencies = [
"autocfg",
"cfg-if",
"lazy_static",
]
[[package]]
name = "crypto-common"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "dashmap"
version = "4.0.2"
@ -329,6 +413,17 @@ dependencies = [
"generic-array",
]
[[package]]
name = "digest"
version = "0.10.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506"
dependencies = [
"block-buffer 0.10.2",
"crypto-common",
"subtle",
]
[[package]]
name = "dotenv"
version = "0.15.0"
@ -374,9 +469,9 @@ dependencies = [
[[package]]
name = "flate2"
version = "1.0.20"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd3aec53de10fe96d7d8c565eb17f2c687bb5518a2ec453b5b1252964526abe0"
checksum = "b39522e96686d38f4bc984b9198e3a0613264abaebaff2c5c918bfa6b6da09af"
dependencies = [
"cfg-if",
"crc32fast",
@ -625,6 +720,15 @@ 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 0.10.3",
]
[[package]]
name = "http"
version = "0.2.3"
@ -633,7 +737,7 @@ checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747"
dependencies = [
"bytes 1.0.1",
"fnv",
"itoa",
"itoa 0.4.7",
]
[[package]]
@ -682,7 +786,7 @@ dependencies = [
"http-body",
"httparse",
"httpdate",
"itoa",
"itoa 0.4.7",
"pin-project 1.0.5",
"socket2",
"tokio",
@ -770,6 +874,21 @@ version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736"
[[package]]
name = "itoa"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
[[package]]
name = "jobserver"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa"
dependencies = [
"libc",
]
[[package]]
name = "js-sys"
version = "0.3.47"
@ -800,9 +919,9 @@ dependencies = [
[[package]]
name = "libc"
version = "0.2.85"
version = "0.2.125"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3"
checksum = "5916d2ae698f6de9bfb891ad7a8d65c09d232dc58cc4ac433c7da3b2fd84bc2b"
[[package]]
name = "libsqlite3-sys"
@ -875,12 +994,11 @@ dependencies = [
[[package]]
name = "miniz_oxide"
version = "0.4.3"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
checksum = "d2b29bd4bc3f33391105ebee3589c19197c4271e3e5a9ec9bfe8127eeff8f082"
dependencies = [
"adler",
"autocfg",
]
[[package]]
@ -984,6 +1102,15 @@ dependencies = [
"libc",
]
[[package]]
name = "num_threads"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aba1801fb138d8e85e11d0fc70baf4fe1cdfffda7c6cd34a854905df588e5ed0"
dependencies = [
"libc",
]
[[package]]
name = "once_cell"
version = "1.5.2"
@ -1029,6 +1156,14 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "osuparse"
version = "2.0.1"
source = "git+https://github.com/eltrufas/osuparse?rev=ad8f6e5e7771e7cbaa2ec96c376558f9731139af#ad8f6e5e7771e7cbaa2ec96c376558f9731139af"
dependencies = [
"unicase",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
@ -1054,6 +1189,29 @@ dependencies = [
"winapi",
]
[[package]]
name = "password-hash"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d791538a6dcc1e7cb7fe6f6b58aca40e7f79403c45b2bc274008b5e647af1d8"
dependencies = [
"base64ct",
"rand_core 0.6.1",
"subtle",
]
[[package]]
name = "pbkdf2"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "271779f35b581956db91a3e55737327a03aa051e90b1c47aeb189508533adfd7"
dependencies = [
"digest 0.10.3",
"hmac",
"password-hash",
"sha2 0.10.2",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -1451,7 +1609,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
dependencies = [
"indexmap",
"itoa",
"itoa 0.4.7",
"ryu",
"serde",
]
@ -1463,7 +1621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9"
dependencies = [
"form_urlencoded",
"itoa",
"itoa 0.4.7",
"ryu",
"serde",
]
@ -1513,26 +1671,48 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4b312c3731e3fe78a185e6b9b911a7aa715b8e31cce117975219aab2acf285d"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if",
"cpuid-bool",
"digest",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha1"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.3",
]
[[package]]
name = "sha2"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa827a14b29ab7f44778d14a88d3cb76e949c45083f7dbfa507d0cb699dc12de"
dependencies = [
"block-buffer",
"block-buffer 0.9.0",
"cfg-if",
"cpuid-bool",
"digest",
"digest 0.9.0",
"opaque-debug",
]
[[package]]
name = "sha2"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55deaec60f81eefe3cce0dc50bda92d6d8e88f2a27df7c5033b42afeb1ed2676"
dependencies = [
"cfg-if",
"cpufeatures",
"digest 0.10.3",
]
[[package]]
name = "slab"
version = "0.4.2"
@ -1616,7 +1796,7 @@ dependencies = [
"futures-util",
"hashlink",
"hex",
"itoa",
"itoa 0.4.7",
"libc",
"libsqlite3-sys",
"log",
@ -1626,7 +1806,7 @@ dependencies = [
"percent-encoding",
"rustls",
"serde",
"sha2",
"sha2 0.9.3",
"smallvec",
"sqlformat",
"sqlx-rt",
@ -1655,7 +1835,7 @@ dependencies = [
"quote",
"serde",
"serde_json",
"sha2",
"sha2 0.9.3",
"sqlx-core",
"sqlx-rt",
"syn",
@ -1689,6 +1869,12 @@ dependencies = [
"unicode-normalization",
]
[[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.60"
@ -1768,6 +1954,24 @@ dependencies = [
"winapi",
]
[[package]]
name = "time"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2702e08a7a860f005826c6815dcac101b19b5eb330c27fe4a5928fec1d20ddd"
dependencies = [
"itoa 1.0.1",
"libc",
"num_threads",
"time-macros",
]
[[package]]
name = "time-macros"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792"
[[package]]
name = "tinyvec"
version = "1.1.1"
@ -1938,9 +2142,9 @@ checksum = "335fb14412163adc9ed4a3e53335afaa7a4b72bdd122e5f72f51b8f1db1a131e"
[[package]]
name = "typenum"
version = "1.12.0"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
[[package]]
name = "unicase"
@ -2303,6 +2507,7 @@ dependencies = [
"chrono",
"dashmap",
"lazy_static",
"osuparse",
"regex",
"reqwest",
"rosu-pp",
@ -2312,6 +2517,7 @@ dependencies = [
"youmubot-db",
"youmubot-db-sql",
"youmubot-prelude",
"zip",
]
[[package]]
@ -2330,3 +2536,52 @@ dependencies = [
"youmubot-db",
"youmubot-db-sql",
]
[[package]]
name = "zip"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf225bcf73bb52cbb496e70475c7bd7a3f769df699c0020f6c7bd9a96dcf0b8d"
dependencies = [
"aes",
"byteorder",
"bzip2",
"constant_time_eq",
"crc32fast",
"crossbeam-utils",
"flate2",
"hmac",
"pbkdf2",
"sha1",
"time 0.3.9",
"zstd",
]
[[package]]
name = "zstd"
version = "0.10.0+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b1365becbe415f3f0fcd024e2f7b45bacfb5bdd055f0dc113571394114e7bdd"
dependencies = [
"zstd-safe",
]
[[package]]
name = "zstd-safe"
version = "4.1.4+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f7cd17c9af1a4d6c24beb1cc54b17e2ef7b593dc92f19e9d9acad8b182bbaee"
dependencies = [
"libc",
"zstd-sys",
]
[[package]]
name = "zstd-sys"
version = "1.6.3+zstd.1.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc49afa5c8d634e75761feda8c592051e7eeb4683ba827211eb0d731d3402ea8"
dependencies = [
"cc",
"libc",
]

6
flake.lock generated
View file

@ -35,11 +35,11 @@
},
"nixpkgs": {
"locked": {
"lastModified": 1648746709,
"narHash": "sha256-MSwb5UZuplAkwhZrA8TsNEX57Dy8tJODtF82aKY+amA=",
"lastModified": 1651263162,
"narHash": "sha256-OOw4ll+7Ql8Fh4NRWWXxnuSxFGD6rrLB3SdGtZrfy4I=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "9cd1fa9bcef3b578a732a391436aad804e03e3ca",
"rev": "e850f1e4d0a645d2ec4cd5fcc427254fd4cec79a",
"type": "github"
},
"original": {

View file

@ -34,16 +34,21 @@
defaultApp = apps.youmubot;
# `nix develop`
devShell = pkgs.mkShell {
nativeBuildInputs =
(with pkgs; [ rustc cargo rust-analyzer rustfmt ]) ++
nixpkgs.lib.optionals (nixpkgs.lib.strings.hasSuffix "darwin" system) (with pkgs; [
libiconv
darwin.apple_sdk.frameworks.Security
]);
};
devShell = pkgs.mkShell
{
nativeBuildInputs =
(with pkgs; [ rustc cargo rust-analyzer rustfmt ])
++ nixpkgs.lib.optionals (nixpkgs.lib.strings.hasSuffix "darwin" system) (with pkgs; [
libiconv
darwin.apple_sdk.frameworks.Security
])
++ nixpkgs.lib.optionals (nixpkgs.lib.strings.hasSuffix "linux" system) (with pkgs; [
pkg-config
openssl
]);
};
# module
nixosModule = import ./module.nix defaultPackage;
});
}

View file

@ -7,16 +7,18 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
serenity = "0.10"
chrono = "0.4"
reqwest = "0.11"
serde = { version = "1.0", features = ["derive"] }
bitflags = "1"
lazy_static = "1"
regex = "1"
rosu-pp = "0.4"
dashmap = "4"
bincode = "1"
bitflags = "1"
chrono = "0.4"
dashmap = "4"
lazy_static = "1"
osuparse = { git = "https://github.com/eltrufas/osuparse", rev = "ad8f6e5e7771e7cbaa2ec96c376558f9731139af" }
regex = "1"
reqwest = "0.11"
rosu-pp = "0.4"
serde = { version = "1.0", features = ["derive"] }
serenity = "0.10"
zip = "0.6.2"
youmubot-db = { path = "../youmubot-db" }
youmubot-db-sql = { path = "../youmubot-db-sql" }

View file

@ -1,9 +1,10 @@
use super::BeatmapWithMode;
use crate::{
discord::oppai_cache::{Accuracy, BeatmapContent, BeatmapInfo, BeatmapInfoWithPP},
models::{Beatmap, Mode, Mods, Rank, Score, User},
models::{Beatmap, Difficulty, Mode, Mods, Rank, Score, User},
};
use serenity::{builder::CreateEmbed, utils::MessageBuilder};
use std::time::Duration;
use youmubot_prelude::*;
/// Writes a number grouped in groups of 3.
@ -55,42 +56,54 @@ fn beatmap_description(b: &Beatmap) -> String {
.build()
}
pub fn beatmap_embed<'a>(
b: &'_ Beatmap,
pub fn beatmap_offline_embed(
b: &'_ crate::discord::oppai_cache::BeatmapContent,
m: Mode,
mods: Mods,
info: Option<BeatmapInfoWithPP>,
c: &'a mut CreateEmbed,
) -> &'a mut CreateEmbed {
let mod_str = if mods == Mods::NOMOD {
"".to_owned()
) -> Result<Box<dyn FnOnce(&mut CreateEmbed) -> &mut CreateEmbed + Send + Sync>> {
let bm = b.content.clone();
let metadata = b.metadata.clone();
let (info, pp) = b.get_possible_pp_with(mods)?;
let total_length = if bm.hit_objects.len() >= 1 {
Duration::from_millis(
(bm.hit_objects.last().unwrap().end_time() - bm.hit_objects.first().unwrap().start_time)
as u64,
)
} else {
format!(" {}", mods)
Duration::from_secs(0)
};
let diff = b
.difficulty
.apply_mods(mods, info.map(|(v, _)| v.stars as f64));
c.title(
MessageBuilder::new()
.push_bold_safe(&b.artist)
.push(" - ")
.push_bold_safe(&b.title)
.push(" [")
.push_bold_safe(&b.difficulty_name)
.push("]")
.push(&mod_str)
.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(b.link())
.image(b.cover_url())
.color(0xffb6c1)
.fields(info.map(|(_, pp)| {
(
let diff = Difficulty {
stars: info.stars,
aim: None, // TODO: this is currently unused
speed: None, // TODO: this is currently unused
cs: bm.cs as f64,
od: bm.od as f64,
ar: bm.ar as f64,
hp: bm.hp as f64,
count_normal: bm.n_circles as u64,
count_slider: bm.n_sliders as u64,
count_spinner: bm.n_spinners as u64,
max_combo: Some(info.max_combo as u64),
bpm: bm.bpm(),
drain_length: total_length, // It's hard to calculate so maybe just skip...
total_length,
}
.apply_mods(mods, Some(info.stars));
Ok(Box::new(move |c: &mut CreateEmbed| {
c.title(beatmap_title(
&metadata.artist,
&metadata.title,
&metadata.version,
mods,
))
.author(|a| {
a.name(&metadata.creator)
.url(format!("https://osu.ppy.sh/users/{}", metadata.creator))
})
.color(0xffb6c1)
.field(
"Calculated pp",
format!(
"95%: **{:.2}**pp, 98%: **{:.2}**pp, 99%: **{:.2}**pp, 100%: **{:.2}**pp",
@ -98,15 +111,73 @@ pub fn beatmap_embed<'a>(
),
false,
)
.field("Information", diff.format_info(m, mods, None), false)
// .description(beatmap_description(b))
}))
.field("Information", diff.format_info(m, mods, b), false)
.description(beatmap_description(b))
.footer(|f| {
if info.is_none() && mods != Mods::NOMOD {
f.text("Star difficulty not reflecting mods applied.");
}
f
})
}
// Some helper functions here
/// Create a properly formatted beatmap title, in the `Artist - Title [Difficulty] +mods` format.
fn beatmap_title(
artist: impl AsRef<str>,
title: impl AsRef<str>,
difficulty: impl AsRef<str>,
mods: Mods,
) -> String {
let mod_str = if mods == Mods::NOMOD {
"".to_owned()
} else {
format!(" {}", mods)
};
MessageBuilder::new()
.push_bold_safe(artist.as_ref())
.push(" - ")
.push_bold_safe(title.as_ref())
.push(" [")
.push_bold_safe(difficulty.as_ref())
.push("]")
.push(&mod_str)
.build()
}
pub fn beatmap_embed<'a>(
b: &'_ Beatmap,
m: Mode,
mods: Mods,
info: Option<BeatmapInfoWithPP>,
c: &'a mut CreateEmbed,
) -> &'a mut CreateEmbed {
let diff = b
.difficulty
.apply_mods(mods, info.map(|(v, _)| v.stars as f64));
c.title(beatmap_title(&b.artist, &b.title, &b.difficulty_name, mods))
.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(b.link())
.image(b.cover_url())
.color(0xffb6c1)
.fields(info.map(|(_, pp)| {
(
"Calculated pp",
format!(
"95%: **{:.2}**pp, 98%: **{:.2}**pp, 99%: **{:.2}**pp, 100%: **{:.2}**pp",
pp[0], pp[1], pp[2], pp[3]
),
false,
)
}))
.field("Information", diff.format_info(m, mods, b), false)
.description(beatmap_description(b))
.footer(|f| {
if info.is_none() && mods != Mods::NOMOD {
f.text("Star difficulty not reflecting mods applied.");
}
f
})
}
const MAX_DIFFS: usize = 25 - 4;

View file

@ -5,7 +5,7 @@ use crate::{
};
use lazy_static::lazy_static;
use regex::Regex;
use serenity::{model::channel::Message, utils::MessageBuilder};
use serenity::{builder::CreateEmbed, model::channel::Message, utils::MessageBuilder};
use std::str::FromStr;
use youmubot_prelude::*;
@ -23,6 +23,101 @@ lazy_static! {
).unwrap();
}
pub fn dot_osu_hook<'a>(
ctx: &'a Context,
msg: &'a Message,
) -> std::pin::Pin<Box<dyn future::Future<Output = Result<()>> + Send + 'a>> {
Box::pin(async move {
if msg.author.bot {
return Ok(());
}
// Take all the .osu attachments
let mut osu_embeds = msg
.attachments
.iter()
.filter(
|a| a.filename.ends_with(".osu") && a.size < 1024 * 1024, /* 1mb */
)
.map(|attachment| {
let url = attachment.url.clone();
async move {
let data = ctx.data.read().await;
let oppai = data.get::<BeatmapCache>().unwrap();
let (beatmap, _) = oppai.download_beatmap_from_url(&url, None).await.ok()?;
let embed_fn = crate::discord::embeds::beatmap_offline_embed(
&beatmap,
Mode::from(beatmap.content.mode as u8), /*For now*/
msg.content.trim().parse().unwrap_or(Mods::NOMOD),
)
.ok()?;
let mut create_embed = CreateEmbed::default();
embed_fn(&mut create_embed);
Some(create_embed)
}
})
.collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| future::ready(v))
.collect::<Vec<_>>()
.await;
let osz_embeds = msg
.attachments
.iter()
.filter(
|a| a.filename.ends_with(".osz") && a.size < 20 * 1024 * 1024, /* 20mb */
)
.map(|attachment| {
let url = attachment.url.clone();
async move {
let data = ctx.data.read().await;
let oppai = data.get::<BeatmapCache>().unwrap();
let beatmaps = oppai.download_osz_from_url(&url).await.pls_ok()?;
Some(
beatmaps
.into_iter()
.filter_map(|beatmap| {
let embed_fn = crate::discord::embeds::beatmap_offline_embed(
&beatmap,
Mode::from(beatmap.content.mode as u8), /*For now*/
msg.content.trim().parse().unwrap_or(Mods::NOMOD),
)
.pls_ok()?;
let mut create_embed = CreateEmbed::default();
embed_fn(&mut create_embed);
Some(create_embed)
})
.collect::<Vec<_>>(),
)
}
})
.collect::<stream::FuturesUnordered<_>>()
.filter_map(|v| future::ready(v))
.filter(|v| future::ready(v.len() > 0))
.collect::<Vec<_>>()
.await
.concat();
osu_embeds.extend(osz_embeds);
if osu_embeds.len() > 0 {
msg.channel_id
.send_message(ctx, |f| {
f.reference_message(msg)
.content(format!("{} attached beatmaps found", osu_embeds.len()))
.add_embeds(osu_embeds)
})
.await
.ok();
}
Ok(())
})
}
pub fn hook<'a>(
ctx: &'a Context,
msg: &'a Message,

View file

@ -31,7 +31,7 @@ mod server_rank;
use db::OsuUser;
use db::{OsuLastBeatmap, OsuSavedUsers, OsuUserBests};
use embeds::{beatmap_embed, score_embed, user_embed};
pub use hook::hook;
pub use hook::{dot_osu_hook, hook};
use server_rank::{SERVER_RANK_COMMAND, UPDATE_LEADERBOARD_COMMAND};
/// The osu! client.

View file

@ -1,5 +1,7 @@
use crate::mods::Mods;
use osuparse::MetadataSection;
use rosu_pp::{Beatmap, BeatmapExt};
use std::io::Read;
use std::sync::Arc;
use youmubot_db_sql::{models::osu as models, Pool};
use youmubot_prelude::*;
@ -7,8 +9,9 @@ use youmubot_prelude::*;
/// the information collected from a download/Oppai request.
#[derive(Debug)]
pub struct BeatmapContent {
id: u64,
content: Arc<Beatmap>,
id: Option<u64>,
pub metadata: MetadataSection,
pub content: Arc<Beatmap>,
}
/// the output of "one" oppai run.
@ -128,25 +131,82 @@ impl BeatmapCache {
BeatmapCache { client, pool }
}
async fn download_beatmap(&self, id: u64) -> Result<BeatmapContent> {
let content = self
fn parse_beatmap(content: impl AsRef<str>, id: Option<u64>) -> Result<BeatmapContent> {
let content = content.as_ref();
let metadata = osuparse::parse_beatmap(content)
.map_err(|e| Error::msg(format!("Cannot parse metadata: {:?}", e)))?
.metadata;
Ok(BeatmapContent {
id,
metadata,
content: Arc::new(Beatmap::parse(content.as_bytes())?),
})
}
/// Downloads the given osz and try to parse every osu file in there (limited to <1mb files)
pub async fn download_osz_from_url(
&self,
url: impl reqwest::IntoUrl,
) -> Result<Vec<BeatmapContent>> {
let osz = self
.client
.borrow()
.await?
.get(&format!("https://osu.ppy.sh/osu/{}", id))
.get(url)
.send()
.await?
.bytes()
.await?;
let bm = BeatmapContent {
id,
content: Arc::new(Beatmap::parse(content.as_ref())?),
};
let mut osz = zip::read::ZipArchive::new(std::io::Cursor::new(osz.as_ref()))?;
let osu_files = osz.file_names().map(|v| v.to_owned()).collect::<Vec<_>>();
let osu_files = osu_files
.into_iter()
.filter(|n| n.ends_with(".osu"))
.filter_map(|v| {
let mut v = osz.by_name(&v[..]).ok()?;
if v.size() > 1024 * 1024
/*1mb*/
{
return None;
};
let mut content = String::new();
v.read_to_string(&mut content).pls_ok()?;
Self::parse_beatmap(content, None).pls_ok()
})
.collect::<Vec<_>>();
Ok(osu_files)
}
/// Downloads the beatmap from an URL and returns it.
/// Does not deal with any caching.
pub async fn download_beatmap_from_url(
&self,
url: impl reqwest::IntoUrl,
id: Option<u64>,
) -> Result<(BeatmapContent, String)> {
let content = self
.client
.borrow()
.await?
.get(url)
.send()
.await?
.text()
.await?;
let bm = Self::parse_beatmap(&content, id)?;
Ok((bm, content))
}
async fn download_beatmap(&self, id: u64) -> Result<BeatmapContent> {
let (bm, content) = self
.download_beatmap_from_url(&format!("https://osu.ppy.sh/osu/{}", id), Some(id))
.await?;
let mut bc = models::CachedBeatmapContent {
beatmap_id: id as i64,
cached_at: chrono::Utc::now(),
content: content.as_ref().to_owned(),
content: content.into_bytes(),
};
bc.store(&self.pool).await?;
Ok(bm)
@ -155,12 +215,7 @@ impl BeatmapCache {
async fn get_beatmap_db(&self, id: u64) -> Result<Option<BeatmapContent>> {
Ok(models::CachedBeatmapContent::by_id(id as i64, &self.pool)
.await?
.map(|v| {
Ok(BeatmapContent {
id,
content: Arc::new(Beatmap::parse(&v.content[..])?),
}) as Result<_>
})
.map(|v| Self::parse_beatmap(String::from_utf8(v.content)?, Some(id)))
.transpose()?)
}

View file

@ -130,34 +130,55 @@ impl Difficulty {
}
/// Format the difficulty info into a short summary.
pub fn format_info(&self, mode: Mode, mods: Mods, original_beatmap: &Beatmap) -> String {
let is_not_ranked = !matches!(original_beatmap.approval, ApprovalStatus::Ranked(_));
let three_lines = is_not_ranked;
pub fn format_info<'a>(
&self,
mode: Mode,
mods: Mods,
original_beatmap: impl Into<Option<&'a Beatmap>> + 'a,
) -> String {
let original_beatmap = original_beatmap.into();
let is_not_ranked = !matches!(
original_beatmap.map(|v| v.approval),
Some(ApprovalStatus::Ranked(_))
);
let three_lines = original_beatmap.is_some() && is_not_ranked;
let bpm = (self.bpm * 100.0).round() / 100.0;
MessageBuilder::new()
.push(format!(
"[[Link]]({}) [[DL]]({}) [[Alt]]({}) (`{}`)",
original_beatmap.link(),
original_beatmap.download_link(false),
original_beatmap.download_link(true),
original_beatmap.short_link(Some(mode), Some(mods))
))
.push(
original_beatmap
.map(|original_beatmap| {
format!(
"[[Link]]({}) [[DL]]({}) [[Alt]]({}) (`{}`)",
original_beatmap.link(),
original_beatmap.download_link(false),
original_beatmap.download_link(true),
original_beatmap.short_link(Some(mode), Some(mods))
)
})
.unwrap_or("**Uploaded**".to_owned()),
)
.push(if three_lines { "\n" } else { ", " })
.push_bold(format!("{:.2}", self.stars))
.push(", ")
.push(
original_beatmap
.difficulty
.max_combo
self.max_combo
.map(|c| format!("max **{}x**, ", c))
.unwrap_or_else(|| "".to_owned()),
)
.push(if is_not_ranked {
format!("status **{}**, mode ", original_beatmap.approval)
format!(
"status **{}**, mode ",
original_beatmap
.map(|v| v.approval)
.unwrap_or(ApprovalStatus::WIP)
)
} else {
"".to_owned()
})
.push_bold_line(format_mode(mode, original_beatmap.mode))
.push_bold_line(format_mode(
mode,
original_beatmap.map(|v| v.mode).unwrap_or(mode),
))
.push("CS")
.push_bold(format!("{:.1}", self.cs))
.push(", AR")

View file

@ -82,6 +82,8 @@ async fn main() {
// Set up hooks
#[cfg(feature = "osu")]
handler.push_hook(youmubot_osu::discord::hook);
#[cfg(feature = "osu")]
handler.push_hook(youmubot_osu::discord::dot_osu_hook);
#[cfg(feature = "codeforces")]
handler.push_hook(youmubot_cf::InfoHook);