aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorNathan Perry <np@nathanperry.dev>2024-05-08 14:15:42 -0400
committerNathan Perry <np@nathanperry.dev>2024-05-08 14:16:01 -0400
commit47817c4166937af24041a93e56ad9f841bf1e8f1 (patch)
treea8ec0a674a5a4e5046e1bc92c1c317a48b5e4815 /src
parentffba60b278162707bc4eb004c3bfb6b2e9595213 (diff)
fixups: memes working
Diffstat (limited to 'src')
-rw-r--r--src/bot.rs77
-rw-r--r--src/commands/meme/create.rs6
-rw-r--r--src/commands/meme/history.rs124
-rw-r--r--src/commands/meme/mod.rs10
-rw-r--r--src/commands/mod.rs3
-rw-r--r--src/commands/playback.rs6
-rw-r--r--src/config.rs2
-rw-r--r--src/game.rs138
-rw-r--r--src/util.rs9
9 files changed, 210 insertions, 165 deletions
diff --git a/src/bot.rs b/src/bot.rs
index 2c9f73b..5d52259 100644
--- a/src/bot.rs
+++ b/src/bot.rs
@@ -5,7 +5,7 @@ use std::{
pin::Pin,
result::Result as StdResult,
str::FromStr,
- sync::Mutex,
+ sync::Arc,
};
use chrono::Datelike;
@@ -26,6 +26,7 @@ use serenity::{
GuildId,
ReactionType,
},
+ async_trait,
framework::{
standard::{
BucketBuilder,
@@ -45,7 +46,15 @@ use serenity::{
},
prelude::*,
};
-use songbird::SerenityInit;
+use songbird::{
+ Call,
+ CoreEvent,
+ Event,
+ EventContext,
+ SerenityInit,
+ TrackEvent,
+};
+use tokio::sync::Mutex;
use crate::{
commands::register_commands,
@@ -82,14 +91,20 @@ impl EventHandler for Handler {
#[cfg(not(debug_assertions))]
let botname = "thulani";
- use serenity::futures::StreamExt;
- serenity::futures::stream::iter(guild.iter())
- .for_each(|g| async move {
- if let Err(e) = g.id.edit_nickname(&ctx, Some(botname)).await {
- error!("changing nickname: {:?}", e);
- }
- })
- .await;
+ if let Some(guild) = guild {
+ if let Err(e) = guild.id.edit_nickname(&ctx, Some(botname)).await {
+ error!("changing nickname: {:?}", e);
+ }
+ }
+
+ let sb = songbird::get(&ctx).await.unwrap();
+
+ let c = sb.get_or_insert(CONFIG.discord.guild());
+ let mut call = c.lock().await;
+
+ call.remove_all_global_events();
+
+ call.add_global_event(Event::Track(TrackEvent::End), SongbirdHandler(c.clone()));
}
async fn resume(&self, _ctx: Context, _resume: ResumedEvent) {
@@ -103,7 +118,21 @@ impl EventHandler for Handler {
deleted_message_id: MessageId,
_: Option<GuildId>,
) {
- MESSAGE_WATCH.lock().unwrap().remove(&deleted_message_id);
+ MESSAGE_WATCH.lock().await.remove(&deleted_message_id);
+ }
+}
+
+struct SongbirdHandler(Arc<Mutex<Call>>);
+
+#[async_trait]
+impl songbird::events::EventHandler for SongbirdHandler {
+ async fn act(&self, _ctx: &EventContext<'_>) -> Option<Event> {
+ let mut call = self.0.lock().await;
+ if call.queue().is_empty() {
+ let _ = call.leave().await;
+ }
+
+ None
}
}
@@ -162,11 +191,11 @@ async fn framework() -> StandardFramework {
register_commands(framework)
}
-fn before_handle(
- ctx: &Context,
- message: &Message,
- cmd: &str,
-) -> Pin<Box<dyn Future<Output = bool> + Send>> {
+fn before_handle<'fut>(
+ ctx: &'fut Context,
+ message: &'fut Message,
+ cmd: &'fut str,
+) -> Pin<Box<dyn Future<Output = bool> + Send + 'fut>> {
debug!("got command '{}' from user '{}' ({})", cmd, message.author.name, message.author.id);
Box::pin(async move {
@@ -206,7 +235,7 @@ fn before_handle(
match util::send_result(ctx, message.channel_id, "no", message.tts).await {
Err(e) => error!("sending restricted prefix response: {}", e),
Ok(msg_id) => {
- let mut mp = MESSAGE_WATCH.lock().unwrap();
+ let mut mp = MESSAGE_WATCH.lock().await;
mp.insert(message.id, msg_id);
},
}
@@ -257,15 +286,13 @@ pub async fn run() -> Result<()> {
let shard_manager = client.shard_manager.clone();
- ctrlc::set_handler(move || {
- info!("shutting down");
+ tokio::spawn(async move {
+ tokio::signal::ctrl_c().await.unwrap();
+ warn!("got ctrl c");
- let shard_manager = shard_manager.clone();
- tokio::task::spawn(async move {
- shard_manager.shutdown_all().await;
- });
- })
- .expect("unable to create SIGINT/SIGTERM handlers");
+ shard_manager.shutdown_all().await;
+ info!("shutdown");
+ });
client.start().await?;
diff --git a/src/commands/meme/create.rs b/src/commands/meme/create.rs
index 1c12f2a..ef6090e 100644
--- a/src/commands/meme/create.rs
+++ b/src/commands/meme/create.rs
@@ -110,6 +110,8 @@ pub async fn addmeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult
#[command]
pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandResult {
+ debug!("running addaudiomeme");
+
let mut args = Args::new(args.rest(), DELIMS.as_ref());
let title = args.single_quoted::<String>()?;
@@ -117,7 +119,7 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
let elems = audio_str.split_whitespace().collect::<Vec<_>>();
- if elems.len() == 0 {
+ if elems.is_empty() {
util::send(ctx, msg.channel_id, "are you stupid", msg.tts).await?;
return Err(anyhow!("no audio link was provided").into());
}
@@ -127,6 +129,7 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
let (start, end) = parse_times(opts);
let youtube_url = util::ytdl_url(audio_link.as_str()).await?;
+ debug!("got download url: {youtube_url}");
let duration_opts = if let Some(e) = end {
vec![
@@ -184,6 +187,7 @@ pub async fn addaudiomeme(ctx: &Context, msg: &Message, args: Args) -> CommandRe
let mut audio_data = Vec::new();
let bytes = audio_reader.read_to_end(&mut audio_data).await?;
+ debug!("downloaded audio ({} bytes)", audio_data.len());
if bytes == 0 {
debug!("read 0 bytes from audio reader");
diff --git a/src/commands/meme/history.rs b/src/commands/meme/history.rs
index ed50e27..33a2de2 100644
--- a/src/commands/meme/history.rs
+++ b/src/commands/meme/history.rs
@@ -2,7 +2,6 @@ use anyhow::anyhow;
use diesel::{
result::Error as DieselError,
NotFound,
- PgConnection,
};
use itertools::Itertools;
use lazy_static::lazy_static;
@@ -115,8 +114,6 @@ pub async fn wat(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
#[command]
pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- let mut conn = connection()?;
-
let n = args.single_quoted::<usize>().unwrap_or(CONFIG.default_hist);
if n > CONFIG.max_hist {
@@ -126,7 +123,10 @@ pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
let n = n.min(CONFIG.max_hist);
- let records = InvocationRecord::last_n(&mut conn, n)?;
+ let records = {
+ let mut conn = connection()?;
+ InvocationRecord::last_n(&mut conn, n)?
+ };
if records.len() == 0 {
info!("no memes in history");
@@ -138,74 +138,70 @@ pub async fn history(ctx: &Context, msg: &Message, mut args: Args) -> CommandRes
info!("reporting meme history (len {})", n);
let resp = serenity::futures::stream::iter(records.into_iter().enumerate().rev())
- .then(|(i, rec)| ir_info(ctx, i, rec, &mut conn))
- .try_collect::<Vec<String>>()
- .await?;
-
- let resp = resp.join("\n");
-
- util::send(ctx, msg.channel_id, &resp, false).await.map_err(CommandError::from)
-}
+ .then(|(i, rec)| async move {
+ let mut conn = connection()?;
-async fn ir_info(
- ctx: &Context,
- i: usize,
- rec: InvocationRecord,
- conn: &mut PgConnection,
-) -> Result<String, CommandError> {
- let dt = chrono::DateTime::from_utc(rec.time, chrono::Utc {});
- let ago = TIME_FORMATTER.convert((chrono::Utc::now() - dt).to_std().unwrap());
-
- let rand = if rec.random {
- "R, "
- } else {
- ""
- };
+ let dt = chrono::DateTime::from_utc(rec.time, chrono::Utc {});
+ let ago = TIME_FORMATTER.convert((chrono::Utc::now() - dt).to_std().unwrap());
- let meme = Meme::find(conn, rec.meme_id)
- .and_then(|meme| Metadata::find(conn, meme.metadata_id).map(|metadata| (metadata, meme)));
+ let rand = if rec.random {
+ "R, "
+ } else {
+ ""
+ };
- let invoker_name = CONFIG
- .discord
- .guild()
- .member(&ctx, rec.user_id as u64)
- .await
- .map(|m| m.display_name().to_owned())
- .unwrap_or("???".to_owned());
+ let meme = Meme::find(&mut conn, rec.meme_id).and_then(|meme| {
+ Metadata::find(&mut conn, meme.metadata_id).map(|metadata| (metadata, meme))
+ });
- let result = match meme {
- Ok((metadata, meme)) => {
- let author_name = CONFIG
+ let invoker_name = CONFIG
.discord
.guild()
- .member(&ctx, metadata.created_by as u64)
+ .member(&ctx, rec.user_id as u64)
.await
.map(|m| m.display_name().to_owned())
.unwrap_or("???".to_owned());
- format!(
- "{}. [{}{}] \"{}\" by {} ({}). invoked by {}.",
- i + 1,
- rand,
- ago,
- meme.title,
- author_name,
- metadata.created.date().format(CLEAN_DATE_FORMAT),
- invoker_name
- )
- },
- Err(e) => {
- if let Some(variant) = e.downcast_ref::<DieselError>() {
- if *variant != NotFound {
- error!("error encountered loading meme history: {}", e);
- }
- }
+ let result = match meme {
+ Ok((metadata, meme)) => {
+ let author_name = CONFIG
+ .discord
+ .guild()
+ .member(&ctx, metadata.created_by as u64)
+ .await
+ .map(|m| m.display_name().to_owned())
+ .unwrap_or("???".to_owned());
- format!("{}. [{}{}] not found. invoked by {}.", i + 1, rand, ago, invoker_name)
- },
- };
+ format!(
+ "{}. [{}{}] \"{}\" by {} ({}). invoked by {}.",
+ i + 1,
+ rand,
+ ago,
+ meme.title,
+ author_name,
+ metadata.created.date().format(CLEAN_DATE_FORMAT),
+ invoker_name
+ )
+ },
+ Err(e) => {
+ if let Some(variant) = e.downcast_ref::<DieselError>() {
+ if *variant != NotFound {
+ error!("error encountered loading meme history: {}", e);
+ }
+ }
+
+ format!("{}. [{}{}] not found. invoked by {}.", i + 1, rand, ago, invoker_name)
+ },
+ };
- Ok(result)
+ Result::<_, CommandError>::Ok(result)
+ })
+ .try_collect::<Vec<String>>()
+ .await?;
+
+ let resp = resp.join("\n");
+
+ util::send(ctx, msg.channel_id, &resp, false).await.map_err(CommandError::from)
}
#[command]
@@ -328,12 +324,12 @@ pub async fn query(ctx: &Context, msg: &Message, mut args: Args) -> CommandResul
static ref AGE_REGEX: Regex = Regex::new(r"(?i)(?:age|order)=(.*)").unwrap();
}
- let guild =
- msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
+ let creator: Option<u64> = {
+ let guild =
+ msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
- let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
+ let guild = guild.guild(&ctx).ok_or(anyhow!("couldn't find guild"))?;
- let creator: Option<u64> = {
let creator = args.quoted().current().map(|s| CREATOR_REGEX.is_match(s)).unwrap_or(false);
if creator {
args.single_quoted::<String>()
diff --git a/src/commands/meme/mod.rs b/src/commands/meme/mod.rs
index 24fc50d..fe69b1c 100644
--- a/src/commands/meme/mod.rs
+++ b/src/commands/meme/mod.rs
@@ -16,7 +16,10 @@ use serenity::{
prelude::*,
};
use songbird::input::{
- core::io::MediaSource,
+ core::{
+ io::MediaSource,
+ probe::Hint,
+ },
AudioStream,
AudioStreamError,
Compose,
@@ -127,9 +130,12 @@ impl Compose for Audio {
let ms = std::io::Cursor::new(self.data.clone());
let ms: Box<dyn MediaSource> = Box::new(ms);
+ let mut hint = Hint::new();
+ hint.mime_type("audio/opus");
+
Ok(AudioStream {
input: ms,
- hint: None,
+ hint: Some(hint),
})
}
diff --git a/src/commands/mod.rs b/src/commands/mod.rs
index c8a7014..3f69a3b 100644
--- a/src/commands/mod.rs
+++ b/src/commands/mod.rs
@@ -21,7 +21,6 @@ pub(crate) mod today;
mod help;
#[group]
-#[prefix = ""]
#[only_in(guild)]
#[commands(roll, today)]
struct General;
@@ -52,7 +51,7 @@ pub fn register_commands(f: StandardFramework) -> StandardFramework {
},
};
- let _ = _play(ctx, msg, &url);
+ let _ = _play(ctx, msg, url).await;
})
})
}
diff --git a/src/commands/playback.rs b/src/commands/playback.rs
index 7ecef47..1c3ab95 100644
--- a/src/commands/playback.rs
+++ b/src/commands/playback.rs
@@ -110,7 +110,7 @@ pub async fn _play(ctx: &Context, msg: &Message, url: &str) -> CommandResult {
#[command]
pub async fn play(ctx: &Context, msg: &Message, mut args: Args) -> CommandResult {
- if args.len() == 0 {
+ if args.is_empty() {
return _resume(ctx, msg).await;
}
@@ -168,9 +168,11 @@ pub async fn skip(ctx: &Context, msg: &Message, _args: Args) -> CommandResult {
pub async fn die(ctx: &Context, msg: &Message, _: Args) -> CommandResult {
let (_sb, call) = songbird(ctx, msg).await?;
- let call = call.lock().await;
+ let mut call = call.lock().await;
call.queue().stop();
+ call.leave().await?;
+
Ok(())
}
diff --git a/src/config.rs b/src/config.rs
index 59bb4c3..84ee88b 100644
--- a/src/config.rs
+++ b/src/config.rs
@@ -17,7 +17,7 @@ lazy_static! {
Config::init_from_env().unwrap()
};
pub static ref FFMPEG_COMMAND: String = {
- let result = CONFIG.ffmpeg.clone().unwrap_or("youtube-dl".to_owned());
+ let result = CONFIG.ffmpeg.clone().unwrap_or("ffmpeg".to_owned());
log::debug!("got ffmpeg: {}", result);
result
diff --git a/src/game.rs b/src/game.rs
index 47ae18c..d3b0785 100644
--- a/src/game.rs
+++ b/src/game.rs
@@ -55,7 +55,6 @@ use crate::{
};
#[group]
-#[prefix = "game"]
#[commands(game, installedgame, ownedgame, updategaem)]
pub struct Game;
@@ -234,80 +233,85 @@ async fn _game(
mut args: Args,
min_status: GameStatus,
) -> CommandResult {
- let guild =
- msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
- let guild = guild.guild(&ctx).ok_or_else(|| anyhow!("couldn't find guild"))?;
+ let users = {
+ let guild =
+ msg.channel_id.to_channel(&ctx).await?.guild().ok_or(anyhow!("couldn't find guild"))?;
+ let guild = guild.guild(&ctx).ok_or_else(|| anyhow!("couldn't find guild"))?;
- let user_args: Vec<String> = if args.rest().is_empty() {
- Vec::new()
- } else {
- args.quoted().iter::<String>().collect::<StdResult<Vec<_>, ArgError<Infallible>>>()?
- };
+ let user_args: Vec<String> = if args.rest().is_empty() {
+ Vec::new()
+ } else {
+ args.quoted().iter::<String>().collect::<StdResult<Vec<_>, ArgError<Infallible>>>()?
+ };
- let mut users = user_args
- .into_iter()
- .filter_map(|u| {
- use std::borrow::Borrow;
+ let mut users = user_args
+ .into_iter()
+ .filter_map(|u| {
+ use std::borrow::Borrow;
- let possible = get_user_id(guild.borrow(), &u);
+ let possible = get_user_id(guild.borrow(), &u);
- debug!("parsed userid {:?}", possible);
+ debug!("parsed userid {:?}", possible);
- match possible {
- Err(UserLookupError::NotFound) => {
- let _ = util::send(
- ctx,
- msg.channel_id,
- &format!("didn't recognize {}", &u),
- msg.tts,
- );
- None
- },
- Ok(x) => Some(x),
- Err(UserLookupError::Ambiguous(x)) => {
- let _ = util::send(
- ctx,
- msg.channel_id,
- &format!("too many matches ({}) for {}", x, &u),
- msg.tts,
- );
- None
- },
- }
- })
- .filter_map(|uid| {
- let res = DISCORD_MAP.get(&uid).map(|s| s.to_lowercase());
+ match possible {
+ Err(UserLookupError::NotFound) => {
+ let _ = util::send(
+ ctx,
+ msg.channel_id,
+ &format!("didn't recognize {}", &u),
+ msg.tts,
+ );
+ None
+ },
+ Ok(x) => Some(x),
+ Err(UserLookupError::Ambiguous(x)) => {
+ let _ = util::send(
+ ctx,
+ msg.channel_id,
+ &format!("too many matches ({}) for {}", x, &u),
+ msg.tts,
+ );
+ None
+ },
+ }
+ })
+ .filter_map(|uid| {
+ let res = DISCORD_MAP.get(&uid).map(|s| s.to_lowercase());
- if let None = res {
- let _ = info!("user {} is not recognized", uid);
- }
+ if let None = res {
+ let _ = info!("user {} is not recognized", uid);
+ }
- res
- })
- .collect::<FnvHashSet<_>>();
+ res
+ })
+ .collect::<FnvHashSet<_>>();
- let inferred = users.len() == 0;
+ if users.len() == 0 {
+ let pairs = guild
+ .voice_states
+ .iter()
+ .filter_map(|(uid, voice)| voice.channel_id.map(|cid| (*uid, cid)))
+ .collect::<FnvHashMap<_, _>>();
- if users.len() == 0 {
- let pairs = guild
- .voice_states
- .iter()
- .filter_map(|(uid, voice)| voice.channel_id.map(|cid| (*uid, cid)))
- .collect::<FnvHashMap<_, _>>();
+ let channel =
+ pairs.get(&msg.author.id).cloned().unwrap_or(CONFIG.discord.voice_channel());
- let channel = pairs.get(&msg.author.id).cloned().unwrap_or(CONFIG.discord.voice_channel());
+ users = pairs
+ .iter()
+ .filter_map(|(uid, cid)| {
+ if *cid == channel {
+ DISCORD_MAP.get(uid).map(|s| s.to_lowercase())
+ } else {
+ None
+ }
+ })
+ .collect::<FnvHashSet<_>>();
+ }
- users = pairs
- .iter()
- .filter_map(|(uid, cid)| {
- if *cid == channel {
- DISCORD_MAP.get(uid).map(|s| s.to_lowercase())
- } else {
- None
- }
- })
- .collect::<FnvHashSet<_>>();
- }
+ users
+ };
+
+ let inferred = users.len() == 0;
if inferred && users.len() < 2 || !inferred && users.len() < 1 {
info!("too few known users to make game comparison");
@@ -434,7 +438,7 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
let client = {
let data = ctx.data.read().await;
- data.get::<HttpKey>().unwrap()
+ data.get::<HttpKey>().unwrap().clone()
};
let arg_user = args.single_quoted::<String>();
@@ -472,7 +476,7 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
},
};
- let spreadsheet = load_spreadsheet(client).await?;
+ let spreadsheet = load_spreadsheet(&client).await?;
let user_column = (0..spreadsheet.len())
.find(|x| spreadsheet[*x][0].to_lowercase() == username.to_lowercase());
@@ -537,7 +541,7 @@ pub async fn updategaem(ctx: &Context, msg: &Message, mut args: Args) -> Command
let client = {
let data = ctx.data.read().await;
- data.get::<HttpKey>().unwrap()
+ data.get::<HttpKey>().unwrap().clone()
};
let games_owned = client
diff --git a/src/util.rs b/src/util.rs
index d59c4da..0fd746b 100644
--- a/src/util.rs
+++ b/src/util.rs
@@ -120,7 +120,14 @@ pub async fn ytdl_url(uri: &str) -> Result<String> {
uri,
];
- let out = Command::new(&*YTDL_COMMAND).args(&args).stdin(Stdio::null()).output().await?;
+ debug!("downloading info for uri: {uri}");
+
+ let mut command = Command::new(&*YTDL_COMMAND);
+ command.args(&args).stdin(Stdio::null());
+
+ debug!("running command: {command:?}");
+
+ let out = command.output().await?;
if !out.status.success() {
return Err(anyhow::anyhow!("running ytdl: {out:?}"));