Add titlebot
This commit is contained in:
parent
a77dc2ad7e
commit
a85113cfd7
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1 +1,2 @@
|
|||
/target
|
||||
uberbot.toml
|
45
.vscode/launch.json
vendored
Normal file
45
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug executable 'uberbot'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"build",
|
||||
"--bin=uberbot",
|
||||
"--package=uberbot"
|
||||
],
|
||||
"filter": {
|
||||
"name": "uberbot",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
{
|
||||
"type": "lldb",
|
||||
"request": "launch",
|
||||
"name": "Debug unit tests in executable 'uberbot'",
|
||||
"cargo": {
|
||||
"args": [
|
||||
"test",
|
||||
"--no-run",
|
||||
"--bin=uberbot",
|
||||
"--package=uberbot"
|
||||
],
|
||||
"filter": {
|
||||
"name": "uberbot",
|
||||
"kind": "bin"
|
||||
}
|
||||
},
|
||||
"args": [],
|
||||
"cwd": "${workspaceFolder}"
|
||||
}
|
||||
]
|
||||
}
|
283
Cargo.lock
generated
283
Cargo.lock
generated
|
@ -2,6 +2,15 @@
|
|||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "aho-corasick"
|
||||
version = "0.7.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ansi_term"
|
||||
version = "0.12.1"
|
||||
|
@ -17,6 +26,38 @@ version = "1.0.52"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84450d0b4a8bd1ba4144ce8ce718fbc5d071358b1e5384bace6536b3d1f2d5b3"
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "171374e7e3b2504e0e5236e3b59260560f9fe94bfe9ac39ba5e4e929c5590625"
|
||||
dependencies = [
|
||||
"async-stream-impl",
|
||||
"futures-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream-impl"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "648ed8c8d2ce5409ccd57453d9d1b214b342a0d69376a6feda1fd6cae3299308"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.52"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "061a7acccaa286c011ddc30970520b98fa40e00c9d644633fb26b5fc63a265e3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.1"
|
||||
|
@ -29,12 +70,36 @@ version = "0.13.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd"
|
||||
|
||||
[[package]]
|
||||
name = "bit-set"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de"
|
||||
dependencies = [
|
||||
"bit-vec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bit-vec"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.8.0"
|
||||
|
@ -68,6 +133,8 @@ dependencies = [
|
|||
"libc",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"rustc-serialize",
|
||||
"serde",
|
||||
"time",
|
||||
"winapi",
|
||||
]
|
||||
|
@ -88,6 +155,30 @@ version = "0.8.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
|
||||
[[package]]
|
||||
name = "encoding"
|
||||
version = "0.2.33"
|
||||
|
@ -161,6 +252,16 @@ dependencies = [
|
|||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fancy-regex"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9d6b8560a05112eb52f04b00e5d3790c0dd75d9d980eb8a122fb23b92a623ccf"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
|
@ -281,6 +382,16 @@ dependencies = [
|
|||
"slab",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
|
@ -317,6 +428,21 @@ version = "0.11.2"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
|
||||
|
||||
[[package]]
|
||||
name = "heck"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "htmlescape"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "0.2.5"
|
||||
|
@ -527,6 +653,17 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-async"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6007f9dad048e0a224f27ca599d669fca8cfa0dac804725aab542b2eb032bce6"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.4.1"
|
||||
|
@ -613,6 +750,12 @@ version = "1.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da32515d9f6e6e489d7bc9d84c71b060db7247dc035bbe44eac88cf87486d8d5"
|
||||
|
||||
[[package]]
|
||||
name = "opaque-debug"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "openssl"
|
||||
version = "0.10.38"
|
||||
|
@ -794,6 +937,8 @@ version = "1.5.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d07a8629359eb56f1e2fb1652bb04212c072a87ba68546a04065d525673ac461"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
"regex-syntax",
|
||||
]
|
||||
|
||||
|
@ -849,6 +994,7 @@ dependencies = [
|
|||
"serde_urlencoded",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-socks",
|
||||
"url",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
|
@ -856,6 +1002,75 @@ dependencies = [
|
|||
"winreg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rspotify"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3cebf0080b78317b3208858454cb744b05ccf296d975f47d72624b8032a05875"
|
||||
dependencies = [
|
||||
"async-stream",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"chrono",
|
||||
"futures",
|
||||
"getrandom",
|
||||
"log",
|
||||
"maybe-async",
|
||||
"rspotify-http",
|
||||
"rspotify-macros",
|
||||
"rspotify-model",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"thiserror",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rspotify-http"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9328d4a8bb0863212dc93fdfc2c8c07eae4c9295f854f4453c25818faf767a10"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"log",
|
||||
"maybe-async",
|
||||
"reqwest",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rspotify-macros"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fbdbcf984d771b418eafcebecad34f65d02f1db64c8a3db49ec0cf599775b5a"
|
||||
|
||||
[[package]]
|
||||
name = "rspotify-model"
|
||||
version = "0.11.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe4b795e0173f15e732fe14b60dda53530e6e4c3d5b7f9441d02c7740e9168d2"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"strum",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc-serialize"
|
||||
version = "0.3.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
|
||||
|
||||
[[package]]
|
||||
name = "rustversion"
|
||||
version = "1.0.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f"
|
||||
|
||||
[[package]]
|
||||
name = "ryu"
|
||||
version = "1.0.9"
|
||||
|
@ -944,6 +1159,19 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b69f9a4c9740d74c5baa3fd2e547f9525fa8088a8a958e0ca2409a514e33f5fa"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
"opaque-debug",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sharded-slab"
|
||||
version = "0.1.4"
|
||||
|
@ -984,6 +1212,28 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum"
|
||||
version = "0.23.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cae14b91c7d11c9a851d3fbc80a963198998c2a64eec840477fa92d8ce9b70bb"
|
||||
dependencies = [
|
||||
"strum_macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strum_macros"
|
||||
version = "0.23.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5bb0dc7ee9c15cea6199cde9a127fa16a4c5819af85395457ad72d68edc85a38"
|
||||
dependencies = [
|
||||
"heck",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustversion",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.84"
|
||||
|
@ -1101,6 +1351,18 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-socks"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51165dfa029d2a65969413a6cc96f354b86b464498702f174a4efa13608fd8c0"
|
||||
dependencies = [
|
||||
"either",
|
||||
"futures-util",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.8"
|
||||
|
@ -1208,14 +1470,23 @@ version = "0.2.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59547bce71d9c38b83d9c0e92b6066c4253371f15005def0c30d9657f50c7642"
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "uberbot"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"fancy-regex",
|
||||
"futures",
|
||||
"htmlescape",
|
||||
"irc",
|
||||
"reqwest",
|
||||
"rspotify",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
@ -1237,6 +1508,12 @@ dependencies = [
|
|||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-xid"
|
||||
version = "0.2.2"
|
||||
|
@ -1261,6 +1538,12 @@ version = "0.2.15"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
|
||||
|
||||
[[package]]
|
||||
name = "want"
|
||||
version = "0.3.0"
|
||||
|
|
|
@ -12,3 +12,6 @@ tracing = "0"
|
|||
tracing-subscriber = { version = "0", features = ["env-filter"] }
|
||||
reqwest = "0"
|
||||
serde_json = "1"
|
||||
fancy-regex = "0"
|
||||
rspotify = "0"
|
||||
htmlescape = "0"
|
||||
|
|
78
src/main.rs
78
src/main.rs
|
@ -1,15 +1,23 @@
|
|||
use std::env;
|
||||
use std::{env, collections::HashMap};
|
||||
|
||||
use futures::stream::StreamExt;
|
||||
use irc::{
|
||||
client::{prelude::Config, Client, ClientStream},
|
||||
proto::{Command, Message},
|
||||
proto::{Command, Message, Prefix},
|
||||
};
|
||||
use rspotify::Credentials;
|
||||
use titlebot::Titlebot;
|
||||
use tokio::select;
|
||||
use tracing::{debug, error, info, warn};
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
mod waifu;
|
||||
mod titlebot;
|
||||
|
||||
const HELP: &str = concat!(
|
||||
"a",
|
||||
"b"
|
||||
);
|
||||
|
||||
#[cfg(unix)]
|
||||
async fn terminate_signal() {
|
||||
|
@ -29,6 +37,13 @@ async fn terminate_signal() {
|
|||
let _ = ctrlc.recv().await;
|
||||
}
|
||||
|
||||
struct AppState {
|
||||
prefix: String,
|
||||
client: Client,
|
||||
last_msgs: HashMap<String, String>,
|
||||
titlebot: Titlebot
|
||||
}
|
||||
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> anyhow::Result<()> {
|
||||
tracing_subscriber::fmt()
|
||||
|
@ -37,13 +52,24 @@ async fn main() -> anyhow::Result<()> {
|
|||
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 prefix = prefix.as_str();
|
||||
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?;
|
||||
client.identify()?;
|
||||
let stream = client.stream()?;
|
||||
|
||||
if let Err(e) = message_loop(stream, prefix, client).await {
|
||||
let state = AppState {
|
||||
prefix, client,
|
||||
last_msgs: HashMap::new(),
|
||||
titlebot: Titlebot::create(spotify_creds).await?
|
||||
};
|
||||
|
||||
if let Err(e) = message_loop(stream, state).await {
|
||||
error!("Error in message loop: {}", e);
|
||||
}
|
||||
|
||||
|
@ -54,8 +80,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
async fn message_loop(
|
||||
mut stream: ClientStream,
|
||||
prefix: &str,
|
||||
client: Client,
|
||||
mut state: AppState
|
||||
) -> anyhow::Result<()> {
|
||||
loop {
|
||||
select! {
|
||||
|
@ -63,7 +88,7 @@ async fn message_loop(
|
|||
if let Some(message) = r.transpose()? {
|
||||
debug!("{}", message.to_string().trim_end());
|
||||
|
||||
if let Err(e) = handle_message(message, prefix, &client).await {
|
||||
if let Err(e) = handle_message(&mut state, message).await {
|
||||
warn!("Error in message handler: {}", e);
|
||||
}
|
||||
} else {
|
||||
|
@ -72,51 +97,66 @@ async fn message_loop(
|
|||
},
|
||||
_ = terminate_signal() => {
|
||||
info!("Sending QUIT message");
|
||||
client.send_quit("überbot shutting down")?;
|
||||
state.client.send_quit("überbot shutting down")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_message(msg: Message, prefix: &str, client: &Client) -> anyhow::Result<()> {
|
||||
async fn handle_message(state: &mut AppState, msg: Message) -> anyhow::Result<()> {
|
||||
// change this to a match when more commands are handled
|
||||
if let Command::PRIVMSG(target, content) = &msg.command {
|
||||
let target = msg.response_target().unwrap_or(target);
|
||||
if let Err(e) = handle_privmsg(target, content, prefix, client).await {
|
||||
client.send_privmsg(target, format!("Error: {}", e))?;
|
||||
let author = if let Some(Prefix::Nickname(ref nick, _, _)) = msg.prefix {
|
||||
Some(nick.as_str())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if let Err(e) = handle_privmsg(state, author, target, content).await {
|
||||
state.client.send_privmsg(target, format!("Error: {}", e))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn handle_privmsg(
|
||||
state: &mut AppState,
|
||||
author: Option<&str>,
|
||||
target: &str,
|
||||
content: &String,
|
||||
prefix: &str,
|
||||
client: &Client,
|
||||
content: &String
|
||||
) -> anyhow::Result<()> {
|
||||
if !content.starts_with(prefix) {
|
||||
if !content.starts_with(state.prefix.as_str()) {
|
||||
if let Some(author) = author {
|
||||
state.last_msgs.insert(author.to_string(), content.clone());
|
||||
}
|
||||
if let Some(titlebot_msg) = state.titlebot.resolve(content).await? {
|
||||
state.client.send_privmsg(target, titlebot_msg)?;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let content = content.trim();
|
||||
let space_index = content.find(' ');
|
||||
let (command, remainder) = if let Some(o) = space_index {
|
||||
(&content[prefix.len()..o], Some(&content[o + 1..]))
|
||||
(&content[state.prefix.len()..o], Some(&content[o + 1..]))
|
||||
} else {
|
||||
(&content[prefix.len()..], None)
|
||||
(&content[state.prefix.len()..], None)
|
||||
};
|
||||
debug!("Command received ({}; {:?})", command, remainder);
|
||||
|
||||
match command {
|
||||
"help" => {
|
||||
state.client.send_privmsg(target, HELP)?;
|
||||
}
|
||||
"waifu" => {
|
||||
let category = remainder.unwrap_or("waifu");
|
||||
let url = waifu::get_waifu_pic(category).await?;
|
||||
let response = url.as_ref().map(|v| v.as_str())
|
||||
.unwrap_or("Invalid category. Valid categories: https://waifu.pics/docs");
|
||||
client.send_privmsg(target, response)?;
|
||||
state.client.send_privmsg(target, response)?;
|
||||
}
|
||||
_ => {
|
||||
client.send_privmsg(target, "Unknown command")?;
|
||||
state.client.send_privmsg(target, "Unknown command")?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
|
100
src/titlebot.rs
Normal file
100
src/titlebot.rs
Normal file
|
@ -0,0 +1,100 @@
|
|||
use fancy_regex::Regex;
|
||||
use htmlescape::decode_html;
|
||||
use rspotify::model::PlayableItem;
|
||||
use rspotify::{Credentials, ClientCredsSpotify, model::Id};
|
||||
use rspotify::clients::BaseClient;
|
||||
use tracing::debug;
|
||||
|
||||
fn calculate_playtime(secs: u64) -> (u64, u64) {
|
||||
let mut dur_sec = secs;
|
||||
let dur_min = dur_sec / 60;
|
||||
dur_sec -= dur_min * 60;
|
||||
(dur_min, dur_sec)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
// if spotify.token.lock().await.unwrap().as_ref().unwrap().is_expired() {
|
||||
// spotify.request_token().await?;
|
||||
// }
|
||||
debug!("Resolving Spotify resource '{}' with id '{}'", resource_type, resource_id);
|
||||
match resource_type {
|
||||
"track" => {
|
||||
let track = spotify.track(&Id::from_id(resource_id)?).await?;
|
||||
let playtime = calculate_playtime(track.duration.as_secs());
|
||||
let artists: Vec<String> = track.artists.into_iter().map(|x| x.name).collect();
|
||||
Ok(format!("\x037[Spotify]\x03 Track: \x039\"{}\"\x03 - \x039\"{}\" \x0311|\x03 Album: \x039\"{}\" \x0311|\x03 Length:\x0315 {}:{:02} \x0311|", artists.join(", "), track.name, track.album.name, playtime.0, playtime.1))
|
||||
}
|
||||
"artist" => {
|
||||
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(", ")))
|
||||
}
|
||||
"album" => {
|
||||
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()));
|
||||
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" => {
|
||||
let playlist = spotify.playlist(&Id::from_id(resource_id)?, None, None).await?;
|
||||
let mut tracks = 0;
|
||||
let playtime = calculate_playtime(playlist.tracks.items.iter().fold(0, |acc, x| {
|
||||
x.track.as_ref().map_or(acc, |item| match item {
|
||||
PlayableItem::Track(t) => {
|
||||
tracks += 1;
|
||||
acc + t.duration.as_secs()
|
||||
}
|
||||
PlayableItem::Episode(e) => {
|
||||
tracks += 1;
|
||||
acc + e.duration.as_secs()
|
||||
}
|
||||
})
|
||||
}));
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Titlebot {
|
||||
url_regex: Regex,
|
||||
title_regex: Regex,
|
||||
spotify_regex: Regex,
|
||||
spotify: ClientCredsSpotify
|
||||
}
|
||||
|
||||
impl Titlebot {
|
||||
pub async fn create(spotify_creds: Credentials) -> anyhow::Result<Self> {
|
||||
let url_regex = Regex::new(r"https?://\w+\.\w+[/\S+]*")?;
|
||||
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 mut spotify = ClientCredsSpotify::new(spotify_creds);
|
||||
spotify.request_token().await?;
|
||||
Ok(Self {
|
||||
url_regex, title_regex, spotify_regex, spotify
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn resolve(&mut self, message: &str) -> anyhow::Result<Option<String>> {
|
||||
if let Some(m) = self.spotify_regex.captures(&message)? {
|
||||
let tp_group = m.get(1).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?))
|
||||
} else if let Some(m) = self.url_regex.find(&message)? {
|
||||
let url = &message[m.start()..m.end()];
|
||||
let response = reqwest::get(url).await?;
|
||||
if let Some(header) = response.headers().get("Content-Type") {
|
||||
if !(header.to_str()? == "text/html") {
|
||||
return Ok(None)
|
||||
}
|
||||
}
|
||||
let body = response.text().await?;
|
||||
if let Some(tm) = self.title_regex.find(&body)? {
|
||||
let title_match = &body[tm.start()..tm.end()];
|
||||
let result = decode_html(title_match).unwrap_or_else(|_| title_match.to_string());
|
||||
return Ok(Some(format!("\x039[Title]\x0311 {}", result)))
|
||||
}
|
||||
}
|
||||
Ok(None)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue