use poise::{ serenity_prelude::{ http::CacheHttp, model::id::{GuildId, UserId}, CreateEmbedFooter, CreateInteractionResponseMessage, }, CreateReply, }; use crate::{ consts::{CNC_GUILD, SUBSCRIPTION_ROLES}, ApplicationContext, Context, Database, Error, }; /// Check if this user/guild combination should be considered subscribed. /// If the guild has a patreon linked, check the user involved in the link. /// Otherwise, check the user and the guild's owner pub async fn check_subscription( ctx: impl CacheHttp, database: impl Executor<'_, Database = Database>, user_id: UserId, guild_id: Option, ) -> bool { if let Some(subscription_guild) = *CNC_GUILD { let user_subscribed = check_user_subscription(&ctx, user_id).await; let owner_subscribed = match guild_id { Some(guild_id) => { if let Some(owner) = ctx.cache().unwrap().guild(guild_id).map(|g| g.owner_id) { check_user_subscription(&ctx, owner).await } else { false } } None => false, }; let link_subscribed = match guild_id { Some(guild_id) => { if let Ok(row) = sqlx::query!( "SELECT user_id FROM patreon_link WHERE guild_id = ?", guild_id.get() ) .fetch_one(database) .await { check_user_subscription(&ctx, row.user_id).await } else { false } } None => false, }; user_subscribed || owner_subscribed || link_subscribed } else { true } } /// Check a user's subscription status, ignoring Patreon linkage pub async fn check_user_subscription( cache_http: impl CacheHttp, user_id: impl Into, ) -> bool { if let Some(subscription_guild) = *CNC_GUILD { let guild_member = GuildId::new(subscription_guild).member(cache_http, user_id).await; if let Ok(member) = guild_member { for role in member.roles { if SUBSCRIPTION_ROLES.contains(&role.get()) { return true; } } } false } else { true } } pub fn reply_to_interaction_response_message( reply: CreateReply, ) -> CreateInteractionResponseMessage { let mut builder = CreateInteractionResponseMessage::new().embeds(reply.embeds); if let Some(components) = reply.components { builder = builder.components(components) } builder } pub fn footer(ctx: Context<'_>) -> CreateEmbedFooter { let shard_count = ctx.serenity_context().cache.shard_count(); let shard = ctx.serenity_context().shard_id; CreateEmbedFooter::new(format!( "{}\nshard {} of {}", concat!(env!("CARGO_PKG_NAME"), " ver ", env!("CARGO_PKG_VERSION")), shard, shard_count, )) } pub trait Recordable { async fn run(self, ctx: Context<'_>) -> Result<(), Error>; } pub use recordable_derive::Recordable; pub trait Extract { fn extract(ctx: ApplicationContext) -> Self; } pub use extract_derive::Extract; use sqlx::Executor; macro_rules! extract_arg { ($ctx:ident, $name:ident, String) => { $ctx.args .iter() .find(|opt| opt.name == stringify!($name)) .map(|opt| &opt.value) .map_or_else( || String::new(), |v| match v { poise::serenity_prelude::ResolvedValue::String(s) => s.to_string(), _ => String::new(), }, ) }; ($ctx:ident, $name:ident, Option) => { $ctx.args .iter() .find(|opt| opt.name == stringify!($name)) .map(|opt| &opt.value) .map(|v| match v { poise::serenity_prelude::ResolvedValue::String(s) => Some(s.to_string()), _ => None, }) .flatten() }; ($ctx:ident, $name:ident, bool) => { $ctx.args.iter().find(|opt| opt.name == stringify!($name)).map(|opt| &opt.value).map_or( false, |v| match v { poise::serenity_prelude::ResolvedValue::Boolean(b) => b.to_owned(), _ => false, }, ) }; ($ctx:ident, $name:ident, Option) => { $ctx.args .iter() .find(|opt| opt.name == stringify!($name)) .map(|opt| &opt.value) .map(|v| match v { poise::serenity_prelude::ResolvedValue::Boolean(b) => Some(b.to_owned()), _ => None, }) .flatten() }; ($ctx:ident, $name:ident, Option) => { $ctx.args .iter() .find(|opt| opt.name == stringify!($name)) .map(|opt| &opt.value) .map(|v| match v { poise::serenity_prelude::ResolvedValue::Channel(partial) => { Some(partial.to_owned()) } _ => None, }) .flatten() .cloned() }; ($ctx:ident, $name:ident, i64) => { $ctx.args .iter() .find(|opt| opt.name == stringify!($name)) .map(|opt| &opt.value) .map(|v| match v { poise::serenity_prelude::ResolvedValue::Integer(int) => *int, _ => 0, }) .flatten() }; ($ctx:ident, $name:ident, Option) => { $ctx.args .iter() .find(|opt| opt.name == stringify!($name)) .map(|opt| &opt.value) .map(|v| match v { poise::serenity_prelude::ResolvedValue::Integer(int) => Some(*int), _ => None, }) .flatten() }; } pub(crate) use extract_arg;