2019-05-16 23:23:09 +00:00
using System.Linq ;
2019-05-11 21:56:56 +00:00
using System.Text.RegularExpressions ;
2019-04-27 14:30:34 +00:00
using System.Threading.Tasks ;
2019-05-16 23:23:09 +00:00
using Discord ;
2019-04-27 14:30:34 +00:00
using Discord.Commands ;
2019-05-13 20:44:49 +00:00
using NodaTime ;
2019-07-09 22:19:18 +00:00
using PluralKit.Core ;
2019-04-27 14:30:34 +00:00
namespace PluralKit.Bot.Commands
{
[Group("member")]
2019-07-10 07:35:37 +00:00
[Alias("m")]
2019-04-27 14:30:34 +00:00
public class MemberCommands : ContextParameterModuleBase < PKMember >
{
2019-05-11 22:44:02 +00:00
public SystemStore Systems { get ; set ; }
2019-04-27 14:30:34 +00:00
public MemberStore Members { get ; set ; }
2019-05-11 22:44:02 +00:00
public EmbedService Embeds { get ; set ; }
2019-04-27 14:30:34 +00:00
public override string Prefix = > "member" ;
public override string ContextNoun = > "member" ;
[Command("new")]
2019-07-10 11:55:48 +00:00
[Alias("n", "add", "create", "register")]
2019-04-27 14:30:34 +00:00
[Remarks("member new <name>")]
2019-04-29 17:43:09 +00:00
[MustHaveSystem]
2019-04-27 14:30:34 +00:00
public async Task NewMember ( [ Remainder ] string memberName ) {
2019-04-29 18:28:53 +00:00
// Hard name length cap
if ( memberName . Length > Limits . MaxMemberNameLength ) throw Errors . MemberNameTooLongError ( memberName . Length ) ;
2019-04-27 14:30:34 +00:00
// Warn if member name will be unproxyable (with/without tag)
2019-04-29 17:43:09 +00:00
if ( memberName . Length > Context . SenderSystem . MaxMemberNameLength ) {
var msg = await Context . Channel . SendMessageAsync ( $"{Emojis.Warn} Member name too long ({memberName.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to create it anyway? (You can change the name later)" ) ;
2019-04-27 14:30:34 +00:00
if ( ! await Context . PromptYesNo ( msg ) ) throw new PKError ( "Member creation cancelled." ) ;
}
// Warn if there's already a member by this name
var existingMember = await Members . GetByName ( Context . SenderSystem , memberName ) ;
if ( existingMember ! = null ) {
2019-07-10 11:44:03 +00:00
var msg = await Context . Channel . SendMessageAsync ( $"{Emojis.Warn} You already have a member in your system with the name \" { existingMember . Name . Sanitize ( ) } \ " (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?" ) ;
2019-04-27 14:30:34 +00:00
if ( ! await Context . PromptYesNo ( msg ) ) throw new PKError ( "Member creation cancelled." ) ;
}
// Create the member
var member = await Members . Create ( Context . SenderSystem , memberName ) ;
2019-04-29 17:43:09 +00:00
// Send confirmation and space hint
2019-07-10 11:44:03 +00:00
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member \" { memberName . Sanitize ( ) } \ " (`{member.Hid}`) registered! Type `pk;help member` for a list of commands to edit this member." ) ;
2019-07-14 19:19:48 +00:00
if ( memberName . Contains ( " " ) ) await Context . Channel . SendMessageAsync ( $"{Emojis.Note} Note that this member's name contains spaces. You will need to surround it with \" double quotes \ " when using commands referring to it, or just use the member's 5-character ID (which is `{member.Hid}`)." ) ;
2019-04-27 14:30:34 +00:00
}
2019-04-29 17:43:09 +00:00
[Command("rename")]
[Alias("name", "changename", "setname")]
[Remarks("member <member> rename <newname>")]
[MustPassOwnMember]
public async Task RenameMember ( [ Remainder ] string newName ) {
// TODO: this method is pretty much a 1:1 copy/paste of the above creation method, find a way to clean?
2019-04-29 18:28:53 +00:00
// Hard name length cap
if ( newName . Length > Limits . MaxMemberNameLength ) throw Errors . MemberNameTooLongError ( newName . Length ) ;
2019-04-29 17:43:09 +00:00
// Warn if member name will be unproxyable (with/without tag)
if ( newName . Length > Context . SenderSystem . MaxMemberNameLength ) {
var msg = await Context . Channel . SendMessageAsync ( $"{Emojis.Warn} New member name too long ({newName.Length} > {Context.SenderSystem.MaxMemberNameLength} characters), this member will be unproxyable. Do you want to change it anyway?" ) ;
if ( ! await Context . PromptYesNo ( msg ) ) throw new PKError ( "Member renaming cancelled." ) ;
}
// Warn if there's already a member by this name
var existingMember = await Members . GetByName ( Context . SenderSystem , newName ) ;
if ( existingMember ! = null ) {
2019-07-10 11:44:03 +00:00
var msg = await Context . Channel . SendMessageAsync ( $"{Emojis.Warn} You already have a member in your system with the name \" { existingMember . Name . Sanitize ( ) } \ " (`{existingMember.Hid}`). Do you want to rename this member to that name too?" ) ;
2019-04-29 17:43:09 +00:00
if ( ! await Context . PromptYesNo ( msg ) ) throw new PKError ( "Member renaming cancelled." ) ;
}
2019-05-11 21:56:56 +00:00
// Rename the member
2019-04-29 17:43:09 +00:00
ContextEntity . Name = newName ;
await Members . Save ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member renamed." ) ;
if ( newName . Contains ( " " ) ) await Context . Channel . SendMessageAsync ( $"{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." ) ;
}
2019-04-29 18:33:21 +00:00
[Command("description")]
2019-07-10 11:55:48 +00:00
[Alias("info", "bio", "text", "desc")]
2019-05-11 21:56:56 +00:00
[Remarks("member <member> description <description>")]
2019-04-29 18:33:21 +00:00
[MustPassOwnMember]
public async Task MemberDescription ( [ Remainder ] string description = null ) {
2019-05-11 21:56:56 +00:00
if ( description . IsLongerThan ( Limits . MaxDescriptionLength ) ) throw Errors . DescriptionTooLongError ( description . Length ) ;
2019-04-29 18:33:21 +00:00
ContextEntity . Description = description ;
await Members . Save ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member description {(description == null ? " cleared " : " changed ")}." ) ;
}
2019-04-29 18:36:09 +00:00
[Command("pronouns")]
[Alias("pronoun")]
2019-05-11 21:56:56 +00:00
[Remarks("member <member> pronouns <pronouns>")]
2019-04-29 18:36:09 +00:00
[MustPassOwnMember]
public async Task MemberPronouns ( [ Remainder ] string pronouns = null ) {
2019-05-11 21:56:56 +00:00
if ( pronouns . IsLongerThan ( Limits . MaxPronounsLength ) ) throw Errors . MemberPronounsTooLongError ( pronouns . Length ) ;
2019-04-29 18:36:09 +00:00
ContextEntity . Pronouns = pronouns ;
await Members . Save ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member pronouns {(pronouns == null ? " cleared " : " changed ")}." ) ;
}
2019-05-11 21:56:56 +00:00
[Command("color")]
[Alias("colour")]
[Remarks("member <member> color <color>")]
[MustPassOwnMember]
public async Task MemberColor ( [ Remainder ] string color = null )
{
if ( color ! = null )
{
if ( color . StartsWith ( "#" ) ) color = color . Substring ( 1 ) ;
2019-07-10 08:02:46 +00:00
if ( ! Regex . IsMatch ( color , "^[0-9a-f]{6}$" ) ) throw Errors . InvalidColorError ( color ) ;
2019-05-11 21:56:56 +00:00
}
ContextEntity . Color = color ;
await Members . Save ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member color {(color == null ? " cleared " : " changed ")}." ) ;
}
2019-05-11 22:44:02 +00:00
2019-05-13 20:44:49 +00:00
[Command("birthday")]
[Alias("birthdate", "bday", "cakeday", "bdate")]
[Remarks("member <member> birthday <birthday>")]
[MustPassOwnMember]
public async Task MemberBirthday ( [ Remainder ] string birthday = null )
{
LocalDate ? date = null ;
if ( birthday ! = null )
{
date = PluralKit . Utils . ParseDate ( birthday , true ) ;
if ( date = = null ) throw Errors . BirthdayParseError ( birthday ) ;
}
ContextEntity . Birthday = date ;
await Members . Save ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member birthdate {(date == null ? " cleared " : $" changed to { ContextEntity . BirthdayString } ")}." ) ;
}
2019-05-13 20:56:22 +00:00
[Command("proxy")]
[Alias("proxy", "tags", "proxytags", "brackets")]
[Remarks("member <member> proxy <proxy tags>")]
[MustPassOwnMember]
public async Task MemberProxy ( [ Remainder ] string exampleProxy = null )
{
// Handling the clear case in an if here to keep the body dedented
if ( exampleProxy = = null )
{
// Just reset and send OK message
ContextEntity . Prefix = null ;
ContextEntity . Suffix = null ;
await Members . Save ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member proxy tags cleared." ) ;
return ;
}
// Make sure there's one and only one instance of "text" in the example proxy given
var prefixAndSuffix = exampleProxy . Split ( "text" ) ;
if ( prefixAndSuffix . Length < 2 ) throw Errors . ProxyMustHaveText ;
if ( prefixAndSuffix . Length > 2 ) throw Errors . ProxyMultipleText ;
// If the prefix/suffix is empty, use "null" instead (for DB)
ContextEntity . Prefix = prefixAndSuffix [ 0 ] . Length > 0 ? prefixAndSuffix [ 0 ] : null ;
ContextEntity . Suffix = prefixAndSuffix [ 1 ] . Length > 0 ? prefixAndSuffix [ 1 ] : null ;
await Members . Save ( ContextEntity ) ;
2019-07-10 11:44:03 +00:00
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member proxy tags changed to `{ContextEntity.ProxyString.Sanitize()}`. Try proxying now!" ) ;
2019-05-13 20:56:22 +00:00
}
2019-05-13 21:08:44 +00:00
[Command("delete")]
2019-07-10 11:55:48 +00:00
[Alias("remove", "destroy", "erase", "yeet")]
2019-05-13 21:08:44 +00:00
[Remarks("member <member> delete")]
[MustPassOwnMember]
public async Task MemberDelete ( )
{
2019-07-10 11:44:03 +00:00
await Context . Channel . SendMessageAsync ( $"{Emojis.Warn} Are you sure you want to delete \" { ContextEntity . Name . Sanitize ( ) } \ "? If so, reply to this message with the member's ID (`{ContextEntity.Hid}`). __***This cannot be undone!***__" ) ;
2019-05-13 21:08:44 +00:00
if ( ! await Context . ConfirmWithReply ( ContextEntity . Hid ) ) throw Errors . MemberDeleteCancelled ;
await Members . Delete ( ContextEntity ) ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member deleted." ) ;
}
2019-07-16 18:17:04 +00:00
[Command("avatar")]
[Alias("profile", "picture", "icon", "image", "pic", "pfp")]
[Remarks("member <member> avatar <avatar url>")]
[MustPassOwnMember]
public async Task MemberAvatarByMention ( IUser member )
{
if ( member . AvatarId = = null ) throw Errors . UserHasNoAvatar ;
ContextEntity . AvatarUrl = member . GetAvatarUrl ( ImageFormat . Png , size : 256 ) ;
var embed = new EmbedBuilder ( ) . WithImageUrl ( ContextEntity . AvatarUrl ) . Build ( ) ;
await Context . Channel . SendMessageAsync (
$"{Emojis.Success} Member avatar changed to {member.Username}'s avatar! {Emojis.Warn} Please note that if {member.Username} changes their avatar, the webhook's avatar will need to be re-set." , embed : embed ) ;
}
2019-05-16 23:23:09 +00:00
[Command("avatar")]
[Alias("profile", "picture", "icon", "image", "pic", "pfp")]
[Remarks("member <member> avatar <avatar url>")]
[MustPassOwnMember]
public async Task MemberAvatar ( [ Remainder ] string avatarUrl = null )
{
string url = avatarUrl ? ? Context . Message . Attachments . FirstOrDefault ( ) ? . ProxyUrl ;
if ( url ! = null ) await Context . BusyIndicator ( ( ) = > Utils . VerifyAvatarOrThrow ( url ) ) ;
ContextEntity . AvatarUrl = url ;
await Members . Save ( ContextEntity ) ;
var embed = url ! = null ? new EmbedBuilder ( ) . WithImageUrl ( url ) . Build ( ) : null ;
await Context . Channel . SendMessageAsync ( $"{Emojis.Success} Member avatar {(url == null ? " cleared " : " changed ")}." , embed : embed ) ;
}
2019-05-11 22:44:02 +00:00
[Command]
2019-05-13 20:44:49 +00:00
[Alias("view", "show", "info")]
2019-07-15 13:28:32 +00:00
[Remarks("member <member>")]
2019-05-11 22:44:02 +00:00
public async Task ViewMember ( PKMember member )
{
2019-05-13 20:44:49 +00:00
var system = await Systems . GetById ( member . System ) ;
2019-05-11 22:44:02 +00:00
await Context . Channel . SendMessageAsync ( embed : await Embeds . CreateMemberEmbed ( system , member ) ) ;
}
2019-05-16 23:23:09 +00:00
2019-04-27 14:30:34 +00:00
public override async Task < PKMember > ReadContextParameterAsync ( string value )
{
var res = await new PKMemberTypeReader ( ) . ReadAsync ( Context , value , _services ) ;
return res . IsSuccess ? res . BestMatch as PKMember : null ;
}
}
}