changed how commands get invoked
This commit is contained in:
parent
e3f30ab085
commit
99e1807097
2
.idea/dataSources.local.xml
generated
2
.idea/dataSources.local.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="dataSourceStorageLocal">
|
||||
<component name="dataSourceStorageLocal" created-in="CL-211.7442.42">
|
||||
<data-source name="MySQL for 5.1 - soundfx@localhost" uuid="1067c1d0-1386-4a39-b3f5-6d48d6f279eb">
|
||||
<database-info product="" version="" jdbc-version="" driver-name="" driver-version="" dbms="MYSQL" exact-version="0" />
|
||||
<secret-storage>master_key</secret-storage>
|
||||
|
@ -6,7 +6,7 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
songbird = { git = "https://github.com/FelixMcFelix/songbird", branch = "ws-fix" }
|
||||
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "next", features = ["voice", "collector"] }
|
||||
serenity = { git = "https://github.com/serenity-rs/serenity", branch = "next", features = ["voice", "collector", "unstable_discord_api"] }
|
||||
sqlx = { version = "0.5", default-features = false, features = ["runtime-tokio-rustls", "macros", "mysql", "bigdecimal"] }
|
||||
dotenv = "0.15"
|
||||
tokio = { version = "1", features = ["fs", "process", "io-util"] }
|
||||
|
@ -57,11 +57,15 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let name = &name[..];
|
||||
|
||||
match_options!(name, values, options, span => [
|
||||
permission_level
|
||||
permission_level;
|
||||
allow_slash
|
||||
]);
|
||||
}
|
||||
|
||||
let Options { permission_level } = options;
|
||||
let Options {
|
||||
permission_level,
|
||||
allow_slash,
|
||||
} = options;
|
||||
|
||||
propagate_err!(create_declaration_validations(&mut fun, DeclarFor::Command));
|
||||
|
||||
@ -88,6 +92,7 @@ pub fn command(attr: TokenStream, input: TokenStream) -> TokenStream {
|
||||
func: #name,
|
||||
name: #lit_name,
|
||||
required_perms: #permission_level,
|
||||
allow_slash: #allow_slash,
|
||||
};
|
||||
|
||||
#visibility fn #name<'fut> (#(#args),*) -> ::serenity::futures::future::BoxFuture<'fut, #ret> {
|
||||
|
@ -226,11 +226,15 @@ impl ToTokens for PermissionLevel {
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Options {
|
||||
pub permission_level: PermissionLevel,
|
||||
pub allow_slash: bool,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
Self {
|
||||
permission_level: PermissionLevel::default(),
|
||||
allow_slash: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ pub fn create_declaration_validations(fun: &mut CommandFun, dec_for: DeclarFor)
|
||||
}
|
||||
|
||||
let context: Type = parse_quote!(&serenity::client::Context);
|
||||
let message: Type = parse_quote!(&serenity::model::channel::Message);
|
||||
let message: Type = parse_quote!(&(dyn crate::framework::CommandInvoke + Sync + Send));
|
||||
let args: Type = parse_quote!(serenity::framework::standard::Args);
|
||||
let args2: Type = parse_quote!(&mut serenity::framework::standard::Args);
|
||||
let options: Type = parse_quote!(&serenity::framework::standard::CommandOptions);
|
||||
|
@ -1,7 +1,20 @@
|
||||
use serenity::async_trait;
|
||||
use songbird::Event;
|
||||
use songbird::EventContext;
|
||||
use songbird::EventHandler as SongbirdEventHandler;
|
||||
use crate::{
|
||||
guild_data::CtxGuildData,
|
||||
join_channel, play_audio,
|
||||
sound::{JoinSoundCtx, Sound},
|
||||
MySQL, ReqwestClient,
|
||||
};
|
||||
|
||||
use serenity::{
|
||||
async_trait,
|
||||
client::{Context, EventHandler},
|
||||
model::{channel::Channel, guild::Guild, id::GuildId, voice::VoiceState},
|
||||
utils::shard_id,
|
||||
};
|
||||
|
||||
use songbird::{Event, EventContext, EventHandler as SongbirdEventHandler};
|
||||
|
||||
use std::{collections::HashMap, env};
|
||||
|
||||
pub struct RestartTrack;
|
||||
|
||||
@ -15,3 +28,130 @@ impl SongbirdEventHandler for RestartTrack {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Handler;
|
||||
|
||||
#[serenity::async_trait]
|
||||
impl EventHandler for Handler {
|
||||
async fn guild_create(&self, ctx: Context, guild: Guild, is_new: bool) {
|
||||
if is_new {
|
||||
if let Ok(token) = env::var("DISCORDBOTS_TOKEN") {
|
||||
let shard_count = ctx.cache.shard_count().await;
|
||||
let current_shard_id = shard_id(guild.id.as_u64().to_owned(), shard_count);
|
||||
|
||||
let guild_count = ctx
|
||||
.cache
|
||||
.guilds()
|
||||
.await
|
||||
.iter()
|
||||
.filter(|g| shard_id(g.as_u64().to_owned(), shard_count) == current_shard_id)
|
||||
.count() as u64;
|
||||
|
||||
let mut hm = HashMap::new();
|
||||
hm.insert("server_count", guild_count);
|
||||
hm.insert("shard_id", current_shard_id);
|
||||
hm.insert("shard_count", shard_count);
|
||||
|
||||
let client = ctx
|
||||
.data
|
||||
.read()
|
||||
.await
|
||||
.get::<ReqwestClient>()
|
||||
.cloned()
|
||||
.expect("Could not get ReqwestClient from data");
|
||||
|
||||
let response = client
|
||||
.post(
|
||||
format!(
|
||||
"https://top.gg/api/bots/{}/stats",
|
||||
ctx.cache.current_user_id().await.as_u64()
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.header("Authorization", token)
|
||||
.json(&hm)
|
||||
.send()
|
||||
.await;
|
||||
|
||||
if let Err(res) = response {
|
||||
println!("DiscordBots Response: {:?}", res);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn voice_state_update(
|
||||
&self,
|
||||
ctx: Context,
|
||||
guild_id_opt: Option<GuildId>,
|
||||
old: Option<VoiceState>,
|
||||
new: VoiceState,
|
||||
) {
|
||||
if let Some(past_state) = old {
|
||||
if let (Some(guild_id), None) = (guild_id_opt, new.channel_id) {
|
||||
if let Some(channel_id) = past_state.channel_id {
|
||||
if let Some(Channel::Guild(channel)) = channel_id.to_channel_cached(&ctx).await
|
||||
{
|
||||
if channel.members(&ctx).await.map(|m| m.len()).unwrap_or(0) <= 1 {
|
||||
let songbird = songbird::get(&ctx).await.unwrap();
|
||||
|
||||
let _ = songbird.remove(guild_id).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let (Some(guild_id), Some(user_channel)) = (guild_id_opt, new.channel_id) {
|
||||
if let Some(guild) = ctx.cache.guild(guild_id).await {
|
||||
let pool = ctx
|
||||
.data
|
||||
.read()
|
||||
.await
|
||||
.get::<MySQL>()
|
||||
.cloned()
|
||||
.expect("Could not get SQLPool from data");
|
||||
|
||||
let guild_data_opt = ctx.guild_data(guild.id).await;
|
||||
|
||||
if let Ok(guild_data) = guild_data_opt {
|
||||
let volume;
|
||||
let allowed_greets;
|
||||
|
||||
{
|
||||
let read = guild_data.read().await;
|
||||
|
||||
volume = read.volume;
|
||||
allowed_greets = read.allow_greets;
|
||||
}
|
||||
|
||||
if allowed_greets {
|
||||
if let Some(join_id) = ctx.join_sound(new.user_id).await {
|
||||
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 (handler, _) = join_channel(&ctx, guild, user_channel).await;
|
||||
|
||||
let _ = play_audio(
|
||||
&mut sound,
|
||||
volume,
|
||||
&mut handler.lock().await,
|
||||
pool,
|
||||
false,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
168
src/framework.rs
168
src/framework.rs
@ -2,13 +2,17 @@ use serenity::{
|
||||
async_trait,
|
||||
client::Context,
|
||||
constants::MESSAGE_CODE_LIMIT,
|
||||
framework::{standard::Args, Framework},
|
||||
framework::{
|
||||
standard::{Args, CommandResult, Delimiter},
|
||||
Framework,
|
||||
},
|
||||
futures::prelude::future::BoxFuture,
|
||||
http::Http,
|
||||
model::{
|
||||
channel::{Channel, GuildChannel, Message},
|
||||
guild::{Guild, Member},
|
||||
id::ChannelId,
|
||||
id::{ChannelId, GuildId, UserId},
|
||||
interactions::Interaction,
|
||||
},
|
||||
Result as SerenityResult,
|
||||
};
|
||||
@ -20,9 +24,164 @@ use regex::{Match, Regex, RegexBuilder};
|
||||
use std::{collections::HashMap, fmt};
|
||||
|
||||
use crate::{guild_data::CtxGuildData, MySQL};
|
||||
use serenity::framework::standard::{CommandResult, Delimiter};
|
||||
use serenity::builder::CreateEmbed;
|
||||
use serenity::cache::Cache;
|
||||
use serenity::model::prelude::InteractionResponseType;
|
||||
use std::sync::Arc;
|
||||
|
||||
type CommandFn = for<'fut> fn(&'fut Context, &'fut Message, Args) -> BoxFuture<'fut, CommandResult>;
|
||||
type CommandFn = for<'fut> fn(
|
||||
&'fut Context,
|
||||
&'fut (dyn CommandInvoke + Sync + Send),
|
||||
Args,
|
||||
) -> BoxFuture<'fut, CommandResult>;
|
||||
|
||||
pub struct CreateGenericResponse {
|
||||
content: String,
|
||||
embed: Option<CreateEmbed>,
|
||||
}
|
||||
|
||||
impl CreateGenericResponse {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
content: "".to_string(),
|
||||
embed: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn content<D: ToString>(mut self, content: D) -> Self {
|
||||
self.content = content.to_string();
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
pub fn embed<F: FnOnce(&mut CreateEmbed) -> &mut CreateEmbed>(mut self, f: F) -> Self {
|
||||
let mut embed = CreateEmbed::default();
|
||||
|
||||
f(&mut embed);
|
||||
|
||||
self.embed = Some(embed);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait CommandInvoke {
|
||||
fn channel_id(&self) -> ChannelId;
|
||||
fn guild_id(&self) -> Option<GuildId>;
|
||||
async fn guild(&self, cache: Arc<Cache>) -> Option<Guild>;
|
||||
fn author_id(&self) -> UserId;
|
||||
async fn member(&self, context: &Context) -> SerenityResult<Member>;
|
||||
fn msg(&self) -> Option<Message>;
|
||||
fn interaction(&self) -> Option<Interaction>;
|
||||
async fn respond(
|
||||
&self,
|
||||
http: Arc<Http>,
|
||||
generic_response: CreateGenericResponse,
|
||||
) -> SerenityResult<()>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandInvoke for Message {
|
||||
fn channel_id(&self) -> ChannelId {
|
||||
self.channel_id
|
||||
}
|
||||
|
||||
fn guild_id(&self) -> Option<GuildId> {
|
||||
self.guild_id
|
||||
}
|
||||
|
||||
async fn guild(&self, cache: Arc<Cache>) -> Option<Guild> {
|
||||
self.guild(cache).await
|
||||
}
|
||||
|
||||
fn author_id(&self) -> UserId {
|
||||
self.author.id
|
||||
}
|
||||
|
||||
async fn member(&self, context: &Context) -> SerenityResult<Member> {
|
||||
self.member(context).await
|
||||
}
|
||||
|
||||
fn msg(&self) -> Option<Message> {
|
||||
Some(self.clone())
|
||||
}
|
||||
|
||||
fn interaction(&self) -> Option<Interaction> {
|
||||
None
|
||||
}
|
||||
|
||||
async fn respond(
|
||||
&self,
|
||||
http: Arc<Http>,
|
||||
generic_response: CreateGenericResponse,
|
||||
) -> SerenityResult<()> {
|
||||
self.channel_id
|
||||
.send_message(http, |m| {
|
||||
m.content(generic_response.content);
|
||||
|
||||
if let Some(embed) = generic_response.embed {
|
||||
m.set_embed(embed.clone());
|
||||
}
|
||||
|
||||
m
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl CommandInvoke for Interaction {
|
||||
fn channel_id(&self) -> ChannelId {
|
||||
self.channel_id.unwrap()
|
||||
}
|
||||
|
||||
fn guild_id(&self) -> Option<GuildId> {
|
||||
self.guild_id
|
||||
}
|
||||
|
||||
async fn guild(&self, cache: Arc<Cache>) -> Option<Guild> {
|
||||
self.guild(cache).await
|
||||
}
|
||||
|
||||
fn author_id(&self) -> UserId {
|
||||
self.member.as_ref().unwrap().user.id
|
||||
}
|
||||
|
||||
async fn member(&self, _: &Context) -> SerenityResult<Member> {
|
||||
Ok(self.member.clone().unwrap())
|
||||
}
|
||||
|
||||
fn msg(&self) -> Option<Message> {
|
||||
None
|
||||
}
|
||||
|
||||
fn interaction(&self) -> Option<Interaction> {
|
||||
Some(self.clone())
|
||||
}
|
||||
|
||||
async fn respond(
|
||||
&self,
|
||||
http: Arc<Http>,
|
||||
generic_response: CreateGenericResponse,
|
||||
) -> SerenityResult<()> {
|
||||
self.create_interaction_response(http, |r| {
|
||||
r.kind(InteractionResponseType::ChannelMessageWithSource)
|
||||
.interaction_response_data(|d| {
|
||||
d.content(generic_response.content);
|
||||
|
||||
if let Some(embed) = generic_response.embed {
|
||||
d.set_embed(embed.clone());
|
||||
}
|
||||
|
||||
d
|
||||
})
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum PermissionLevel {
|
||||
@ -35,6 +194,7 @@ pub struct Command {
|
||||
pub name: &'static str,
|
||||
pub required_perms: PermissionLevel,
|
||||
pub func: CommandFn,
|
||||
pub allow_slash: bool,
|
||||
}
|
||||
|
||||
impl Command {
|
||||
|
627
src/main.rs
627
src/main.rs
File diff suppressed because it is too large
Load Diff
21
src/sound.rs
21
src/sound.rs
@ -121,13 +121,16 @@ pub struct Sound {
|
||||
}
|
||||
|
||||
impl Sound {
|
||||
pub async fn search_for_sound(
|
||||
pub async fn search_for_sound<G: Into<u64>, U: Into<u64>>(
|
||||
query: &str,
|
||||
guild_id: u64,
|
||||
user_id: u64,
|
||||
guild_id: G,
|
||||
user_id: U,
|
||||
db_pool: MySqlPool,
|
||||
strict: bool,
|
||||
) -> Result<Vec<Sound>, sqlx::Error> {
|
||||
let guild_id = guild_id.into();
|
||||
let user_id = user_id.into();
|
||||
|
||||
fn extract_id(s: &str) -> Option<u32> {
|
||||
if s.len() > 3 && s.to_lowercase().starts_with("id:") {
|
||||
match s[3..].parse::<u32>() {
|
||||
@ -403,8 +406,8 @@ INSERT INTO sounds (name, server_id, uploader_id, public, src)
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_user_sounds(
|
||||
user_id: u64,
|
||||
pub async fn get_user_sounds<U: Into<u64>>(
|
||||
user_id: U,
|
||||
db_pool: MySqlPool,
|
||||
) -> Result<Vec<Sound>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let sounds = sqlx::query_as_unchecked!(
|
||||
@ -414,7 +417,7 @@ SELECT name, id, plays, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE uploader_id = ?
|
||||
",
|
||||
user_id
|
||||
user_id.into()
|
||||
)
|
||||
.fetch_all(&db_pool)
|
||||
.await?;
|
||||
@ -422,8 +425,8 @@ SELECT name, id, plays, public, server_id, uploader_id
|
||||
Ok(sounds)
|
||||
}
|
||||
|
||||
pub async fn get_guild_sounds(
|
||||
guild_id: u64,
|
||||
pub async fn get_guild_sounds<G: Into<u64>>(
|
||||
guild_id: G,
|
||||
db_pool: MySqlPool,
|
||||
) -> Result<Vec<Sound>, Box<dyn std::error::Error + Send + Sync>> {
|
||||
let sounds = sqlx::query_as_unchecked!(
|
||||
@ -433,7 +436,7 @@ SELECT name, id, plays, public, server_id, uploader_id
|
||||
FROM sounds
|
||||
WHERE server_id = ?
|
||||
",
|
||||
guild_id
|
||||
guild_id.into()
|
||||
)
|
||||
.fetch_all(&db_pool)
|
||||
.await?;
|
||||
|
Loading…
Reference in New Issue
Block a user