2020-06-13 14:03:57 +00:00
#nullable enable
using System ;
2020-02-01 12:03:02 +00:00
using System.Threading.Tasks ;
2020-04-24 19:50:28 +00:00
using DSharpPlus.Entities ;
2020-02-01 12:03:02 +00:00
2020-02-12 14:16:19 +00:00
using PluralKit.Core ;
2020-02-01 12:03:02 +00:00
2020-02-12 14:16:19 +00:00
namespace PluralKit.Bot
2020-02-01 12:03:02 +00:00
{
public class MemberAvatar
{
2020-06-13 17:36:43 +00:00
private readonly IDatabase _db ;
2020-08-29 11:46:27 +00:00
private readonly ModelRepository _repo ;
2020-02-01 12:03:02 +00:00
2020-08-29 11:46:27 +00:00
public MemberAvatar ( IDatabase db , ModelRepository repo )
2020-02-01 12:03:02 +00:00
{
2020-06-13 14:03:57 +00:00
_db = db ;
2020-08-29 11:46:27 +00:00
_repo = repo ;
2020-02-01 12:03:02 +00:00
}
2020-06-13 14:03:57 +00:00
private async Task AvatarClear ( AvatarLocation location , Context ctx , PKMember target , MemberGuildSettings ? mgs )
2020-02-01 12:03:02 +00:00
{
2020-06-13 14:03:57 +00:00
await UpdateAvatar ( location , ctx , target , null ) ;
if ( location = = AvatarLocation . Server )
2020-03-04 17:13:36 +00:00
{
2020-06-13 14:03:57 +00:00
if ( target . AvatarUrl ! = null )
await ctx . Reply ( $"{Emojis.Success} Member server avatar cleared. This member will now use the global avatar in this server (**{ctx.Guild.Name}**)." ) ;
else
await ctx . Reply ( $"{Emojis.Success} Member server avatar cleared. This member now has no avatar." ) ;
}
else
{
if ( mgs ? . AvatarUrl ! = null )
2020-08-25 20:33:04 +00:00
await ctx . Reply ( $"{Emojis.Success} Member avatar cleared. Note that this member has a server-specific avatar set here, type `pk;member {target.Reference()} serveravatar clear` if you wish to clear that too." ) ;
2020-03-04 17:13:36 +00:00
else
await ctx . Reply ( $"{Emojis.Success} Member avatar cleared." ) ;
}
2020-06-13 14:03:57 +00:00
}
private async Task AvatarShow ( AvatarLocation location , Context ctx , PKMember target , MemberGuildSettings ? guildData )
{
var currentValue = location = = AvatarLocation . Member ? target . AvatarUrl : guildData ? . AvatarUrl ;
2020-06-20 14:00:50 +00:00
var canAccess = location ! = AvatarLocation . Member | | target . AvatarPrivacy . CanAccess ( ctx . LookupContextFor ( target ) ) ;
2020-06-20 14:10:22 +00:00
if ( string . IsNullOrEmpty ( currentValue ) | | ! canAccess )
2020-02-01 12:03:02 +00:00
{
2020-06-13 14:03:57 +00:00
if ( location = = AvatarLocation . Member )
2020-02-01 12:03:02 +00:00
{
if ( target . System = = ctx . System ? . Id )
2020-06-13 14:03:57 +00:00
throw new PKSyntaxError ( "This member does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention." ) ;
throw new PKError ( "This member does not have an avatar set." ) ;
2020-02-01 12:03:02 +00:00
}
2020-06-13 14:03:57 +00:00
if ( location = = AvatarLocation . Server )
2020-08-25 20:33:04 +00:00
throw new PKError ( $"This member does not have a server avatar set. Type `pk;member {target.Reference()} avatar` to see their global avatar." ) ;
2020-02-01 12:03:02 +00:00
}
2020-07-07 21:41:51 +00:00
var field = location = = AvatarLocation . Server ? $"server avatar (for {ctx.Guild.Name})" : "avatar" ;
var cmd = location = = AvatarLocation . Server ? "serveravatar" : "avatar" ;
2020-06-13 14:03:57 +00:00
var eb = new DiscordEmbedBuilder ( )
2020-06-20 15:36:03 +00:00
. WithTitle ( $"{target.NameFor(ctx)}'s {field}" )
2020-06-13 14:03:57 +00:00
. WithImageUrl ( currentValue ) ;
if ( target . System = = ctx . System ? . Id )
2020-08-25 20:33:04 +00:00
eb . WithDescription ( $"To clear, use `pk;member {target.Reference()} {cmd} clear`." ) ;
2020-06-13 14:03:57 +00:00
await ctx . Reply ( embed : eb . Build ( ) ) ;
}
2020-02-12 16:42:12 +00:00
public async Task ServerAvatar ( Context ctx , PKMember target )
{
ctx . CheckGuildContext ( ) ;
2020-08-29 11:46:27 +00:00
var guildData = await _db . Execute ( c = > _repo . GetMemberGuild ( c , ctx . Guild . Id , target . Id ) ) ;
2020-06-13 14:03:57 +00:00
await AvatarCommandTree ( AvatarLocation . Server , ctx , target , guildData ) ;
}
public async Task Avatar ( Context ctx , PKMember target )
{
var guildData = ctx . Guild ! = null ?
2020-08-29 11:46:27 +00:00
await _db . Execute ( c = > _repo . GetMemberGuild ( c , ctx . Guild . Id , target . Id ) )
2020-06-13 14:03:57 +00:00
: null ;
2020-02-12 16:42:12 +00:00
2020-06-13 14:03:57 +00:00
await AvatarCommandTree ( AvatarLocation . Member , ctx , target , guildData ) ;
}
2020-02-12 16:42:12 +00:00
2020-06-13 14:03:57 +00:00
private async Task AvatarCommandTree ( AvatarLocation location , Context ctx , PKMember target , MemberGuildSettings ? guildData )
{
2020-07-07 21:41:51 +00:00
// First, see if we need to *clear*
2020-10-04 08:53:07 +00:00
if ( await ctx . MatchClear ( "this member's avatar" ) )
2020-07-07 21:41:51 +00:00
{
ctx . CheckSystem ( ) . CheckOwnMember ( target ) ;
2020-06-13 14:03:57 +00:00
await AvatarClear ( location , ctx , target , guildData ) ;
2020-07-07 21:41:51 +00:00
return ;
}
// Then, parse an image from the command (from various sources...)
var avatarArg = await ctx . MatchImage ( ) ;
if ( avatarArg = = null )
{
// If we didn't get any, just show the current avatar
2020-06-13 14:03:57 +00:00
await AvatarShow ( location , ctx , target , guildData ) ;
2020-07-07 21:41:51 +00:00
return ;
}
ctx . CheckSystem ( ) . CheckOwnMember ( target ) ;
await ValidateUrl ( avatarArg . Value . Url ) ;
await UpdateAvatar ( location , ctx , target , avatarArg . Value . Url ) ;
await PrintResponse ( location , ctx , target , avatarArg . Value , guildData ) ;
}
private static Task ValidateUrl ( string url )
{
if ( url . Length > Limits . MaxUriLength )
throw Errors . InvalidUrl ( url ) ;
return AvatarUtils . VerifyAvatarOrThrow ( url ) ;
}
private Task PrintResponse ( AvatarLocation location , Context ctx , PKMember target , ParsedImage avatar ,
MemberGuildSettings ? targetGuildData )
{
var typeFrag = location switch
{
AvatarLocation . Server = > "server avatar" ,
AvatarLocation . Member = > "avatar" ,
_ = > throw new ArgumentOutOfRangeException ( nameof ( location ) )
} ;
var serverFrag = location switch
{
AvatarLocation . Server = > $" This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**)." ,
AvatarLocation . Member when targetGuildData ? . AvatarUrl ! = null = > $"\n{Emojis.Note} Note that this member *also* has a server-specific avatar set in this server (**{ctx.Guild.Name}**), and thus changing the global avatar will have no effect here." ,
_ = > ""
} ;
var msg = avatar . Source switch
{
AvatarSource . User = > $"{Emojis.Success} Member {typeFrag} changed to {avatar.SourceUser?.Username}'s avatar!{serverFrag}\n{Emojis.Warn} If {avatar.SourceUser?.Username} changes their avatar, the member's avatar will need to be re-set." ,
AvatarSource . Url = > $"{Emojis.Success} Member {typeFrag} changed to the image at the given URL.{serverFrag}" ,
AvatarSource . Attachment = > $"{Emojis.Success} Member {typeFrag} changed to attached image.{serverFrag}\n{Emojis.Warn} If you delete the message containing the attachment, the avatar will stop working." ,
_ = > throw new ArgumentOutOfRangeException ( )
} ;
// The attachment's already right there, no need to preview it.
var hasEmbed = avatar . Source ! = AvatarSource . Attachment ;
return hasEmbed
? ctx . Reply ( msg , embed : new DiscordEmbedBuilder ( ) . WithImageUrl ( avatar . Url ) . Build ( ) )
: ctx . Reply ( msg ) ;
2020-06-13 14:03:57 +00:00
}
2020-02-12 16:42:12 +00:00
2020-07-07 21:41:51 +00:00
private Task UpdateAvatar ( AvatarLocation location , Context ctx , PKMember target , string? url )
2020-06-29 13:20:28 +00:00
{
switch ( location )
2020-02-12 16:42:12 +00:00
{
2020-06-29 13:20:28 +00:00
case AvatarLocation . Server :
2020-07-07 21:41:51 +00:00
var serverPatch = new MemberGuildPatch { AvatarUrl = url } ;
2020-08-29 11:46:27 +00:00
return _db . Execute ( c = > _repo . UpsertMemberGuild ( c , target . Id , ctx . Guild . Id , serverPatch ) ) ;
2020-06-29 13:20:28 +00:00
case AvatarLocation . Member :
2020-07-07 21:41:51 +00:00
var memberPatch = new MemberPatch { AvatarUrl = url } ;
2020-08-29 11:46:27 +00:00
return _db . Execute ( c = > _repo . UpdateMember ( c , target . Id , memberPatch ) ) ;
2020-06-29 13:20:28 +00:00
default :
throw new ArgumentOutOfRangeException ( $"Unknown avatar location {location}" ) ;
}
}
2020-02-12 16:42:12 +00:00
2020-06-13 14:03:57 +00:00
private enum AvatarLocation
{
Member ,
Server
2020-02-12 16:42:12 +00:00
}
2020-02-01 12:03:02 +00:00
}
}