Merge pull request 'Switched to async_circe instead of irc, added more logging to titlebot (and fixed response header check)' (#1) from famfo/uberbot:master into master
Reviewed-on: lemonsh/uberbot#1
This commit is contained in:
commit
d5226b5f3f
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
||||||
/target
|
/target
|
||||||
uberbot.toml
|
uberbot_*.toml
|
||||||
|
uberbot.toml
|
||||||
|
|
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
[submodule "async-circe"]
|
||||||
|
path = async-circe
|
||||||
|
url = ssh://gitea@git.karx.xyz:1604/circe/async-circe.git
|
227
Cargo.lock
generated
227
Cargo.lock
generated
|
@ -26,6 +26,27 @@ version = "1.0.52"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-circe"
|
||||||
|
version = "0.1.5"
|
||||||
|
dependencies = [
|
||||||
|
"async-native-tls",
|
||||||
|
"tokio",
|
||||||
|
"tracing",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-native-tls"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d57d4cec3c647232e1094dc013546c0b33ce785d8aeb251e1f20dfaf8a9a13fe"
|
||||||
|
dependencies = [
|
||||||
|
"native-tls",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
"url",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-stream"
|
name = "async-stream"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
|
@ -179,70 +200,6 @@ version = "1.6.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding"
|
|
||||||
version = "0.2.33"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
|
|
||||||
dependencies = [
|
|
||||||
"encoding-index-japanese",
|
|
||||||
"encoding-index-korean",
|
|
||||||
"encoding-index-simpchinese",
|
|
||||||
"encoding-index-singlebyte",
|
|
||||||
"encoding-index-tradchinese",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-japanese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-korean"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-simpchinese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-singlebyte"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding-index-tradchinese"
|
|
||||||
version = "1.20141219.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
|
|
||||||
dependencies = [
|
|
||||||
"encoding_index_tests",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "encoding_index_tests"
|
|
||||||
version = "0.1.4"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "encoding_rs"
|
name = "encoding_rs"
|
||||||
version = "0.8.30"
|
version = "0.8.30"
|
||||||
|
@ -437,6 +394,15 @@ dependencies = [
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "hermit-abi"
|
||||||
|
version = "0.1.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "htmlescape"
|
name = "htmlescape"
|
||||||
version = "0.3.1"
|
version = "0.3.1"
|
||||||
|
@ -535,58 +501,12 @@ dependencies = [
|
||||||
"hashbrown",
|
"hashbrown",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "instant"
|
|
||||||
version = "0.1.12"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ipnet"
|
name = "ipnet"
|
||||||
version = "2.3.1"
|
version = "2.3.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
checksum = "68f2d64f2edebec4ce84ad108148e67e1064789bee435edc5b60ad398714a3a9"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "irc"
|
|
||||||
version = "0.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "c5510c4c4631e53c57d6b05c44ab8447d1db6beef28fb9d12c4d6a46fad9dfcc"
|
|
||||||
dependencies = [
|
|
||||||
"chrono",
|
|
||||||
"encoding",
|
|
||||||
"futures-util",
|
|
||||||
"irc-proto",
|
|
||||||
"log",
|
|
||||||
"native-tls",
|
|
||||||
"parking_lot",
|
|
||||||
"pin-project",
|
|
||||||
"serde",
|
|
||||||
"serde_derive",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tokio-native-tls",
|
|
||||||
"tokio-stream",
|
|
||||||
"tokio-util",
|
|
||||||
"toml",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "irc-proto"
|
|
||||||
version = "0.15.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "55fa0a52d825e59ba8aea5b7503890245aea000f77e68d9b1903f3491fa33643"
|
|
||||||
dependencies = [
|
|
||||||
"bytes",
|
|
||||||
"encoding",
|
|
||||||
"thiserror",
|
|
||||||
"tokio",
|
|
||||||
"tokio-util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itoa"
|
name = "itoa"
|
||||||
version = "0.4.8"
|
version = "0.4.8"
|
||||||
|
@ -620,15 +540,6 @@ version = "0.2.112"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
checksum = "1b03d17f364a3a042d5e5d46b053bbbf82c92c9430c592dd4c064dc6ee997125"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "lock_api"
|
|
||||||
version = "0.4.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "712a4d093c9976e24e7dbca41db895dabcbac38eb5f4045393d17a95bdfb1109"
|
|
||||||
dependencies = [
|
|
||||||
"scopeguard",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "log"
|
name = "log"
|
||||||
version = "0.4.14"
|
version = "0.4.14"
|
||||||
|
@ -744,6 +655,16 @@ dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num_cpus"
|
||||||
|
version = "1.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
|
||||||
|
dependencies = [
|
||||||
|
"hermit-abi",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.9.0"
|
version = "1.9.0"
|
||||||
|
@ -789,57 +710,12 @@ dependencies = [
|
||||||
"vcpkg",
|
"vcpkg",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot"
|
|
||||||
version = "0.11.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
|
|
||||||
dependencies = [
|
|
||||||
"instant",
|
|
||||||
"lock_api",
|
|
||||||
"parking_lot_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "parking_lot_core"
|
|
||||||
version = "0.8.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216"
|
|
||||||
dependencies = [
|
|
||||||
"cfg-if",
|
|
||||||
"instant",
|
|
||||||
"libc",
|
|
||||||
"redox_syscall",
|
|
||||||
"smallvec",
|
|
||||||
"winapi",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "percent-encoding"
|
name = "percent-encoding"
|
||||||
version = "2.1.0"
|
version = "2.1.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project"
|
|
||||||
version = "1.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "1622113ce508488160cff04e6abc60960e676d330e1ca0f77c0b8df17c81438f"
|
|
||||||
dependencies = [
|
|
||||||
"pin-project-internal",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pin-project-internal"
|
|
||||||
version = "1.0.9"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b95af56fee93df76d721d356ac1ca41fccf168bc448eb14049234df764ba3e76"
|
|
||||||
dependencies = [
|
|
||||||
"proc-macro2",
|
|
||||||
"quote",
|
|
||||||
"syn",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pin-project-lite"
|
name = "pin-project-lite"
|
||||||
version = "0.2.7"
|
version = "0.2.7"
|
||||||
|
@ -1087,12 +963,6 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "scopeguard"
|
|
||||||
version = "1.1.0"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "security-framework"
|
name = "security-framework"
|
||||||
version = "2.4.2"
|
version = "2.4.2"
|
||||||
|
@ -1323,6 +1193,7 @@ dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
"memchr",
|
"memchr",
|
||||||
"mio",
|
"mio",
|
||||||
|
"num_cpus",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"signal-hook-registry",
|
"signal-hook-registry",
|
||||||
|
@ -1363,17 +1234,6 @@ dependencies = [
|
||||||
"tokio",
|
"tokio",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "tokio-stream"
|
|
||||||
version = "0.1.8"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "50145484efff8818b5ccd256697f36863f587da82cf8b409c53adf1e840798e3"
|
|
||||||
dependencies = [
|
|
||||||
"futures-core",
|
|
||||||
"pin-project-lite",
|
|
||||||
"tokio",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio-util"
|
name = "tokio-util"
|
||||||
version = "0.6.9"
|
version = "0.6.9"
|
||||||
|
@ -1481,14 +1341,15 @@ name = "uberbot"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"async-circe",
|
||||||
"fancy-regex",
|
"fancy-regex",
|
||||||
"futures",
|
|
||||||
"htmlescape",
|
"htmlescape",
|
||||||
"irc",
|
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"rspotify",
|
"rspotify",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
25
Cargo.toml
25
Cargo.toml
|
@ -4,14 +4,17 @@ version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
irc = "0"
|
tokio = { version = "1.15", features = ["rt", "macros", "signal"] }
|
||||||
tokio = { version = "1", features = ["rt", "macros", "signal"] }
|
anyhow = "1.0"
|
||||||
anyhow = "1"
|
tracing = "0.1"
|
||||||
futures = "0"
|
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
||||||
tracing = "0"
|
reqwest = "0.11"
|
||||||
tracing-subscriber = { version = "0", features = ["env-filter"] }
|
serde_json = "1.0"
|
||||||
reqwest = "0"
|
fancy-regex = "0.7"
|
||||||
serde_json = "1"
|
rspotify = "0.11"
|
||||||
fancy-regex = "0"
|
htmlescape = "0.3"
|
||||||
rspotify = "0"
|
toml = "0.5"
|
||||||
htmlescape = "0"
|
serde = "1.0"
|
||||||
|
|
||||||
|
[dependencies.async-circe]
|
||||||
|
path = "async-circe/"
|
||||||
|
|
1
async-circe
Submodule
1
async-circe
Submodule
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 1ede9a1b03d6692e4cdb21bc021737b5c4b7403b
|
|
@ -1,5 +1,13 @@
|
||||||
nickname = "uberbot"
|
# IRC config
|
||||||
server = "karx.xyz"
|
host = "karx.xyz"
|
||||||
use_tls = true
|
port = 6697
|
||||||
channels = ["#main"]
|
username = "uberbot"
|
||||||
umodes = "+B"
|
channels = ["#main, #no-normies"]
|
||||||
|
mode = "+B"
|
||||||
|
|
||||||
|
# Spotify config
|
||||||
|
spotify_client_id = ""
|
||||||
|
spotify_client_secret = ""
|
||||||
|
|
||||||
|
# Bot config
|
||||||
|
prefix = "!"
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
pub mod weeb;
|
|
||||||
pub mod title;
|
pub mod title;
|
||||||
|
pub mod weeb;
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
use fancy_regex::Regex;
|
use fancy_regex::Regex;
|
||||||
use htmlescape::decode_html;
|
use htmlescape::decode_html;
|
||||||
use rspotify::model::PlayableItem;
|
|
||||||
use rspotify::{Credentials, ClientCredsSpotify, model::Id};
|
|
||||||
use rspotify::clients::BaseClient;
|
use rspotify::clients::BaseClient;
|
||||||
|
use rspotify::model::PlayableItem;
|
||||||
|
use rspotify::{model::Id, ClientCredsSpotify, Credentials};
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
fn calculate_playtime(secs: u64) -> (u64, u64) {
|
fn calculate_playtime(secs: u64) -> (u64, u64) {
|
||||||
|
@ -12,13 +12,20 @@ fn calculate_playtime(secs: u64) -> (u64, u64) {
|
||||||
(dur_min, dur_sec)
|
(dur_min, dur_sec)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn resolve_spotify(spotify: &mut ClientCredsSpotify, resource_type: &str, resource_id: &str) -> anyhow::Result<String> {
|
async fn resolve_spotify(
|
||||||
|
spotify: &mut ClientCredsSpotify,
|
||||||
|
resource_type: &str,
|
||||||
|
resource_id: &str,
|
||||||
|
) -> anyhow::Result<String> {
|
||||||
// uncomment this if titlebot commits suicide after exactly 30 minutes
|
// uncomment this if titlebot commits suicide after exactly 30 minutes
|
||||||
|
|
||||||
// if spotify.token.lock().await.unwrap().as_ref().unwrap().is_expired() {
|
// if spotify.token.lock().await.unwrap().as_ref().unwrap().is_expired() {
|
||||||
// spotify.request_token().await?;
|
// spotify.request_token().await?;
|
||||||
// }
|
// }
|
||||||
debug!("Resolving Spotify resource '{}' with id '{}'", resource_type, resource_id);
|
debug!(
|
||||||
|
"Resolving Spotify resource '{}' with id '{}'",
|
||||||
|
resource_type, resource_id
|
||||||
|
);
|
||||||
match resource_type {
|
match resource_type {
|
||||||
"track" => {
|
"track" => {
|
||||||
let track = spotify.track(&Id::from_id(resource_id)?).await?;
|
let track = spotify.track(&Id::from_id(resource_id)?).await?;
|
||||||
|
@ -28,15 +35,27 @@ async fn resolve_spotify(spotify: &mut ClientCredsSpotify, resource_type: &str,
|
||||||
}
|
}
|
||||||
"artist" => {
|
"artist" => {
|
||||||
let artist = spotify.artist(&Id::from_id(resource_id)?).await?;
|
let artist = spotify.artist(&Id::from_id(resource_id)?).await?;
|
||||||
Ok(format!("\x037[Spotify]\x03 Artist: \x039\"{}\" \x0311|\x03 Genres:\x039 {} \x0311|", artist.name, artist.genres.join(", ")))
|
Ok(format!(
|
||||||
|
"\x037[Spotify]\x03 Artist: \x039\"{}\" \x0311|\x03 Genres:\x039 {} \x0311|",
|
||||||
|
artist.name,
|
||||||
|
artist.genres.join(", ")
|
||||||
|
))
|
||||||
}
|
}
|
||||||
"album" => {
|
"album" => {
|
||||||
let album = spotify.album(&Id::from_id(resource_id)?).await?;
|
let album = spotify.album(&Id::from_id(resource_id)?).await?;
|
||||||
let playtime = calculate_playtime(album.tracks.items.iter().fold(0, |acc, x| acc + x.duration.as_secs()));
|
let playtime = calculate_playtime(
|
||||||
|
album
|
||||||
|
.tracks
|
||||||
|
.items
|
||||||
|
.iter()
|
||||||
|
.fold(0, |acc, x| acc + x.duration.as_secs()),
|
||||||
|
);
|
||||||
Ok(format!("\x037[Spotify]\x03 Album: \x039\"{}\" \x0311|\x03 Tracks:\x0315 {} \x0311|\x03 Release date:\x039 {} \x0311|\x03 Length:\x0315 {}:{:02} \x0311|", album.name, album.tracks.total, album.release_date, playtime.0, playtime.1))
|
Ok(format!("\x037[Spotify]\x03 Album: \x039\"{}\" \x0311|\x03 Tracks:\x0315 {} \x0311|\x03 Release date:\x039 {} \x0311|\x03 Length:\x0315 {}:{:02} \x0311|", album.name, album.tracks.total, album.release_date, playtime.0, playtime.1))
|
||||||
}
|
}
|
||||||
"playlist" => {
|
"playlist" => {
|
||||||
let playlist = spotify.playlist(&Id::from_id(resource_id)?, None, None).await?;
|
let playlist = spotify
|
||||||
|
.playlist(&Id::from_id(resource_id)?, None, None)
|
||||||
|
.await?;
|
||||||
let mut tracks = 0;
|
let mut tracks = 0;
|
||||||
let playtime = calculate_playtime(playlist.tracks.items.iter().fold(0, |acc, x| {
|
let playtime = calculate_playtime(playlist.tracks.items.iter().fold(0, |acc, x| {
|
||||||
x.track.as_ref().map_or(acc, |item| match item {
|
x.track.as_ref().map_or(acc, |item| match item {
|
||||||
|
@ -52,7 +71,7 @@ async fn resolve_spotify(spotify: &mut ClientCredsSpotify, resource_type: &str,
|
||||||
}));
|
}));
|
||||||
Ok(format!("\x037[Spotify]\x03 Playlist: \x039\"{}\" \x0311|\x03 Tracks/Episodes:\x0315 {} \x0311|\x03 Length:\x0315 {}:{:02} \x0311|\x03 Description: \x039\"{}\" \x0311|", playlist.name, tracks, playtime.0, playtime.1, playlist.description.unwrap_or_else(|| "<empty>".into())))
|
Ok(format!("\x037[Spotify]\x03 Playlist: \x039\"{}\" \x0311|\x03 Tracks/Episodes:\x0315 {} \x0311|\x03 Length:\x0315 {}:{:02} \x0311|\x03 Description: \x039\"{}\" \x0311|", playlist.name, tracks, playtime.0, playtime.1, playlist.description.unwrap_or_else(|| "<empty>".into())))
|
||||||
}
|
}
|
||||||
_ => Ok("\x037[Spotify]\x03 Error: Invalid resource type".into())
|
_ => Ok("\x037[Spotify]\x03 Error: Invalid resource type".into()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,39 +79,55 @@ pub struct Titlebot {
|
||||||
url_regex: Regex,
|
url_regex: Regex,
|
||||||
title_regex: Regex,
|
title_regex: Regex,
|
||||||
spotify_regex: Regex,
|
spotify_regex: Regex,
|
||||||
spotify: ClientCredsSpotify
|
spotify: ClientCredsSpotify,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Titlebot {
|
impl Titlebot {
|
||||||
pub async fn create(spotify_creds: Credentials) -> anyhow::Result<Self> {
|
pub async fn create(spotify_creds: Credentials) -> anyhow::Result<Self> {
|
||||||
let url_regex = Regex::new(r"https?://\w+\.\w+[/\S+]*")?;
|
let url_regex = Regex::new(r"https?://\w+\.\w+[/\S+]*")?;
|
||||||
let title_regex = Regex::new(r"(?<=<title>)(.*)(?=</title>)")?;
|
let title_regex = Regex::new(r"(?<=<title>)(.*)(?=</title>)")?;
|
||||||
let spotify_regex = Regex::new(r"(?:https?|spotify):(?://open\.spotify\.com/)?(track|artist|album|playlist)[/:]([a-zA-Z0-9]*)")?;
|
let spotify_regex = Regex::new(
|
||||||
|
r"(?:https?|spotify):(?://open\.spotify\.com/)?(track|artist|album|playlist)[/:]([a-zA-Z0-9]*)",
|
||||||
|
)?;
|
||||||
let mut spotify = ClientCredsSpotify::new(spotify_creds);
|
let mut spotify = ClientCredsSpotify::new(spotify_creds);
|
||||||
spotify.request_token().await?;
|
spotify.request_token().await?;
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
url_regex, title_regex, spotify_regex, spotify
|
url_regex,
|
||||||
|
title_regex,
|
||||||
|
spotify_regex,
|
||||||
|
spotify,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn resolve(&mut self, message: &str) -> anyhow::Result<Option<String>> {
|
pub async fn resolve(&mut self, message: &str) -> anyhow::Result<Option<String>> {
|
||||||
if let Some(m) = self.spotify_regex.captures(&message)? {
|
if let Some(m) = self.spotify_regex.captures(&message)? {
|
||||||
|
tracing::debug!("{}", message);
|
||||||
let tp_group = m.get(1).unwrap();
|
let tp_group = m.get(1).unwrap();
|
||||||
let id_group = m.get(2).unwrap();
|
let id_group = m.get(2).unwrap();
|
||||||
return Ok(Some(resolve_spotify(&mut self.spotify, &message[tp_group.start()..tp_group.end()], &message[id_group.start()..id_group.end()]).await?))
|
return Ok(Some(
|
||||||
|
resolve_spotify(
|
||||||
|
&mut self.spotify,
|
||||||
|
&message[tp_group.start()..tp_group.end()],
|
||||||
|
&message[id_group.start()..id_group.end()],
|
||||||
|
)
|
||||||
|
.await?,
|
||||||
|
));
|
||||||
} else if let Some(m) = self.url_regex.find(&message)? {
|
} else if let Some(m) = self.url_regex.find(&message)? {
|
||||||
let url = &message[m.start()..m.end()];
|
let url = &message[m.start()..m.end()];
|
||||||
|
tracing::debug!("url: {}", url);
|
||||||
let response = reqwest::get(url).await?;
|
let response = reqwest::get(url).await?;
|
||||||
if let Some(header) = response.headers().get("Content-Type") {
|
if let Some(header) = response.headers().get("Content-Type") {
|
||||||
if !(header.to_str()? == "text/html") {
|
tracing::debug!("response header: {}", header.to_str()?);
|
||||||
return Ok(None)
|
if !(header.to_str()?.contains("text/html")) {
|
||||||
|
return Ok(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let body = response.text().await?;
|
let body = response.text().await?;
|
||||||
if let Some(tm) = self.title_regex.find(&body)? {
|
if let Some(tm) = self.title_regex.find(&body)? {
|
||||||
let title_match = &body[tm.start()..tm.end()];
|
let title_match = &body[tm.start()..tm.end()];
|
||||||
let result = decode_html(title_match).unwrap_or_else(|_| title_match.to_string());
|
let result = decode_html(title_match).unwrap_or_else(|_| title_match.to_string());
|
||||||
return Ok(Some(format!("\x039[Title]\x0311 {}", result)))
|
tracing::debug!("result: {}", result);
|
||||||
|
return Ok(Some(format!("\x039[Title]\x0311 {}", result)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None)
|
Ok(None)
|
||||||
|
|
|
@ -2,7 +2,10 @@ use serde_json::Value;
|
||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
pub async fn get_waifu_pic(category: &str) -> anyhow::Result<Option<String>> {
|
pub async fn get_waifu_pic(category: &str) -> anyhow::Result<Option<String>> {
|
||||||
let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category)).await?.text().await?;
|
let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category))
|
||||||
|
.await?
|
||||||
|
.text()
|
||||||
|
.await?;
|
||||||
let api_resp = api_resp.trim();
|
let api_resp = api_resp.trim();
|
||||||
debug!("API response: {}", api_resp);
|
debug!("API response: {}", api_resp);
|
||||||
let value: Value = serde_json::from_str(&api_resp)?;
|
let value: Value = serde_json::from_str(&api_resp)?;
|
||||||
|
@ -10,4 +13,4 @@ pub async fn get_waifu_pic(category: &str) -> anyhow::Result<Option<String>> {
|
||||||
Ok(url)
|
Ok(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: add owofier
|
// TODO: add owofier
|
||||||
|
|
157
src/main.rs
157
src/main.rs
|
@ -1,21 +1,20 @@
|
||||||
use std::{env, collections::HashMap};
|
use async_circe::{commands::Command, Client, Config};
|
||||||
|
|
||||||
use futures::stream::StreamExt;
|
|
||||||
use irc::{
|
|
||||||
client::{prelude::Config, Client, ClientStream},
|
|
||||||
proto::{Command, Message, Prefix},
|
|
||||||
};
|
|
||||||
use rspotify::Credentials;
|
|
||||||
use bots::title::Titlebot;
|
use bots::title::Titlebot;
|
||||||
use bots::weeb;
|
use bots::weeb;
|
||||||
|
use rspotify::Credentials;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::Read;
|
||||||
|
use std::{collections::HashMap, env};
|
||||||
use tokio::select;
|
use tokio::select;
|
||||||
use tracing::{debug, error, info, warn};
|
|
||||||
use tracing_subscriber::EnvFilter;
|
use tracing_subscriber::EnvFilter;
|
||||||
|
|
||||||
mod bots;
|
mod bots;
|
||||||
|
|
||||||
const HELP: &str = concat!(
|
const HELP: &str = concat!(
|
||||||
"=- \x1d\x02Ü\x02berbot\x0f ", env!("CARGO_PKG_VERSION"), " -="
|
"=- \x1d\x02Ü\x02berbot\x0f ",
|
||||||
|
env!("CARGO_PKG_VERSION"),
|
||||||
|
" -="
|
||||||
);
|
);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -24,8 +23,8 @@ async fn terminate_signal() {
|
||||||
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
let mut sigterm = signal(SignalKind::terminate()).unwrap();
|
||||||
let mut sigint = signal(SignalKind::interrupt()).unwrap();
|
let mut sigint = signal(SignalKind::interrupt()).unwrap();
|
||||||
select! {
|
select! {
|
||||||
_ = sigterm.recv() => break,
|
_ = sigterm.recv() => return,
|
||||||
_ = sigint.recv() => break
|
_ = sigint.recv() => return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +39,20 @@ struct AppState {
|
||||||
prefix: String,
|
prefix: String,
|
||||||
client: Client,
|
client: Client,
|
||||||
last_msgs: HashMap<String, String>,
|
last_msgs: HashMap<String, String>,
|
||||||
titlebot: Titlebot
|
titlebot: Titlebot,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct ClientConf {
|
||||||
|
channels: Vec<String>,
|
||||||
|
host: String,
|
||||||
|
mode: Option<String>,
|
||||||
|
nickname: Option<String>,
|
||||||
|
port: u16,
|
||||||
|
username: String,
|
||||||
|
spotify_client_id: String,
|
||||||
|
spotify_client_secret: String,
|
||||||
|
prefix: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tokio::main(flavor = "current_thread")]
|
#[tokio::main(flavor = "current_thread")]
|
||||||
|
@ -48,72 +60,71 @@ async fn main() -> anyhow::Result<()> {
|
||||||
tracing_subscriber::fmt()
|
tracing_subscriber::fmt()
|
||||||
.with_env_filter(EnvFilter::from_env("UBERBOT_LOG"))
|
.with_env_filter(EnvFilter::from_env("UBERBOT_LOG"))
|
||||||
.init();
|
.init();
|
||||||
let mut config =
|
|
||||||
Config::load(env::var("UBERBOT_CONFIG").unwrap_or_else(|_| "uberbot.toml".to_owned()))?;
|
|
||||||
let prefix = config.options.remove("prefix").unwrap_or("!".into());
|
|
||||||
let spotify_cred_options = (config.options.remove("spotify_client_id"), config.options.remove("spotify_client_secret"));
|
|
||||||
let spotify_creds = if let (Some(id), Some(sec)) = spotify_cred_options {
|
|
||||||
Credentials::new(id.as_str(), sec.as_str())
|
|
||||||
} else {
|
|
||||||
return Err(anyhow::anyhow!("Config doesn't contain Spotify credentials."))
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut client = Client::from_config(config).await?;
|
let mut file = File::open("uberbot.toml").unwrap();
|
||||||
client.identify()?;
|
let mut client_conf = String::new();
|
||||||
let stream = client.stream()?;
|
file.read_to_string(&mut client_conf).unwrap();
|
||||||
|
|
||||||
|
let client_config: ClientConf = toml::from_str(&client_conf).unwrap();
|
||||||
|
|
||||||
|
let spotify_creds = Credentials::new(
|
||||||
|
&client_config.spotify_client_id,
|
||||||
|
&client_config.spotify_client_secret,
|
||||||
|
);
|
||||||
|
|
||||||
|
let config = Config::runtime_config(
|
||||||
|
client_config.channels,
|
||||||
|
client_config.host,
|
||||||
|
client_config.mode,
|
||||||
|
client_config.nickname,
|
||||||
|
client_config.port,
|
||||||
|
client_config.username,
|
||||||
|
);
|
||||||
|
let mut client = Client::new(config).await?;
|
||||||
|
client.identify().await?;
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
prefix, client,
|
prefix: client_config.prefix,
|
||||||
|
client,
|
||||||
last_msgs: HashMap::new(),
|
last_msgs: HashMap::new(),
|
||||||
titlebot: Titlebot::create(spotify_creds).await?
|
titlebot: Titlebot::create(spotify_creds).await?,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(e) = message_loop(stream, state).await {
|
if let Err(e) = message_loop(state).await {
|
||||||
error!("Error in message loop: {}", e);
|
tracing::error!("Error in message loop: {}", e);
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Shutting down");
|
tracing::info!("Shutting down");
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn message_loop(
|
async fn message_loop(mut state: AppState) -> anyhow::Result<()> {
|
||||||
mut stream: ClientStream,
|
|
||||||
mut state: AppState
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
loop {
|
loop {
|
||||||
select! {
|
select! {
|
||||||
r = stream.next() => {
|
r = state.client.read() => {
|
||||||
if let Some(message) = r.transpose()? {
|
if let Ok(command) = r {
|
||||||
debug!("{}", message.to_string().trim_end());
|
handle_message(&mut state, command).await?;
|
||||||
|
|
||||||
if let Err(e) = handle_message(&mut state, message).await {
|
|
||||||
warn!("Error in message handler: {}", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
_ = terminate_signal() => {
|
_ = terminate_signal() => {
|
||||||
info!("Sending QUIT message");
|
tracing::info!("Sending QUIT message");
|
||||||
state.client.send_quit("überbot shutting down")?;
|
state.client.quit(Some("überbot shutting down")).await?;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_message(state: &mut AppState, msg: Message) -> anyhow::Result<()> {
|
async fn handle_message(state: &mut AppState, command: Command) -> anyhow::Result<()> {
|
||||||
// change this to a match when more commands are handled
|
// change this to a match when more commands are handled
|
||||||
if let Command::PRIVMSG(target, content) = &msg.command {
|
if let Command::PRIVMSG(nick, channel, message) = command {
|
||||||
let target = msg.response_target().unwrap_or(target);
|
if let Err(e) = handle_privmsg(state, nick, &channel, message).await {
|
||||||
let author = if let Some(Prefix::Nickname(ref nick, _, _)) = msg.prefix {
|
state
|
||||||
Some(nick.as_str())
|
.client
|
||||||
} else {
|
.privmsg(&channel, &format!("Error: {}", e))
|
||||||
None
|
.await?;
|
||||||
};
|
|
||||||
if let Err(e) = handle_privmsg(state, author, target, content).await {
|
|
||||||
state.client.send_privmsg(target, format!("Error: {}", e))?;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -121,41 +132,41 @@ async fn handle_message(state: &mut AppState, msg: Message) -> anyhow::Result<()
|
||||||
|
|
||||||
async fn handle_privmsg(
|
async fn handle_privmsg(
|
||||||
state: &mut AppState,
|
state: &mut AppState,
|
||||||
author: Option<&str>,
|
nick: String,
|
||||||
target: &str,
|
channel: &str,
|
||||||
content: &String
|
message: String,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
if !content.starts_with(state.prefix.as_str()) {
|
if !message.starts_with(state.prefix.as_str()) {
|
||||||
if let Some(author) = author {
|
state.last_msgs.insert(nick, message.clone());
|
||||||
state.last_msgs.insert(author.to_string(), content.clone());
|
|
||||||
}
|
if let Some(titlebot_msg) = state.titlebot.resolve(&message).await? {
|
||||||
if let Some(titlebot_msg) = state.titlebot.resolve(content).await? {
|
state.client.privmsg(&channel, &titlebot_msg).await?;
|
||||||
state.client.send_privmsg(target, titlebot_msg)?;
|
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let content = content.trim();
|
let space_index = message.find(' ');
|
||||||
let space_index = content.find(' ');
|
|
||||||
let (command, remainder) = if let Some(o) = space_index {
|
let (command, remainder) = if let Some(o) = space_index {
|
||||||
(&content[state.prefix.len()..o], Some(&content[o + 1..]))
|
(&message[state.prefix.len()..o], Some(&message[o + 1..]))
|
||||||
} else {
|
} else {
|
||||||
(&content[state.prefix.len()..], None)
|
(&message[state.prefix.len()..], None)
|
||||||
};
|
};
|
||||||
debug!("Command received ({}; {:?})", command, remainder);
|
tracing::debug!("Command received ({}; {:?})", command, remainder);
|
||||||
|
|
||||||
match command {
|
match command {
|
||||||
"help" => {
|
"help" => {
|
||||||
state.client.send_privmsg(target, HELP)?;
|
state.client.privmsg(&channel, HELP).await?;
|
||||||
}
|
}
|
||||||
"waifu" => {
|
"waifu" => {
|
||||||
let category = remainder.unwrap_or("waifu");
|
let category = remainder.unwrap_or("waifu");
|
||||||
let url = weeb::get_waifu_pic(category).await?;
|
let url = weeb::get_waifu_pic(category).await?;
|
||||||
let response = url.as_ref().map(|v| v.as_str())
|
let response = url
|
||||||
|
.as_ref()
|
||||||
|
.map(|v| v.as_str())
|
||||||
.unwrap_or("Invalid category. Valid categories: https://waifu.pics/docs");
|
.unwrap_or("Invalid category. Valid categories: https://waifu.pics/docs");
|
||||||
state.client.send_privmsg(target, response)?;
|
state.client.privmsg(&channel, response).await?;
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
state.client.send_privmsg(target, "Unknown command")?;
|
state.client.privmsg(&channel, "Unknown command").await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
Loading…
Reference in a new issue