feat: bot now plays songs, started to work on queue system
This commit is contained in:
parent
edc22a91f2
commit
a16d8a6b60
12 changed files with 289 additions and 130 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -1338,6 +1338,12 @@ dependencies = [
|
||||||
"unicase",
|
"unicase",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "queues"
|
||||||
|
version = "1.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1475abae4f8ad4998590fe3acfe20104f0a5d48fc420c817cd2c09c3f56151f0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.35"
|
version = "1.0.35"
|
||||||
|
@ -1561,6 +1567,8 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"confy",
|
"confy",
|
||||||
"futures",
|
"futures",
|
||||||
|
"once_cell",
|
||||||
|
"queues",
|
||||||
"reqwest",
|
"reqwest",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -21,3 +21,5 @@ tracing = "0.1.40"
|
||||||
tracing-subscriber = "0.3.18"
|
tracing-subscriber = "0.3.18"
|
||||||
tracing-futures = "0.2.5"
|
tracing-futures = "0.2.5"
|
||||||
futures = "0.3.1"
|
futures = "0.3.1"
|
||||||
|
queues = "1"
|
||||||
|
once_cell = "1"
|
||||||
|
|
|
@ -7,9 +7,9 @@ pub async fn run(_ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
||||||
let username = command.user.name.as_str();
|
let username = command.user.name.as_str();
|
||||||
|
|
||||||
Embed::create(
|
Embed::create(
|
||||||
username,
|
|
||||||
"",
|
"",
|
||||||
"Botendo v7\ndeveloped by [moonleay](https://moonleay.net)\n\nCheck out the repository: https://git.moonleay.net/DiscordBots/Rustendo",
|
"Botendo v7",
|
||||||
|
"developed by [moonleay](https://moonleay.net)\n\nCheck out the repository: https://git.moonleay.net/DiscordBots/Rustendo",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
use serenity::all::{CommandDataOptionValue, CommandInteraction, Context};
|
use serenity::all::{CommandDataOptionValue, CommandInteraction, Context};
|
||||||
use serenity::builder::{CreateCommand, CreateCommandOption, CreateEmbed};
|
use serenity::builder::{CreateCommand, CreateCommandOption, CreateEmbed};
|
||||||
use serenity::model::application::CommandOptionType;
|
use serenity::model::application::CommandOptionType;
|
||||||
|
use crate::music::music_manager;
|
||||||
|
|
||||||
use crate::util::embed::Embed;
|
use crate::util::embed::Embed;
|
||||||
use crate::util::user_util::{self, get_vc_id};
|
|
||||||
|
|
||||||
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
||||||
let username = command.user.name.as_str();
|
let username = command.user.name.as_str();
|
||||||
let options = &command.data.options;
|
let options = &command.data.options;
|
||||||
|
|
||||||
let query = command.data.options.first().and_then(|option| {
|
let query = options.first().and_then(|option| {
|
||||||
if let CommandDataOptionValue::String(query) = &option.value {
|
if let CommandDataOptionValue::String(query) = &option.value {
|
||||||
Some(query)
|
Some(query)
|
||||||
} else {
|
} else {
|
||||||
|
@ -28,40 +28,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
println!("Guild ID: {:?}", guild_id);
|
music_manager::attempt_to_queue_song(&ctx, &guild_id, &command.user.id, &command.user.name, query.unwrap()).await
|
||||||
|
|
||||||
let connect_to = get_vc_id(ctx, &guild_id, &command.user.id).await.expect("Cannot get channel id");
|
|
||||||
|
|
||||||
let manager = &songbird::get(ctx)
|
|
||||||
.await
|
|
||||||
.expect("Cannot get Songbird.")
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let self_channel = user_util::get_self_vc_id(ctx, &guild_id).await;
|
|
||||||
if !user_util::is_self_connected_to_vc(ctx, &guild_id).await {
|
|
||||||
// self is connected to vc, check if user is in same vc
|
|
||||||
|
|
||||||
if self_channel.is_none() {
|
|
||||||
// Connect to VC
|
|
||||||
manager
|
|
||||||
.join(*guild_id, connect_to)
|
|
||||||
.await
|
|
||||||
.expect("Cannot connect>...");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
let self_channel = self_channel.expect("Cannot get self channel");
|
|
||||||
|
|
||||||
// Check if user is in the same VC as the bot
|
|
||||||
if self_channel != connect_to {
|
|
||||||
return Embed::create(
|
|
||||||
username,
|
|
||||||
"You are not in my VC.",
|
|
||||||
"Connect to my VC to control the music.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Embed::create(username, "Searching...", format!("Looking for {:?}", query))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
pub fn register() -> CreateCommand {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use serenity::all::{CommandInteraction, Context};
|
use serenity::all::{CommandInteraction, Context};
|
||||||
use serenity::builder::{CreateCommand, CreateEmbed};
|
use serenity::builder::{CreateCommand, CreateEmbed};
|
||||||
|
use crate::music::music_manager;
|
||||||
use crate::util::embed::Embed;
|
use crate::util::embed::Embed;
|
||||||
use crate::util::user_util;
|
|
||||||
|
|
||||||
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
||||||
let username = command.user.name.as_str();
|
let username = command.user.name.as_str();
|
||||||
|
@ -14,38 +13,7 @@ pub async fn run(ctx: &Context, command: &CommandInteraction) -> CreateEmbed {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !user_util::is_self_connected_to_vc_cached(ctx, guild_id) {
|
music_manager::attempt_to_stop(&ctx, &guild_id, &command.user.id, &command.user.name).await
|
||||||
// Bot is not connectd to vc; no need to dc
|
|
||||||
return Embed::create(
|
|
||||||
username,
|
|
||||||
"Bot is not connected",
|
|
||||||
"And therefore I cannot stop playing.",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let manager = songbird::get(ctx)
|
|
||||||
.await
|
|
||||||
.expect("Cannot get Songbird")
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let has_handler = manager.get(*guild_id).is_some();
|
|
||||||
|
|
||||||
if has_handler {
|
|
||||||
if let Err(e) = manager.remove(*guild_id).await {
|
|
||||||
return Embed::create(username, "There was an error", format!("Failed: {:?}", e));
|
|
||||||
}
|
|
||||||
return Embed::create(
|
|
||||||
username,
|
|
||||||
"I stopped and left\nJust like your girlfriend.",
|
|
||||||
"",
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Embed::create(
|
|
||||||
username,
|
|
||||||
"Bot is not connected",
|
|
||||||
"And therefore I cannot stop playing.\nSomething happend, which shouldn't have.",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn register() -> CreateCommand {
|
pub fn register() -> CreateCommand {
|
||||||
|
|
35
src/main.rs
35
src/main.rs
|
@ -1,15 +1,15 @@
|
||||||
mod commands;
|
mod commands;
|
||||||
mod handler;
|
|
||||||
mod util;
|
mod util;
|
||||||
|
mod music;
|
||||||
|
|
||||||
use serenity::all::{CommandInteraction, OnlineStatus};
|
use serenity::all::{CommandInteraction, CreateInteractionResponseFollowup, OnlineStatus, VoiceState};
|
||||||
use serenity::async_trait;
|
use serenity::async_trait;
|
||||||
use serenity::builder::{CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage};
|
use serenity::builder::{CreateEmbed};
|
||||||
use serenity::gateway::ActivityData;
|
use serenity::gateway::ActivityData;
|
||||||
use serenity::model::application::{Command, Interaction};
|
use serenity::model::application::{Command, Interaction};
|
||||||
use serenity::model::gateway::Ready;
|
use serenity::model::gateway::Ready;
|
||||||
use serenity::prelude::*;
|
use serenity::prelude::*;
|
||||||
use util::{config, embed::Embed};
|
use util::{config, embed::Embed, user_util};
|
||||||
|
|
||||||
// This trait adds the `register_songbird` and `register_songbird_with` methods
|
// This trait adds the `register_songbird` and `register_songbird_with` methods
|
||||||
// to the client builder below, making it easy to install this voice client.
|
// to the client builder below, making it easy to install this voice client.
|
||||||
|
@ -30,6 +30,8 @@ struct Handler;
|
||||||
impl EventHandler for Handler {
|
impl EventHandler for Handler {
|
||||||
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
async fn interaction_create(&self, ctx: Context, interaction: Interaction) {
|
||||||
if let Interaction::Command(command) = interaction {
|
if let Interaction::Command(command) = interaction {
|
||||||
|
let _ = &command.defer(&ctx.http()).await.expect("Cannot defer");
|
||||||
|
|
||||||
let content = Some(match command.data.name.as_str() {
|
let content = Some(match command.data.name.as_str() {
|
||||||
"info" => commands::info::run(&ctx, &command).await,
|
"info" => commands::info::run(&ctx, &command).await,
|
||||||
"play" => commands::play::run(&ctx, &command).await,
|
"play" => commands::play::run(&ctx, &command).await,
|
||||||
|
@ -38,10 +40,9 @@ impl EventHandler for Handler {
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(embed) = content {
|
if let Some(embed) = content {
|
||||||
let data = CreateInteractionResponseMessage::new().embed(embed);
|
let followup = CreateInteractionResponseFollowup::new().embed(embed);
|
||||||
let builder = CreateInteractionResponse::Message(data);
|
if let Err(why) = command.create_followup(&ctx.http, followup).await {
|
||||||
if let Err(why) = command.create_response(&ctx.http, builder).await {
|
println!("Cannot followup to slash command: {why}")
|
||||||
println!("Cannot respond to slash command: {why}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,8 +54,24 @@ impl EventHandler for Handler {
|
||||||
let _command = Command::create_global_command(&ctx.http, commands::info::register()).await;
|
let _command = Command::create_global_command(&ctx.http, commands::info::register()).await;
|
||||||
let _command = Command::create_global_command(&ctx.http, commands::stop::register()).await;
|
let _command = Command::create_global_command(&ctx.http, commands::stop::register()).await;
|
||||||
let _command = Command::create_global_command(&ctx.http, commands::play::register()).await;
|
let _command = Command::create_global_command(&ctx.http, commands::play::register()).await;
|
||||||
|
println!("Commands are registered and Rustendo is ready for Freddy.");
|
||||||
|
}
|
||||||
|
|
||||||
println!("Created all public / commands");
|
async fn voice_state_update(&self, ctx: Context, old: Option<VoiceState>, new: VoiceState) {
|
||||||
|
if !new.channel_id.is_none() {
|
||||||
|
return; // User did not leave, ignore
|
||||||
|
}
|
||||||
|
if let Some(old) = old {
|
||||||
|
if !user_util::is_self_connected_to_vc(&ctx, &old.guild_id.unwrap()).await {
|
||||||
|
return; // Bot is not connected to a VC, ignore
|
||||||
|
}
|
||||||
|
if user_util::get_amount_of_members_in_vc(&ctx, &old.guild_id.unwrap(), &old.channel_id.unwrap()).await < 2 {
|
||||||
|
let manager = songbird::get(&ctx).await.expect("Cannot get Songbird");
|
||||||
|
if let Err(e) = manager.remove(old.guild_id.unwrap()).await {
|
||||||
|
println!("Failed to remove handler: {:?}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // else: new user joined, ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
src/music/mod.rs
Normal file
2
src/music/mod.rs
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
pub mod music_manager;
|
||||||
|
pub mod music_events;
|
42
src/music/music_events.rs
Normal file
42
src/music/music_events.rs
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
use queues::IsQueue;
|
||||||
|
use serenity::all::{ChannelId, GuildId, Http};
|
||||||
|
use serenity::async_trait;
|
||||||
|
use songbird::{Event, EventContext, EventHandler};
|
||||||
|
use crate::music::music_manager;
|
||||||
|
|
||||||
|
pub struct TrackEndNotifier {
|
||||||
|
pub guild_id: GuildId,
|
||||||
|
pub channel_id: ChannelId,
|
||||||
|
pub http: Arc<Http>,
|
||||||
|
pub cmdctx: Arc<serenity::client::Context>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl EventHandler for TrackEndNotifier {
|
||||||
|
async fn act(&self, ctx: &EventContext<'_>) -> Option<Event> {
|
||||||
|
unsafe { // TODO: Does this need to be unsafe?
|
||||||
|
if let EventContext::Track(track_list) = ctx {
|
||||||
|
println!("The track ended!");
|
||||||
|
let queue = music_manager::get_queue(&self.guild_id);
|
||||||
|
if queue.size() == 0 {
|
||||||
|
let stopped = match music_manager::stop(&self.cmdctx, &self.guild_id).await {
|
||||||
|
Ok(stopped) => stopped,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Cannot stop: {:?}", e);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if stopped {
|
||||||
|
println!("Stopped playing successfully.");
|
||||||
|
} else {
|
||||||
|
println!("Failed to stop playing.");
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
173
src/music/music_manager.rs
Normal file
173
src/music/music_manager.rs
Normal file
|
@ -0,0 +1,173 @@
|
||||||
|
use std::collections::{HashMap};
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::time::Duration;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use queues::Queue;
|
||||||
|
use serenity::all::{Context, CreateEmbed, GuildId, UserId};
|
||||||
|
use songbird::{Event, TrackEvent};
|
||||||
|
use songbird::error::JoinError;
|
||||||
|
use songbird::input::{Compose, YoutubeDl};
|
||||||
|
use crate::HttpKey;
|
||||||
|
use crate::music::music_events;
|
||||||
|
use crate::util::embed::Embed;
|
||||||
|
use crate::util::user_util;
|
||||||
|
use crate::util::user_util::get_vc_id;
|
||||||
|
|
||||||
|
/// pub static mut MUSIC_QUEUE: HashMap<GuildId, Queue<String>> = HashMap::new();
|
||||||
|
pub static mut MUSIC_QUEUE: Lazy<Mutex<HashMap<GuildId, Queue<String>>>> = Lazy::new(|| {
|
||||||
|
Mutex::new(HashMap::new())
|
||||||
|
}); // TODO: This does not work and this is not the way to do it. This is a placeholder for now.
|
||||||
|
|
||||||
|
pub unsafe fn get_queue(guild_id: &GuildId) -> &Queue<String> {
|
||||||
|
MUSIC_QUEUE.lock().unwrap().entry(*guild_id).or_insert_with(Queue::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn attempt_to_queue_song(ctx: &Context, guild_id: &GuildId, user_id: &UserId, username: &str, query: &str) -> CreateEmbed {
|
||||||
|
if !user_util::is_user_connected_to_vc(ctx, guild_id, user_id).await {
|
||||||
|
return Embed::create(username, "You are not connected to a VC", "Connect to my VC to control the music.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let connect_to = match get_vc_id(ctx, &guild_id, &user_id).await {
|
||||||
|
Some(channel_id) => channel_id,
|
||||||
|
None => {
|
||||||
|
return Embed::create(username, "Error", "Cannot get channel id");
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
let manager = &songbird::get(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Cannot get Songbird.")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let self_channel = user_util::get_self_vc_id(ctx, &guild_id).await;
|
||||||
|
if !user_util::is_self_connected_to_vc(ctx, &guild_id).await {
|
||||||
|
// self is connected to vc, check if user is in same vc
|
||||||
|
|
||||||
|
if self_channel.is_none() { // TODO This could maybe be removed?
|
||||||
|
// Connect to VC
|
||||||
|
manager
|
||||||
|
.join(*guild_id, connect_to)
|
||||||
|
.await
|
||||||
|
.expect("Cannot connect>...");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
let self_channel = self_channel.expect("Cannot get self channel");
|
||||||
|
|
||||||
|
// Check if user is in the same VC as the bot
|
||||||
|
if self_channel != connect_to {
|
||||||
|
return Embed::create(
|
||||||
|
username,
|
||||||
|
"You are not in my VC.",
|
||||||
|
"Connect to my VC to control the music.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get query
|
||||||
|
let do_search = !query.starts_with("http");
|
||||||
|
let http_client = {
|
||||||
|
let data = ctx.data.read().await;
|
||||||
|
data.get::<HttpKey>()
|
||||||
|
.cloned()
|
||||||
|
.expect("Guaranteed to exist in the typemap.")
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create source
|
||||||
|
let mut src = if do_search {
|
||||||
|
YoutubeDl::new_search(http_client, query.to_string())
|
||||||
|
} else {
|
||||||
|
YoutubeDl::new(http_client, query.to_string())
|
||||||
|
};
|
||||||
|
let handler_lock = match manager.get(*guild_id) {
|
||||||
|
Some(handler) => handler,
|
||||||
|
None => {
|
||||||
|
return Embed::create(username, "Error", "Cannot get handler");
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
// Start playing
|
||||||
|
let mut handler = handler_lock.lock().await;
|
||||||
|
let track_handle = handler.play_input(src.clone().into()); // TODO: Add event handlers
|
||||||
|
handler.add_global_event(
|
||||||
|
Event::Track(TrackEvent::End),
|
||||||
|
music_events::TrackEndNotifier {
|
||||||
|
guild_id: *guild_id,
|
||||||
|
channel_id: connect_to,
|
||||||
|
http: Arc::clone(&ctx.http),
|
||||||
|
cmdctx: Arc::new(ctx.clone()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get metadata
|
||||||
|
let metadata = src.aux_metadata().await.expect("Cannot get metadata");
|
||||||
|
let title = metadata.title.unwrap_or("Unknown title".to_string());
|
||||||
|
let author = metadata.artist.unwrap_or("Unknown artist".to_string());
|
||||||
|
let duration = metadata.duration.unwrap_or(Duration::from_millis(0));
|
||||||
|
let thumbnail = metadata.thumbnail.unwrap_or("https://http.cat/images/404.jpg".to_string());
|
||||||
|
let link = metadata.source_url.unwrap_or("https://piped.moonleay.net/404".to_string());
|
||||||
|
|
||||||
|
Embed::create(username, "Added to queue", format!("{} by {} ({}min {}sec) was added to the queue.\n [[Link]({})]",
|
||||||
|
title, author, duration.as_secs() / 60, duration.as_secs() % 60, link))
|
||||||
|
.thumbnail(thumbnail)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
pub async fn attempt_to_stop(ctx: &Context, guild_id: &GuildId, user_id: &UserId, username: &str) -> CreateEmbed {
|
||||||
|
if !user_util::is_self_connected_to_vc(ctx, guild_id).await {
|
||||||
|
// Bot is not connectd to vc; no need to dc
|
||||||
|
return Embed::create(
|
||||||
|
username,
|
||||||
|
"Bot is not connected",
|
||||||
|
"And therefore there is no need to do anything.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
let self_channel = user_util::get_self_vc_id(ctx, &guild_id).await.expect("Cannot get self channel");
|
||||||
|
let connect_to = get_vc_id(ctx, &guild_id, &user_id).await.expect("Cannot get channel id");
|
||||||
|
|
||||||
|
// Check if user is in the same VC as the bot
|
||||||
|
if self_channel != connect_to {
|
||||||
|
return Embed::create(
|
||||||
|
username,
|
||||||
|
"You are not in my VC.",
|
||||||
|
"Connect to my VC to control the music.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let stopped = match stop(ctx, guild_id).await {
|
||||||
|
Ok(stopped) => stopped,
|
||||||
|
Err(e) => {
|
||||||
|
println!("Error while stopping: {:?}", e);
|
||||||
|
return Embed::create(username, "There was an error", "Tell moonleay to check the logs.".to_string());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !stopped {
|
||||||
|
return Embed::create(username, "Can't stop, what ain't running.", "I am not connected. I cant stop doing something, when I'm not doing it".to_string());
|
||||||
|
} else {
|
||||||
|
return Embed::create(
|
||||||
|
username,
|
||||||
|
"I stopped and left",
|
||||||
|
"Just like your girlfriend.",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the bot leave the voice channel. Returns Ok(true) if bot was connected, returns Ok(false) if bot was not connected. Returns Err if something went wrong.
|
||||||
|
pub async fn stop(ctx: &Context, guild_id: &GuildId) -> Result<bool, JoinError> {
|
||||||
|
let manager = songbird::get(ctx)
|
||||||
|
.await
|
||||||
|
.expect("Cannot get Songbird")
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
let has_handler = manager.get(*guild_id).is_some();
|
||||||
|
|
||||||
|
if has_handler {
|
||||||
|
if let Err(e) = manager.remove(*guild_id).await {
|
||||||
|
return Err(e); // Failed to remove handler
|
||||||
|
}
|
||||||
|
return Ok(true) // Handler removed
|
||||||
|
}
|
||||||
|
Ok(false) // No handler, so it's already stopped
|
||||||
|
}
|
|
@ -22,7 +22,7 @@ impl Embed {
|
||||||
.title(title)
|
.title(title)
|
||||||
.description(message)
|
.description(message)
|
||||||
.footer(CreateEmbedFooter::new(format!(
|
.footer(CreateEmbedFooter::new(format!(
|
||||||
"> {} | {}",
|
"> {} - {}",
|
||||||
current_time, username
|
current_time, username
|
||||||
)))
|
)))
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,7 @@
|
||||||
use serenity::all::{CacheHttp, ChannelId, Context, Guild, GuildId, GuildRef, PartialGuild, UserId};
|
use std::collections::HashMap;
|
||||||
use serenity::Error;
|
use serenity::all::{ChannelId, Context, Guild, GuildId, PartialGuild, UserId, VoiceState};
|
||||||
|
|
||||||
/// Get a guild by id
|
|
||||||
pub fn get_guild_cached(ctx: &Context, guild_id: &GuildId) -> Option<Guild> {
|
|
||||||
let guild = match ctx.cache.guild(guild_id) {
|
|
||||||
Some(guild) => guild,
|
|
||||||
None => {
|
|
||||||
println!("Cannot get guild with id {:?}!", guild_id);
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Got guild: {:?}", guild.name);
|
|
||||||
|
|
||||||
Some(guild.clone())
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Request a guild by id, get it from cache
|
||||||
pub fn request_guild(ctx: &Context, guild_id: &GuildId) -> Guild {
|
pub fn request_guild(ctx: &Context, guild_id: &GuildId) -> Guild {
|
||||||
let guild = match guild_id.to_guild_cached(&ctx.cache) {
|
let guild = match guild_id.to_guild_cached(&ctx.cache) {
|
||||||
Some(guild) => guild.clone(),
|
Some(guild) => guild.clone(),
|
||||||
|
@ -39,26 +25,7 @@ pub async fn request_partial_guild(ctx: &Context, guild_id: &GuildId) -> Partial
|
||||||
guild
|
guild
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current channel id of the bot
|
/// Get the voice channel id of a user
|
||||||
pub fn get_vc_id_cached(ctx: &Context, guild_id: &GuildId, user_id: &UserId) -> Option<ChannelId> {
|
|
||||||
let guild = match get_guild_cached(&ctx, guild_id){
|
|
||||||
Some(guild) => guild,
|
|
||||||
None => {
|
|
||||||
println!("Cannot get guild while getting channel id!");
|
|
||||||
return None
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let channel_id = guild
|
|
||||||
.voice_states
|
|
||||||
.get(user_id)
|
|
||||||
.and_then(|voice_state| voice_state.channel_id)?;
|
|
||||||
|
|
||||||
println!("Got vc with id: {:?}", channel_id);
|
|
||||||
|
|
||||||
Some(channel_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn get_vc_id(ctx: &Context, guild_id: &GuildId, user_id: &UserId) -> Option<ChannelId> {
|
pub async fn get_vc_id(ctx: &Context, guild_id: &GuildId, user_id: &UserId) -> Option<ChannelId> {
|
||||||
let guild = request_guild(&ctx, guild_id);
|
let guild = request_guild(&ctx, guild_id);
|
||||||
guild
|
guild
|
||||||
|
@ -69,27 +36,40 @@ pub async fn get_vc_id(ctx: &Context, guild_id: &GuildId, user_id: &UserId) -> O
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the bot is connected to a voice channel
|
/// Check if the bot is connected to a voice channel
|
||||||
pub fn is_self_connected_to_vc_cached(ctx: &Context, guild_id: &GuildId) -> bool {
|
|
||||||
let channel_id = get_self_vc_id_cached(ctx, guild_id);
|
|
||||||
|
|
||||||
!channel_id.is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn is_self_connected_to_vc(ctx: &Context, guild_id: &GuildId) -> bool {
|
pub async fn is_self_connected_to_vc(ctx: &Context, guild_id: &GuildId) -> bool {
|
||||||
let channel_id = get_self_vc_id(ctx, guild_id);
|
let channel_id = get_self_vc_id(ctx, guild_id);
|
||||||
|
|
||||||
!channel_id.await.is_none()
|
!channel_id.await.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current channel id of the bot
|
/// Check if a user is connected to a voice channel
|
||||||
pub fn get_self_vc_id_cached(ctx: &Context, guild_id: &GuildId) -> Option<ChannelId> {
|
pub async fn is_user_connected_to_vc(ctx: &Context, guild_id: &GuildId, user_id: &UserId) -> bool {
|
||||||
let channel_id = get_vc_id_cached(ctx, guild_id, &ctx.cache.current_user().id)?;
|
let channel_id = get_vc_id(ctx, guild_id, user_id).await;
|
||||||
|
|
||||||
Some(channel_id)
|
!channel_id.is_none()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the voice channel id of the bot
|
||||||
pub async fn get_self_vc_id(ctx: &Context, guild_id: &GuildId) -> Option<ChannelId> {
|
pub async fn get_self_vc_id(ctx: &Context, guild_id: &GuildId) -> Option<ChannelId> {
|
||||||
let user_id = ctx.cache.current_user().id;
|
let user_id = ctx.cache.current_user().id;
|
||||||
|
|
||||||
get_vc_id(ctx, guild_id, &user_id).await
|
get_vc_id(ctx, guild_id, &user_id).await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get all voice states of a guild
|
||||||
|
pub async fn get_voice_states(ctx: &Context, guild_id: &GuildId) -> HashMap<UserId, VoiceState>{
|
||||||
|
let guild = request_guild(ctx, guild_id);
|
||||||
|
let voice_states = guild.voice_states.clone();
|
||||||
|
voice_states
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Get the amount of members in a voice channel
|
||||||
|
pub async fn get_amount_of_members_in_vc(ctx: &Context, guild_id: &GuildId, channel_id: &ChannelId,) -> usize {
|
||||||
|
let voice_states = get_voice_states(ctx, guild_id).await;
|
||||||
|
let amount = voice_states
|
||||||
|
.iter()
|
||||||
|
.filter(|(_, voice_state)| voice_state.channel_id == Some(*channel_id))
|
||||||
|
.count();
|
||||||
|
amount
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue