2020-02-01 13:03:02 +01:00
using System.Text.RegularExpressions ;
using System.Threading.Tasks ;
2020-02-28 00:23:54 +01:00
using Discord ;
2020-02-01 13:03:02 +01:00
using NodaTime ;
using PluralKit.Core ;
2020-02-12 15:16:19 +01:00
namespace PluralKit.Bot
2020-02-01 13:03:02 +01:00
{
public class MemberEdit
{
private IDataStore _data ;
2020-02-01 14:40:57 +01:00
public MemberEdit ( IDataStore data )
2020-02-01 13:03:02 +01:00
{
_data = data ;
}
public async Task Name ( Context ctx , PKMember target ) {
// TODO: this method is pretty much a 1:1 copy/paste of the above creation method, find a way to clean?
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
var newName = ctx . RemainderOrNull ( ) ? ? throw new PKSyntaxError ( "You must pass a new name for the member." ) ;
// Hard name length cap
if ( newName . Length > Limits . MaxMemberNameLength ) throw Errors . MemberNameTooLongError ( newName . Length ) ;
// Warn if there's already a member by this name
var existingMember = await _data . GetMemberByName ( ctx . System , newName ) ;
if ( existingMember ! = null ) {
var msg = await ctx . Reply ( $"{Emojis.Warn} You already have a member in your system with the name \" { existingMember . Name . SanitizeMentions ( ) } \ " (`{existingMember.Hid}`). Do you want to rename this member to that name too?" ) ;
if ( ! await ctx . PromptYesNo ( msg ) ) throw new PKError ( "Member renaming cancelled." ) ;
}
// Rename the member
target . Name = newName ;
await _data . SaveMember ( target ) ;
await ctx . Reply ( $"{Emojis.Success} Member renamed." ) ;
if ( newName . Contains ( " " ) ) await ctx . Reply ( $"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \" double quotes \ " when using commands referring to it." ) ;
if ( target . DisplayName ! = null ) await ctx . Reply ( $"{Emojis.Note} Note that this member has a display name set ({target.DisplayName.SanitizeMentions()}), and will be proxied using that name instead." ) ;
if ( ctx . Guild ! = null )
{
var memberGuildConfig = await _data . GetMemberGuildSettings ( target , ctx . Guild . Id ) ;
if ( memberGuildConfig . DisplayName ! = null )
await ctx . Reply ( $"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName.SanitizeMentions()}) in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name here." ) ;
}
}
public async Task Description ( Context ctx , PKMember target ) {
2020-02-28 00:23:54 +01:00
var description = ctx . RemainderOrNull ( ) ? . NormalizeLineEndSpacing ( ) ;
var lookupContext = ctx . LookupContextFor ( target . System ) ;
if ( description = = null )
{
if ( ! target . MemberPrivacy . CanAccess ( lookupContext ) )
throw Errors . LookupNotAllowed ;
if ( target . Description = = null )
if ( lookupContext = = LookupContext . ByOwner )
await ctx . Reply ( "This member does not have a description set. To set one, type `pk;s description <description>`." ) ;
else
await ctx . Reply ( "This member does not have a description set." ) ;
else if ( ctx . MatchFlag ( "r" , "raw" ) )
await ctx . Reply ( $"```\n{target.Description}\n```" ) ;
else
await ctx . Reply ( embed : new EmbedBuilder ( )
. WithTitle ( "Member description" )
. WithDescription ( target . Description )
. WithFooter ( $"To print the description with formatting, type `pk;m {target.Hid} description -raw`."
+ ( lookupContext = = LookupContext . ByOwner ? $" To clear it, type `pk;m {target.Hid} description -clear`." : "" ) )
. Build ( ) ) ;
return ;
}
2020-02-01 13:03:02 +01:00
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
2020-02-28 00:23:54 +01:00
if ( ctx . MatchFlag ( "c" , "clear" ) | | ctx . Match ( "clear" ) )
target . Description = null ;
else if ( description . IsLongerThan ( Limits . MaxDescriptionLength ) )
throw Errors . DescriptionTooLongError ( description . Length ) ;
else target . Description = description ;
2020-02-01 13:03:02 +01:00
await _data . SaveMember ( target ) ;
2020-02-28 00:23:54 +01:00
await ctx . Reply ( $"{Emojis.Success} Member description {(target.Description == null ? " cleared " : " changed ")}." ) ;
2020-02-01 13:03:02 +01:00
}
public async Task Pronouns ( Context ctx , PKMember target ) {
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
var pronouns = ctx . RemainderOrNull ( ) ;
if ( pronouns . IsLongerThan ( Limits . MaxPronounsLength ) ) throw Errors . MemberPronounsTooLongError ( pronouns . Length ) ;
target . Pronouns = pronouns ;
await _data . SaveMember ( target ) ;
await ctx . Reply ( $"{Emojis.Success} Member pronouns {(pronouns == null ? " cleared " : " changed ")}." ) ;
}
public async Task Color ( Context ctx , PKMember target )
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
var color = ctx . RemainderOrNull ( ) ;
if ( color ! = null )
{
if ( color . StartsWith ( "#" ) ) color = color . Substring ( 1 ) ;
if ( ! Regex . IsMatch ( color , "^[0-9a-fA-F]{6}$" ) ) throw Errors . InvalidColorError ( color ) ;
}
2020-02-25 16:37:52 +01:00
target . Color = color ? . ToLower ( ) ;
2020-02-01 13:03:02 +01:00
await _data . SaveMember ( target ) ;
await ctx . Reply ( $"{Emojis.Success} Member color {(color == null ? " cleared " : " changed ")}." ) ;
}
public async Task Birthday ( Context ctx , PKMember target )
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
LocalDate ? date = null ;
var birthday = ctx . RemainderOrNull ( ) ;
if ( birthday ! = null )
{
2020-02-12 15:16:19 +01:00
date = DateUtils . ParseDate ( birthday , true ) ;
2020-02-01 13:03:02 +01:00
if ( date = = null ) throw Errors . BirthdayParseError ( birthday ) ;
}
target . Birthday = date ;
await _data . SaveMember ( target ) ;
await ctx . Reply ( $"{Emojis.Success} Member birthdate {(date == null ? " cleared " : $" changed to { target . BirthdayString } ")}." ) ;
}
public async Task DisplayName ( Context ctx , PKMember target )
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
var newDisplayName = ctx . RemainderOrNull ( ) ;
target . DisplayName = newDisplayName ;
await _data . SaveMember ( target ) ;
var successStr = $"{Emojis.Success} " ;
if ( newDisplayName ! = null )
successStr + = $"Member display name changed. This member will now be proxied using the name \" { newDisplayName . SanitizeMentions ( ) } \ "." ;
else
successStr + = $"Member display name cleared. This member will now be proxied using their member name \" { target . Name . SanitizeMentions ( ) } \ "." ;
if ( ctx . Guild ! = null )
{
var memberGuildConfig = await _data . GetMemberGuildSettings ( target , ctx . Guild . Id ) ;
if ( memberGuildConfig . DisplayName ! = null )
successStr + = $" However, this member has a server name set in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name, \" { memberGuildConfig . DisplayName . SanitizeMentions ( ) } \ ", here." ;
}
await ctx . Reply ( successStr ) ;
}
public async Task ServerName ( Context ctx , PKMember target )
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
// TODO: allow setting server names for different servers/in DMs by ID
ctx . CheckGuildContext ( ) ;
var newServerName = ctx . RemainderOrNull ( ) ;
var guildSettings = await _data . GetMemberGuildSettings ( target , ctx . Guild . Id ) ;
guildSettings . DisplayName = newServerName ;
await _data . SetMemberGuildSettings ( target , ctx . Guild . Id , guildSettings ) ;
var successStr = $"{Emojis.Success} " ;
if ( newServerName ! = null )
successStr + = $"Member server name changed. This member will now be proxied using the name \" { newServerName . SanitizeMentions ( ) } \ " in this server ({ctx.Guild.Name.SanitizeMentions()})." ;
else if ( target . DisplayName ! = null )
successStr + = $"Member server name cleared. This member will now be proxied using their global display name \" { target . DisplayName . SanitizeMentions ( ) } \ " in this server ({ctx.Guild.Name.SanitizeMentions()})." ;
else
successStr + = $"Member server name cleared. This member will now be proxied using their member name \" { target . Name . SanitizeMentions ( ) } \ " in this server ({ctx.Guild.Name.SanitizeMentions()})." ;
await ctx . Reply ( successStr ) ;
}
public async Task KeepProxy ( Context ctx , PKMember target )
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
bool newValue ;
if ( ctx . Match ( "on" , "enabled" , "true" , "yes" ) ) newValue = true ;
else if ( ctx . Match ( "off" , "disabled" , "false" , "no" ) ) newValue = false ;
else if ( ctx . HasNext ( ) ) throw new PKSyntaxError ( "You must pass either \"on\" or \"off\"." ) ;
else newValue = ! target . KeepProxy ;
target . KeepProxy = newValue ;
await _data . SaveMember ( target ) ;
if ( newValue )
await ctx . Reply ( $"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying." ) ;
else
await ctx . Reply ( $"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying." ) ;
}
2020-02-07 22:20:40 +01:00
public async Task Privacy ( Context ctx , PKMember target , PrivacyLevel ? newValueFromCommand )
2020-02-01 13:03:02 +01:00
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
2020-02-07 22:20:40 +01:00
PrivacyLevel newValue ;
if ( ctx . Match ( "private" , "hide" , "hidden" , "on" , "enable" , "yes" ) ) newValue = PrivacyLevel . Private ;
else if ( ctx . Match ( "public" , "show" , "shown" , "displayed" , "off" , "disable" , "no" ) ) newValue = PrivacyLevel . Public ;
2020-02-01 13:03:02 +01:00
else if ( ctx . HasNext ( ) ) throw new PKSyntaxError ( "You must pass either \"private\" or \"public\"." ) ;
2020-02-07 22:20:40 +01:00
// If we're getting a value from command (eg. "pk;m <name> private" == always private, "pk;m <name> public == always public"), use that instead of parsing/toggling
else newValue = newValueFromCommand ? ? ( target . MemberPrivacy ! = PrivacyLevel . Private ? PrivacyLevel . Private : PrivacyLevel . Public ) ;
2020-02-01 13:03:02 +01:00
2020-02-07 22:20:40 +01:00
target . MemberPrivacy = newValue ;
2020-02-01 13:03:02 +01:00
await _data . SaveMember ( target ) ;
2020-02-07 22:20:40 +01:00
if ( newValue = = PrivacyLevel . Private )
2020-02-01 13:03:02 +01:00
await ctx . Reply ( $"{Emojis.Success} Member privacy set to **private**. This member will no longer show up in member lists and will return limited information when queried by other accounts." ) ;
else
await ctx . Reply ( $"{Emojis.Success} Member privacy set to **public**. This member will now show up in member lists and will return all information when queried by other accounts." ) ;
}
public async Task Delete ( Context ctx , PKMember target )
{
if ( ctx . System = = null ) throw Errors . NoSystemError ;
if ( target . System ! = ctx . System . Id ) throw Errors . NotOwnMemberError ;
await ctx . Reply ( $"{Emojis.Warn} Are you sure you want to delete \" { target . Name . SanitizeMentions ( ) } \ "? If so, reply to this message with the member's ID (`{target.Hid}`). __***This cannot be undone!***__" ) ;
if ( ! await ctx . ConfirmWithReply ( target . Hid ) ) throw Errors . MemberDeleteCancelled ;
await _data . DeleteMember ( target ) ;
await ctx . Reply ( $"{Emojis.Success} Member deleted." ) ;
}
}
}