soundboard and some tools

This commit is contained in:
2024-02-21 12:29:30 +01:00
parent ef29e5319a
commit b86e8b9daf
12 changed files with 470 additions and 55 deletions

View File

@@ -10,7 +10,6 @@ pub mod queue;
pub mod repeat;
pub mod resume;
pub mod seek;
pub mod shuffle;
pub mod skip;
pub mod soundboard;
pub mod stop;
@@ -26,7 +25,6 @@ pub use queue::queue;
pub use repeat::repeat;
pub use resume::resume;
pub use seek::seek;
pub use shuffle::shuffle;
pub use skip::skip;
pub use stop::stop;
pub use volume::volume;

View File

@@ -19,7 +19,6 @@ pub async fn resume(ctx: Context<'_>) -> Result<(), Error> {
let queue = handler.queue();
let _ = queue.resume();
ctx.say(format!("Song resumed.")).await?;
ctx.send(
CreateReply::default().embed(
embed(ctx, "Resumed!", "Currently paused song is now resumed!", "")

View File

@@ -1,11 +1,51 @@
use crate::{commands::embeds::embed, Context, Error};
use std::time::Duration;
use crate::{
commands::embeds::{embed, error_embed},
Context, Error,
};
use poise::CreateReply;
/// Seeks a track by provided seconds
#[poise::command(prefix_command, slash_command, category = "Music")]
pub async fn seek(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "", "", "").await.unwrap()))
pub async fn seek(
ctx: Context<'_>,
#[description = "How many seconds shall I seek"] seek: u64,
) -> Result<(), Error> {
let guild_id = ctx.guild_id().unwrap();
let manager = songbird::get(&ctx.serenity_context())
.await
.expect("Songbird client placed at init")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
let queue = handler.queue();
let seek_duration = Duration::from_secs(seek);
let track = queue.current().unwrap();
let _ = track.seek(seek_duration);
ctx.send(
CreateReply::default().embed(
embed(
ctx,
"Track seeked!",
&format!("Track seeked by: {} seconds", seek),
"",
)
.await
.unwrap(),
),
)
.await?;
} else {
let msg = "I am not in a voice channel!";
ctx.send(CreateReply::default().embed(error_embed(ctx, msg).await.unwrap()))
.await?;
}
Ok(())
}

View File

@@ -1,11 +0,0 @@
use crate::{commands::embeds::embed, Context, Error};
use poise::CreateReply;
/// Shuffles the playlist
#[poise::command(prefix_command, slash_command, category = "Music")]
pub async fn shuffle(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "", "", "").await.unwrap()))
.await?;
Ok(())
}

View File

@@ -1,11 +1,117 @@
use crate::{commands::embeds::embed, Context, Error};
use crate::{commands::embeds::error_embed, Context, Error};
use poise::serenity_prelude::model::Timestamp;
use poise::serenity_prelude::Colour;
use poise::serenity_prelude::CreateEmbed;
use poise::CreateReply;
use serenity::builder::CreateEmbedAuthor;
use serenity::builder::CreateEmbedFooter;
use songbird::events::TrackEvent;
use songbird::input::AuxMetadata;
use songbird::input::{Compose, YoutubeDl};
use std::time::Duration;
use crate::commands::music::notifier::TrackErrorNotifier;
use crate::http::HttpKey;
/// Plays one of available audio effects
#[poise::command(prefix_command, slash_command, category = "Music")]
pub async fn effect(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "Playing an effect", "", "").await.unwrap()))
.await?;
pub async fn effect(
ctx: Context<'_>,
#[description = "Shall output pause?"]
#[flag]
pause: bool,
#[description = "Provide a query or an url"]
#[rest]
mut song: String,
) -> Result<(), Error> {
let is_query = !song.starts_with("http");
let guild_id = ctx.guild_id().unwrap();
let http_client = {
let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>()
.cloned()
.expect("Guaranteed to exist in the typemap.")
};
let manager = songbird::get(&ctx.serenity_context())
.await
.expect("Songbird Voice placed at init")
.clone();
if pause {
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
let queue = handler.queue();
let _ = queue.pause();
}
}
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier);
if is_query {
song = format!("ytsearch:{}", song);
}
let src = YoutubeDl::new_ytdl_like("yt-dlp", http_client, song);
let embed = generate_embed(ctx, src.clone()).await;
let response = CreateReply::default().embed(embed.unwrap());
ctx.send(response).await?;
let _ = handler.play_input(src.clone().into());
} else {
let msg = "I am not in a voice channel!";
ctx.send(CreateReply::default().embed(error_embed(ctx, msg).await.unwrap()))
.await?;
}
Ok(())
}
async fn generate_embed(ctx: Context<'_>, src: YoutubeDl) -> Result<CreateEmbed, Error> {
let metadata = src.clone().aux_metadata().await.unwrap();
let AuxMetadata {
title,
thumbnail,
source_url,
artist,
duration,
..
} = metadata;
let timestamp = Timestamp::now();
let duration_minutes = duration.unwrap_or(Duration::new(0, 0)).clone().as_secs() / 60;
let duration_seconds = duration.unwrap_or(Duration::new(0, 0)).clone().as_secs() % 60;
let description = format!("Playing now!");
let embed = CreateEmbed::default()
.author(CreateEmbedAuthor::new("Playing an effect!").icon_url(ctx.author().clone().face()))
.colour(Colour::from_rgb(255, 58, 97))
.title(title.unwrap())
.url(source_url.unwrap())
.thumbnail(thumbnail.unwrap_or(ctx.cache().current_user().face()))
.field(
"Artist",
artist.unwrap_or("Unknown Artist".to_string()),
true,
)
.field(
"Duration",
format!("{:02}:{:02}", duration_minutes, duration_seconds),
true,
)
.field("DJ", ctx.author().name.clone(), true)
.description(description)
.timestamp(timestamp)
.footer(
CreateEmbedFooter::new(ctx.cache().current_user().name.to_string())
.icon_url(ctx.cache().current_user().face()),
);
Ok(embed)
}

View File

@@ -1,11 +1,126 @@
use crate::{commands::embeds::embed, Context, Error};
use poise::CreateReply;
use crate::{commands::embeds::error_embed, Context, Error};
/// Hijacks current audio output and plays selected audio
#[poise::command(prefix_command, slash_command, aliases("override"), category = "Music")]
pub async fn stream(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "Playing audio", "", "").await.unwrap()))
.await?;
use poise::serenity_prelude::model::Timestamp;
use poise::serenity_prelude::Colour;
use poise::serenity_prelude::CreateEmbed;
use poise::CreateReply;
use serenity::builder::CreateEmbedAuthor;
use serenity::builder::CreateEmbedFooter;
use songbird::events::TrackEvent;
use songbird::input::AuxMetadata;
use songbird::input::{Compose, YoutubeDl};
use std::time::Duration;
use crate::commands::music::notifier::TrackErrorNotifier;
use crate::http::HttpKey;
/// Hijacks output and plays audio; \
/// search by query or paste an url; \
/// aliases: stream, override, hijack
#[poise::command(
prefix_command,
slash_command,
aliases("override", "hijack"),
category = "Music"
)]
pub async fn stream(
ctx: Context<'_>,
#[description = "Shall output pause?"]
#[flag]
pause: bool,
#[description = "Provide a query or an url"]
#[rest]
mut song: String,
) -> Result<(), Error> {
let is_query = !song.starts_with("http");
let guild_id = ctx.guild_id().unwrap();
let http_client = {
let data = ctx.serenity_context().data.read().await;
data.get::<HttpKey>()
.cloned()
.expect("Guaranteed to exist in the typemap.")
};
let manager = songbird::get(&ctx.serenity_context())
.await
.expect("Songbird Voice placed at init")
.clone();
if pause {
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
let queue = handler.queue();
let _ = queue.pause();
}
}
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
handler.add_global_event(TrackEvent::Error.into(), TrackErrorNotifier);
if is_query {
song = format!("ytsearch:{}", song);
}
let src = YoutubeDl::new_ytdl_like("yt-dlp", http_client, song);
let embed = generate_embed(ctx, src.clone()).await;
let response = CreateReply::default().embed(embed.unwrap());
ctx.send(response).await?;
let _ = handler.play_input(src.clone().into());
} else {
let msg = "I am not in a voice channel!";
ctx.send(CreateReply::default().embed(error_embed(ctx, msg).await.unwrap()))
.await?;
}
Ok(())
}
async fn generate_embed(ctx: Context<'_>, src: YoutubeDl) -> Result<CreateEmbed, Error> {
let metadata = src.clone().aux_metadata().await.unwrap();
let AuxMetadata {
title,
thumbnail,
source_url,
artist,
duration,
..
} = metadata;
let timestamp = Timestamp::now();
let duration_minutes = duration.unwrap_or(Duration::new(0, 0)).clone().as_secs() / 60;
let duration_seconds = duration.unwrap_or(Duration::new(0, 0)).clone().as_secs() % 60;
let description = format!("Playing now!");
let embed = CreateEmbed::default()
.author(
CreateEmbedAuthor::new("Audio output hijacked!").icon_url(ctx.author().clone().face()),
)
.colour(Colour::from_rgb(255, 58, 97))
.title(title.unwrap())
.url(source_url.unwrap())
.thumbnail(thumbnail.unwrap_or(ctx.cache().current_user().face()))
.field(
"Artist",
artist.unwrap_or("Unknown Artist".to_string()),
true,
)
.field(
"Duration",
format!("{:02}:{:02}", duration_minutes, duration_seconds),
true,
)
.field("DJ", ctx.author().name.clone(), true)
.description(description)
.timestamp(timestamp)
.footer(
CreateEmbedFooter::new(ctx.cache().current_user().name.to_string())
.icon_url(ctx.cache().current_user().face()),
);
Ok(embed)
}

View File

@@ -1,11 +1,44 @@
use crate::{commands::embeds::embed, Context, Error};
use crate::{
commands::embeds::{embed, error_embed},
Context, Error,
};
use poise::CreateReply;
/// Changes output volume
#[poise::command(prefix_command, slash_command, category = "Music")]
pub async fn volume(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "", "", "").await.unwrap()))
pub async fn volume(ctx: Context<'_>, #[description = "Volume"] volume: f32) -> Result<(), Error> {
let guild_id = ctx.guild_id().unwrap();
let manager = songbird::get(&ctx.serenity_context())
.await
.expect("Songbird client placed at init")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let handler = handler_lock.lock().await;
let queue = handler.queue();
let track = queue.current().unwrap();
let _ = track.set_volume(volume / 100.0);
ctx.send(
CreateReply::default().embed(
embed(
ctx,
"Volume changed",
"",
&format!("Set volume to {}%", volume),
)
.await
.unwrap(),
),
)
.await?;
} else {
let msg = "I am not in a voice channel!";
ctx.send(CreateReply::default().embed(error_embed(ctx, msg).await.unwrap()))
.await?;
}
Ok(())
}

View File

@@ -1,12 +1,72 @@
use poise::CreateReply;
use serde::{Deserialize, Serialize};
use url::form_urlencoded;
use crate::{commands::embeds::embed, Context, Error};
/// Explains provided query
#[poise::command(prefix_command, slash_command, category = "Tools")]
pub async fn dictionary(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "", "", "").await.unwrap()))
.await?;
#[poise::command(prefix_command, slash_command, aliases("dict"), category = "Tools")]
pub async fn dictionary(
ctx: Context<'_>,
#[description = "Word you're looking for"]
#[rest]
word: String,
) -> Result<(), Error> {
let data: String = form_urlencoded::byte_serialize(word.as_bytes()).collect();
let client = reqwest::Client::new();
let response = client
.get(format!(
"https://api.dictionaryapi.dev/api/v2/entries/en/{}",
data
))
.send()
.await
.unwrap();
match response.status() {
reqwest::StatusCode::OK => match response.json::<Vec<Word>>().await {
Ok(parsed) => {
println!("{:?}", parsed);
ctx.send(CreateReply::default().embed(embed(ctx, "", "", "").await.unwrap()))
.await?;
}
Err(err) => println!("Something is messed up! {:?}", err),
},
reqwest::StatusCode::UNAUTHORIZED => {
println!("Unauthorized.. Uoops!!");
}
error => {
println!("Something went wrong: {:?}", error);
}
}
Ok(())
}
#[derive(Serialize, Deserialize, Debug)]
struct Definition {
definition: String,
}
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
struct Meaning {
partOfSpeech: String,
definitions: Vec<Definition>,
}
#[allow(non_snake_case)]
#[derive(Serialize, Deserialize, Debug)]
struct Phonetic {
text: Option<String>,
audio: Option<String>,
}
#[derive(Serialize, Deserialize, Debug)]
struct Word {
word: String,
phonetics: Vec<Phonetic>,
meanings: Vec<Meaning>,
}

View File

@@ -1,11 +1,17 @@
use owoify::OwOifiable;
use poise::CreateReply;
use crate::{commands::embeds::embed, Context, Error};
/// Owoifies whatever you want uwu
#[poise::command(prefix_command, slash_command, category = "Tools")]
pub async fn owoify(ctx: Context<'_>) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "", "", "").await.unwrap()))
pub async fn owoify(
ctx: Context<'_>,
#[description = "Text to owoify w-woify OwO"]
#[rest]
text: String,
) -> Result<(), Error> {
ctx.send(CreateReply::default().embed(embed(ctx, "OwO", &text.owoify(), "").await.unwrap()))
.await?;
Ok(())