Initial commit

This commit is contained in:
Alex Page 2021-09-14 00:27:19 -04:00
commit 6bcedfd0cd
7 changed files with 420 additions and 0 deletions

179
src/commands.rs Normal file
View file

@ -0,0 +1,179 @@
use serenity::client::Context;
use serenity::model::interactions::application_command::{
ApplicationCommandInteraction, ApplicationCommandInteractionDataOptionValue,
};
use serenity::utils::{EmbedMessageBuilding, MessageBuilder};
use songbird::create_player;
use crate::{CurrentVolume, CurrentlyPlayingTrack};
pub async fn join(ctx: &Context, command: &ApplicationCommandInteraction) -> String {
let guild_id = command.guild_id.unwrap();
let guild = guild_id.to_guild_cached(&ctx.cache).await.unwrap();
let channel_id = guild
.voice_states
.get(&command.user.id)
.and_then(|voice_state| voice_state.channel_id);
let connect_to = match channel_id {
Some(channel) => channel,
None => {
return "You're not in a voice channel".to_string();
}
};
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let _handler = manager.join(guild_id, connect_to).await;
"Joining your channel".to_string()
}
pub async fn leave(ctx: &Context, command: &ApplicationCommandInteraction) -> String {
let guild_id = command.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
let has_handler = manager.get(guild_id).is_some();
if has_handler {
if let Err(e) = manager.remove(guild_id).await {
return format!("Failed: {:?}", e);
}
"Left voice channel".to_string()
} else {
"I'm not in a voice channel".to_string()
}
}
pub async fn play(ctx: &mut Context, command: &ApplicationCommandInteraction) -> String {
let options = command
.data
.options
.get(0)
.expect("Expected url option")
.resolved
.as_ref()
.expect("Expected url object");
let url = match options {
ApplicationCommandInteractionDataOptionValue::String(url) => url,
_ => {
return "Must provide a URL to a video or audio".to_string();
}
};
if !url.starts_with("http") {
return "Must provide a valid URL".to_string();
}
let guild_id = command.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
let source = match songbird::ytdl(&url).await {
Ok(source) => source,
Err(why) => {
println!("Err starting source: {:?}", why);
return "Error sourcing ffmpeg".to_string();
}
};
let message = {
if let Some(title) = &source.metadata.title {
let mut msg = MessageBuilder::new();
msg.push_line("Playing song:");
msg.push_named_link(title, url);
msg.build()
} else {
"Playing song".to_string()
}
};
let (mut audio, track_handle) = create_player(source);
let mut data = ctx.data.write().await;
let current_track = data.get_mut::<CurrentlyPlayingTrack>().unwrap();
*current_track = Some(track_handle);
let volume = data.get::<CurrentVolume>().unwrap();
audio.set_volume(*volume);
handler.play_only(audio);
message
} else {
"Not in a voice channel to play in".to_string()
}
}
pub async fn stop(ctx: &Context, command: &ApplicationCommandInteraction) -> String {
let guild_id = command.guild_id.unwrap();
let manager = songbird::get(ctx)
.await
.expect("Songbird Voice client placed in at initialisation.")
.clone();
if let Some(handler_lock) = manager.get(guild_id) {
let mut handler = handler_lock.lock().await;
handler.stop();
"Stopping song".to_string()
} else {
"Not in a voice channel to play in".to_string()
}
}
pub async fn set_volume(ctx: &mut Context, command: &ApplicationCommandInteraction) -> String {
let options = command
.data
.options
.get(0)
.expect("Expected volume option")
.resolved
.as_ref()
.expect("Expected volume object");
let volume = match options {
ApplicationCommandInteractionDataOptionValue::Number(volume) => *volume,
_ => {
return "Must provide a volume level".to_string();
}
};
if !(0.0..=100.0).contains(&volume) {
return "Must provide a value between 0 and 100".to_string();
}
let mut data = ctx.data.write().await;
let current_volume = data.get_mut::<CurrentVolume>().unwrap();
let new_volume = (volume / 100.0) as f32;
*current_volume = new_volume;
let current_track = data.get::<CurrentlyPlayingTrack>().unwrap();
if let Some(track) = current_track {
track.set_volume(new_volume).unwrap();
format!("Setting volume to {}%", volume)
} else {
"No track is currently playing".to_string()
}
}

185
src/main.rs Normal file
View file

@ -0,0 +1,185 @@
#![warn(clippy::all)]
use std::env;
use serenity::{
async_trait,
model::{
gateway::Ready,
id::GuildId,
interactions::{
application_command::{ApplicationCommandOptionType, ApplicationCommandPermissionType},
Interaction, InteractionResponseType,
},
},
prelude::*,
};
use songbird::{tracks::TrackHandle, SerenityInit};
mod commands;
struct CurrentlyPlayingTrack;
impl TypeMapKey for CurrentlyPlayingTrack {
type Value = Option<TrackHandle>;
}
struct CurrentVolume;
impl TypeMapKey for CurrentVolume {
type Value = f32;
}
struct Handler;
#[async_trait]
impl EventHandler for Handler {
async fn interaction_create(&self, mut ctx: Context, interaction: Interaction) {
if let Interaction::ApplicationCommand(command) = interaction {
let content = match command.data.name.as_str() {
"join" => commands::join(&ctx, &command).await,
"leave" => commands::leave(&ctx, &command).await,
"play" => commands::play(&mut ctx, &command).await,
"stop" => commands::stop(&ctx, &command).await,
"volume" => commands::set_volume(&mut ctx, &command).await,
_ => "not implemented :(".to_string(),
};
if let Err(why) = command
.create_interaction_response(&ctx.http, |response| {
response
.kind(InteractionResponseType::ChannelMessageWithSource)
.interaction_response_data(|message| message.content(content))
})
.await
{
println!("Cannot respond to slash command: {}", why);
}
}
}
async fn cache_ready(&self, ctx: Context, guilds: Vec<GuildId>) {
for guild in guilds {
let commands = guild
.set_application_commands(&ctx.http, |commands| {
commands
.create_application_command(|command| {
command
.name("join")
.description("Join your current channel")
.default_permission(false)
})
.create_application_command(|command| {
command
.name("leave")
.description("Leave the bot's current channel")
.default_permission(false)
})
.create_application_command(|command| {
command
.name("play")
.description("Play a song")
.create_option(|option| {
option
.name("url")
.description("The URL of the song to play")
.kind(ApplicationCommandOptionType::String)
.required(true)
})
.default_permission(false)
})
.create_application_command(|command| {
command
.name("stop")
.description("Stop any currently playing songs")
.default_permission(false)
})
.create_application_command(|command| {
command
.name("volume")
.description("Set the bot's playback volume")
.create_option(|option| {
option
.name("volume")
.description("The volume on a scale from 0 to 100")
.kind(ApplicationCommandOptionType::Number)
.required(true)
})
.default_permission(false)
})
})
.await
.expect("Couldn't create commands");
println!("I created the following guild commands: {:#?}", commands);
let role_id = env::var("ROLE_ID")
.expect("Expected a role id in the environment")
.parse::<u64>()
.expect("Role id is not a valid id");
let permissions = guild
.set_application_commands_permissions(&ctx.http, |permissions| {
for command in commands {
permissions.create_application_command(|permissions| {
permissions
.id(command.id.into())
.create_permissions(|permission| {
permission
.id(role_id)
.kind(ApplicationCommandPermissionType::Role)
.permission(true)
})
});
}
permissions
})
.await
.expect("Couldn't set permissions");
println!("I created the following permissions: {:#?}", permissions);
}
}
async fn ready(&self, _ctx: Context, ready: Ready) {
println!("{} is connected!", ready.user.name);
}
}
#[tokio::main]
async fn main() {
let token =
env::var("DISCORD_TOKEN").expect("Expected a bot token in the environment: DISCORD_TOKEN");
let application_id: u64 = env::var("APPLICATION_ID")
.expect("Expected an application id in the environment: APPLICATION_ID")
.parse()
.expect("application id is not a valid id");
env::var("ROLE_ID")
.expect("Expected a role id in the environment: ROLE_ID")
.parse::<u64>()
.expect("Role id is not a valid id");
env::var("CHANNEL_ID")
.expect("Expected a channel id in the environment: CHANNEL_ID")
.parse::<u64>()
.expect("Channel id is not a valid id");
let mut client = Client::builder(token)
.event_handler(Handler)
.application_id(application_id)
.register_songbird()
.await
.expect("Error creating client");
{
let mut data = client.data.write().await;
data.insert::<CurrentlyPlayingTrack>(None);
data.insert::<CurrentVolume>(1.0);
}
if let Err(why) = client.start().await {
println!("Client error: {:?}", why);
}
}