feat: fully implemented radio player
This commit is contained in:
parent
8f82cb3b09
commit
00c783bbfa
12 changed files with 375 additions and 123 deletions
|
@ -4,6 +4,7 @@ mod voice_utils;
|
|||
pub mod player_common;
|
||||
pub mod radio;
|
||||
pub mod general_player;
|
||||
pub mod voice_types;
|
||||
|
||||
// ! not working
|
||||
// pub mod yt;
|
||||
|
|
|
@ -2,8 +2,7 @@ use std::vec;
|
|||
|
||||
use songbird::input::YoutubeDl;
|
||||
|
||||
use crate::utils::poise_context_extension::ContextExt;
|
||||
use crate::types::{Context, Error};
|
||||
use crate::types::{Context, ContextExt, Error};
|
||||
use crate::commands::voice::voice_utils::autocomplete_channel;
|
||||
|
||||
use super::connect;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::vec;
|
||||
|
||||
use poise::CreateReply;
|
||||
use songbird::input::{File, Input};
|
||||
|
||||
use crate::utils::debug::send_error;
|
||||
use crate::utils::poise_context_extension::ContextExt;
|
||||
use crate::types::{Context, ContextExt, Error};
|
||||
use crate::utils::utilities::get_local_songs;
|
||||
use crate::types::{Context, Error};
|
||||
|
||||
use super::voice_utils::{connect, autocomplete_channel};
|
||||
|
||||
|
@ -114,3 +114,48 @@ pub async fn play_local(ctx: Context<'_>,
|
|||
ctx.reply_ephemeral("Done!").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
description_localized("en-US", "Display currently playing info")
|
||||
)]
|
||||
pub async fn playing(ctx: Context<'_>) -> Result<(), Error> {
|
||||
|
||||
let Some(guild_id) = ctx.guild_id() else {
|
||||
ctx.reply_ephemeral("Guild id not found").await?;
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
let manager = songbird::get(ctx.serenity_context())
|
||||
.await
|
||||
.expect("Songbird Voice client placed in at initialisation.")
|
||||
.clone();
|
||||
|
||||
let Some(_) = manager.get(guild_id) else {
|
||||
ctx.reply_ephemeral("I am not connected to any voice channel right now").await?;
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
// println!("here");
|
||||
|
||||
let embed = {
|
||||
let mutex_hashmap = ctx.data().playing_info.lock().await;
|
||||
let Some(playing_info) = mutex_hashmap.get(&guild_id) else {
|
||||
ctx.reply_ephemeral("Entry not found, try to reconnect me").await?;
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
playing_info.generate_embed().await
|
||||
};
|
||||
|
||||
dbg!(&embed);
|
||||
|
||||
ctx.send(
|
||||
CreateReply::default()
|
||||
.embed(embed)
|
||||
).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
use std::vec;
|
||||
|
||||
use radiobrowser::RadioBrowserAPI;
|
||||
use poise::ChoiceParameter;
|
||||
use reqwest::Client;
|
||||
|
||||
use songbird::input::Input;
|
||||
use songbird::input::HttpRequest;
|
||||
|
||||
use super::super::connect;
|
||||
use crate::utils::poise_context_extension::ContextExt;
|
||||
use super::link_or_string;
|
||||
use super::parse_radio_autocomplete;
|
||||
use super::LinkString;
|
||||
use crate::commands::voice_types::NumberOfEntries;
|
||||
use crate::commands::voice_types::PlayingInfoType;
|
||||
use crate::types::ContextExt;
|
||||
use crate::types::{Context, Error};
|
||||
use crate::commands::voice::voice_utils::autocomplete_channel;
|
||||
|
||||
use super::radio_utils::paginate_search;
|
||||
use super::radio_utils::paginate_search_stations;
|
||||
|
||||
use super::super::voice_utils::MAX_ENTRIES;
|
||||
|
||||
|
@ -24,6 +29,8 @@ pub async fn radio(_ctx: Context<'_>) -> Result<(), Error> {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// TODO: deduplicate you use a lot of the same code
|
||||
|
||||
// TODO: autocomplete radio stream URLs
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
|
@ -43,6 +50,47 @@ pub async fn play(ctx: Context<'_>,
|
|||
return Ok(());
|
||||
}
|
||||
|
||||
let api = &ctx.data().radio_browser;
|
||||
|
||||
let stations_result = match link_or_string(&name) {
|
||||
LinkString::Link => {
|
||||
if name.ends_with(".m3u") {
|
||||
ctx.reply_ephemeral("Sorry, currently I don't support m3u files").await?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
api.get_stations().byurl(name).await
|
||||
},
|
||||
LinkString::String => {
|
||||
let (name, country, language) = match parse_radio_autocomplete(&name) {
|
||||
Some(tuple) => tuple,
|
||||
None => {
|
||||
ctx.reply_ephemeral("Please use either direct URL or provided autocomplete").await?;
|
||||
return Ok(())
|
||||
}
|
||||
};
|
||||
|
||||
api.get_stations()
|
||||
.name_exact(true)
|
||||
.name(name)
|
||||
.country_exact(true)
|
||||
.country(country)
|
||||
.language_exact(true)
|
||||
.language(language)
|
||||
.send().await
|
||||
}
|
||||
};
|
||||
|
||||
let Ok(stations) = stations_result else {
|
||||
ctx.reply_ephemeral("There was an error with radio API!").await?;
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
let Some(station) = stations.first() else {
|
||||
ctx.reply_ephemeral("Radio station not found!").await?;
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
let manager = songbird::get(ctx.serenity_context())
|
||||
.await
|
||||
.expect("Songbird Voice client placed in at initialisation.")
|
||||
|
@ -54,8 +102,9 @@ pub async fn play(ctx: Context<'_>,
|
|||
};
|
||||
|
||||
if manager.get(guild_id).is_none() {
|
||||
if let Err(e) = connect(&ctx, guild_id, channel).await {
|
||||
if let Err(e) = connect(&ctx, guild_id, channel).await {
|
||||
ctx.reply_ephemeral(&e.to_string()).await?;
|
||||
println!("SONGBIRD MANAGER ERROR: {}", e.to_string());
|
||||
return Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +113,7 @@ pub async fn play(ctx: Context<'_>,
|
|||
let mut handler = handler_lock.lock().await;
|
||||
|
||||
let client = Client::new();
|
||||
let request = HttpRequest::new(client, name);
|
||||
let request = HttpRequest::new(client, station.url.clone());
|
||||
let input = Input::from(request);
|
||||
handler.play_only_input(input);
|
||||
}
|
||||
|
@ -73,6 +122,11 @@ pub async fn play(ctx: Context<'_>,
|
|||
return Ok(())
|
||||
}
|
||||
|
||||
{
|
||||
let mut hash_map = ctx.data().playing_info.lock().await;
|
||||
hash_map.insert(guild_id, PlayingInfoType::RadioInfo(station.clone()));
|
||||
}
|
||||
|
||||
ctx.reply_ephemeral("Done!").await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -82,17 +136,7 @@ async fn autocomplete_radio(
|
|||
partial: &str
|
||||
) -> Vec<String> {
|
||||
|
||||
let api = {
|
||||
match &ctx.data().radio_browser {
|
||||
Some(v) => v.clone(),
|
||||
None => {
|
||||
let Ok(radio_browser) = RadioBrowserAPI::new().await else {
|
||||
return vec!["There was an error with radio API!".to_owned()]
|
||||
};
|
||||
radio_browser
|
||||
}
|
||||
}
|
||||
};
|
||||
let api = &ctx.data().radio_browser;
|
||||
|
||||
let stations: Vec<String> = match api.get_stations()
|
||||
.limit(MAX_ENTRIES)
|
||||
|
@ -100,14 +144,14 @@ async fn autocomplete_radio(
|
|||
.hidebroken(true)
|
||||
.send().await {
|
||||
Ok(stations) => {
|
||||
stations.iter().map(|station|
|
||||
format!("Name: {} Language: {} Bitrate: {}", station.name, station.countrycode, station.bitrate)
|
||||
stations.iter().map(|station: &radiobrowser::ApiStation|
|
||||
format!("Name: {} Country: {} Language: {}", station.name, station.country, station.language)
|
||||
).collect()
|
||||
},
|
||||
Err(_) => return vec!["".to_owned()]
|
||||
};
|
||||
|
||||
dbg!(&stations);
|
||||
// dbg!(&stations);
|
||||
|
||||
return stations
|
||||
// return vec![];
|
||||
|
@ -118,7 +162,11 @@ async fn autocomplete_radio(
|
|||
// search with buttons and list in embed with direct links to stream
|
||||
// embed showing currently playing song
|
||||
|
||||
// use trace::trace;
|
||||
// trace::init_depth_var!();
|
||||
|
||||
// TODO: autocomplete radio stream URLs
|
||||
// #[trace]
|
||||
#[poise::command(
|
||||
slash_command,
|
||||
description_localized("en-US", "Search for a radio")
|
||||
|
@ -129,27 +177,23 @@ pub async fn search(ctx: Context<'_>,
|
|||
#[description = "Country: "]
|
||||
country: Option<String>,
|
||||
#[description = "Tag: "]
|
||||
tag: Option<String>
|
||||
tag: Option<String>,
|
||||
#[description = "Number of entries on page: "]
|
||||
entries: Option<NumberOfEntries>
|
||||
) -> Result<(), Error> {
|
||||
|
||||
let api = match &ctx.data().radio_browser {
|
||||
Some(v) => v.clone(),
|
||||
None => {
|
||||
let Ok(radio_browser) = RadioBrowserAPI::new().await else {
|
||||
let _ = ctx.reply_ephemeral("There was an error with radio API!").await;
|
||||
return Ok(())
|
||||
};
|
||||
radio_browser
|
||||
}
|
||||
};
|
||||
let api = &ctx.data().radio_browser;
|
||||
|
||||
let station_search_builder = api.get_stations()
|
||||
let entries = entries.unwrap_or(NumberOfEntries::Ten);
|
||||
|
||||
let search_builder = api.get_stations()
|
||||
.limit(entries.name())
|
||||
.name(name)
|
||||
.tag(tag.unwrap_or("".to_owned()))
|
||||
.country(country.unwrap_or("".to_owned()))
|
||||
.hidebroken(true);
|
||||
|
||||
paginate_search(ctx, station_search_builder);
|
||||
paginate_search_stations(&ctx, &search_builder, entries).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -1,83 +1,144 @@
|
|||
use poise::{ChoiceParameter, CreateReply};
|
||||
use radiobrowser::StationSearchBuilder;
|
||||
use serenity::{all::{CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage}, futures::SinkExt};
|
||||
use poise::CreateReply;
|
||||
use radiobrowser::{ApiStation, StationSearchBuilder};
|
||||
use regex::Regex;
|
||||
use serenity::all::{CreateActionRow, CreateButton, CreateEmbed, CreateEmbedFooter, CreateInteractionResponse, CreateInteractionResponseMessage};
|
||||
|
||||
use crate::types::Context;
|
||||
use crate::{commands::voice_types::NumberOfEntries, types::Context};
|
||||
|
||||
#[derive(ChoiceParameter)]
|
||||
pub enum WelcomeChoice {
|
||||
#[name = "5"]
|
||||
A,
|
||||
#[name = "10"]
|
||||
B,
|
||||
#[name = "15"]
|
||||
C
|
||||
}
|
||||
|
||||
pub async fn paginate_search(
|
||||
ctx: Context<'_>,
|
||||
search_builder: StationSearchBuilder,
|
||||
pub async fn paginate_search_stations(
|
||||
ctx: &Context<'_>,
|
||||
search_builder: &StationSearchBuilder,
|
||||
limit: NumberOfEntries
|
||||
) -> Result<(), serenity::Error> {
|
||||
// Define some unique identifiers for the navigation buttons
|
||||
let ctx_id = ctx.id();
|
||||
let prev_button_id = format!("{}prev", ctx_id);
|
||||
let next_button_id = format!("{}next", ctx_id);
|
||||
|
||||
let Ok(stations) = search_builder.send().await else {
|
||||
let _ = ctx.reply("Something went wrong, try searching again").await;
|
||||
let search_builder = search_builder;
|
||||
|
||||
let Ok(stations) = search_builder.clone().send().await else {
|
||||
ctx.reply("Something went wrong, try searching again").await?;
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
let mut page = 0;
|
||||
|
||||
let embed = create_station_list_embed(&stations, page);
|
||||
|
||||
// Send the embed with the first page as content
|
||||
let reply = {
|
||||
let components = CreateActionRow::Buttons(vec![
|
||||
CreateButton::new(&prev_button_id).emoji('◀'),
|
||||
CreateButton::new(&next_button_id).emoji('▶'),
|
||||
]);
|
||||
]);
|
||||
|
||||
CreateReply::default()
|
||||
.embed(CreateEmbed::new())
|
||||
CreateReply::default()
|
||||
.embed(embed)
|
||||
.components(vec![components])
|
||||
};
|
||||
};
|
||||
|
||||
ctx.send(reply).await?;
|
||||
ctx.send(reply).await?;
|
||||
|
||||
// Loop through incoming interactions with the navigation buttons
|
||||
let mut current_page = 0;
|
||||
while let Some(press) = serenity::collector::ComponentInteractionCollector::new(ctx)
|
||||
// Loop through incoming interactions with the navigation buttons
|
||||
let mut offset = 0;
|
||||
let limit_int = limit as u32;
|
||||
while let Some(press) = serenity::collector::ComponentInteractionCollector::new(ctx)
|
||||
// We defined our button IDs to start with `ctx_id`. If they don't, some other command's
|
||||
// button was pressed
|
||||
.filter(move |press| press.data.custom_id.starts_with(&ctx_id.to_string()))
|
||||
// Timeout when no navigation button has been pressed for 24 hours
|
||||
.timeout(std::time::Duration::from_secs(3600 * 24))
|
||||
.await
|
||||
{
|
||||
// Depending on which button was pressed, go to next or previous page
|
||||
{
|
||||
// Depending on which button was pressed, go to next or previous page
|
||||
if press.data.custom_id == next_button_id {
|
||||
current_page += 1;
|
||||
// TODO find a way to check end
|
||||
// if current_page >= pages.len() {
|
||||
// current_page = 0;
|
||||
// }
|
||||
offset += limit_int;
|
||||
page += 1;
|
||||
} else if press.data.custom_id == prev_button_id {
|
||||
// TODO find a way to get pages len
|
||||
// current_page = current_page.checked_sub(1).unwrap_or(pages.len() - 1);
|
||||
offset = if offset < limit_int { offset } else { offset - limit_int };
|
||||
page = if page == 0 { 0 } else { page - 1 };
|
||||
} else {
|
||||
// This is an unrelated button interaction
|
||||
continue;
|
||||
}
|
||||
|
||||
let Ok(mut stations) = search_builder.clone().offset(offset.to_string()).send().await else {
|
||||
ctx.reply("Something went wrong, try searching again").await?;
|
||||
return Ok(())
|
||||
};
|
||||
|
||||
if stations.is_empty() {
|
||||
offset = 0;
|
||||
page = 0;
|
||||
|
||||
let Ok(new_stations) = search_builder.clone().offset(offset.to_string()).send().await else {
|
||||
ctx.reply("Something went wrong, try searching again").await?;
|
||||
return Ok(())
|
||||
};
|
||||
stations = new_stations;
|
||||
}
|
||||
|
||||
let embed = create_station_list_embed(&stations, page);
|
||||
|
||||
// Update the message with the new page contents
|
||||
press
|
||||
.create_response(
|
||||
ctx.serenity_context(),
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(CreateEmbed::new()),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
.create_response(
|
||||
ctx.serenity_context(),
|
||||
CreateInteractionResponse::UpdateMessage(
|
||||
CreateInteractionResponseMessage::new()
|
||||
.embed(embed),
|
||||
),
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_station_list_embed(stations: &Vec<ApiStation>, page: u32) -> CreateEmbed {
|
||||
let result = CreateEmbed::new()
|
||||
.fields(
|
||||
stations.iter().map(|station| {
|
||||
(
|
||||
station.name.clone(),
|
||||
format!("Country: {} Stream: {}",
|
||||
station.country, station.url),
|
||||
false
|
||||
)
|
||||
})
|
||||
)
|
||||
.footer(CreateEmbedFooter::new(format!("Page: {}", page + 1)));
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
pub enum LinkString {
|
||||
Link,
|
||||
String
|
||||
}
|
||||
|
||||
|
||||
pub fn link_or_string(haystack: &str) -> LinkString {
|
||||
let Ok(re) = Regex::new(r"^https?://([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w \.-]*)*\/?$") else {
|
||||
panic!("Wrong regex expression!");
|
||||
};
|
||||
|
||||
return if re.is_match(haystack) { LinkString::Link } else { LinkString::String }
|
||||
}
|
||||
|
||||
pub fn parse_radio_autocomplete(haystack: &str) -> Option<(String, String, String)> {
|
||||
let Ok(re) = Regex::new(r"^Name: (.*) Country: (.*) Language: (.*)") else {
|
||||
panic!("Wrong regex expression!");
|
||||
};
|
||||
|
||||
let Some(captures) = re.captures(haystack) else {
|
||||
return None
|
||||
};
|
||||
|
||||
return Some((
|
||||
String::from(&captures[1]),
|
||||
String::from(&captures[2]),
|
||||
String::from(&captures[3])
|
||||
))
|
||||
}
|
||||
|
|
91
src/commands/voice/voice_types.rs
Normal file
91
src/commands/voice/voice_types.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use poise::ChoiceParameter;
|
||||
use radiobrowser::ApiStation;
|
||||
use serenity::{all::CreateEmbed, async_trait};
|
||||
|
||||
use crate::utils::utilities::hash_to_u32;
|
||||
|
||||
#[derive(ChoiceParameter, Debug)]
|
||||
pub enum NumberOfEntries {
|
||||
#[name = "5"]
|
||||
Five = 5,
|
||||
#[name = "10"]
|
||||
Ten = 10,
|
||||
#[name = "15"]
|
||||
Fifteen = 15
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait GenerateEmbed {
|
||||
async fn generate_embed(&self) -> CreateEmbed;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct SpotifyInfo {
|
||||
pub name: String,
|
||||
pub interpret: String,
|
||||
// client
|
||||
pub duration: String,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct GeneralInfo {
|
||||
pub name: String,
|
||||
pub site: String,
|
||||
// client
|
||||
pub duration: Option<String>
|
||||
}
|
||||
|
||||
|
||||
#[async_trait]
|
||||
impl GenerateEmbed for GeneralInfo {
|
||||
async fn generate_embed(&self) -> CreateEmbed {
|
||||
CreateEmbed::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GenerateEmbed for SpotifyInfo {
|
||||
async fn generate_embed(&self) -> CreateEmbed {
|
||||
CreateEmbed::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl GenerateEmbed for ApiStation {
|
||||
async fn generate_embed(&self) -> CreateEmbed {
|
||||
let embed= CreateEmbed::new()
|
||||
.title("Current station:")
|
||||
.thumbnail(&self.favicon)
|
||||
.field("Name:", &self.name, true)
|
||||
.field("Country:", &self.country, true)
|
||||
.field("Language:", &self.language, true)
|
||||
.field("Website:", &self.homepage, true)
|
||||
.field("Stream:", &self.url, true)
|
||||
.field("Tags:", &self.tags, false)
|
||||
.color(hash_to_u32(&self.stationuuid));
|
||||
|
||||
embed
|
||||
}
|
||||
}
|
||||
|
||||
// pub struct PlayingInfo<T> {
|
||||
// pub data: T,
|
||||
// pub create_embed: Box<dyn Fn(T) -> CreateEmbed + Send + Sync>
|
||||
// }
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub enum PlayingInfoType {
|
||||
SpotifyInfo(SpotifyInfo),
|
||||
RadioInfo(ApiStation),
|
||||
GeneralInfo(GeneralInfo)
|
||||
}
|
||||
|
||||
impl PlayingInfoType {
|
||||
pub async fn generate_embed(&self) -> CreateEmbed {
|
||||
match self {
|
||||
PlayingInfoType::SpotifyInfo(v) => v.generate_embed().await,
|
||||
PlayingInfoType::RadioInfo(v) => v.generate_embed().await,
|
||||
PlayingInfoType::GeneralInfo(v) => v.generate_embed().await
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
use poise::{CreateReply};
|
||||
use serenity::all::{ChannelId, CreateActionRow, CreateButton, CreateEmbed, CreateInteractionResponse, CreateInteractionResponseMessage, Embed, GuildId};
|
||||
use serenity::all::{ChannelId, GuildId};
|
||||
use serenity::async_trait;
|
||||
|
||||
use songbird::events::{Event, EventContext, EventHandler as VoiceEventHandler};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
use std::collections::HashMap;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
@ -6,6 +7,7 @@ use std::error;
|
|||
use std::env;
|
||||
|
||||
use serenity::async_trait;
|
||||
use serenity::futures::lock::Mutex;
|
||||
use serenity::prelude::GatewayIntents;
|
||||
use serenity::client::Context;
|
||||
use serenity::model::gateway::Ready;
|
||||
|
@ -19,6 +21,8 @@ use dotenv::dotenv;
|
|||
use songbird::SerenityInit;
|
||||
use tokio_cron_scheduler::{JobScheduler, Job};
|
||||
|
||||
use radiobrowser::RadioBrowserAPI;
|
||||
|
||||
mod message_handler;
|
||||
use message_handler::handle;
|
||||
|
||||
|
@ -90,6 +94,7 @@ async fn main() -> anyhow::Result<()> {
|
|||
commands::radio::radio(),
|
||||
commands::general_player::play(),
|
||||
commands::player_common::disconnect(),
|
||||
commands::player_common::playing()
|
||||
],
|
||||
prefix_options: poise::PrefixFrameworkOptions {
|
||||
prefix: Some("/".into()),
|
||||
|
@ -131,7 +136,8 @@ async fn main() -> anyhow::Result<()> {
|
|||
|
||||
Ok(Data {
|
||||
http_client: HttpClient::new(),
|
||||
radio_browser: RadioBrowserAPI::new().await.ok()
|
||||
radio_browser: RadioBrowserAPI::new().await?,
|
||||
playing_info: Mutex::new(HashMap::new())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
30
src/types.rs
30
src/types.rs
|
@ -1,23 +1,37 @@
|
|||
// pub struct Data {}
|
||||
|
||||
use std::{ffi::OsString, path::PathBuf};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use poise::CreateReply;
|
||||
use radiobrowser::RadioBrowserAPI;
|
||||
use reqwest::Client as HttpClient;
|
||||
use serenity::{all::{futures::lock::Mutex, GuildId}, async_trait};
|
||||
|
||||
use crate::commands::voice_types::PlayingInfoType;
|
||||
|
||||
// TODO: this should be probably expanded also to be used by multiple servers and commands
|
||||
// radio and spotify commands always create new HttpClient
|
||||
|
||||
pub struct Data {
|
||||
pub http_client: HttpClient,
|
||||
pub radio_browser: Option<RadioBrowserAPI>,
|
||||
pub radio_browser: RadioBrowserAPI,
|
||||
pub playing_info: Mutex<HashMap<GuildId, PlayingInfoType>>
|
||||
}
|
||||
|
||||
pub type Error = Box<dyn std::error::Error + Send + Sync>;
|
||||
// replace () with Data if you ever need to store some additional data
|
||||
pub type Context<'a> = poise::Context<'a, Data, Error>;
|
||||
|
||||
pub struct Track {
|
||||
pub path: PathBuf,
|
||||
pub name: OsString,
|
||||
/// Trait to extend `Context` with additional methods.
|
||||
#[async_trait]
|
||||
pub trait ContextExt {
|
||||
async fn reply_ephemeral(&self, content: &str) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ContextExt for Context<'_> {
|
||||
async fn reply_ephemeral(&self, content: &str) -> anyhow::Result<()> {
|
||||
self.send(CreateReply::default()
|
||||
.ephemeral(true)
|
||||
.content(content)
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
pub use utilities::*;
|
||||
|
||||
pub mod debug;
|
||||
pub mod utilities;
|
||||
pub mod gifs;
|
||||
pub mod poise_context_extension;
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
use poise::CreateReply;
|
||||
use serenity::async_trait;
|
||||
|
||||
use crate::types::Context;
|
||||
|
||||
|
||||
/// Trait to extend `Context` with additional methods.
|
||||
#[async_trait]
|
||||
pub trait ContextExt {
|
||||
async fn reply_ephemeral(&self, content: &str) -> anyhow::Result<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ContextExt for Context<'_> {
|
||||
async fn reply_ephemeral(&self, content: &str) -> anyhow::Result<()> {
|
||||
self.send(CreateReply::default()
|
||||
.ephemeral(true)
|
||||
.content(content)
|
||||
).await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -1,7 +1,13 @@
|
|||
use std::{fs, io, path::Path, sync::Arc, vec};
|
||||
use std::{fs, hash::{DefaultHasher, Hash, Hasher}, io, path::Path, sync::Arc, vec};
|
||||
|
||||
use serenity::{all::{ChannelId, ChannelType, CreateMessage, GuildId, GuildRef, Message}, http::Http};
|
||||
|
||||
use poise::CreateReply;
|
||||
use serenity::async_trait;
|
||||
|
||||
use crate::types::Context;
|
||||
|
||||
|
||||
pub async fn get_system_channel(guild_id: GuildId, http: &Http) -> anyhow::Result<ChannelId> {
|
||||
use anyhow::Context;
|
||||
return http.get_guild(guild_id).await?.system_channel_id
|
||||
|
@ -58,3 +64,10 @@ pub fn get_local_songs(partial: &str) -> io::Result<Vec<String>> {
|
|||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn hash_to_u32<T: Hash>(from: &T) -> u32 {
|
||||
let mut hasher = DefaultHasher::new();
|
||||
from.hash(&mut hasher);
|
||||
let hash_val = hasher.finish();
|
||||
(hash_val & 0xFFFFFF) as u32
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue