diff --git a/src/bots/title.rs b/src/bots/title.rs deleted file mode 100644 index 9bb8f28..0000000 --- a/src/bots/title.rs +++ /dev/null @@ -1,139 +0,0 @@ -use fancy_regex::Regex; -use htmlescape::decode_html; -use rspotify::clients::BaseClient; -use rspotify::model::PlayableItem; -use rspotify::{model::Id, ClientCredsSpotify, Credentials}; - -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 { - // 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?; - // } - tracing::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 = 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(|| "".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 { - let url_regex = Regex::new(r"https?://\w+\.\w+[/\S+]*")?; - let title_regex = Regex::new(r"(?<=)(.*)(?=)")?; - 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> { - if let Some(m) = self.spotify_regex.captures(message)? { - tracing::debug!("{}", 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()]; - tracing::debug!("url: {}", url); - - let response = reqwest::get(url).await?; - if let Some(header) = response.headers().get("Content-Type") { - tracing::debug!("response header: {}", header.to_str()?); - if !(header.to_str()?.contains("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()); - tracing::debug!("result: {}", result); - return Ok(Some(format!("\x039[Title]\x0311 {}", result))); - } - } - Ok(None) - } -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 24e8b2f..363279e 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -6,4 +6,5 @@ pub mod leek; pub mod waifu; pub mod sed; pub mod eval; -pub mod spotify; \ No newline at end of file +pub mod spotify; +pub mod title; \ No newline at end of file diff --git a/src/commands/title.rs b/src/commands/title.rs new file mode 100644 index 0000000..8ae14ee --- /dev/null +++ b/src/commands/title.rs @@ -0,0 +1,50 @@ +use async_trait::async_trait; +use fancy_regex::{Captures, Regex}; +use reqwest::Client; +use htmlescape::decode_html; +use crate::bot::{Message, Trigger}; + +pub struct Title { + http: Client, + title_regex: Regex +} + +impl Title { + pub fn new() -> anyhow::Result { + Ok(Title { + http: Client::new(), + title_regex: Regex::new(r"(?<=)(.*)(?=)")? + }) + } +} + +#[async_trait] +impl Trigger for Title { + async fn execute<'a>(&mut self, _msg: Message<'a>, captures: Captures<'a>) -> anyhow::Result { + let url = captures.get(0).unwrap().as_str(); + tracing::debug!("url: {}", url); + + let request = self.http.get(url).build()?; + let response = self.http.execute(request).await?; + let headers = response.headers(); + return if let Some(header) = headers.get("Content-Type") { + let mime = header.to_str()?; + if mime.contains("text/html") { + 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()); + Ok(format!("\x039[Title]\x0311 {}", result)) + } else { + Ok("\x039[Title]\x0311 No title".into()) + } + } else { + let content_length = response.content_length().map(|l| (l/1024).to_string()); + let size = content_length.as_deref().unwrap_or("unknown"); + Ok(format!("\x039[Title]\x0311 File: {}; {}kb", mime, size)) + } + } else { + Ok("\x039[Title]\x0311 No Content-Type header".into()) + } + } +} \ No newline at end of file diff --git a/src/commands/waifu.rs b/src/commands/waifu.rs index 95e3959..cadc74c 100644 --- a/src/commands/waifu.rs +++ b/src/commands/waifu.rs @@ -1,20 +1,25 @@ use crate::bot::{Message, Command}; use async_trait::async_trait; +use reqwest::Client; use serde_json::Value; -pub struct Waifu; +#[derive(Default)] +pub struct Waifu { + http: Client +} #[async_trait] impl Command for Waifu { //noinspection RsNeedlessLifetimes async fn execute<'a>(&mut self, msg: Message<'a>) -> anyhow::Result { let category = msg.content.unwrap_or("waifu"); - let api_resp = reqwest::get(format!("https://api.waifu.pics/sfw/{}", category)) + let request = self.http.get(format!("https://api.waifu.pics/sfw/{}", category)).build()?; + let response = self.http.execute(request) .await? .text() .await?; - let api_resp = api_resp.trim(); - let value: Value = serde_json::from_str(api_resp)?; + let response = response.trim(); + let value: Value = serde_json::from_str(response)?; let url = value["url"] .as_str() .unwrap_or("Invalid API Response.") diff --git a/src/main.rs b/src/main.rs index 584cb4a..b92e5e4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,6 +21,7 @@ use crate::commands::help::Help; use crate::commands::leek::Owo; use crate::commands::sed::Sed; use crate::commands::spotify::Spotify; +use crate::commands::title::Title; use crate::config::UberConfig; use crate::database::{DbExecutor, ExecutorConnection}; @@ -100,16 +101,10 @@ async fn main() -> anyhow::Result<()> { }); bot.add_command("help".into(), Help); - bot.add_command("waifu".into(), Waifu); + bot.add_command("waifu".into(), Waifu::default()); bot.add_command("owo".into(), Owo); bot.add_command("ev".into(), Eval::default()); bot.add_trigger(Regex::new(r"^(?:(?\S+):\s+)?s/(?[^/]*)/(?[^/]*)(?:/(?[a-z]*))?\s*")?, Sed); - #[cfg(feature = "debug")] - { - use commands::debug::*; - bot.add_command("lastmsg".into(), LastMsg); - } - if let Some(spotcfg) = cfg.spotify { let creds = Credentials::new(&spotcfg.client_id, &spotcfg.client_secret); let spotify = Spotify::new(creds).await?; @@ -117,6 +112,12 @@ async fn main() -> anyhow::Result<()> { } else { tracing::warn!("Spotify module is disabled, because the config is missing") } + bot.add_trigger(Regex::new(r"https?://[^\s/$.?#].\S*")?, Title::new()?); + #[cfg(feature = "debug")] + { + use commands::debug::*; + bot.add_command("lastmsg".into(), LastMsg); + } let state = AppState { client: client.clone(),