2020-04-29 22:09:31 +00:00
#[ macro_use ]
extern crate lazy_static ;
2020-05-20 00:46:41 +00:00
mod guilddata ;
mod sound ;
mod error ;
use sound ::Sound ;
use guilddata ::GuildData ;
2020-04-13 23:22:31 +00:00
use serenity ::{
2020-04-26 00:51:51 +00:00
client ::{
2020-04-26 22:12:31 +00:00
bridge ::{
gateway ::GatewayIntents ,
voice ::ClientVoiceManager ,
} ,
Client , Context ,
2020-04-26 00:51:51 +00:00
} ,
framework ::standard ::{
2020-05-20 16:54:46 +00:00
Args , CommandResult , CheckResult , DispatchError , StandardFramework , Reason ,
2020-04-26 22:12:31 +00:00
macros ::{
2020-05-20 16:54:46 +00:00
command , group , check , hook ,
2020-04-26 22:12:31 +00:00
}
2020-04-26 00:51:51 +00:00
} ,
model ::{
2020-05-19 20:03:07 +00:00
channel ::Message ,
2020-04-30 00:21:34 +00:00
id ::{
GuildId ,
RoleId ,
2020-05-20 14:33:28 +00:00
UserId ,
2020-04-30 00:21:34 +00:00
} ,
2020-05-19 20:03:07 +00:00
voice ::VoiceState ,
2020-04-26 00:51:51 +00:00
} ,
2020-04-28 00:24:06 +00:00
prelude ::{
Mutex as SerenityMutex ,
*
} ,
2020-05-20 00:46:41 +00:00
voice ::Handler as VoiceHandler ,
2020-04-13 23:22:31 +00:00
} ;
use sqlx ::{
Pool ,
2020-04-26 22:12:31 +00:00
mysql ::{
MySqlPool ,
MySqlConnection ,
}
2020-04-13 23:22:31 +00:00
} ;
use dotenv ::dotenv ;
2020-04-26 22:12:31 +00:00
use tokio ::{
2020-05-19 15:29:57 +00:00
sync ::{
Mutex ,
MutexGuard
} ,
2020-05-18 22:44:51 +00:00
time ,
2020-04-26 22:12:31 +00:00
} ;
use std ::{
2020-05-19 21:05:10 +00:00
collections ::{
HashMap ,
HashSet ,
} ,
2020-04-26 22:12:31 +00:00
env ,
sync ::Arc ,
2020-04-29 01:20:52 +00:00
time ::Duration ,
2020-04-26 22:12:31 +00:00
} ;
2020-05-20 16:54:46 +00:00
use serenity ::framework ::standard ::CommandError ;
2020-04-29 01:20:52 +00:00
2020-04-13 23:22:31 +00:00
struct SQLPool ;
impl TypeMapKey for SQLPool {
type Value = Pool < MySqlConnection > ;
}
2020-04-26 00:51:51 +00:00
struct VoiceManager ;
impl TypeMapKey for VoiceManager {
2020-04-28 00:24:06 +00:00
type Value = Arc < SerenityMutex < ClientVoiceManager > > ;
2020-04-26 00:51:51 +00:00
}
2020-05-18 22:44:51 +00:00
struct VoiceGuilds ;
impl TypeMapKey for VoiceGuilds {
2020-05-19 21:05:10 +00:00
type Value = Arc < Mutex < HashMap < GuildId , u8 > > > ;
2020-05-18 22:44:51 +00:00
}
2020-04-28 00:24:06 +00:00
static THEME_COLOR : u32 = 0x00e0f3 ;
2020-04-29 22:09:31 +00:00
lazy_static! {
static ref MAX_SOUNDS : u32 = {
dotenv ( ) . unwrap ( ) ;
env ::var ( " MAX_SOUNDS " ) . unwrap ( ) . parse ::< u32 > ( ) . unwrap ( )
} ;
2020-04-30 00:21:34 +00:00
static ref PATREON_GUILD : u64 = {
dotenv ( ) . unwrap ( ) ;
env ::var ( " PATREON_GUILD " ) . unwrap ( ) . parse ::< u64 > ( ) . unwrap ( )
} ;
static ref PATREON_ROLE : u64 = {
dotenv ( ) . unwrap ( ) ;
env ::var ( " PATREON_ROLE " ) . unwrap ( ) . parse ::< u64 > ( ) . unwrap ( )
} ;
2020-05-20 00:23:14 +00:00
static ref DISCONNECT_CYCLES : u8 = {
dotenv ( ) . unwrap ( ) ;
2020-05-20 00:46:41 +00:00
env ::var ( " DISCONNECT_CYCLES " ) . unwrap_or ( " 2 " . to_string ( ) ) . parse ::< u8 > ( ) . unwrap ( )
2020-05-20 00:23:14 +00:00
} ;
2020-04-29 22:09:31 +00:00
}
2020-04-13 23:22:31 +00:00
#[ group ]
2020-05-19 20:03:07 +00:00
#[ commands(info, help, list_sounds, change_public, search_sounds, show_popular_sounds, show_random_sounds, set_greet_sound) ]
2020-04-30 22:57:22 +00:00
struct AllUsers ;
#[ group ]
2020-05-22 14:28:25 +00:00
#[ commands(play, upload_new_sound, change_volume, delete_sound, stop_playing) ]
2020-04-30 22:57:22 +00:00
#[ checks(role_check) ]
struct RoleManagedUsers ;
#[ group ]
2020-05-29 13:35:24 +00:00
#[ commands(change_prefix, set_allowed_roles, allow_greet_sounds) ]
2020-04-30 22:57:22 +00:00
#[ checks(permission_check) ]
struct PermissionManagedUsers ;
#[ check ]
#[ name( " role_check " ) ]
2020-05-16 23:40:35 +00:00
async fn role_check ( ctx : & Context , msg : & Message , _args : & mut Args ) -> CheckResult {
2020-04-30 22:57:22 +00:00
2020-05-20 16:54:46 +00:00
async fn check_for_roles ( ctx : & & Context , msg : & & Message ) -> CheckResult {
2020-04-30 22:57:22 +00:00
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-05-20 18:08:59 +00:00
let guild_opt = msg . guild ( & ctx ) . await ;
2020-04-30 22:57:22 +00:00
2020-05-20 18:08:59 +00:00
match guild_opt {
Some ( guild ) = > {
let member_res = guild . member ( * ctx , msg . author . id ) . await ;
2020-04-30 22:57:22 +00:00
2020-05-20 18:08:59 +00:00
match member_res {
Ok ( member ) = > {
let user_roles : String = member . roles
. iter ( )
. map ( | r | ( * r . as_u64 ( ) ) . to_string ( ) )
. collect ::< Vec < String > > ( )
. join ( " , " ) ;
2020-04-30 22:57:22 +00:00
2020-05-20 18:08:59 +00:00
println! ( " {} " , user_roles ) ;
let guild_id = * msg . guild_id . unwrap ( ) . as_u64 ( ) ;
let role_res = sqlx ::query! (
"
2020-04-30 22:57:22 +00:00
SELECT COUNT ( 1 ) as count
FROM roles
2020-05-20 18:08:59 +00:00
WHERE
( guild_id = ? AND role IN ( ? ) ) OR
( role = ? )
" ,
guild_id , user_roles , guild_id
)
. fetch_one ( & pool ) . await ;
2020-04-30 22:57:22 +00:00
2020-05-20 18:08:59 +00:00
match role_res {
Ok ( role_count ) = > {
if role_count . count > 0 {
CheckResult ::Success
}
else {
CheckResult ::Failure ( Reason ::User ( " User has not got a sufficient role " . to_string ( ) ) )
}
}
Err ( _ ) = > {
CheckResult ::Failure ( Reason ::User ( " User has not got a sufficient role " . to_string ( ) ) )
}
2020-05-20 16:54:46 +00:00
}
}
Err ( _ ) = > {
2020-05-20 18:08:59 +00:00
CheckResult ::Failure ( Reason ::User ( " Unexpected error looking up user roles " . to_string ( ) ) )
2020-05-20 16:54:46 +00:00
}
2020-04-30 22:57:22 +00:00
}
}
None = > {
2020-05-20 18:08:59 +00:00
CheckResult ::Failure ( Reason ::User ( " Unexpected error looking up guild " . to_string ( ) ) )
2020-04-30 22:57:22 +00:00
}
}
}
2020-05-20 16:54:46 +00:00
if perform_permission_check ( ctx , & msg ) . await . is_success ( ) {
2020-04-30 22:57:22 +00:00
CheckResult ::Success
}
else {
2020-05-20 16:54:46 +00:00
check_for_roles ( & ctx , & msg ) . await
2020-04-30 22:57:22 +00:00
}
}
#[ check ]
#[ name( " permission_check " ) ]
2020-05-16 23:40:35 +00:00
async fn permission_check ( ctx : & Context , msg : & Message , _args : & mut Args ) -> CheckResult {
2020-04-30 22:57:22 +00:00
perform_permission_check ( ctx , & msg ) . await
}
async fn perform_permission_check ( ctx : & Context , msg : & & Message ) -> CheckResult {
if let Some ( guild_id ) = msg . guild_id {
if let Ok ( member ) = guild_id . member ( ctx . clone ( ) , msg . author . id ) . await {
if let Ok ( perms ) = member . permissions ( ctx ) . await {
if perms . manage_guild ( ) {
return CheckResult ::Success
}
}
}
}
CheckResult ::Failure ( Reason ::User ( String ::from ( " User needs `Manage Guild` permission " ) ) )
}
2020-04-26 00:51:51 +00:00
2020-04-13 23:22:31 +00:00
// create event handler for bot
struct Handler ;
2020-04-26 22:12:31 +00:00
#[ serenity::async_trait ]
2020-05-19 20:03:07 +00:00
impl EventHandler for Handler {
async fn voice_state_update ( & self , ctx : Context , guild_id_opt : Option < GuildId > , old : Option < VoiceState > , new : VoiceState ) {
if let ( Some ( guild_id ) , Some ( user_channel ) ) = ( guild_id_opt , new . channel_id ) {
if old . is_none ( ) {
2020-05-29 13:35:24 +00:00
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let guild_data_opt = GuildData ::get_from_id ( * guild_id . as_u64 ( ) , pool . clone ( ) ) . await ;
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
if let Some ( guild_data ) = guild_data_opt {
if guild_data . allow_greets {
let join_id_res = sqlx ::query! (
2020-05-19 20:03:07 +00:00
"
2020-05-29 13:35:24 +00:00
SELECT join_sound_id
FROM users
WHERE user = ? AND join_sound_id IS NOT NULL
2020-05-19 20:03:07 +00:00
" ,
2020-05-29 13:35:24 +00:00
new . user_id . as_u64 ( )
2020-05-19 20:03:07 +00:00
)
. fetch_one ( & pool )
2020-05-29 13:35:24 +00:00
. await ;
match join_id_res {
Ok ( join_id_record ) = > {
let join_id = join_id_record . join_sound_id ;
let mut sound = sqlx ::query_as_unchecked! (
Sound ,
"
SELECT name , id , plays , public , server_id , uploader_id
FROM sounds
WHERE id = ?
" ,
join_id
)
. fetch_one ( & pool )
. await . unwrap ( ) ;
let voice_manager_lock = ctx . data . read ( ) . await
. get ::< VoiceManager > ( ) . cloned ( ) . expect ( " Could not get VoiceManager from data " ) ;
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
let mut voice_manager = voice_manager_lock . lock ( ) . await ;
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
let voice_guilds_lock = ctx . data . read ( ) . await
. get ::< VoiceGuilds > ( ) . cloned ( ) . expect ( " Could not get VoiceGuilds from data " ) ;
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
let voice_guilds = voice_guilds_lock . lock ( ) . await ;
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
let guild_data = GuildData ::get_from_id ( * guild_id . as_u64 ( ) , pool . clone ( ) ) . await . unwrap ( ) ;
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
if let Some ( handler ) = voice_manager . join ( guild_id , user_channel ) {
let _audio = play_audio ( & mut sound , guild_data , handler , voice_guilds , pool ) . await ;
}
}
2020-05-19 20:03:07 +00:00
2020-05-29 13:35:24 +00:00
Err ( _ ) = > { }
2020-05-19 20:03:07 +00:00
}
}
}
}
}
}
}
2020-05-19 21:05:10 +00:00
async fn play_audio ( sound : & mut Sound , guild : GuildData , handler : & mut VoiceHandler , mut voice_guilds : MutexGuard < '_ , HashMap < GuildId , u8 > > , pool : MySqlPool )
2020-05-19 20:03:07 +00:00
-> Result < ( ) , Box < dyn std ::error ::Error > > {
2020-05-20 16:54:46 +00:00
let audio = handler . play_only ( sound . store_sound_source ( pool . clone ( ) ) . await ? ) ;
2020-05-19 20:03:07 +00:00
{
let mut locked = audio . lock ( ) . await ;
locked . volume ( guild . volume as f32 / 100.0 ) ;
}
sound . plays + = 1 ;
sound . commit ( pool ) . await ? ;
2020-05-20 00:23:14 +00:00
voice_guilds . insert ( GuildId ( guild . id ) , * DISCONNECT_CYCLES ) ;
2020-05-19 20:03:07 +00:00
Ok ( ( ) )
}
2020-04-13 23:22:31 +00:00
2020-05-20 16:54:46 +00:00
#[ hook ]
async fn log_errors ( _ : & Context , m : & Message , cmd_name : & str , error : Result < ( ) , CommandError > ) {
if let Err ( e ) = error {
println! ( " Error in command {} ( {} ): {:?} " , cmd_name , m . content , e ) ;
}
}
#[ hook ]
async fn dispatch_error_hook ( ctx : & Context , msg : & Message , error : DispatchError ) {
match error {
DispatchError ::CheckFailed ( _f , reason ) = > {
if let Reason ::User ( description ) = reason {
let _ = msg . reply ( ctx , format! ( " You cannot do this command: {} " , description ) ) . await ;
}
}
_ = > { }
}
}
2020-04-13 23:22:31 +00:00
// entry point
2020-04-26 00:51:51 +00:00
#[ tokio::main ]
2020-04-26 22:12:31 +00:00
async fn main ( ) -> Result < ( ) , Box < dyn std ::error ::Error > > {
2020-05-20 16:54:46 +00:00
2020-04-26 22:12:31 +00:00
dotenv ( ) ? ;
2020-04-13 23:22:31 +00:00
2020-05-19 21:05:10 +00:00
let voice_guilds = Arc ::new ( Mutex ::new ( HashMap ::new ( ) ) ) ;
2020-05-18 22:44:51 +00:00
2020-04-26 00:51:51 +00:00
let framework = StandardFramework ::new ( )
2020-05-18 22:44:51 +00:00
. configure ( | c | c
. dynamic_prefix ( | ctx , msg | Box ::pin ( async move {
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-04-28 00:24:06 +00:00
2020-05-29 15:13:20 +00:00
let guild = match msg . guild ( & ctx . cache ) . await {
Some ( guild ) = > guild ,
2020-05-18 22:44:51 +00:00
2020-05-29 15:13:20 +00:00
None = > {
return Some ( String ::from ( " ? " ) ) ;
}
} ;
match GuildData ::get_from_id ( * msg . guild_id . unwrap ( ) . as_u64 ( ) , pool . clone ( ) ) . await {
Some ( mut guild_data ) = > {
let name = Some ( guild . name ) ;
if guild_data . name ! = name {
guild_data . name = name ;
guild_data . commit ( pool ) . await . unwrap ( ) ;
}
Some ( guild_data . prefix )
} ,
None = > {
GuildData ::create_from_guild ( guild , pool ) . await . unwrap ( ) ;
Some ( String ::from ( " ? " ) )
}
2020-05-18 22:44:51 +00:00
}
} ) )
. allow_dm ( false )
. ignore_bots ( true )
. ignore_webhooks ( true )
2020-05-20 14:33:28 +00:00
. on_mention ( env ::var ( " CLIENT_ID " )
. and_then ( | val | Ok ( UserId ( val . parse ::< u64 > ( ) . expect ( " CLIENT_ID not valid " ) ) ) ) . ok ( ) )
2020-05-18 22:44:51 +00:00
)
2020-04-30 22:57:22 +00:00
. group ( & ALLUSERS_GROUP )
. group ( & ROLEMANAGEDUSERS_GROUP )
2020-05-20 16:54:46 +00:00
. group ( & PERMISSIONMANAGEDUSERS_GROUP )
. after ( log_errors )
. on_dispatch_error ( dispatch_error_hook ) ;
2020-04-13 23:22:31 +00:00
2020-05-16 23:40:35 +00:00
let mut client = Client ::new ( & env ::var ( " DISCORD_TOKEN " ) . expect ( " Missing token from environment " ) )
. intents ( GatewayIntents ::GUILD_VOICE_STATES | GatewayIntents ::GUILD_MESSAGES | GatewayIntents ::GUILDS )
. framework ( framework )
. event_handler ( Handler )
. await . expect ( " Error occurred creating client " ) ;
2020-04-13 23:22:31 +00:00
2020-04-26 00:51:51 +00:00
{
2020-04-26 22:12:31 +00:00
let pool = MySqlPool ::new ( & env ::var ( " DATABASE_URL " ) . expect ( " No database URL provided " ) ) . await . unwrap ( ) ;
2020-04-13 23:22:31 +00:00
2020-04-26 00:51:51 +00:00
let mut data = client . data . write ( ) . await ;
2020-04-13 23:22:31 +00:00
data . insert ::< SQLPool > ( pool ) ;
2020-04-26 00:51:51 +00:00
data . insert ::< VoiceManager > ( Arc ::clone ( & client . voice_manager ) ) ;
2020-05-18 22:44:51 +00:00
data . insert ::< VoiceGuilds > ( voice_guilds . clone ( ) ) ;
2020-04-13 23:22:31 +00:00
}
2020-05-18 22:44:51 +00:00
let cvm = Arc ::clone ( & client . voice_manager ) ;
2020-05-20 00:23:14 +00:00
let disconnect_cycle_delay = env ::var ( " DISCONNECT_CYCLE_DELAY " )
. unwrap_or ( " 300 " . to_string ( ) )
. parse ::< u64 > ( ) ? ;
2020-05-18 22:44:51 +00:00
// select on the client and client auto disconnector (when the client terminates, terminate the disconnector
tokio ::select! {
2020-05-20 19:13:38 +00:00
_ = client . start_autosharded ( ) = > { }
2020-05-20 00:23:14 +00:00
_ = disconnect_from_inactive ( cvm , voice_guilds , disconnect_cycle_delay ) = > { }
2020-05-18 22:44:51 +00:00
} ;
2020-04-26 22:12:31 +00:00
Ok ( ( ) )
2020-04-26 00:51:51 +00:00
}
2020-05-20 00:23:14 +00:00
async fn disconnect_from_inactive ( voice_manager_mutex : Arc < SerenityMutex < ClientVoiceManager > > , voice_guilds : Arc < Mutex < HashMap < GuildId , u8 > > > , wait_time : u64 ) {
2020-05-18 22:44:51 +00:00
loop {
2020-05-20 00:23:14 +00:00
time ::delay_for ( Duration ::from_secs ( wait_time ) ) . await ;
2020-05-19 20:03:07 +00:00
2020-05-18 22:44:51 +00:00
let mut voice_guilds_acquired = voice_guilds . lock ( ) . await ;
let mut voice_manager = voice_manager_mutex . lock ( ) . await ;
let mut to_remove = HashSet ::new ( ) ;
2020-05-19 21:05:10 +00:00
for ( guild , ticker ) in voice_guilds_acquired . iter_mut ( ) {
if * ticker = = 0 {
let manager_opt = voice_manager . get_mut ( guild ) ;
2020-05-19 20:03:07 +00:00
2020-05-19 21:05:10 +00:00
if let Some ( manager ) = manager_opt {
2020-05-18 22:44:51 +00:00
manager . leave ( ) ;
2020-05-19 21:05:10 +00:00
}
2020-05-22 14:28:25 +00:00
to_remove . insert ( guild . clone ( ) ) ;
2020-05-18 22:44:51 +00:00
}
2020-05-19 20:03:07 +00:00
else {
2020-05-19 21:05:10 +00:00
* ticker - = 1 ;
2020-05-19 20:03:07 +00:00
}
2020-05-18 22:44:51 +00:00
}
for val in to_remove . iter ( ) {
voice_guilds_acquired . remove ( val ) ;
}
}
}
2020-04-28 16:17:32 +00:00
#[ command( " play " ) ]
#[ aliases( " p " ) ]
2020-05-16 23:40:35 +00:00
async fn play ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
2020-05-19 15:29:57 +00:00
2020-04-26 22:12:31 +00:00
let guild = match msg . guild ( & ctx . cache ) . await {
Some ( guild ) = > guild ,
2020-04-26 00:51:51 +00:00
2020-04-26 22:12:31 +00:00
None = > {
return Ok ( ( ) ) ;
2020-04-26 00:51:51 +00:00
}
2020-04-26 22:12:31 +00:00
} ;
2020-05-16 23:40:35 +00:00
let guild_id = guild . id ;
2020-04-26 00:51:51 +00:00
2020-05-16 23:40:35 +00:00
let channel_to_join = guild
2020-04-26 22:12:31 +00:00
. voice_states . get ( & msg . author . id )
. and_then ( | voice_state | voice_state . channel_id ) ;
2020-04-26 00:51:51 +00:00
2020-04-26 22:12:31 +00:00
match channel_to_join {
Some ( user_channel ) = > {
let search_term = args . rest ( ) ;
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-05-16 23:40:35 +00:00
let mut sound_vec = Sound ::search_for_sound (
2020-04-28 00:24:06 +00:00
search_term ,
* guild_id . as_u64 ( ) ,
* msg . author . id . as_u64 ( ) ,
2020-05-16 23:40:35 +00:00
pool . clone ( ) ,
2020-05-16 20:28:47 +00:00
true ) . await ? ;
2020-05-16 23:40:35 +00:00
let sound_res = sound_vec . first_mut ( ) ;
2020-04-26 22:12:31 +00:00
2020-04-28 00:24:06 +00:00
match sound_res {
2020-05-16 20:28:47 +00:00
Some ( sound ) = > {
2020-04-28 00:24:06 +00:00
let voice_manager_lock = ctx . data . read ( ) . await
. get ::< VoiceManager > ( ) . cloned ( ) . expect ( " Could not get VoiceManager from data " ) ;
2020-04-26 22:12:31 +00:00
2020-04-28 00:24:06 +00:00
let mut voice_manager = voice_manager_lock . lock ( ) . await ;
2020-04-26 22:12:31 +00:00
2020-05-18 22:44:51 +00:00
let voice_guilds_lock = ctx . data . read ( ) . await
. get ::< VoiceGuilds > ( ) . cloned ( ) . expect ( " Could not get VoiceGuilds from data " ) ;
2020-05-19 15:29:57 +00:00
let voice_guilds = voice_guilds_lock . lock ( ) . await ;
let guild_data = GuildData ::get_from_id ( * guild_id . as_u64 ( ) , pool . clone ( ) ) . await . unwrap ( ) ;
2020-05-18 22:44:51 +00:00
2020-05-19 21:05:10 +00:00
match voice_manager . join ( guild_id , user_channel ) {
2020-04-26 22:12:31 +00:00
Some ( handler ) = > {
2020-05-19 15:29:57 +00:00
play_audio ( sound , guild_data , handler , voice_guilds , pool ) . await ? ;
2020-04-26 22:12:31 +00:00
}
None = > {
2020-05-19 21:05:10 +00:00
msg . channel_id . say ( & ctx , " Failed to join channel " ) . await ? ;
2020-04-26 22:12:31 +00:00
}
2020-05-19 21:05:10 +00:00
} ;
2020-04-28 00:24:06 +00:00
}
2020-05-16 20:28:47 +00:00
None = > {
2020-04-28 00:24:06 +00:00
msg . channel_id . say ( & ctx , " Couldn't find sound by term provided " ) . await ? ;
2020-04-26 22:12:31 +00:00
}
}
}
None = > {
msg . channel_id . say ( & ctx , " You are not in a voice chat! " ) . await ? ;
2020-04-26 00:51:51 +00:00
}
}
2020-04-26 22:12:31 +00:00
Ok ( ( ) )
2020-04-13 23:22:31 +00:00
}
2020-04-28 00:24:06 +00:00
#[ command ]
2020-05-16 23:40:35 +00:00
async fn help ( ctx : & Context , msg : & Message , _args : Args ) -> CommandResult {
2020-04-28 00:24:06 +00:00
msg . channel_id . send_message ( & ctx , | m | m
. embed ( | e | e
. title ( " Help " )
. color ( THEME_COLOR )
. description ( " Please visit our website at https://soundfx.jellywx.com/help " ) ) ) . await ? ;
Ok ( ( ) )
}
#[ command ]
2020-05-16 23:40:35 +00:00
async fn info ( ctx : & Context , msg : & Message , _args : Args ) -> CommandResult {
2020-04-28 00:24:06 +00:00
msg . channel_id . send_message ( & ctx , | m | m
. embed ( | e | e
. title ( " Info " )
. color ( THEME_COLOR )
2020-05-20 19:13:38 +00:00
. description ( format! ( " Default prefix: `?`
2020-04-28 00:24:06 +00:00
2020-05-20 19:13:38 +00:00
Reset prefix : ` < @ { 0 } > prefix ? `
2020-04-28 00:24:06 +00:00
2020-05-20 19:13:38 +00:00
Invite me : https ://discordapp.com/oauth2/authorize?client_id={0}&scope=bot&permissions=36703232
2020-04-28 00:24:06 +00:00
* * Welcome to SoundFX ! * *
Developer : < @ 203532103185465344 >
Find me on https ://discord.jellywx.com/ and on https://github.com/JellyWX :)
* * An online dashboard is available ! * * Visit https ://soundfx.jellywx.com/dashboard
2020-05-20 19:13:38 +00:00
There is a maximum sound limit per user . This can be removed by donating at https ://patreon.com/jellywx", env::var("CLIENT_ID").unwrap())))).await?;
2020-04-28 00:24:06 +00:00
Ok ( ( ) )
}
2020-04-28 16:17:32 +00:00
#[ command( " volume " ) ]
2020-05-16 23:40:35 +00:00
async fn change_volume ( ctx : & Context , msg : & Message , mut args : Args ) -> CommandResult {
2020-04-28 16:17:32 +00:00
let guild = match msg . guild ( & ctx . cache ) . await {
Some ( guild ) = > guild ,
None = > {
return Ok ( ( ) ) ;
}
} ;
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-05-16 23:40:35 +00:00
let mut guild_data_opt = GuildData ::get_from_id ( * guild . id . as_u64 ( ) , pool . clone ( ) ) . await ;
2020-04-28 16:17:32 +00:00
let mut guild_data = guild_data_opt . unwrap ( ) ;
if args . len ( ) = = 1 {
match args . single ::< u8 > ( ) {
Ok ( volume ) = > {
guild_data . volume = volume ;
guild_data . commit ( pool ) . await ? ;
msg . channel_id . say ( & ctx , format! ( " Volume changed to {} % " , volume ) ) . await ? ;
}
Err ( _ ) = > {
msg . channel_id . say ( & ctx ,
format! ( " Current server volume: {vol} %. Change the volume with ``` {prefix} volume <new volume>``` " ,
vol = guild_data . volume , prefix = guild_data . prefix ) ) . await ? ;
}
}
}
else {
msg . channel_id . say ( & ctx ,
format! ( " Current server volume: {vol} %. Change the volume with ``` {prefix} volume <new volume>``` " ,
vol = guild_data . volume , prefix = guild_data . prefix ) ) . await ? ;
}
Ok ( ( ) )
}
2020-04-28 16:40:26 +00:00
#[ command( " prefix " ) ]
2020-05-16 23:40:35 +00:00
async fn change_prefix ( ctx : & Context , msg : & Message , mut args : Args ) -> CommandResult {
2020-04-28 16:40:26 +00:00
let guild = match msg . guild ( & ctx . cache ) . await {
Some ( guild ) = > guild ,
None = > {
return Ok ( ( ) ) ;
}
} ;
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let mut guild_data ;
{
2020-05-16 23:40:35 +00:00
let mut guild_data_opt = GuildData ::get_from_id ( * guild . id . as_u64 ( ) , pool . clone ( ) ) . await ;
2020-04-28 16:40:26 +00:00
guild_data = guild_data_opt . unwrap ( ) ;
}
if args . len ( ) = = 1 {
match args . single ::< String > ( ) {
Ok ( prefix ) = > {
if prefix . len ( ) < = 5 {
guild_data . prefix = prefix ;
guild_data . commit ( pool ) . await ? ;
msg . channel_id . say ( & ctx , format! ( " Prefix changed to ` {} ` " , guild_data . prefix ) ) . await ? ;
}
else {
msg . channel_id . say ( & ctx , " Prefix must be less than 5 characters long " ) . await ? ;
}
}
Err ( _ ) = > {
msg . channel_id . say ( & ctx , format! ( " Usage: ` {prefix} prefix <new prefix>` " , prefix = guild_data . prefix ) ) . await ? ;
}
}
}
else {
msg . channel_id . say ( & ctx , format! ( " Usage: ` {prefix} prefix <new prefix>` " , prefix = guild_data . prefix ) ) . await ? ;
}
Ok ( ( ) )
}
2020-04-29 01:20:52 +00:00
#[ command( " upload " ) ]
2020-05-16 23:40:35 +00:00
async fn upload_new_sound ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
2020-05-15 21:04:22 +00:00
let new_name = args . rest ( ) . to_string ( ) ;
2020-04-29 01:20:52 +00:00
if ! new_name . is_empty ( ) & & new_name . len ( ) < = 20 {
2020-04-29 22:09:31 +00:00
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-05-15 21:04:22 +00:00
// need to check the name is not currently in use by the user
let count_name = Sound ::count_named_user_sounds ( * msg . author . id . as_u64 ( ) , & new_name , pool . clone ( ) ) . await ? ;
if count_name > 0 {
msg . channel_id . say ( & ctx , " You are already using that name. Please choose a unique name for your upload. " ) . await ? ;
}
2020-04-29 01:20:52 +00:00
2020-05-15 21:04:22 +00:00
else {
// need to check how many sounds user currently has
let count = Sound ::count_user_sounds ( * msg . author . id . as_u64 ( ) , pool . clone ( ) ) . await ? ;
let mut permit_upload = true ;
2020-04-29 22:09:31 +00:00
2020-05-15 21:04:22 +00:00
// need to check if user is patreon or nah
if count > = * MAX_SOUNDS {
2020-05-16 23:40:35 +00:00
let patreon_guild_member = GuildId ( * PATREON_GUILD ) . member ( ctx , msg . author . id ) . await ? ;
2020-05-15 21:04:22 +00:00
if patreon_guild_member . roles . contains ( & RoleId ( * PATREON_ROLE ) ) {
permit_upload = true ;
}
else {
permit_upload = false ;
}
2020-04-30 00:21:34 +00:00
}
2020-04-29 01:20:52 +00:00
2020-05-15 21:04:22 +00:00
if permit_upload {
msg . channel_id . say ( & ctx , " Please now upload an audio file under 1MB in size (larger files will be automatically trimmed): " ) . await ? ;
let reply = msg . channel_id . await_reply ( & ctx )
. author_id ( msg . author . id )
. timeout ( Duration ::from_secs ( 30 ) )
. await ;
match reply {
Some ( reply_msg ) = > {
if reply_msg . attachments . len ( ) = = 1 {
match Sound ::create_anon (
& new_name ,
& reply_msg . attachments [ 0 ] . url ,
* msg . guild_id . unwrap ( ) . as_u64 ( ) ,
* msg . author . id . as_u64 ( ) ,
pool ) . await {
Ok ( _ ) = > {
msg . channel_id . say ( & ctx , " Sound has been uploaded " ) . await ? ;
}
2020-04-30 00:21:34 +00:00
2020-05-15 21:04:22 +00:00
Err ( _ ) = > {
msg . channel_id . say ( & ctx , " Sound failed to upload. " ) . await ? ;
}
2020-04-30 00:21:34 +00:00
}
2020-05-15 21:04:22 +00:00
} else {
msg . channel_id . say ( & ctx , " Please upload 1 attachment following your upload command. Aborted " ) . await ? ;
2020-04-29 01:20:52 +00:00
}
}
2020-05-15 21:04:22 +00:00
None = > {
msg . channel_id . say ( & ctx , " Upload timed out. Please redo the command " ) . await ? ;
}
2020-04-30 00:21:34 +00:00
}
2020-04-29 01:20:52 +00:00
}
2020-05-15 21:04:22 +00:00
else {
msg . channel_id . say (
& ctx ,
format! (
" You have reached the maximum number of sounds ({}). Either delete some with `?delete` or join our Patreon for unlimited uploads at **https://patreon.com/jellywx** " ,
* MAX_SOUNDS ,
) ) . await ? ;
}
2020-04-30 00:21:34 +00:00
}
2020-04-29 01:20:52 +00:00
}
else {
msg . channel_id . say ( & ctx , " Usage: `?upload <name>`. Please ensure the name provided is less than 20 characters in length " ) . await ? ;
}
Ok ( ( ) )
}
2020-04-29 22:09:31 +00:00
2020-05-16 20:28:47 +00:00
#[ command( " roles " ) ]
2020-05-16 23:40:35 +00:00
async fn set_allowed_roles ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
2020-05-20 18:08:59 +00:00
let guild_id = * msg . guild_id . unwrap ( ) . as_u64 ( ) ;
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-04-30 22:57:22 +00:00
if args . len ( ) = = 0 {
2020-05-20 18:08:59 +00:00
let roles = sqlx ::query! (
"
SELECT role
FROM roles
WHERE guild_id = ?
" ,
guild_id
)
. fetch_all ( & pool )
. await ? ;
let all_roles = roles . iter ( ) . map ( | i | format! ( " <@& {} > " , i . role . to_string ( ) ) ) . collect ::< Vec < String > > ( ) . join ( " , " ) ;
msg . channel_id . say ( & ctx , format! ( " Usage: `?roles <role mentions or anything else to disable>`. Current roles: {} " , all_roles ) ) . await ? ;
2020-04-30 22:57:22 +00:00
}
else {
sqlx ::query! (
"
DELETE FROM roles
WHERE guild_id = ?
" ,
guild_id
) . execute ( & pool ) . await ? ;
if msg . mention_roles . len ( ) > 0 {
for role in msg . mention_roles . iter ( ) . map ( | r | * r . as_u64 ( ) ) {
sqlx ::query! (
2020-05-20 18:08:59 +00:00
"
2020-04-30 22:57:22 +00:00
INSERT INTO roles ( guild_id , role )
VALUES
( ? , ? )
2020-05-20 18:08:59 +00:00
" ,
guild_id , role
)
. execute ( & pool )
. await ? ;
2020-04-30 22:57:22 +00:00
}
msg . channel_id . say ( & ctx , " Specified roles whitelisted " ) . await ? ;
}
else {
2020-05-20 18:08:59 +00:00
sqlx ::query! (
"
INSERT INTO roles ( guild_id , role )
VALUES
( ? , ? )
" ,
guild_id , guild_id
)
. execute ( & pool )
. await ? ;
2020-04-30 22:57:22 +00:00
msg . channel_id . say ( & ctx , " Role whitelisting disabled " ) . await ? ;
}
}
Ok ( ( ) )
}
2020-05-04 00:07:47 +00:00
2020-05-15 00:25:46 +00:00
#[ command( " list " ) ]
2020-05-16 23:40:35 +00:00
async fn list_sounds ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
2020-05-04 00:07:47 +00:00
let pool = ctx . data . read ( ) . await
2020-05-15 00:25:46 +00:00
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
2020-05-04 00:07:47 +00:00
let sounds ;
2020-05-15 00:25:46 +00:00
let mut message_buffer ;
2020-05-04 00:07:47 +00:00
if args . rest ( ) = = " me " {
sounds = Sound ::get_user_sounds ( * msg . author . id . as_u64 ( ) , pool ) . await ? ;
2020-05-15 00:25:46 +00:00
message_buffer = " All your sounds: " . to_string ( ) ;
2020-05-04 00:07:47 +00:00
}
else {
sounds = Sound ::get_guild_sounds ( * msg . guild_id . unwrap ( ) . as_u64 ( ) , pool ) . await ? ;
2020-05-15 00:25:46 +00:00
message_buffer = " All sounds on this server: " . to_string ( ) ;
}
for sound in sounds {
message_buffer . push_str ( format! ( " ** {} ** ( {} ), " , sound . name , if sound . public { " 🔓 " } else { " 🔒 " } ) . as_str ( ) ) ;
if message_buffer . len ( ) > 2000 {
msg . channel_id . say ( & ctx , message_buffer ) . await ? ;
message_buffer = " " . to_string ( ) ;
}
}
if message_buffer . len ( ) > 0 {
msg . channel_id . say ( & ctx , message_buffer ) . await ? ;
2020-05-04 00:07:47 +00:00
}
Ok ( ( ) )
}
2020-05-15 21:04:22 +00:00
#[ command( " public " ) ]
2020-05-16 23:40:35 +00:00
async fn change_public ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
2020-05-15 21:04:22 +00:00
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let uid = msg . author . id . as_u64 ( ) ;
2020-05-16 20:28:47 +00:00
let name = args . rest ( ) ;
let gid = * msg . guild_id . unwrap ( ) . as_u64 ( ) ;
2020-05-15 21:04:22 +00:00
2020-05-16 20:28:47 +00:00
let mut sound_vec = Sound ::search_for_sound ( name , gid , * uid , pool . clone ( ) , true ) . await ? ;
let sound_result = sound_vec . first_mut ( ) ;
2020-05-15 21:04:22 +00:00
match sound_result {
2020-05-16 20:28:47 +00:00
Some ( sound ) = > {
2020-05-20 14:33:28 +00:00
if sound . uploader_id ! = Some ( * uid ) {
2020-05-15 21:04:22 +00:00
msg . channel_id . say ( & ctx , " You can only change the availability of sounds you have uploaded. Use `?list me` to view your sounds " ) . await ? ;
}
else {
if sound . public {
sound . public = false ;
msg . channel_id . say ( & ctx , " Sound has been set to private 🔒 " ) . await ? ;
} else {
sound . public = true ;
msg . channel_id . say ( & ctx , " Sound has been set to public 🔓 " ) . await ? ;
}
sound . commit ( pool ) . await ?
}
}
2020-05-16 20:28:47 +00:00
None = > {
2020-05-15 21:04:22 +00:00
msg . channel_id . say ( & ctx , " Sound could not be found by that name. " ) . await ? ;
}
}
Ok ( ( ) )
}
2020-05-16 20:28:47 +00:00
#[ command( " delete " ) ]
2020-05-16 23:40:35 +00:00
async fn delete_sound ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
2020-05-16 20:28:47 +00:00
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let uid = * msg . author . id . as_u64 ( ) ;
let gid = * msg . guild_id . unwrap ( ) . as_u64 ( ) ;
let name = args . rest ( ) ;
let sound_vec = Sound ::search_for_sound ( name , gid , uid , pool . clone ( ) , true ) . await ? ;
let sound_result = sound_vec . first ( ) ;
match sound_result {
Some ( sound ) = > {
2020-05-20 14:33:28 +00:00
if sound . uploader_id ! = Some ( uid ) & & sound . server_id ! = gid {
2020-05-16 20:28:47 +00:00
msg . channel_id . say ( & ctx , " You can only delete sounds from this guild or that you have uploaded. " ) . await ? ;
}
else {
sound . delete ( pool ) . await ? ;
msg . channel_id . say ( & ctx , " Sound has been deleted " ) . await ? ;
}
}
None = > {
msg . channel_id . say ( & ctx , " Sound could not be found by that name. " ) . await ? ;
}
}
Ok ( ( ) )
}
2020-05-18 00:18:37 +00:00
async fn format_search_results ( search_results : Vec < Sound > , msg : & Message , ctx : & Context ) -> Result < ( ) , Box < dyn std ::error ::Error > > {
2020-05-16 23:40:35 +00:00
let mut current_character_count = 0 ;
let title = " Public sounds matching filter: " ;
let field_iter = search_results . iter ( ) . take ( 25 ) . map ( | item | {
2020-05-20 16:54:46 +00:00
( & item . name , format! ( " ID: {} \n Plays: {} " , item . id , item . plays ) , true )
2020-05-16 23:40:35 +00:00
} ) . filter ( | item | {
current_character_count + = item . 0. len ( ) + item . 1. len ( ) ;
current_character_count < = 2048 - title . len ( )
} ) ;
msg . channel_id . send_message ( & ctx , | m | {
m . embed ( | e | { e
. title ( title )
. fields ( field_iter )
} )
} ) . await ? ;
2020-05-16 20:28:47 +00:00
Ok ( ( ) )
}
2020-05-18 00:18:37 +00:00
#[ command( " search " ) ]
async fn search_sounds ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let query = args . rest ( ) ;
let search_results = Sound ::search_for_sound ( query , * msg . guild_id . unwrap ( ) . as_u64 ( ) , * msg . author . id . as_u64 ( ) , pool , false ) . await ? ;
format_search_results ( search_results , msg , ctx ) . await ? ;
Ok ( ( ) )
}
2020-05-18 22:44:51 +00:00
#[ command( " popular " ) ]
async fn show_popular_sounds ( ctx : & Context , msg : & Message , _args : Args ) -> CommandResult {
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let search_results = sqlx ::query_as_unchecked! (
Sound ,
"
2020-05-20 16:54:46 +00:00
SELECT name , id , plays , public , server_id , uploader_id
FROM sounds
2020-05-19 20:03:07 +00:00
ORDER BY plays DESC
LIMIT 25
2020-05-18 22:44:51 +00:00
"
)
. fetch_all ( & pool )
. await ? ;
format_search_results ( search_results , msg , ctx ) . await ? ;
Ok ( ( ) )
}
#[ command( " random " ) ]
async fn show_random_sounds ( ctx : & Context , msg : & Message , _args : Args ) -> CommandResult {
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let search_results = sqlx ::query_as_unchecked! (
Sound ,
"
2020-05-20 16:54:46 +00:00
SELECT name , id , plays , public , server_id , uploader_id
FROM sounds
2020-05-19 15:29:57 +00:00
ORDER BY rand ( )
2020-05-19 20:03:07 +00:00
LIMIT 25
2020-05-18 22:44:51 +00:00
"
)
. fetch_all ( & pool )
2020-05-20 14:33:28 +00:00
. await . unwrap ( ) ;
2020-05-18 22:44:51 +00:00
2020-05-20 14:33:28 +00:00
format_search_results ( search_results , msg , ctx ) . await . unwrap ( ) ;
2020-05-18 22:44:51 +00:00
Ok ( ( ) )
}
2020-05-18 00:18:37 +00:00
#[ command( " greet " ) ]
async fn set_greet_sound ( ctx : & Context , msg : & Message , args : Args ) -> CommandResult {
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not get SQLPool from data " ) ;
let query = args . rest ( ) ;
let user_id = * msg . author . id . as_u64 ( ) ;
2020-05-20 22:25:08 +00:00
let _ = sqlx ::query! (
"
INSERT IGNORE INTO users ( user )
VALUES ( ? )
" ,
user_id
)
. execute ( & pool )
. await ;
2020-05-18 00:18:37 +00:00
if query . len ( ) = = 0 {
sqlx ::query! (
"
UPDATE users
SET
join_sound_id = NULL
WHERE
user = ?
" ,
user_id
)
. execute ( & pool )
. await ? ;
msg . channel_id . say ( & ctx , " Your greet sound has been unset. " ) . await ? ;
}
else {
let sound_vec = Sound ::search_for_sound ( query , * msg . guild_id . unwrap ( ) . as_u64 ( ) , user_id , pool . clone ( ) , true ) . await ? ;
match sound_vec . first ( ) {
Some ( sound ) = > {
sound . set_as_greet ( user_id , pool ) . await ? ;
msg . channel_id . say ( & ctx , format! ( " Greet sound has been set to {} (ID {} ) " , sound . name , sound . id ) ) . await ? ;
}
None = > {
msg . channel_id . say ( & ctx , " Could not find a sound by that name. " ) . await ? ;
}
}
}
Ok ( ( ) )
}
2020-05-22 14:28:25 +00:00
#[ command( " stop " ) ]
async fn stop_playing ( ctx : & Context , msg : & Message , _args : Args ) -> CommandResult {
let voice_manager_lock = ctx . data . read ( ) . await
. get ::< VoiceManager > ( ) . cloned ( ) . expect ( " Could not get VoiceManager from data " ) ;
let mut voice_manager = voice_manager_lock . lock ( ) . await ;
let manager_opt = voice_manager . get_mut ( msg . guild_id . unwrap ( ) ) ;
if let Some ( manager ) = manager_opt {
manager . leave ( ) ;
}
Ok ( ( ) )
}
2020-05-29 13:35:24 +00:00
#[ command( " allow_greet " ) ]
async fn allow_greet_sounds ( ctx : & Context , msg : & Message , _args : Args ) -> CommandResult {
let pool = ctx . data . read ( ) . await
. get ::< SQLPool > ( ) . cloned ( ) . expect ( " Could not acquire SQL pool from data " ) ;
let guild_data_opt = GuildData ::get_from_id ( * msg . guild_id . unwrap ( ) . as_u64 ( ) , pool . clone ( ) ) . await ;
if let Some ( mut guild_data ) = guild_data_opt {
guild_data . allow_greets = ! guild_data . allow_greets ;
guild_data . commit ( pool ) . await ? ;
msg . channel_id . say ( & ctx , format! ( " Greet sounds have been {} abled in this server " , if guild_data . allow_greets { " en " } else { " dis " } ) ) . await ? ;
}
Ok ( ( ) )
}