This commit is contained in:
lemonsh 2022-07-17 20:10:52 +02:00
parent 25a7635408
commit 329f6d6dae
5 changed files with 64 additions and 73 deletions

View file

@ -1,14 +1,13 @@
use async_trait::async_trait;
use crate::bot::{Command, Context}; use crate::bot::{Command, Context};
use async_trait::async_trait;
pub struct LastMsg; pub struct LastMsg;
#[async_trait] #[async_trait]
impl Command for LastMsg { impl Command for LastMsg {
//noinspection RsNeedlessLifetimes async fn execute(&mut self, msg: Context<'_>) -> anyhow::Result<String> {
async fn execute<'a>(&mut self, msg: Context<'a>) -> anyhow::Result<String> {
let nick = msg.content.unwrap_or(msg.author); let nick = msg.content.unwrap_or(msg.author);
let lastmsg = msg.last_msg.read().await; let lastmsg = msg.history.read().await;
Ok(format!("{}: {:?}", nick, lastmsg.get(nick))) Ok(format!("{}: {:?}", nick, lastmsg.get(nick)))
} }
} }

View file

@ -1,9 +1,9 @@
use rspotify::{ClientCredsSpotify, Credentials}; use crate::bot::{Context, Trigger};
use async_trait::async_trait; use async_trait::async_trait;
use fancy_regex::Captures; use fancy_regex::Captures;
use rspotify::clients::BaseClient; use rspotify::clients::BaseClient;
use rspotify::model::{Id, PlayableItem}; use rspotify::model::{Id, PlayableItem};
use crate::bot::{Context, Trigger}; use rspotify::{ClientCredsSpotify, Credentials};
pub struct Spotify { pub struct Spotify {
spotify: ClientCredsSpotify, spotify: ClientCredsSpotify,
@ -13,22 +13,25 @@ impl Spotify {
pub async fn new(creds: Credentials) -> anyhow::Result<Self> { pub async fn new(creds: Credentials) -> anyhow::Result<Self> {
let mut spotify = ClientCredsSpotify::new(creds); let mut spotify = ClientCredsSpotify::new(creds);
spotify.request_token().await?; spotify.request_token().await?;
Ok(Self { Ok(Self { spotify })
spotify
})
} }
} }
#[async_trait] #[async_trait]
impl Trigger for Spotify { impl Trigger for Spotify {
async fn execute<'a>(&mut self, msg: Context<'a>, captures: Captures<'a>) -> anyhow::Result<String> { async fn execute<'a>(
&mut self,
msg: Context<'a>,
captures: Captures<'a>,
) -> anyhow::Result<String> {
let tp_group = captures.get(1).unwrap(); let tp_group = captures.get(1).unwrap();
let id_group = captures.get(2).unwrap(); let id_group = captures.get(2).unwrap();
resolve_spotify( resolve_spotify(
&mut self.spotify, &mut self.spotify,
&msg.content.unwrap()[tp_group.start()..tp_group.end()], &msg.content.unwrap()[tp_group.start()..tp_group.end()],
&msg.content.unwrap()[id_group.start()..id_group.end()], &msg.content.unwrap()[id_group.start()..id_group.end()],
).await )
.await
} }
} }

View file

@ -1,26 +1,30 @@
use crate::bot::{Context, Trigger};
use async_trait::async_trait; use async_trait::async_trait;
use fancy_regex::{Captures, Regex}; use fancy_regex::{Captures, Regex};
use reqwest::Client;
use htmlescape::decode_html; use htmlescape::decode_html;
use crate::bot::{Context, Trigger}; use reqwest::Client;
pub struct Title { pub struct Title {
http: Client, http: Client,
title_regex: Regex title_regex: Regex,
} }
impl Title { impl Title {
pub fn new() -> anyhow::Result<Self> { pub fn new() -> anyhow::Result<Self> {
Ok(Title { Ok(Title {
http: Client::new(), http: Client::new(),
title_regex: Regex::new(r"(?<=<title>)(.*)(?=</title>)")? title_regex: Regex::new(r"(?<=<title>)(.*)(?=</title>)")?,
}) })
} }
} }
#[async_trait] #[async_trait]
impl Trigger for Title { impl Trigger for Title {
async fn execute<'a>(&mut self, _msg: Context<'a>, captures: Captures<'a>) -> anyhow::Result<String> { async fn execute<'a>(
&mut self,
_msg: Context<'a>,
captures: Captures<'a>,
) -> anyhow::Result<String> {
let url = captures.get(0).unwrap().as_str(); let url = captures.get(0).unwrap().as_str();
tracing::debug!("url: {}", url); tracing::debug!("url: {}", url);
@ -33,18 +37,19 @@ impl Trigger for Title {
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());
Ok(format!("\x039[Title]\x0311 {}", result)) Ok(format!("\x039[Title]\x0311 {}", result))
} else { } else {
Ok("\x039[Title]\x0311 No title".into()) Ok("\x039[Title]\x0311 No title".into())
} }
} else { } else {
let content_length = response.content_length().map(|l| (l/1024).to_string()); let content_length = response.content_length().map(|l| (l / 1024).to_string());
let size = content_length.as_deref().unwrap_or("unknown"); let size = content_length.as_deref().unwrap_or("unknown");
Ok(format!("\x039[Title]\x0311 File: {}; {}kb", mime, size)) Ok(format!("\x039[Title]\x0311 File: {}; {}kb", mime, size))
} }
} else { } else {
Ok("\x039[Title]\x0311 No Content-Type header".into()) Ok("\x039[Title]\x0311 No Content-Type header".into())
} };
} }
} }

View file

@ -1,22 +1,22 @@
use crate::bot::{Context, Command}; use crate::bot::{Command, Context};
use async_trait::async_trait; use async_trait::async_trait;
use reqwest::Client; use reqwest::Client;
use serde_json::Value; use serde_json::Value;
#[derive(Default)] #[derive(Default)]
pub struct Waifu { pub struct Waifu {
http: Client http: Client,
} }
#[async_trait] #[async_trait]
impl Command for Waifu { impl Command for Waifu {
async fn execute(&mut self, msg: Context<'_>) -> anyhow::Result<String> { async fn execute(&mut self, msg: Context<'_>) -> anyhow::Result<String> {
let category = msg.content.unwrap_or("waifu"); let category = msg.content.unwrap_or("waifu");
let request = self.http.get(format!("https://api.waifu.pics/sfw/{}", category)).build()?; let request = self
let response = self.http.execute(request) .http
.await? .get(format!("https://api.waifu.pics/sfw/{}", category))
.text() .build()?;
.await?; let response = self.http.execute(request).await?.text().await?;
let response = response.trim(); let response = response.trim();
let value: Value = serde_json::from_str(response)?; let value: Value = serde_json::from_str(response)?;
let url = value["url"] let url = value["url"]

View file

@ -7,10 +7,12 @@ use tokio::sync::{
#[derive(Debug)] #[derive(Debug)]
enum Task { enum Task {
AddQuote(oneshot::Sender<bool>, Quote), AddQuote(oneshot::Sender<rusqlite::Result<()>>, Quote),
GetQuote(oneshot::Sender<Option<Quote>>, Option<String>), GetQuote(
SearchQuotes(oneshot::Sender<Option<Vec<Quote>>>, String), oneshot::Sender<rusqlite::Result<Option<Quote>>>,
RandomNQuotes(oneshot::Sender<Option<Vec<Quote>>>, u8), Option<String>,
),
SearchQuotes(oneshot::Sender<rusqlite::Result<Vec<Quote>>>, String),
} }
pub struct DbExecutor { pub struct DbExecutor {
@ -40,45 +42,34 @@ impl DbExecutor {
while let Some(task) = self.rx.blocking_recv() { while let Some(task) = self.rx.blocking_recv() {
match task { match task {
Task::AddQuote(tx, quote) => { Task::AddQuote(tx, quote) => {
if let Err(e) = self.db.execute( let result = self
"insert into quotes(quote,username) values(?,?)", .db
params![quote.quote, quote.author], .execute(
) { "insert into quotes(quote,username) values(?,?)",
tracing::error!("A database error has occurred: {}", e); params![quote.quote, quote.author],
tx.send(false).unwrap(); )
} else { .map(|_| ());
tx.send(true).unwrap(); tx.send(result).unwrap();
}
} }
Task::GetQuote(tx, author) => { Task::GetQuote(tx, author) => {
let quote = if let Some(ref author) = author { let result = if let Some(ref author) = author {
self.db.query_row("select quote,username from quotes where username=? order by random() limit 1", params![author], |v| Ok(Quote {quote:v.get(0)?, author:v.get(1)?})) self.db.query_row("select quote,username from quotes where username=? order by random() limit 1", params![author], |v| Ok(Quote {quote:v.get(0)?, author:v.get(1)?}))
} else { } else {
self.db.query_row("select quote,username from quotes order by random() limit 1", params![], |v| Ok(Quote {quote:v.get(0)?, author:v.get(1)?})) self.db.query_row("select quote,username from quotes order by random() limit 1", params![], |v| Ok(Quote {quote:v.get(0)?, author:v.get(1)?}))
}.optional().unwrap_or_else(|e| { }.optional();
tracing::error!("A database error has occurred: {}", e); tx.send(result).unwrap();
None
});
tx.send(quote).unwrap();
} }
Task::SearchQuotes(tx, query) => { Task::SearchQuotes(tx, query) => {
tx.send(self.yield_quotes("select quote,username from quotes where quote like '%'||?1||'%' order by quote asc limit 5", params![query])).unwrap(); tx.send(self.yield_quotes("select quote,username from quotes where quote like '%'||?1||'%' order by quote asc limit 5", params![query])).unwrap();
} }
Task::RandomNQuotes(tx, count) => {
tx.send(self.yield_quotes(
"select quote,username from quotes order by random() limit ?",
params![count],
))
.unwrap();
}
} }
} }
} }
fn yield_quotes<P: Params>(&self, sql: &str, params: P) -> Option<Vec<Quote>> { fn yield_quotes<P: Params>(&self, sql: &str, params: P) -> rusqlite::Result<Vec<Quote>> {
match self.db.prepare(sql).and_then(|mut v| { self.db.prepare(sql).and_then(|mut v| {
v.query(params).and_then(|mut v| { v.query(params).and_then(|mut v| {
let mut quotes: Vec<Quote> = Vec::with_capacity(50); let mut quotes: Vec<Quote> = Vec::new();
while let Some(row) = v.next()? { while let Some(row) = v.next()? {
quotes.push(Quote { quotes.push(Quote {
quote: row.get(0)?, quote: row.get(0)?,
@ -87,13 +78,7 @@ impl DbExecutor {
} }
Ok(quotes) Ok(quotes)
}) })
}) { })
Ok(o) => Some(o),
Err(e) => {
tracing::error!("A database error has occurred: {}", e);
None
}
}
} }
} }
@ -128,23 +113,22 @@ macro_rules! executor_wrapper {
impl ExecutorConnection { impl ExecutorConnection {
// WARNING: these methods are NOT cancel-safe // WARNING: these methods are NOT cancel-safe
executor_wrapper!(add_quote, Task::AddQuote, bool, quote: Quote); executor_wrapper!(
add_quote,
Task::AddQuote,
rusqlite::Result<()>,
quote: Quote
);
executor_wrapper!( executor_wrapper!(
get_quote, get_quote,
Task::GetQuote, Task::GetQuote,
Option<Quote>, rusqlite::Result<Option<Quote>>,
author: Option<String> author: Option<String>
); );
executor_wrapper!( executor_wrapper!(
search_quotes, search_quotes,
Task::SearchQuotes, Task::SearchQuotes,
Option<Vec<Quote>>, rusqlite::Result<Vec<Quote>>,
query: String query: String
); );
executor_wrapper!(
random_n_quotes,
Task::RandomNQuotes,
Option<Vec<Quote>>,
count: u8
);
} }