Example #1
0
        private async Task AvatarCommandTree(AvatarLocation location, Context ctx, PKMember target, MemberGuildSettings?guildData)
        {
            // First, see if we need to *clear*
            if (await ctx.MatchClear(location == AvatarLocation.Server ? "this member's server avatar" : "this member's avatar"))
            {
                ctx.CheckSystem().CheckOwnMember(target);
                await AvatarClear(location, ctx, target, guildData);

                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
                await AvatarShow(location, ctx, target, guildData);

                return;
            }

            ctx.CheckSystem().CheckOwnMember(target);
            await AvatarUtils.VerifyAvatarOrThrow(avatarArg.Value.Url);

            await UpdateAvatar(location, ctx, target, avatarArg.Value.Url);
            await PrintResponse(location, ctx, target, avatarArg.Value, guildData);
        }
        public async Task Avatar(Context ctx)
        {
            ctx.CheckSystem();

            if (ctx.Match("clear") || ctx.MatchFlag("c", "clear"))
            {
                ctx.System.AvatarUrl = null;
                await _data.SaveSystem(ctx.System);

                await ctx.Reply($"{Emojis.Success} System avatar cleared.");

                return;
            }
            else if (ctx.RemainderOrNull() == null && ctx.Message.Attachments.Count == 0)
            {
                if ((ctx.System.AvatarUrl?.Trim() ?? "").Length > 0)
                {
                    var eb = new EmbedBuilder()
                             .WithTitle($"System avatar")
                             .WithImageUrl(ctx.System.AvatarUrl)
                             .WithDescription($"To clear, use `pk;system avatar clear`.");
                    await ctx.Reply(embed : eb.Build());
                }
                else
                {
                    throw new PKSyntaxError($"This system does not have an avatar set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
                }

                return;
            }

            var member = await ctx.MatchUser();

            if (member != null)
            {
                if (member.AvatarId == null)
                {
                    throw Errors.UserHasNoAvatar;
                }
                ctx.System.AvatarUrl = member.GetAvatarUrl(ImageFormat.Png, size: 256);
                await _data.SaveSystem(ctx.System);

                var embed = new EmbedBuilder().WithImageUrl(ctx.System.AvatarUrl).Build();
                await ctx.Reply(
                    $"{Emojis.Success} System avatar changed to {member.Username}'s avatar! {Emojis.Warn} Please note that if {member.Username} changes their avatar, the system's avatar will need to be re-set.", embed : embed);
            }
            else
            {
                // They can't both be null - otherwise we would've hit the conditional at the very top
                string url = ctx.RemainderOrNull() ?? ctx.Message.Attachments.FirstOrDefault()?.ProxyUrl;
                await ctx.BusyIndicator(() => AvatarUtils.VerifyAvatarOrThrow(url));

                ctx.System.AvatarUrl = url;
                await _data.SaveSystem(ctx.System);

                var embed = url != null ? new EmbedBuilder().WithImageUrl(url).Build() : null;
                await ctx.Reply($"{Emojis.Success} System avatar changed.", embed : embed);
            }
        }
Example #3
0
 private static Task ValidateUrl(string url)
 {
     if (url.Length > Limits.MaxUriLength)
     {
         throw Errors.InvalidUrl(url);
     }
     return(AvatarUtils.VerifyAvatarOrThrow(url));
 }
Example #4
0
        private async Task AvatarFromAttachment(AvatarLocation location, Context ctx, PKMember target, DiscordAttachment attachment)
        {
            ctx.CheckSystem().CheckOwnMember(target);
            await AvatarUtils.VerifyAvatarOrThrow(attachment.Url);

            await UpdateAvatar(location, ctx, target, attachment.Url);

            if (location == AvatarLocation.Server)
            {
                await ctx.Reply($"{Emojis.Success} Member server avatar changed to attached image. This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**). Please note that if you delete the message containing the attachment, the avatar will stop working.");
            }
            else if (location == AvatarLocation.Member)
            {
                await ctx.Reply($"{Emojis.Success} Member avatar changed to attached image. Please note that if you delete the message containing the attachment, the avatar will stop working.");
            }
        }
Example #5
0
        private async Task AvatarFromArg(AvatarLocation location, Context ctx, PKMember target, string url)
        {
            ctx.CheckSystem().CheckOwnMember(target);
            if (url.Length > Limits.MaxUriLength)
            {
                throw Errors.InvalidUrl(url);
            }
            await AvatarUtils.VerifyAvatarOrThrow(url);

            await UpdateAvatar(location, ctx, target, url);

            var embed = new DiscordEmbedBuilder().WithImageUrl(url).Build();

            if (location == AvatarLocation.Server)
            {
                await ctx.Reply($"{Emojis.Success} Member server avatar changed. This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).", embed : embed);
            }
        }
Example #6
0
        public async Task Avatar(Context ctx, PKMember target)
        {
            var guildData = ctx.Guild != null ? await _data.GetMemberGuildSettings(target, ctx.Guild.Id) : null;

            if (ctx.RemainderOrNull() == null && ctx.Message.Attachments.Count == 0)
            {
                if ((target.AvatarUrl?.Trim() ?? "").Length > 0)
                {
                    var eb = new EmbedBuilder()
                             .WithTitle($"{target.Name.SanitizeMentions()}'s avatar")
                             .WithImageUrl(target.AvatarUrl);
                    if (target.System == ctx.System?.Id)
                    {
                        eb.WithDescription($"To clear, use `pk;member {target.Hid} avatar clear`.");
                    }
                    await ctx.Reply(embed : eb.Build());
                }
                else
                {
                    if (target.System == ctx.System?.Id)
                    {
                        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.");
                }

                return;
            }

            if (ctx.System == null)
            {
                throw Errors.NoSystemError;
            }
            if (target.System != ctx.System.Id)
            {
                throw Errors.NotOwnMemberError;
            }

            if (ctx.Match("clear", "remove", "reset"))
            {
                target.AvatarUrl = null;
                await _data.SaveMember(target);

                if (guildData?.AvatarUrl != null)
                {
                    await ctx.Reply($"{Emojis.Success} Member avatar cleared. Note that this member has a server-specific avatar set here, type `pk;member {target.Hid} serveravatar clear` if you wish to clear that too.");
                }
                else
                {
                    await ctx.Reply($"{Emojis.Success} Member avatar cleared.");
                }
            }
            else if (await ctx.MatchUser() is IUser user)
            {
                if (user.AvatarId == null)
                {
                    throw Errors.UserHasNoAvatar;
                }
                target.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256);

                await _data.SaveMember(target);

                var embed = new EmbedBuilder().WithImageUrl(target.AvatarUrl).Build();
                await ctx.Reply(
                    $"{Emojis.Success} Member avatar changed to {user.Username}'s avatar! {Emojis.Warn} Please note that if {user.Username} changes their avatar, the member's avatar will need to be re-set.", embed : embed);
            }
            else if (ctx.RemainderOrNull() is string url)
            {
                await AvatarUtils.VerifyAvatarOrThrow(url);

                target.AvatarUrl = url;
                await _data.SaveMember(target);

                var embed = new EmbedBuilder().WithImageUrl(url).Build();
                await ctx.Reply($"{Emojis.Success} Member avatar changed.", embed : embed);
            }
            else if (ctx.Message.Attachments.FirstOrDefault() is Attachment attachment)
            {
                await AvatarUtils.VerifyAvatarOrThrow(attachment.Url);

                target.AvatarUrl = attachment.Url;
                await _data.SaveMember(target);

                await ctx.Reply($"{Emojis.Success} Member avatar changed to attached image. Please note that if you delete the message containing the attachment, the avatar will stop working.");
            }
            // No-arguments no-attachment case covered by conditional at the very top
        }
Example #7
0
        public async Task ServerAvatar(Context ctx, PKMember target)
        {
            ctx.CheckGuildContext();
            var guildData = await _data.GetMemberGuildSettings(target, ctx.Guild.Id);

            if (ctx.RemainderOrNull() == null && ctx.Message.Attachments.Count == 0)
            {
                if ((guildData.AvatarUrl?.Trim() ?? "").Length > 0)
                {
                    var eb = new EmbedBuilder()
                             .WithTitle($"{target.Name.SanitizeMentions()}'s server avatar (for {ctx.Guild.Name})")
                             .WithImageUrl(guildData.AvatarUrl);
                    if (target.System == ctx.System?.Id)
                    {
                        eb.WithDescription($"To clear, use `pk;member {target.Hid} serveravatar clear`.");
                    }
                    await ctx.Reply(embed : eb.Build());
                }
                else
                {
                    throw new PKError($"This member does not have a server avatar set. Type `pk;member {target.Hid} avatar` to see their global avatar.");
                }

                return;
            }

            if (ctx.System == null)
            {
                throw Errors.NoSystemError;
            }
            if (target.System != ctx.System.Id)
            {
                throw Errors.NotOwnMemberError;
            }

            if (ctx.Match("clear", "remove", "reset"))
            {
                guildData.AvatarUrl = null;
                await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildData);

                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 (await ctx.MatchUser() is IUser user)
            {
                if (user.AvatarId == null)
                {
                    throw Errors.UserHasNoAvatar;
                }
                guildData.AvatarUrl = user.GetAvatarUrl(ImageFormat.Png, size: 256);
                await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildData);

                var embed = new EmbedBuilder().WithImageUrl(guildData.AvatarUrl).Build();
                await ctx.Reply(
                    $"{Emojis.Success} Member server avatar changed to {user.Username}'s avatar! This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**). {Emojis.Warn} Please note that if {user.Username} changes their avatar, the member's server avatar will need to be re-set.", embed : embed);
            }
            else if (ctx.RemainderOrNull() is string url)
            {
                await AvatarUtils.VerifyAvatarOrThrow(url);

                guildData.AvatarUrl = url;
                await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildData);

                var embed = new EmbedBuilder().WithImageUrl(url).Build();
                await ctx.Reply($"{Emojis.Success} Member server avatar changed. This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).", embed : embed);
            }
            else if (ctx.Message.Attachments.FirstOrDefault() is Attachment attachment)
            {
                await AvatarUtils.VerifyAvatarOrThrow(attachment.Url);

                guildData.AvatarUrl = attachment.Url;
                await _data.SetMemberGuildSettings(target, ctx.Guild.Id, guildData);

                await ctx.Reply($"{Emojis.Success} Member server avatar changed to attached image. This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**). Please note that if you delete the message containing the attachment, the avatar will stop working.");
            }
            // No-arguments no-attachment case covered by conditional at the very top
        }
Example #8
0
        public async Task GroupIcon(Context ctx, PKGroup target)
        {
            async Task ClearIcon()
            {
                ctx.CheckOwnGroup(target);

                await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {
                    Icon = null
                }));

                await ctx.Reply($"{Emojis.Success} Group icon cleared.");
            }

            async Task SetIcon(ParsedImage img)
            {
                ctx.CheckOwnGroup(target);

                await AvatarUtils.VerifyAvatarOrThrow(img.Url);

                await _db.Execute(c => _repo.UpdateGroup(c, target.Id, new GroupPatch {
                    Icon = img.Url
                }));

                var msg = img.Source switch
                {
                    AvatarSource.User => $"{Emojis.Success} Group icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the group icon will need to be re-set.",
                    AvatarSource.Url => $"{Emojis.Success} Group icon changed to the image at the given URL.",
                    AvatarSource.Attachment => $"{Emojis.Success} Group icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the group icon will stop working.",
                    _ => throw new ArgumentOutOfRangeException()
                };

                // The attachment's already right there, no need to preview it.
                var hasEmbed = img.Source != AvatarSource.Attachment;

                await(hasEmbed
                    ? ctx.Reply(msg, embed: new DiscordEmbedBuilder().WithImageUrl(img.Url).Build())
                    : ctx.Reply(msg));
            }

            async Task ShowIcon()
            {
                if ((target.Icon?.Trim() ?? "").Length > 0)
                {
                    var eb = new DiscordEmbedBuilder()
                             .WithTitle("Group icon")
                             .WithImageUrl(target.Icon);

                    if (target.System == ctx.System?.Id)
                    {
                        eb.WithDescription($"To clear, use `pk;group {target.Reference()} icon -clear`.");
                    }

                    await ctx.Reply(embed : eb.Build());
                }
                else
                {
                    throw new PKSyntaxError("This group does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
                }
            }

            if (await ctx.MatchClear("this group's icon"))
            {
                await ClearIcon();
            }
            else if (await ctx.MatchImage() is {} img)
            {
                await SetIcon(img);
            }
Example #9
0
        public async Task Avatar(Context ctx)
        {
            ctx.CheckSystem();

            async Task ClearIcon()
            {
                await _db.Execute(c => c.UpdateSystem(ctx.System.Id, new SystemPatch {
                    AvatarUrl = null
                }));

                await ctx.Reply($"{Emojis.Success} System icon cleared.");
            }

            async Task SetIcon(ParsedImage img)
            {
                if (img.Url.Length > Limits.MaxUriLength)
                {
                    throw Errors.InvalidUrl(img.Url);
                }
                await AvatarUtils.VerifyAvatarOrThrow(img.Url);

                await _db.Execute(c => c.UpdateSystem(ctx.System.Id, new SystemPatch {
                    AvatarUrl = img.Url
                }));

                var msg = img.Source switch
                {
                    AvatarSource.User => $"{Emojis.Success} System icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon will need to be re-set.",
                    AvatarSource.Url => $"{Emojis.Success} System icon changed to the image at the given URL.",
                    AvatarSource.Attachment => $"{Emojis.Success} System icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon will stop working.",
                    _ => throw new ArgumentOutOfRangeException()
                };

                // The attachment's already right there, no need to preview it.
                var hasEmbed = img.Source != AvatarSource.Attachment;

                await(hasEmbed
                    ? ctx.Reply(msg, embed: new DiscordEmbedBuilder().WithImageUrl(img.Url).Build())
                    : ctx.Reply(msg));
            }

            async Task ShowIcon()
            {
                if ((ctx.System.AvatarUrl?.Trim() ?? "").Length > 0)
                {
                    var eb = new DiscordEmbedBuilder()
                             .WithTitle("System icon")
                             .WithImageUrl(ctx.System.AvatarUrl)
                             .WithDescription("To clear, use `pk;system icon clear`.");
                    await ctx.Reply(embed : eb.Build());
                }
                else
                {
                    throw new PKSyntaxError("This system does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention.");
                }
            }

            if (ctx.MatchClear())
            {
                await ClearIcon();
            }
            else if (await ctx.MatchImage() is {} img)
            {
                await SetIcon(img);
            }
Example #10
0
        public async Task NewMember(Context ctx)
        {
            if (ctx.System == null)
            {
                throw Errors.NoSystemError;
            }
            var memberName = ctx.RemainderOrNull() ?? throw new PKSyntaxError("You must pass a member name.");

            // Hard name length cap
            if (memberName.Length > Limits.MaxMemberNameLength)
            {
                throw Errors.MemberNameTooLongError(memberName.Length);
            }

            // Warn if there's already a member by this name
            var existingMember = await _db.Execute(c => _repo.GetMemberByName(c, ctx.System.Id, memberName));

            if (existingMember != null)
            {
                var msg = $"{Emojis.Warn} You already have a member in your system with the name \"{existingMember.NameFor(ctx)}\" (with ID `{existingMember.Hid}`). Do you want to create another member with the same name?";
                if (!await ctx.PromptYesNo(msg, "Create"))
                {
                    throw new PKError("Member creation cancelled.");
                }
            }

            await using var conn = await _db.Obtain();

            // Enforce per-system member limit
            var memberCount = await _repo.GetSystemMemberCount(conn, ctx.System.Id);

            var memberLimit = ctx.System.MemberLimitOverride ?? Limits.MaxMemberCount;

            if (memberCount >= memberLimit)
            {
                throw Errors.MemberLimitReachedError(memberLimit);
            }

            // Create the member
            var member = await _repo.CreateMember(conn, ctx.System.Id, memberName);

            memberCount++;

            // Try to match an image attached to the message
            var       avatarArg       = ctx.Message.Attachments.FirstOrDefault();
            Exception imageMatchError = null;

            if (avatarArg != null)
            {
                try {
                    await AvatarUtils.VerifyAvatarOrThrow(avatarArg.Url);

                    await _db.Execute(conn => _repo.UpdateMember(conn, member.Id, new MemberPatch {
                        AvatarUrl = avatarArg.Url
                    }));
                } catch (Exception e) {
                    imageMatchError = e;
                }
            }

            // Send confirmation and space hint
            await ctx.Reply($"{Emojis.Success} Member \"{memberName}\" (`{member.Hid}`) registered! Check out the getting started page for how to get a member up and running: https://pluralkit.me/start#create-a-member");

            if (await _db.Execute(conn => conn.QuerySingleAsync <bool>("select has_private_members(@System)",
                                                                       new { System = ctx.System.Id }))) //if has private members
            {
                await ctx.Reply($"{Emojis.Warn} This member is currently **public**. To change this, use `pk;member {member.Hid} private`.");
            }
            if (avatarArg != null)
            {
                if (imageMatchError == null)
                {
                    await ctx.Reply($"{Emojis.Success} Member avatar set to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the avatar will stop working.");
                }
                else
                {
                    await ctx.Reply($"{Emojis.Error} Couldn't set avatar: {imageMatchError.Message}");
                }
            }
            if (memberName.Contains(" "))
            {
                await ctx.Reply($"{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}`).");
            }
            if (memberCount >= memberLimit)
            {
                await ctx.Reply($"{Emojis.Warn} You have reached the per-system member limit ({memberLimit}). You will be unable to create additional members until existing members are deleted.");
            }
            else if (memberCount >= Limits.MaxMembersWarnThreshold(memberLimit))
            {
                await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {memberLimit} members). Please review your member list for unused or duplicate members.");
            }
        }