Implement basic rich presence
This commit is contained in:
parent
5186c7e8b8
commit
68c46fb7cd
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"rust-analyzer.cargo.allTargets": false
|
||||
}
|
568
Cargo.lock
generated
568
Cargo.lock
generated
|
@ -182,6 +182,12 @@ dependencies = [
|
|||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
|
@ -211,6 +217,73 @@ dependencies = [
|
|||
"arrayvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f43644eed690f5374f1af436ecd6aea01cd201f6fbdf0178adaf6907afb2cec"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"axum-macros",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.4.1",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower 0.5.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6b8ba012a258d63c9adfa28b9ddcf66149da6f986c5b5452e629d5ee64bf00"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-macros"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57d123550fa8d071b7255cb0cc04dc302baa6c8c4a79f55701552684d8399bce"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.74"
|
||||
|
@ -475,7 +548,7 @@ dependencies = [
|
|||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"core-graphics-types",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -498,7 +571,7 @@ checksum = "c9d2790b5c08465d49f8dc05c8bcae9fea467855947db39b0f8145c091aaced5"
|
|||
dependencies = [
|
||||
"core-foundation",
|
||||
"core-graphics",
|
||||
"foreign-types",
|
||||
"foreign-types 0.5.0",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
@ -687,6 +760,18 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "discord-rich-presence"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f632a41e3e97febf8edff46b1405f9875894c29e20a25c5abe566872226b3f84"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"serde_json",
|
||||
"uuid 0.8.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dlib"
|
||||
version = "0.5.2"
|
||||
|
@ -859,6 +944,15 @@ dependencies = [
|
|||
"yeslogic-fontconfig-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
|
||||
dependencies = [
|
||||
"foreign-types-shared 0.1.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types"
|
||||
version = "0.5.0"
|
||||
|
@ -866,7 +960,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965"
|
||||
dependencies = [
|
||||
"foreign-types-macros",
|
||||
"foreign-types-shared",
|
||||
"foreign-types-shared 0.3.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -880,6 +974,12 @@ dependencies = [
|
|||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
|
||||
|
||||
[[package]]
|
||||
name = "foreign-types-shared"
|
||||
version = "0.3.1"
|
||||
|
@ -1077,6 +1177,25 @@ dependencies = [
|
|||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "h2"
|
||||
version = "0.4.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http 1.1.0",
|
||||
"indexmap 2.5.0",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "half"
|
||||
version = "2.4.1"
|
||||
|
@ -1162,6 +1281,29 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http 1.1.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http-body-util"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "httparse"
|
||||
version = "1.9.4"
|
||||
|
@ -1184,9 +1326,9 @@ dependencies = [
|
|||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"http-body 0.4.6",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
|
@ -1198,6 +1340,27 @@ dependencies = [
|
|||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2 0.4.6",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
"tokio",
|
||||
"want",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.24.2"
|
||||
|
@ -1206,12 +1369,65 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590"
|
|||
dependencies = [
|
||||
"futures-util",
|
||||
"http 0.2.12",
|
||||
"hyper",
|
||||
"hyper 0.14.30",
|
||||
"rustls 0.21.12",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-rustls"
|
||||
version = "0.27.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333"
|
||||
dependencies = [
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"hyper 1.4.1",
|
||||
"hyper-util",
|
||||
"rustls 0.23.13",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
"tokio-rustls 0.26.0",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-tls"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"http-body-util",
|
||||
"hyper 1.4.1",
|
||||
"hyper-util",
|
||||
"native-tls",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hyper-util"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"hyper 1.4.1",
|
||||
"pin-project-lite",
|
||||
"socket2",
|
||||
"tokio",
|
||||
"tower 0.4.13",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hypertesseract"
|
||||
version = "0.1.0"
|
||||
|
@ -1532,6 +1748,12 @@ dependencies = [
|
|||
"imgref",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "matrixmultiply"
|
||||
version = "0.3.9"
|
||||
|
@ -1640,6 +1862,23 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "native-tls"
|
||||
version = "0.2.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"openssl",
|
||||
"openssl-probe",
|
||||
"openssl-sys",
|
||||
"schannel",
|
||||
"security-framework",
|
||||
"security-framework-sys",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "new_debug_unreachable"
|
||||
version = "1.0.6"
|
||||
|
@ -1768,6 +2007,38 @@ version = "1.19.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.66"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"cfg-if",
|
||||
"foreign-types 0.3.2",
|
||||
"libc",
|
||||
"once_cell",
|
||||
"openssl-macros",
|
||||
"openssl-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-macros"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "openssl-probe"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
|
||||
|
||||
[[package]]
|
||||
name = "openssl-sys"
|
||||
version = "0.9.103"
|
||||
|
@ -1843,6 +2114,26 @@ version = "2.3.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e"
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
|
||||
dependencies = [
|
||||
"pin-project-internal",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-internal"
|
||||
version = "1.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project-lite"
|
||||
version = "0.2.14"
|
||||
|
@ -2054,7 +2345,7 @@ checksum = "eb14dba8247a6a15b7fdbc7d389e2e6f03ee9f184f87117706d509c092dfe846"
|
|||
dependencies = [
|
||||
"r2d2",
|
||||
"rusqlite",
|
||||
"uuid",
|
||||
"uuid 1.10.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2232,11 +2523,11 @@ dependencies = [
|
|||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"h2 0.3.26",
|
||||
"http 0.2.12",
|
||||
"http-body",
|
||||
"hyper",
|
||||
"hyper-rustls",
|
||||
"http-body 0.4.6",
|
||||
"hyper 0.14.30",
|
||||
"hyper-rustls 0.24.2",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
|
@ -2246,12 +2537,12 @@ dependencies = [
|
|||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls 0.21.12",
|
||||
"rustls-pemfile",
|
||||
"rustls-pemfile 1.0.4",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper",
|
||||
"system-configuration",
|
||||
"sync_wrapper 0.1.2",
|
||||
"system-configuration 0.5.1",
|
||||
"tokio",
|
||||
"tokio-rustls 0.24.1",
|
||||
"tokio-util",
|
||||
|
@ -2265,6 +2556,49 @@ dependencies = [
|
|||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "reqwest"
|
||||
version = "0.12.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"encoding_rs",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"h2 0.4.6",
|
||||
"http 1.1.0",
|
||||
"http-body 1.0.1",
|
||||
"http-body-util",
|
||||
"hyper 1.4.1",
|
||||
"hyper-rustls 0.27.3",
|
||||
"hyper-tls",
|
||||
"hyper-util",
|
||||
"ipnet",
|
||||
"js-sys",
|
||||
"log",
|
||||
"mime",
|
||||
"native-tls",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustls-pemfile 2.1.3",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"system-configuration 0.6.1",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tower-service",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.50"
|
||||
|
@ -2369,6 +2703,19 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls"
|
||||
version = "0.23.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"rustls-pki-types",
|
||||
"rustls-webpki 0.102.8",
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "1.0.4"
|
||||
|
@ -2378,6 +2725,16 @@ dependencies = [
|
|||
"base64 0.21.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pemfile"
|
||||
version = "2.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustls-pki-types"
|
||||
version = "1.8.0"
|
||||
|
@ -2405,6 +2762,12 @@ dependencies = [
|
|||
"untrusted",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.18"
|
||||
|
@ -2429,6 +2792,15 @@ dependencies = [
|
|||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "schannel"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scheduled-thread-pool"
|
||||
version = "0.2.7"
|
||||
|
@ -2464,6 +2836,29 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework"
|
||||
version = "2.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
"security-framework-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "security-framework-sys"
|
||||
version = "2.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ea4a292869320c0272d7bc55a5a6aafaff59b4f63404a003887b679a2e05b4b6"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
|
@ -2514,6 +2909,16 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_spanned"
|
||||
version = "0.6.7"
|
||||
|
@ -2584,7 +2989,7 @@ dependencies = [
|
|||
"mime_guess",
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"reqwest",
|
||||
"reqwest 0.11.27",
|
||||
"secrecy",
|
||||
"serde",
|
||||
"serde_cow",
|
||||
|
@ -2625,20 +3030,24 @@ name = "shimmeringmoon"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"axum",
|
||||
"base16ct",
|
||||
"chrono",
|
||||
"clap",
|
||||
"discord-rich-presence",
|
||||
"freetype-rs",
|
||||
"hypertesseract",
|
||||
"image 0.25.2",
|
||||
"imageproc",
|
||||
"include_dir",
|
||||
"num",
|
||||
"paste",
|
||||
"plotters",
|
||||
"poise",
|
||||
"postcard",
|
||||
"r2d2",
|
||||
"r2d2_sqlite",
|
||||
"reqwest 0.12.7",
|
||||
"rusqlite",
|
||||
"rusqlite_migration",
|
||||
"serde",
|
||||
|
@ -2772,6 +3181,15 @@ version = "0.1.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sys"
|
||||
version = "0.1.0"
|
||||
|
@ -2790,7 +3208,18 @@ checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
|
|||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"core-foundation",
|
||||
"system-configuration-sys",
|
||||
"system-configuration-sys 0.5.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"core-foundation",
|
||||
"system-configuration-sys 0.6.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -2803,6 +3232,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-configuration-sys"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4"
|
||||
dependencies = [
|
||||
"core-foundation-sys",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "system-deps"
|
||||
version = "6.2.2"
|
||||
|
@ -2953,6 +3392,16 @@ dependencies = [
|
|||
"syn 2.0.77",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-native-tls"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
|
||||
dependencies = [
|
||||
"native-tls",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.24.1"
|
||||
|
@ -2974,6 +3423,17 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-rustls"
|
||||
version = "0.26.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
|
||||
dependencies = [
|
||||
"rustls 0.23.13",
|
||||
"rustls-pki-types",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-tungstenite"
|
||||
version = "0.21.0"
|
||||
|
@ -3037,6 +3497,43 @@ dependencies = [
|
|||
"winnow",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2873938d487c3cfb9aed7546dc9f2711d867c9f90c46b889989a2cb84eba6b4f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"pin-project-lite",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tokio",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tower-layer"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e"
|
||||
|
||||
[[package]]
|
||||
name = "tower-service"
|
||||
version = "0.3.3"
|
||||
|
@ -3221,6 +3718,15 @@ version = "0.2.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "uuid"
|
||||
version = "1.10.0"
|
||||
|
@ -3446,6 +3952,36 @@ dependencies = [
|
|||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-registry"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-strings",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-result"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e"
|
||||
dependencies = [
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-strings"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10"
|
||||
dependencies = [
|
||||
"windows-result",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
|
|
16
Cargo.toml
16
Cargo.toml
|
@ -16,6 +16,14 @@ path = "src/bin/discord-bot/main.rs"
|
|||
name = "shimmering-cli"
|
||||
path = "src/bin/cli/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "shimmering-server"
|
||||
path = "src/bin/server/main.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "shimmering-discord-presence"
|
||||
path = "src/bin/discord-presence/main.rs"
|
||||
|
||||
[dependencies]
|
||||
chrono = "0.4.38"
|
||||
freetype-rs = "0.36.0"
|
||||
|
@ -40,6 +48,10 @@ serde_with = "3.9.0"
|
|||
anyhow = "1.0.87"
|
||||
sha2 = "0.10.8"
|
||||
base16ct = { version = "0.2.0", features = ["alloc"] }
|
||||
axum = { version = "0.7.6", features = ["macros"] }
|
||||
paste = "1.0.15"
|
||||
discord-rich-presence = "0.2.4"
|
||||
reqwest = { version = "0.12.7", features = ["json"] }
|
||||
|
||||
[profile.dev.package."*"]
|
||||
opt-level = 3
|
||||
# [profile.dev.package."*"]
|
||||
# opt-level = 3
|
||||
|
|
|
@ -41,11 +41,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1726755586,
|
||||
"narHash": "sha256-PmUr/2GQGvFTIJ6/Tvsins7Q43KTMvMFhvG6oaYK+Wk=",
|
||||
"lastModified": 1726937504,
|
||||
"narHash": "sha256-bvGoiQBvponpZh8ClUcmJ6QnsNKw0EMrCQJARK3bI1c=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c04d5652cfa9742b1d519688f65d1bbccea9eb7e",
|
||||
"rev": "9357f4f23713673f310988025d9dc261c20e70c6",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
|
|
@ -41,7 +41,12 @@
|
|||
};
|
||||
devShell = pkgs.mkShell rec {
|
||||
nativeBuildInputs = with pkgs; [
|
||||
toolchain
|
||||
cargo
|
||||
rustc
|
||||
clippy
|
||||
rust-analyzer
|
||||
rustfmt
|
||||
|
||||
ruff
|
||||
imagemagick
|
||||
pkg-config
|
||||
|
|
|
@ -5,13 +5,14 @@ use std::{fmt::Display, num::NonZeroU16, path::PathBuf};
|
|||
use anyhow::anyhow;
|
||||
use image::{ImageBuffer, Rgb};
|
||||
use rusqlite::types::{FromSql, FromSqlError, FromSqlResult, ValueRef};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::bitmap::Color;
|
||||
use crate::context::{DbConnection, Error};
|
||||
// }}}
|
||||
|
||||
// {{{ Difficuly
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Difficulty {
|
||||
PST,
|
||||
PRS,
|
||||
|
@ -69,7 +70,7 @@ pub const DIFFICULTY_MENU_PIXEL_COLORS: [Color; Difficulty::DIFFICULTIES.len()]
|
|||
];
|
||||
// }}}
|
||||
// {{{ Level
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub enum Level {
|
||||
Unknown,
|
||||
One,
|
||||
|
@ -144,7 +145,7 @@ impl FromSql for Level {
|
|||
}
|
||||
// }}}
|
||||
// {{{ Side
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
|
||||
pub enum Side {
|
||||
Light,
|
||||
Conflict,
|
||||
|
@ -178,7 +179,7 @@ impl FromSql for Side {
|
|||
}
|
||||
// }}}
|
||||
// {{{ Song
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Song {
|
||||
pub id: u32,
|
||||
pub title: String,
|
||||
|
@ -199,7 +200,7 @@ pub struct Jacket {
|
|||
pub bitmap: &'static ImageBuffer<Rgb<u8>, Vec<u8>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Chart {
|
||||
pub id: u32,
|
||||
pub song_id: u32,
|
||||
|
@ -212,17 +213,9 @@ pub struct Chart {
|
|||
pub note_count: u32,
|
||||
pub chart_constant: u32,
|
||||
|
||||
#[serde(skip)]
|
||||
pub cached_jacket: Option<Jacket>,
|
||||
}
|
||||
|
||||
impl Chart {
|
||||
#[inline]
|
||||
pub fn jacket_path(&self, data_dir: &Path) -> PathBuf {
|
||||
data_dir
|
||||
.join("jackets")
|
||||
.join(format!("{}-{}.jpg", self.song_id, self.id))
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
// {{{ Cached song
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
@ -26,6 +26,7 @@ pub struct ImageVec {
|
|||
|
||||
impl ImageVec {
|
||||
// {{{ (Image => vector) encoding
|
||||
#[allow(clippy::identity_op)]
|
||||
pub fn from_image(image: &impl GenericImageView<Pixel = Rgba<u8>>) -> Self {
|
||||
let mut colors = [0.0; IMAGE_VEC_DIM];
|
||||
let chunk_width = image.width() / SPLIT_FACTOR;
|
||||
|
@ -55,7 +56,6 @@ impl ImageVec {
|
|||
let r = (r as f64 / count).sqrt();
|
||||
let g = (g as f64 / count).sqrt();
|
||||
let b = (b as f64 / count).sqrt();
|
||||
#[allow(clippy::identity_op)]
|
||||
colors[i as usize * 3 + 0] = r as f32;
|
||||
colors[i as usize * 3 + 1] = g as f32;
|
||||
colors[i as usize * 3 + 2] = b as f32;
|
||||
|
|
|
@ -12,6 +12,8 @@ use num::Rational32;
|
|||
use num::Zero;
|
||||
use poise::serenity_prelude::{CreateAttachment, CreateEmbed, CreateEmbedAuthor, Timestamp};
|
||||
use rusqlite::Row;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
use crate::arcaea::chart::{Chart, Song};
|
||||
use crate::context::ErrorKind;
|
||||
|
@ -140,7 +142,7 @@ impl CreatePlay {
|
|||
}
|
||||
// }}}
|
||||
// {{{ Score data
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct ScoreCollection([Score; ScoringSystem::SCORING_SYSTEMS.len()]);
|
||||
|
||||
impl ScoreCollection {
|
||||
|
@ -152,7 +154,7 @@ impl ScoreCollection {
|
|||
}
|
||||
// }}}
|
||||
// {{{ Play
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct Play {
|
||||
pub id: u32,
|
||||
#[allow(unused)]
|
||||
|
@ -267,9 +269,7 @@ impl Play {
|
|||
} else {
|
||||
Some('P')
|
||||
}
|
||||
} else if let Some(distribution) = self.distribution(chart.note_count)
|
||||
&& distribution.3 == 0
|
||||
{
|
||||
} else if let Some((_, _, _, 0)) = self.distribution(chart.note_count) {
|
||||
Some('F')
|
||||
} else {
|
||||
Some('C')
|
||||
|
@ -555,3 +555,11 @@ pub async fn generate_missing_scores(ctx: &UserContext) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
// }}}
|
||||
// {{{ Play + chart + song triplet
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PlayWithDetails {
|
||||
pub play: Play,
|
||||
pub song: Song,
|
||||
pub chart: Chart,
|
||||
}
|
||||
// }}}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
use std::fmt::{Display, Write};
|
||||
|
||||
use num::{Rational32, Rational64};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::context::Error;
|
||||
|
||||
|
@ -71,7 +72,7 @@ impl Display for Grade {
|
|||
}
|
||||
// }}}
|
||||
// {{{ Score
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
|
||||
pub struct Score(pub u32);
|
||||
|
||||
impl Score {
|
||||
|
|
|
@ -6,13 +6,9 @@ use std::{env::var, sync::Arc, time::Duration};
|
|||
|
||||
// {{{ Error handler
|
||||
async fn on_error(error: poise::FrameworkError<'_, UserContext, Error>) {
|
||||
match error {
|
||||
error => {
|
||||
if let Err(e) = poise::builtins::on_error(error).await {
|
||||
println!("Error while handling error: {}", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
|
||||
|
@ -34,7 +30,7 @@ async fn main() {
|
|||
} else if message.content.starts_with("!") {
|
||||
Ok(Some(message.content.split_at(1)))
|
||||
} else if message.guild_id.is_none() {
|
||||
if message.content.trim().len() == 0 {
|
||||
if message.content.trim().is_empty() {
|
||||
Ok(Some(("", "score magic")))
|
||||
} else {
|
||||
Ok(Some(("", &message.content[..])))
|
||||
|
|
73
src/bin/discord-presence/main.rs
Normal file
73
src/bin/discord-presence/main.rs
Normal file
|
@ -0,0 +1,73 @@
|
|||
use std::time::Duration;
|
||||
|
||||
use anyhow::anyhow;
|
||||
// {{{ Imports
|
||||
use discord_rich_presence::activity::{Activity, Assets};
|
||||
use discord_rich_presence::{DiscordIpc, DiscordIpcClient};
|
||||
use shimmeringmoon::arcaea::chart::Difficulty;
|
||||
use shimmeringmoon::arcaea::play::PlayWithDetails;
|
||||
use shimmeringmoon::arcaea::score::ScoringSystem;
|
||||
use shimmeringmoon::assets::get_var;
|
||||
use shimmeringmoon::context::Error;
|
||||
// }}}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
let server_url = get_var("SHIMMERING_SERVER_URL");
|
||||
let client_id = get_var("SHIMMERING_DISCORD_ID");
|
||||
|
||||
println!("Connecting to discord...");
|
||||
let mut ipc = DiscordIpcClient::new(&client_id).map_err(|e| anyhow!("{}", e))?;
|
||||
ipc.connect().map_err(|e| anyhow!("{}", e))?;
|
||||
|
||||
println!("Starting presence loop...");
|
||||
for i in 0.. {
|
||||
println!("Getting most recent score...");
|
||||
let res = reqwest::get(format!("{}/plays/latest", server_url)).await;
|
||||
|
||||
let res = match res.and_then(|r| r.error_for_status()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
ipc.clear_activity().map_err(|e| anyhow!("{}", e))?;
|
||||
println!("{e}");
|
||||
|
||||
tokio::time::sleep(Duration::from_secs(10)).await;
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
let triplet = res.json::<PlayWithDetails>().await?;
|
||||
|
||||
let jacket_url = format!(
|
||||
"{}/jackets/by_chart_id/{}.png",
|
||||
server_url, &triplet.chart.id
|
||||
);
|
||||
let jacket_url = "https://static.wikia.nocookie.net/iowiro/images/c/c2/Fracture_Ray.jpg/revision/latest?cb=20230928061927";
|
||||
println!("Jacket url: {}", jacket_url);
|
||||
|
||||
let jacket_text = format!("{} — {}", &triplet.song.title, &triplet.song.artist);
|
||||
|
||||
let assets = Assets::new()
|
||||
.large_image(&jacket_url)
|
||||
.large_text(&jacket_text);
|
||||
|
||||
let details = format!(
|
||||
"{} [{} {}]",
|
||||
&triplet.song.title,
|
||||
Difficulty::DIFFICULTY_SHORTHANDS[triplet.chart.difficulty.to_index()],
|
||||
&triplet.chart.level,
|
||||
);
|
||||
|
||||
let state = format!("{}", &triplet.play.score(ScoringSystem::Standard));
|
||||
let activity = Activity::new()
|
||||
.assets(assets)
|
||||
.details(&details)
|
||||
.state(&state);
|
||||
|
||||
println!("Sending activity");
|
||||
ipc.set_activity(activity).map_err(|e| anyhow!("{}", e))?;
|
||||
tokio::time::sleep(Duration::from_secs(30)).await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
12
src/bin/server/context.rs
Normal file
12
src/bin/server/context.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
use shimmeringmoon::context::UserContext;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct AppContext {
|
||||
pub ctx: &'static UserContext,
|
||||
}
|
||||
|
||||
impl AppContext {
|
||||
pub fn new(ctx: &'static UserContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
34
src/bin/server/error.rs
Normal file
34
src/bin/server/error.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use axum::{
|
||||
http::StatusCode,
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
|
||||
pub struct AppError {
|
||||
pub error: anyhow::Error,
|
||||
pub status_code: StatusCode,
|
||||
}
|
||||
|
||||
impl AppError {
|
||||
pub fn new(error: anyhow::Error, status_code: StatusCode) -> Self {
|
||||
Self { error, status_code }
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
self.status_code,
|
||||
format!("Something went wrong: {}", self.error),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
impl<E> From<E> for AppError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self::new(err.into(), StatusCode::INTERNAL_SERVER_ERROR)
|
||||
}
|
||||
}
|
33
src/bin/server/main.rs
Normal file
33
src/bin/server/main.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use context::AppContext;
|
||||
use routes::jacket::get_jacket_image;
|
||||
use routes::recent_plays::get_recent_play;
|
||||
use shimmeringmoon::assets::get_var;
|
||||
use shimmeringmoon::context::{Error, UserContext};
|
||||
|
||||
mod context;
|
||||
mod error;
|
||||
mod routes;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Error> {
|
||||
let ctx = Box::leak(Box::new(UserContext::new().await?));
|
||||
|
||||
let app = axum::Router::new()
|
||||
.route("/plays/latest", axum::routing::get(get_recent_play))
|
||||
.route(
|
||||
"/jackets/by_chart_id/:chart_id",
|
||||
axum::routing::get(get_jacket_image),
|
||||
)
|
||||
.with_state(AppContext::new(ctx));
|
||||
|
||||
let port: u32 = get_var("SHIMMERING_SERVER_PORT").parse()?;
|
||||
let listener = tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
println!("listening on {}", listener.local_addr().unwrap());
|
||||
|
||||
axum::serve(listener, app).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
44
src/bin/server/routes/jacket.rs
Normal file
44
src/bin/server/routes/jacket.rs
Normal file
|
@ -0,0 +1,44 @@
|
|||
use std::io::Cursor;
|
||||
|
||||
use axum::extract::{Path, State};
|
||||
use axum::http::{header, HeaderName, StatusCode};
|
||||
|
||||
use crate::{context::AppContext, error::AppError};
|
||||
|
||||
pub async fn get_jacket_image(
|
||||
State(state): State<AppContext>,
|
||||
Path(filename): Path<String>,
|
||||
) -> Result<([(HeaderName, String); 2], Vec<u8>), AppError> {
|
||||
let chart_id = filename
|
||||
.strip_suffix(".png")
|
||||
.unwrap_or(&filename)
|
||||
.parse::<u32>()
|
||||
.map_err(|e| AppError::new(e.into(), StatusCode::NOT_FOUND))?;
|
||||
|
||||
let (_song, chart) = state
|
||||
.ctx
|
||||
.song_cache
|
||||
.lookup_chart(chart_id)
|
||||
.map_err(|e| AppError::new(e, StatusCode::NOT_FOUND))?;
|
||||
|
||||
let headers = [
|
||||
(header::CONTENT_TYPE, "image/png".to_owned()),
|
||||
(
|
||||
header::HeaderName::from_static("pngrok-skip-browser-warning"),
|
||||
"-".to_owned(),
|
||||
),
|
||||
// (
|
||||
// header::CONTENT_DISPOSITION,
|
||||
// format!("attachment; filename=\"chart_{}.jpg\"", chart_id),
|
||||
// ),
|
||||
];
|
||||
let mut buffer = Vec::new();
|
||||
let mut cursor = Cursor::new(&mut buffer);
|
||||
chart
|
||||
.cached_jacket
|
||||
.unwrap()
|
||||
.bitmap
|
||||
.write_to(&mut cursor, image::ImageFormat::Png)?;
|
||||
|
||||
Ok((headers, buffer))
|
||||
}
|
2
src/bin/server/routes/mod.rs
Normal file
2
src/bin/server/routes/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
|||
pub mod jacket;
|
||||
pub mod recent_plays;
|
50
src/bin/server/routes/recent_plays.rs
Normal file
50
src/bin/server/routes/recent_plays.rs
Normal file
|
@ -0,0 +1,50 @@
|
|||
// {{{ Imports
|
||||
use crate::context::AppContext;
|
||||
use crate::error::AppError;
|
||||
use anyhow::anyhow;
|
||||
use axum::{extract::State, http::StatusCode, Json};
|
||||
use chrono::{TimeDelta, Utc};
|
||||
use shimmeringmoon::arcaea::play::{Play, PlayWithDetails};
|
||||
// }}}
|
||||
|
||||
pub async fn get_recent_play(
|
||||
State(state): State<AppContext>,
|
||||
) -> Result<Json<PlayWithDetails>, AppError> {
|
||||
let after = Utc::now()
|
||||
.checked_sub_signed(TimeDelta::minutes(30))
|
||||
.unwrap()
|
||||
.naive_utc();
|
||||
|
||||
let (play, song, chart) = state
|
||||
.ctx
|
||||
.db
|
||||
.get()?
|
||||
.prepare_cached(
|
||||
"
|
||||
SELECT
|
||||
p.id, p.chart_id, p.user_id, p.created_at,
|
||||
p.max_recall, p.far_notes, s.score
|
||||
FROM plays p
|
||||
JOIN scores s ON s.play_id = p.id
|
||||
WHERE s.scoring_system='standard'
|
||||
AND p.user_id=?
|
||||
AND p.created_at>=?
|
||||
ORDER BY p.created_at DESC
|
||||
LIMIT 1
|
||||
",
|
||||
)?
|
||||
.query_and_then((2, after), |row| -> Result<_, AppError> {
|
||||
let (song, chart) = state.ctx.song_cache.lookup_chart(row.get("chart_id")?)?;
|
||||
let play = Play::from_sql(chart, row)?;
|
||||
Ok((play, song, chart))
|
||||
})?
|
||||
.next()
|
||||
.ok_or_else(|| AppError::new(anyhow!("No recent plays found"), StatusCode::NOT_FOUND))??;
|
||||
|
||||
// Perhaps I need to make a Serialize-only version of this type which takes refs?
|
||||
Ok(axum::response::Json(PlayWithDetails {
|
||||
play,
|
||||
song: song.clone(),
|
||||
chart: chart.clone(),
|
||||
}))
|
||||
}
|
|
@ -363,14 +363,16 @@ impl BitmapCanvas {
|
|||
})?;
|
||||
|
||||
let face = &mut faces[face_index];
|
||||
if let Some((prev_face_index, prev_glyth_index)) = previous
|
||||
&& prev_face_index == face_index
|
||||
&& kerning[face_index]
|
||||
{
|
||||
let delta =
|
||||
face.get_kerning(prev_glyth_index, glyph_index, KerningMode::KerningDefault)?;
|
||||
if let Some((prev_face_index, prev_glyth_index)) = previous {
|
||||
if prev_face_index == face_index && kerning[face_index] {
|
||||
let delta = face.get_kerning(
|
||||
prev_glyth_index,
|
||||
glyph_index,
|
||||
KerningMode::KerningDefault,
|
||||
)?;
|
||||
pen_x += delta.x >> 6; // we shift to get rid of sub-pixel accuracy
|
||||
}
|
||||
}
|
||||
|
||||
face.load_glyph(glyph_index, LoadFlag::DEFAULT)?;
|
||||
|
||||
|
@ -579,13 +581,14 @@ impl LayoutManager {
|
|||
) {
|
||||
let current = self.boxes[id.0];
|
||||
|
||||
if let Some((current_points_to, dx, dy)) = current.relative_to
|
||||
&& current_points_to != id_relative_to
|
||||
{
|
||||
match current.relative_to {
|
||||
Some((current_points_to, dx, dy)) if current_points_to != id_relative_to => {
|
||||
self.edit_to_relative(current_points_to, id_relative_to, x - dx, y - dy);
|
||||
} else {
|
||||
}
|
||||
_ => {
|
||||
self.boxes[id.0].relative_to = Some((id_relative_to, x, y));
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let a = self.lookup(id);
|
||||
|
|
|
@ -101,13 +101,13 @@ async fn info_impl(ctx: &mut impl MessageContext, name: &str) -> Result<(), Tagg
|
|||
// {{{ Tests
|
||||
#[cfg(test)]
|
||||
mod info_tests {
|
||||
use crate::{commands::discord::mock::MockContext, with_test_ctx};
|
||||
use crate::{commands::discord::mock::MockContext, golden_test, with_test_ctx};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_suffix() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/chart/info/no_suffix", async |ctx| {
|
||||
with_test_ctx!("commands/commands/chart/info/no_suffix", |ctx| async move {
|
||||
info_impl(ctx, "Pentiment").await?;
|
||||
Ok(())
|
||||
})
|
||||
|
@ -115,23 +115,21 @@ mod info_tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn specify_difficulty() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/chart/info/specify_difficulty", async |ctx| {
|
||||
info_impl(ctx, "Hellohell [ETR]").await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn last_byd() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/chart/info/last_byd",
|
||||
async |ctx: &mut MockContext| {
|
||||
info_impl(ctx, "Last | Moment [BYD]").await?;
|
||||
info_impl(ctx, "Last | Eternity [BYD]").await?;
|
||||
"commands/commands/chart/info/specify_difficulty",
|
||||
|ctx| async move {
|
||||
info_impl(ctx, "Hellohell [ETR]").await?;
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
golden_test!(last_byd, "commands/chart/info/last_byd");
|
||||
async fn last_byd(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
info_impl(ctx, "Last | Moment [BYD]").await?;
|
||||
info_impl(ctx, "Last | Eternity [BYD]").await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
// {{{ Discord wrapper
|
||||
|
@ -208,28 +206,25 @@ async fn best_impl<C: MessageContext>(ctx: &mut C, name: &str) -> Result<Play, T
|
|||
// {{{ Tests
|
||||
#[cfg(test)]
|
||||
mod best_tests {
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
commands::{discord::mock::MockContext, score::magic_impl},
|
||||
with_test_ctx,
|
||||
golden_test, with_test_ctx,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_scores() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/chart/best/no_scores", async |ctx| {
|
||||
with_test_ctx!("commands/chart/best/no_scores", |ctx| async move {
|
||||
best_impl(ctx, "Pentiment").await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn pick_correct_score() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/chart/best/pick_correct_score",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(pick_correct_score, "commands/chart/best/pick_correct_score");
|
||||
async fn pick_correct_score(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let plays = magic_impl(
|
||||
ctx,
|
||||
&[
|
||||
|
@ -247,8 +242,6 @@ mod best_tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
// }}}
|
||||
|
|
|
@ -4,7 +4,7 @@ use crate::arcaea::score::Score;
|
|||
use crate::context::{Context, Error, ErrorKind, TagError, TaggedError};
|
||||
use crate::recognition::recognize::{ImageAnalyzer, ScoreKind};
|
||||
use crate::user::User;
|
||||
use crate::{get_user_error, timed};
|
||||
use crate::{get_user_error, timed, try_block};
|
||||
use anyhow::anyhow;
|
||||
use image::DynamicImage;
|
||||
use poise::{serenity_prelude as serenity, CreateReply};
|
||||
|
@ -48,7 +48,7 @@ pub async fn magic_impl<C: MessageContext>(
|
|||
let mut grayscale_image = DynamicImage::ImageLuma8(image.to_luma8());
|
||||
// }}}
|
||||
|
||||
let result: Result<(), TaggedError> = try {
|
||||
let result: Result<(), TaggedError> = try_block!({
|
||||
// {{{ Detection
|
||||
|
||||
let kind = timed!("read_score_kind", {
|
||||
|
@ -113,7 +113,7 @@ pub async fn magic_impl<C: MessageContext>(
|
|||
embeds.push(embed);
|
||||
attachments.extend(attachment);
|
||||
// }}}
|
||||
};
|
||||
});
|
||||
|
||||
if let Err(err) = result {
|
||||
let user_err = get_user_error!(err);
|
||||
|
@ -140,45 +140,36 @@ pub async fn magic_impl<C: MessageContext>(
|
|||
#[cfg(test)]
|
||||
mod magic_tests {
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
use crate::{
|
||||
arcaea::score::ScoringSystem,
|
||||
commands::discord::{mock::MockContext, play_song_title},
|
||||
with_test_ctx,
|
||||
golden_test, with_test_ctx,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_pics() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/score/magic/no_pics", async |ctx| {
|
||||
with_test_ctx!("commands/score/magic/no_pics", |ctx| async move {
|
||||
magic_impl(ctx, &[]).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn simple_pic() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/score/magic/single_pic",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(simple_pic, "score/magic/single_pic");
|
||||
async fn simple_pic(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let plays =
|
||||
magic_impl(ctx, &[PathBuf::from_str("test/screenshots/alter_ego.jpg")?])
|
||||
.await?;
|
||||
magic_impl(ctx, &[PathBuf::from_str("test/screenshots/alter_ego.jpg")?]).await?;
|
||||
assert_eq!(plays.len(), 1);
|
||||
assert_eq!(plays[0].score(ScoringSystem::Standard).0, 9926250);
|
||||
assert_eq!(play_song_title(ctx, &plays[0])?, "ALTER EGO");
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn weird_kerning() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/score/magic/weird_kerning",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(weird_kerning, "score/magic/weird_kerning");
|
||||
async fn weird_kerning(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let plays = magic_impl(
|
||||
ctx,
|
||||
&[
|
||||
|
@ -196,8 +187,6 @@ mod magic_tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
// {{{ Discord wrapper
|
||||
|
@ -293,12 +282,12 @@ pub async fn show_impl<C: MessageContext>(
|
|||
#[cfg(test)]
|
||||
mod show_tests {
|
||||
use super::*;
|
||||
use crate::{commands::discord::mock::MockContext, with_test_ctx};
|
||||
use std::path::PathBuf;
|
||||
use crate::{commands::discord::mock::MockContext, golden_test, with_test_ctx};
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_ids() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/score/show/no_ids", async |ctx| {
|
||||
with_test_ctx!("commands/score/show/no_ids", |ctx| async move {
|
||||
show_impl(ctx, &[]).await?;
|
||||
Ok(())
|
||||
})
|
||||
|
@ -306,17 +295,14 @@ mod show_tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn nonexistent_id() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/score/show/nonexistent_id", async |ctx| {
|
||||
with_test_ctx!("commands/score/show/nonexistent_id", |ctx| async move {
|
||||
show_impl(ctx, &[666]).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn agrees_with_magic() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/score/show/agrees_with_magic",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(agrees_with_magic, "commands/score/show/agrees_with_magic");
|
||||
async fn agrees_with_magic(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let created_plays = magic_impl(
|
||||
ctx,
|
||||
&[
|
||||
|
@ -334,8 +320,6 @@ mod show_tests {
|
|||
assert_eq!(created_plays, plays);
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
// {{{ Discord wrapper
|
||||
|
@ -392,13 +376,13 @@ mod delete_tests {
|
|||
use super::*;
|
||||
use crate::{
|
||||
commands::discord::{mock::MockContext, play_song_title},
|
||||
with_test_ctx,
|
||||
golden_test, with_test_ctx,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
use std::{path::PathBuf, str::FromStr};
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_ids() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/score/delete/no_ids", async |ctx| {
|
||||
with_test_ctx!("commands/score/delete/no_ids", |ctx| async move {
|
||||
delete_impl(ctx, &[]).await?;
|
||||
Ok(())
|
||||
})
|
||||
|
@ -406,36 +390,29 @@ mod delete_tests {
|
|||
|
||||
#[tokio::test]
|
||||
async fn nonexistent_id() -> Result<(), Error> {
|
||||
with_test_ctx!("test/commands/score/delete/nonexistent_id", async |ctx| {
|
||||
with_test_ctx!("commands/score/delete/nonexistent_id", |ctx| async move {
|
||||
delete_impl(ctx, &[666]).await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_twice() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/score/delete/delete_twice",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(delete_twice, "commands/score/delete/delete_twice");
|
||||
async fn delete_twice(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let plays =
|
||||
magic_impl(ctx, &[PathBuf::from_str("test/screenshots/alter_ego.jpg")?])
|
||||
.await?;
|
||||
magic_impl(ctx, &[PathBuf::from_str("test/screenshots/alter_ego.jpg")?]).await?;
|
||||
|
||||
let id = plays[0].id;
|
||||
delete_impl(ctx, &[id, id]).await?;
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn no_show_after_delete() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/score/delete/no_show_after_delete",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(
|
||||
no_show_after_delete,
|
||||
"commands/score/delete/no_show_after_delete"
|
||||
);
|
||||
async fn no_show_after_delete(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let plays =
|
||||
magic_impl(ctx, &[PathBuf::from_str("test/screenshots/alter_ego.jpg")?])
|
||||
.await?;
|
||||
magic_impl(ctx, &[PathBuf::from_str("test/screenshots/alter_ego.jpg")?]).await?;
|
||||
|
||||
// Showcase proper usage
|
||||
let ids = [plays[0].id];
|
||||
|
@ -447,14 +424,9 @@ mod delete_tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn delete_multiple() -> Result<(), Error> {
|
||||
with_test_ctx!(
|
||||
"test/commands/score/delete/delete_multiple",
|
||||
async |ctx: &mut MockContext| {
|
||||
golden_test!(delete_multiple, "commands/score/delete/delete_multiple");
|
||||
async fn delete_multiple(ctx: &mut MockContext) -> Result<(), TaggedError> {
|
||||
let plays = magic_impl(
|
||||
ctx,
|
||||
&[
|
||||
|
@ -473,8 +445,6 @@ mod delete_tests {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
// }}}
|
||||
// {{{ Discord wrapper
|
||||
|
|
|
@ -145,7 +145,7 @@ pub mod testing {
|
|||
|
||||
pub async fn get_shared_context() -> &'static UserContext {
|
||||
static CELL: tokio::sync::OnceCell<UserContext> = tokio::sync::OnceCell::const_new();
|
||||
CELL.get_or_init(async || {
|
||||
CELL.get_or_init(|| async move {
|
||||
// env::set_var("SHIMMERING_DATA_DIR", "")
|
||||
UserContext::new().await.unwrap()
|
||||
})
|
||||
|
@ -165,6 +165,20 @@ pub mod testing {
|
|||
);
|
||||
}
|
||||
|
||||
// rustfmt fucks up the formatting here,
|
||||
// but the skip attribute doesn't seem to work well on macros 🤔
|
||||
#[macro_export]
|
||||
macro_rules! golden_test {
|
||||
($name:ident, $test_path:expr) => {
|
||||
paste::paste! {
|
||||
#[tokio::test]
|
||||
async fn [<$name _test>]() -> Result<(), $crate::context::Error> {
|
||||
$crate::with_test_ctx!($test_path, $name)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! with_test_ctx {
|
||||
($test_path:expr, $f:expr) => {{
|
||||
|
@ -179,10 +193,11 @@ pub mod testing {
|
|||
let res = $crate::user::User::create_from_context(&ctx);
|
||||
ctx.handle_error(res).await?;
|
||||
|
||||
let res: Result<(), $crate::context::TaggedError> = $f(&mut ctx).await;
|
||||
let ctx: &mut $crate::commands::discord::mock::MockContext = &mut ctx;
|
||||
let res: Result<(), $crate::context::TaggedError> = $f(ctx).await;
|
||||
ctx.handle_error(res).await?;
|
||||
|
||||
ctx.golden(&std::path::PathBuf::from_str($test_path)?)?;
|
||||
ctx.golden(&std::path::PathBuf::from_str("test")?.join($test_path))?;
|
||||
Ok(())
|
||||
}};
|
||||
}
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -1,16 +1,6 @@
|
|||
#![allow(async_fn_in_trait)]
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
#![allow(clippy::redundant_closure)]
|
||||
#![feature(iter_map_windows)]
|
||||
#![feature(anonymous_lifetime_in_impl_trait)]
|
||||
#![feature(let_chains)]
|
||||
#![feature(array_try_map)]
|
||||
#![feature(async_closure)]
|
||||
#![feature(try_blocks)]
|
||||
#![feature(thread_local)]
|
||||
#![feature(generic_arg_infer)]
|
||||
#![feature(iter_collect_into)]
|
||||
#![feature(stmt_expr_attributes)]
|
||||
|
||||
pub mod arcaea;
|
||||
pub mod assets;
|
||||
|
@ -23,3 +13,4 @@ pub mod recognition;
|
|||
pub mod time;
|
||||
pub mod transform;
|
||||
pub mod user;
|
||||
pub mod utils;
|
||||
|
|
|
@ -74,9 +74,7 @@ pub fn guess_chart_name<'a>(
|
|||
let mut close_enough: Vec<_> = cache
|
||||
.charts()
|
||||
.filter_map(|chart| {
|
||||
if let Some(difficulty) = difficulty
|
||||
&& chart.difficulty != difficulty
|
||||
{
|
||||
if difficulty.map_or(false, |d| d != chart.difficulty) {
|
||||
return None;
|
||||
}
|
||||
|
||||
|
@ -92,24 +90,26 @@ pub fn guess_chart_name<'a>(
|
|||
|
||||
// Cut title to the length of the text, and then check
|
||||
let shortest_len = Ord::min(song_title.len(), text.len());
|
||||
if let Some(sliced) = &song_title.get(..shortest_len)
|
||||
&& (text.len() >= 6 || unsafe_heuristics)
|
||||
{
|
||||
if let Some(sliced) = &song_title.get(..shortest_len) {
|
||||
if text.len() >= 6 || unsafe_heuristics {
|
||||
let slice_distance = edit_distance_with(text, sliced, &mut levenshtein_vec);
|
||||
if slice_distance == 0 {
|
||||
distance_vec.push(3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shorthand-based matching
|
||||
if let Some(shorthand) = &chart.shorthand
|
||||
&& unsafe_heuristics
|
||||
{
|
||||
let short_distance = edit_distance_with(text, shorthand, &mut levenshtein_vec);
|
||||
if let Some(shorthand) = &chart.shorthand {
|
||||
if unsafe_heuristics {
|
||||
let short_distance =
|
||||
edit_distance_with(text, shorthand, &mut levenshtein_vec);
|
||||
|
||||
if short_distance <= shorthand.len() / 3 {
|
||||
distance_vec.push(short_distance * 10 + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
distance_vec
|
||||
.iter()
|
||||
|
|
|
@ -70,13 +70,13 @@ impl ComponentVec {
|
|||
|
||||
for x in x_start..x_end {
|
||||
for y in y_start..y_end {
|
||||
if let Some(p) = components.components.get_pixel_checked(x, y)
|
||||
&& p.0[0] == component
|
||||
{
|
||||
if let Some(p) = components.components.get_pixel_checked(x, y) {
|
||||
if p.0[0] == component {
|
||||
count += 255 - components.image[(x, y)].0[0] as u32;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let size = (x_end + 1 - x_start) * (y_end + 1 - y_start);
|
||||
|
||||
|
|
|
@ -162,14 +162,14 @@ impl ImageAnalyzer {
|
|||
);
|
||||
|
||||
// Discard scores if it's impossible
|
||||
if result.0 <= 10_010_000
|
||||
&& note_count.map_or(true, |note_count| {
|
||||
let valid_analysis = note_count.map_or(true, |note_count| {
|
||||
let (zeta, shinies, score_units) = result.analyse(note_count);
|
||||
8_000_000 <= zeta.0
|
||||
&& zeta.0 <= 10_000_000
|
||||
&& shinies <= note_count
|
||||
&& score_units <= 2 * note_count
|
||||
}) {
|
||||
});
|
||||
if result.0 <= 10_010_000 && valid_analysis {
|
||||
Ok(result)
|
||||
} else {
|
||||
Err(anyhow!("Score {result} is not vaild"))
|
||||
|
|
20
src/utils.rs
Normal file
20
src/utils.rs
Normal file
|
@ -0,0 +1,20 @@
|
|||
/// Performs "Ok-wrapping" on the result of an expression.
|
||||
/// This is compatible with [`Result`], [`Option`], [`ControlFlow`], and any type that
|
||||
/// implements the unstable [`std::ops::Try`] trait.
|
||||
///
|
||||
/// The destination type must be specified with a type ascription somewhere.
|
||||
#[macro_export]
|
||||
macro_rules! wrap_ok {
|
||||
($e:expr) => {
|
||||
::core::iter::empty().try_fold($e, |_, __x: ::core::convert::Infallible| match __x {})
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! try_block {
|
||||
{ $($token:tt)* } => {
|
||||
(|| $crate::wrap_ok!({
|
||||
$($token)*
|
||||
}))()
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue