mirror of
				https://gitlab.com/Kwoth/nadekobot.git
				synced 2025-11-04 00:34:26 -05:00 
			
		
		
		
	Finished new response system
This commit is contained in:
		@@ -95,7 +95,7 @@ public partial class Administration : NadekoModule<AdministrationService>
 | 
			
		||||
        var guild = (SocketGuild)ctx.Guild;
 | 
			
		||||
        var (enabled, channels) = _service.GetDelMsgOnCmdData(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                       .WithOkColor()
 | 
			
		||||
                       .WithTitle(GetText(strs.server_delmsgoncmd))
 | 
			
		||||
                       .WithDescription(enabled ? "✅" : "❌");
 | 
			
		||||
 
 | 
			
		||||
@@ -35,22 +35,22 @@ public partial class Administration
 | 
			
		||||
        {
 | 
			
		||||
            var result = _ds.SelectSql(sql);
 | 
			
		||||
 | 
			
		||||
            return ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                cur =>
 | 
			
		||||
                {
 | 
			
		||||
                    var items = result.Results.Skip(cur * 20).Take(20).ToList();
 | 
			
		||||
            return Response()
 | 
			
		||||
                   .Paginated()
 | 
			
		||||
                   .Items(result.Results)
 | 
			
		||||
                   .PageSize(20)
 | 
			
		||||
                   .Page((items, _) =>
 | 
			
		||||
                   {
 | 
			
		||||
                       if (!items.Any())
 | 
			
		||||
                           return _sender.CreateEmbed().WithErrorColor().WithFooter(sql).WithDescription("-");
 | 
			
		||||
 | 
			
		||||
                    if (!items.Any())
 | 
			
		||||
                        return new EmbedBuilder().WithErrorColor().WithFooter(sql).WithDescription("-");
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                       return _sender.CreateEmbed()
 | 
			
		||||
                              .WithOkColor()
 | 
			
		||||
                              .WithFooter(sql)
 | 
			
		||||
                              .WithTitle(string.Join(" ║ ", result.ColumnNames))
 | 
			
		||||
                              .WithDescription(string.Join('\n', items.Select(x => string.Join(" ║ ", x))));
 | 
			
		||||
                },
 | 
			
		||||
                result.Results.Count,
 | 
			
		||||
                20);
 | 
			
		||||
                   })
 | 
			
		||||
                   .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -99,9 +99,9 @@ public partial class Administration
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                               .WithTitle(GetText(strs.sql_confirm_exec))
 | 
			
		||||
                               .WithDescription(Format.Code(sql));
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                            .WithTitle(GetText(strs.sql_confirm_exec))
 | 
			
		||||
                            .WithDescription(Format.Code(sql));
 | 
			
		||||
 | 
			
		||||
                if (!await PromptUserConfirmAsync(embed))
 | 
			
		||||
                    return;
 | 
			
		||||
@@ -119,8 +119,8 @@ public partial class Administration
 | 
			
		||||
        [OwnerOnly]
 | 
			
		||||
        public async Task PurgeUser(ulong userId)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                           .WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                .WithDescription(GetText(strs.purge_user_confirm(Format.Bold(userId.ToString()))));
 | 
			
		||||
 | 
			
		||||
            if (!await PromptUserConfirmAsync(embed))
 | 
			
		||||
                return;
 | 
			
		||||
 
 | 
			
		||||
@@ -97,7 +97,7 @@ public class GreetService : INService, IReadyExecutor
 | 
			
		||||
        {
 | 
			
		||||
            var newContent = await _repSvc.ReplaceAsync(toSend,
 | 
			
		||||
                new(client: _client, guild: user.Guild, channel: channel, users: user));
 | 
			
		||||
            var toDelete = await channel.SendAsync(newContent);
 | 
			
		||||
            var toDelete = await _sender.Response(channel).Text(newContent).SendAsync();
 | 
			
		||||
            if (conf.BoostMessageDeleteAfter > 0)
 | 
			
		||||
                toDelete.DeleteAfter(conf.BoostMessageDeleteAfter);
 | 
			
		||||
 | 
			
		||||
@@ -217,12 +217,12 @@ public class GreetService : INService, IReadyExecutor
 | 
			
		||||
        text = await _repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var toDelete = await channel.SendAsync(text);
 | 
			
		||||
            var toDelete = await _sender.Response(channel).Text(text).SendAsync();
 | 
			
		||||
            if (conf.AutoDeleteByeMessagesTimer > 0)
 | 
			
		||||
                toDelete.DeleteAfter(conf.AutoDeleteByeMessagesTimer);
 | 
			
		||||
        }
 | 
			
		||||
        catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions ||
 | 
			
		||||
                                       ex.DiscordCode == DiscordErrorCode.UnknownChannel)
 | 
			
		||||
        catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
 | 
			
		||||
                                       || ex.DiscordCode == DiscordErrorCode.UnknownChannel)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning(ex,
 | 
			
		||||
                "Missing permissions to send a bye message, the bye message will be disabled on server: {GuildId}",
 | 
			
		||||
@@ -258,12 +258,12 @@ public class GreetService : INService, IReadyExecutor
 | 
			
		||||
        text = await _repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var toDelete = await channel.SendAsync(text);
 | 
			
		||||
            var toDelete = await _sender.Response(channel).Text(text).SendAsync();
 | 
			
		||||
            if (conf.AutoDeleteGreetMessagesTimer > 0)
 | 
			
		||||
                toDelete.DeleteAfter(conf.AutoDeleteGreetMessagesTimer);
 | 
			
		||||
        }
 | 
			
		||||
        catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions ||
 | 
			
		||||
                                       ex.DiscordCode == DiscordErrorCode.UnknownChannel)
 | 
			
		||||
        catch (HttpException ex) when (ex.DiscordCode == DiscordErrorCode.InsufficientPermissions
 | 
			
		||||
                                       || ex.DiscordCode == DiscordErrorCode.UnknownChannel)
 | 
			
		||||
        {
 | 
			
		||||
            Log.Warning(ex,
 | 
			
		||||
                "Missing permissions to send a bye message, the greet message will be disabled on server: {GuildId}",
 | 
			
		||||
@@ -352,9 +352,10 @@ public class GreetService : INService, IReadyExecutor
 | 
			
		||||
                    {
 | 
			
		||||
                        // if there is less than 10 embeds, add an embed with footer only
 | 
			
		||||
                        seta.Embeds = seta.Embeds.Append(new SmartEmbedArrayElementText()
 | 
			
		||||
                        {
 | 
			
		||||
                            Footer = CreateFooterSource(user)
 | 
			
		||||
                        }).ToArray();
 | 
			
		||||
                                          {
 | 
			
		||||
                                              Footer = CreateFooterSource(user)
 | 
			
		||||
                                          })
 | 
			
		||||
                                          .ToArray();
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -112,7 +112,7 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        public async Task LanguagesList()
 | 
			
		||||
            => await Response().Embed(new EmbedBuilder()
 | 
			
		||||
            => await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                               .WithOkColor()
 | 
			
		||||
                                               .WithTitle(GetText(strs.lang_list))
 | 
			
		||||
                                               .WithDescription(string.Join("\n",
 | 
			
		||||
 
 | 
			
		||||
@@ -123,7 +123,7 @@ public class MuteService : INService
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        _ = Task.Run(() => _sender.Response(user)
 | 
			
		||||
                                  .Embed(new EmbedBuilder()
 | 
			
		||||
                                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                         .WithDescription($"You've been muted in {user.Guild} server")
 | 
			
		||||
                                         .AddField("Mute Type", type.ToString())
 | 
			
		||||
                                         .AddField("Moderator", mod.ToString())
 | 
			
		||||
@@ -141,7 +141,7 @@ public class MuteService : INService
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        _ = Task.Run(() => _sender.Response(user)
 | 
			
		||||
                                  .Embed(new EmbedBuilder()
 | 
			
		||||
                                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                         .WithDescription($"You've been unmuted in {user.Guild} server")
 | 
			
		||||
                                         .AddField("Unmute Type", type.ToString())
 | 
			
		||||
                                         .AddField("Moderator", mod.ToString())
 | 
			
		||||
 
 | 
			
		||||
@@ -25,8 +25,10 @@ public partial class Administration
 | 
			
		||||
            var aggregatePerms = perms.Aggregate((acc, seed) => seed | acc);
 | 
			
		||||
            await _service.AddOverride(ctx.Guild.Id, cmd.Name, aggregatePerms);
 | 
			
		||||
 | 
			
		||||
            await Response().Confirm(strs.perm_override(Format.Bold(aggregatePerms.ToString()),
 | 
			
		||||
                Format.Code(cmd.Name))).SendAsync();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Confirm(strs.perm_override(Format.Bold(aggregatePerms.ToString()),
 | 
			
		||||
                      Format.Code(cmd.Name)))
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -34,9 +36,9 @@ public partial class Administration
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        public async Task DiscordPermOverrideReset()
 | 
			
		||||
        {
 | 
			
		||||
            var result = await PromptUserConfirmAsync(new EmbedBuilder()
 | 
			
		||||
                                                         .WithOkColor()
 | 
			
		||||
                                                         .WithDescription(GetText(strs.perm_override_all_confirm)));
 | 
			
		||||
            var result = await PromptUserConfirmAsync(_sender.CreateEmbed()
 | 
			
		||||
                                                      .WithOkColor()
 | 
			
		||||
                                                      .WithDescription(GetText(strs.perm_override_all_confirm)));
 | 
			
		||||
 | 
			
		||||
            if (!result)
 | 
			
		||||
                return;
 | 
			
		||||
@@ -54,27 +56,28 @@ public partial class Administration
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var overrides = await _service.GetAllOverrides(ctx.Guild.Id);
 | 
			
		||||
            var allOverrides = await _service.GetAllOverrides(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var eb = new EmbedBuilder().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(allOverrides)
 | 
			
		||||
                  .PageSize(9)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var eb = _sender.CreateEmbed().WithTitle(GetText(strs.perm_overrides)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    var thisPageOverrides = overrides.Skip(9 * curPage).Take(9).ToList();
 | 
			
		||||
                      if (items.Count == 0)
 | 
			
		||||
                          eb.WithDescription(GetText(strs.perm_override_page_none));
 | 
			
		||||
                      else
 | 
			
		||||
                      {
 | 
			
		||||
                          eb.WithDescription(items.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}")
 | 
			
		||||
                                                  .Join("\n"));
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                    if (thisPageOverrides.Count == 0)
 | 
			
		||||
                        eb.WithDescription(GetText(strs.perm_override_page_none));
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        eb.WithDescription(thisPageOverrides.Select(ov => $"{ov.Command} => {ov.Perm.ToString()}")
 | 
			
		||||
                                                            .Join("\n"));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    return eb;
 | 
			
		||||
                },
 | 
			
		||||
                overrides.Count,
 | 
			
		||||
                9);
 | 
			
		||||
                      return eb;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -241,7 +241,7 @@ public partial class Administration
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.prot_active));
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.prot_active));
 | 
			
		||||
 | 
			
		||||
            if (spam is not null)
 | 
			
		||||
                embed.AddField("Anti-Spam", GetAntiSpamString(spam).TrimTo(1024), true);
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ public partial class Administration
 | 
			
		||||
        {
 | 
			
		||||
            _rero = rero;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.ManageRoles)]
 | 
			
		||||
@@ -29,15 +29,16 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
            if (levelReq < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            var msg = await ctx.Channel.GetMessageAsync(messageId);
 | 
			
		||||
            if (msg is null)
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Error(strs.not_found).SendAsync();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            if (ctx.User.Id != ctx.Guild.OwnerId && ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position) <= role.Position)
 | 
			
		||||
 | 
			
		||||
            if (ctx.User.Id != ctx.Guild.OwnerId
 | 
			
		||||
                && ((IGuildUser)ctx.User).GetRoles().Max(x => x.Position) <= role.Position)
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Error(strs.hierarchy).SendAsync();
 | 
			
		||||
                return;
 | 
			
		||||
@@ -71,48 +72,52 @@ public partial class Administration
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
            var reros = await _rero.GetReactionRolesAsync(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page, curPage =>
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                               .WithOkColor();
 | 
			
		||||
            var allReros = await _rero.GetReactionRolesAsync(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
                var content = string.Empty;
 | 
			
		||||
                foreach (var g in reros.OrderBy(x => x.Group)
 | 
			
		||||
                                       .Skip(curPage * 10)
 | 
			
		||||
                                       .GroupBy(x => x.MessageId)
 | 
			
		||||
                                       .OrderBy(x => x.Key))
 | 
			
		||||
                {
 | 
			
		||||
                    var messageId = g.Key;
 | 
			
		||||
                    content +=
 | 
			
		||||
                        $"[{messageId}](https://discord.com/channels/{ctx.Guild.Id}/{g.First().ChannelId}/{g.Key})\n";
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(allReros.OrderBy(x => x.Group).ToList())
 | 
			
		||||
                  .PageSize(10)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var embed = _sender.CreateEmbed()
 | 
			
		||||
                          .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    var groupGroups = g.GroupBy(x => x.Group);
 | 
			
		||||
                      var content = string.Empty;
 | 
			
		||||
                      foreach (var g in items
 | 
			
		||||
                                        .GroupBy(x => x.MessageId)
 | 
			
		||||
                                        .OrderBy(x => x.Key))
 | 
			
		||||
                      {
 | 
			
		||||
                          var messageId = g.Key;
 | 
			
		||||
                          content +=
 | 
			
		||||
                              $"[{messageId}](https://discord.com/channels/{ctx.Guild.Id}/{g.First().ChannelId}/{g.Key})\n";
 | 
			
		||||
 | 
			
		||||
                    foreach (var ggs in groupGroups)
 | 
			
		||||
                    {
 | 
			
		||||
                        content += $"`< {(g.Key == 0 ? ("Not Exclusive (Group 0)") : ($"Group {ggs.Key}"))} >`\n";
 | 
			
		||||
                          var groupGroups = g.GroupBy(x => x.Group);
 | 
			
		||||
 | 
			
		||||
                        foreach (var rero in ggs)
 | 
			
		||||
                        {
 | 
			
		||||
                            content +=
 | 
			
		||||
                                $"\t{rero.Emote} -> {(ctx.Guild.GetRole(rero.RoleId)?.Mention ?? "<missing role>")}";
 | 
			
		||||
                            if (rero.LevelReq > 0)
 | 
			
		||||
                                content += $" (lvl {rero.LevelReq}+)";
 | 
			
		||||
                            content += '\n';
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                          foreach (var ggs in groupGroups)
 | 
			
		||||
                          {
 | 
			
		||||
                              content += $"`< {(g.Key == 0 ? ("Not Exclusive (Group 0)") : ($"Group {ggs.Key}"))} >`\n";
 | 
			
		||||
 | 
			
		||||
                }
 | 
			
		||||
                              foreach (var rero in ggs)
 | 
			
		||||
                              {
 | 
			
		||||
                                  content +=
 | 
			
		||||
                                      $"\t{rero.Emote} -> {(ctx.Guild.GetRole(rero.RoleId)?.Mention ?? "<missing role>")}";
 | 
			
		||||
                                  if (rero.LevelReq > 0)
 | 
			
		||||
                                      content += $" (lvl {rero.LevelReq}+)";
 | 
			
		||||
                                  content += '\n';
 | 
			
		||||
                              }
 | 
			
		||||
                          }
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                embed.WithDescription(string.IsNullOrWhiteSpace(content)
 | 
			
		||||
                    ? "There are no reaction roles on this server"
 | 
			
		||||
                    : content);
 | 
			
		||||
                      embed.WithDescription(string.IsNullOrWhiteSpace(content)
 | 
			
		||||
                          ? "There are no reaction roles on this server"
 | 
			
		||||
                          : content);
 | 
			
		||||
 | 
			
		||||
                return embed;
 | 
			
		||||
            }, reros.Count, 10);
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ public sealed class CheckForUpdatesService : INService, IReadyExecutor
 | 
			
		||||
                            if (user is null)
 | 
			
		||||
                                return;
 | 
			
		||||
 | 
			
		||||
                            var eb = new EmbedBuilder()
 | 
			
		||||
                            var eb = _sender.CreateEmbed()
 | 
			
		||||
                                .WithOkColor()
 | 
			
		||||
                                .WithAuthor($"NadekoBot v{latestVersion} Released!")
 | 
			
		||||
                                .WithTitle("Changelog")
 | 
			
		||||
 
 | 
			
		||||
@@ -51,7 +51,6 @@ public partial class Administration
 | 
			
		||||
        {
 | 
			
		||||
            var downloadUsersTask = guild.DownloadUsersAsync();
 | 
			
		||||
            var message = await Response().Pending(strs.cache_users_pending).SendAsync();
 | 
			
		||||
            using var dbContext = _db.GetDbContext();
 | 
			
		||||
 | 
			
		||||
            await downloadUsersTask;
 | 
			
		||||
 | 
			
		||||
@@ -62,10 +61,10 @@ public partial class Administration
 | 
			
		||||
            var (added, updated) = await _service.RefreshUsersAsync(users);
 | 
			
		||||
 | 
			
		||||
            await message.ModifyAsync(x =>
 | 
			
		||||
                x.Embed = new EmbedBuilder()
 | 
			
		||||
                             .WithDescription(GetText(strs.cache_users_done(added, updated)))
 | 
			
		||||
                             .WithOkColor()
 | 
			
		||||
                             .Build()
 | 
			
		||||
                x.Embed = _sender.CreateEmbed()
 | 
			
		||||
                          .WithDescription(GetText(strs.cache_users_done(added, updated)))
 | 
			
		||||
                          .WithOkColor()
 | 
			
		||||
                          .Build()
 | 
			
		||||
            );
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -81,6 +80,7 @@ public partial class Administration
 | 
			
		||||
                && ctx.Message is SocketUserMessage msg)
 | 
			
		||||
            {
 | 
			
		||||
                var fakeMessage = new DoAsUserMessage(msg, user, message);
 | 
			
		||||
                
 | 
			
		||||
 | 
			
		||||
                await _cmdHandler.TryRunCommand(sg, ch, fakeMessage);
 | 
			
		||||
            }
 | 
			
		||||
@@ -113,14 +113,16 @@ public partial class Administration
 | 
			
		||||
            };
 | 
			
		||||
            _service.AddNewAutoCommand(cmd);
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                                .WithOkColor()
 | 
			
		||||
                                .WithTitle(GetText(strs.scadd))
 | 
			
		||||
                                .AddField(GetText(strs.server),
 | 
			
		||||
                                    cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
 | 
			
		||||
                                    true)
 | 
			
		||||
                                .AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
 | 
			
		||||
                                .AddField(GetText(strs.command_text), cmdText)).SendAsync();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.scadd))
 | 
			
		||||
                         .AddField(GetText(strs.server),
 | 
			
		||||
                             cmd.GuildId is null ? "-" : $"{cmd.GuildName}/{cmd.GuildId}",
 | 
			
		||||
                             true)
 | 
			
		||||
                         .AddField(GetText(strs.channel), $"{cmd.ChannelName}/{cmd.ChannelId}", true)
 | 
			
		||||
                         .AddField(GetText(strs.command_text), cmdText))
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -328,18 +330,21 @@ public partial class Administration
 | 
			
		||||
                                                     + $"| {st.GuildCount.ToString().PadBoth(maxGuildCountLength)} `";
 | 
			
		||||
                                          })
 | 
			
		||||
                                          .ToArray();
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var str = string.Join("\n", allShardStrings.Skip(25 * curPage).Take(25));
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(allShardStrings)
 | 
			
		||||
                  .PageSize(25)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var str = string.Join("\n", items);
 | 
			
		||||
 | 
			
		||||
                    if (string.IsNullOrWhiteSpace(str))
 | 
			
		||||
                        str = GetText(strs.no_shards_on_page);
 | 
			
		||||
                      if (string.IsNullOrWhiteSpace(str))
 | 
			
		||||
                          str = GetText(strs.no_shards_on_page);
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder().WithOkColor().WithDescription($"{status}\n\n{str}");
 | 
			
		||||
                },
 | 
			
		||||
                allShardStrings.Length,
 | 
			
		||||
                25);
 | 
			
		||||
                      return _sender.CreateEmbed().WithOkColor().WithDescription($"{status}\n\n{str}");
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private static string ConnectionStateToEmoji(ShardStatus status)
 | 
			
		||||
@@ -566,7 +571,7 @@ public partial class Administration
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                text = await repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
                await ch.SendAsync(text);
 | 
			
		||||
                await Response().Channel(ch).Text(text).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
            else if (ids[1].ToUpperInvariant().StartsWith("U:", StringComparison.InvariantCulture))
 | 
			
		||||
            {
 | 
			
		||||
@@ -577,7 +582,7 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
                var ch = await user.CreateDMChannelAsync();
 | 
			
		||||
                text = await repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
                await ch.SendAsync(text);
 | 
			
		||||
                await Response().Channel(ch).Text(text).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -98,53 +98,54 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
            var (exclusive, roles, groups) = _service.GetRoles(ctx.Guild);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                cur =>
 | 
			
		||||
                {
 | 
			
		||||
                    var rolesStr = new StringBuilder();
 | 
			
		||||
                    var roleGroups = roles.OrderBy(x => x.Model.Group)
 | 
			
		||||
                                          .Skip(cur * 20)
 | 
			
		||||
                                          .Take(20)
 | 
			
		||||
                                          .GroupBy(x => x.Model.Group)
 | 
			
		||||
                                          .OrderBy(x => x.Key);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(roles.OrderBy(x => x.Model.Group).ToList())
 | 
			
		||||
                  .PageSize(20)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var rolesStr = new StringBuilder();
 | 
			
		||||
                      var roleGroups = items
 | 
			
		||||
                                       .GroupBy(x => x.Model.Group)
 | 
			
		||||
                                       .OrderBy(x => x.Key);
 | 
			
		||||
 | 
			
		||||
                    foreach (var kvp in roleGroups)
 | 
			
		||||
                    {
 | 
			
		||||
                        string groupNameText;
 | 
			
		||||
                        if (!groups.TryGetValue(kvp.Key, out var name))
 | 
			
		||||
                            groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
 | 
			
		||||
                        else
 | 
			
		||||
                            groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
 | 
			
		||||
                      foreach (var kvp in roleGroups)
 | 
			
		||||
                      {
 | 
			
		||||
                          string groupNameText;
 | 
			
		||||
                          if (!groups.TryGetValue(kvp.Key, out var name))
 | 
			
		||||
                              groupNameText = Format.Bold(GetText(strs.self_assign_group(kvp.Key)));
 | 
			
		||||
                          else
 | 
			
		||||
                              groupNameText = Format.Bold($"{kvp.Key} - {name.TrimTo(25, true)}");
 | 
			
		||||
 | 
			
		||||
                        rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
 | 
			
		||||
                        foreach (var (model, role) in kvp.AsEnumerable())
 | 
			
		||||
                        {
 | 
			
		||||
                            if (role is null)
 | 
			
		||||
                            {
 | 
			
		||||
                            }
 | 
			
		||||
                            else
 | 
			
		||||
                            {
 | 
			
		||||
                                // first character is invisible space
 | 
			
		||||
                                if (model.LevelRequirement == 0)
 | 
			
		||||
                                    rolesStr.AppendLine("   " + role.Name);
 | 
			
		||||
                                else
 | 
			
		||||
                                    rolesStr.AppendLine("   " + role.Name + $" (lvl {model.LevelRequirement}+)");
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                          rolesStr.AppendLine("\t\t\t\t ⟪" + groupNameText + "⟫");
 | 
			
		||||
                          foreach (var (model, role) in kvp.AsEnumerable())
 | 
			
		||||
                          {
 | 
			
		||||
                              if (role is null)
 | 
			
		||||
                              {
 | 
			
		||||
                              }
 | 
			
		||||
                              else
 | 
			
		||||
                              {
 | 
			
		||||
                                  // first character is invisible space
 | 
			
		||||
                                  if (model.LevelRequirement == 0)
 | 
			
		||||
                                      rolesStr.AppendLine("   " + role.Name);
 | 
			
		||||
                                  else
 | 
			
		||||
                                      rolesStr.AppendLine("   " + role.Name + $" (lvl {model.LevelRequirement}+)");
 | 
			
		||||
                              }
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                        rolesStr.AppendLine();
 | 
			
		||||
                    }
 | 
			
		||||
                          rolesStr.AppendLine();
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                              .WithOkColor()
 | 
			
		||||
                              .WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
 | 
			
		||||
                              .WithDescription(rolesStr.ToString())
 | 
			
		||||
                              .WithFooter(exclusive
 | 
			
		||||
                                  ? GetText(strs.self_assign_are_exclusive)
 | 
			
		||||
                                  : GetText(strs.self_assign_are_not_exclusive));
 | 
			
		||||
                },
 | 
			
		||||
                roles.Count(),
 | 
			
		||||
                20);
 | 
			
		||||
                      return _sender.CreateEmbed()
 | 
			
		||||
                             .WithOkColor()
 | 
			
		||||
                             .WithTitle(Format.Bold(GetText(strs.self_assign_list(roles.Count()))))
 | 
			
		||||
                             .WithDescription(rolesStr.ToString())
 | 
			
		||||
                             .WithFooter(exclusive
 | 
			
		||||
                                 ? GetText(strs.self_assign_are_exclusive)
 | 
			
		||||
                                 : GetText(strs.self_assign_are_not_exclusive));
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -164,7 +164,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
                var title = GetText(logChannel.Guild, strs.thread_deleted);
 | 
			
		||||
 | 
			
		||||
                await _sender.Response(logChannel).Embed(new EmbedBuilder()
 | 
			
		||||
                await _sender.Response(logChannel).Embed(_sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("🗑 " + title)
 | 
			
		||||
                    .WithDescription($"{ch.Name} | {ch.Id}")
 | 
			
		||||
@@ -194,7 +194,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
                var title = GetText(logChannel.Guild, strs.thread_created);
 | 
			
		||||
 | 
			
		||||
                await _sender.Response(logChannel).Embed(new EmbedBuilder()
 | 
			
		||||
                await _sender.Response(logChannel).Embed(_sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("🆕 " + title)
 | 
			
		||||
                    .WithDescription($"{ch.Name} | {ch.Id}")
 | 
			
		||||
@@ -327,7 +327,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
        if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserWarned)) is null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
            .WithOkColor()
 | 
			
		||||
            .WithTitle($"⚠️ User Warned")
 | 
			
		||||
            .WithDescription($"<@{arg.UserId}> | {arg.UserId}")
 | 
			
		||||
@@ -356,7 +356,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(g, logSetting, LogType.UserUpdated)) is null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder();
 | 
			
		||||
                var embed = _sender.CreateEmbed();
 | 
			
		||||
 | 
			
		||||
                if (before.Username != after.Username)
 | 
			
		||||
                {
 | 
			
		||||
@@ -495,7 +495,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithAuthor(mutes)
 | 
			
		||||
                    .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
 | 
			
		||||
                    .WithFooter(CurrentTime(usr.Guild))
 | 
			
		||||
@@ -542,7 +542,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithAuthor(mutes)
 | 
			
		||||
                    .WithTitle($"{usr.Username}#{usr.Discriminator} | {usr.Id}")
 | 
			
		||||
                    .WithFooter($"{CurrentTime(usr.Guild)}")
 | 
			
		||||
@@ -596,7 +596,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                        break;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithAuthor($"🛡 Anti-{protection}")
 | 
			
		||||
                    .WithTitle(GetText(logChannel.Guild, strs.users) + " " + punishment)
 | 
			
		||||
                    .WithDescription(string.Join("\n", users.Select(u => u.ToString())))
 | 
			
		||||
@@ -649,7 +649,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                if (logSetting.UserUpdatedId is not null
 | 
			
		||||
                    && (logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.UserUpdated)) is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    var embed = new EmbedBuilder()
 | 
			
		||||
                    var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithFooter(CurrentTime(before.Guild))
 | 
			
		||||
                        .WithTitle($"{before.Username}#{before.Discriminator} | {before.Id}");
 | 
			
		||||
@@ -720,7 +720,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(before.Guild, logSetting, LogType.ChannelUpdated)) is null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder().WithOkColor().WithFooter(CurrentTime(before.Guild));
 | 
			
		||||
                var embed = _sender.CreateEmbed().WithOkColor().WithFooter(CurrentTime(before.Guild));
 | 
			
		||||
 | 
			
		||||
                var beforeTextChannel = cbefore as ITextChannel;
 | 
			
		||||
                var afterTextChannel = cafter as ITextChannel;
 | 
			
		||||
@@ -776,7 +776,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                else
 | 
			
		||||
                    title = GetText(logChannel.Guild, strs.text_chan_destroyed);
 | 
			
		||||
 | 
			
		||||
                await _sender.Response(logChannel).Embed(new EmbedBuilder()
 | 
			
		||||
                await _sender.Response(logChannel).Embed(_sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("🆕 " + title)
 | 
			
		||||
                    .WithDescription($"{ch.Name} | {ch.Id}")
 | 
			
		||||
@@ -812,7 +812,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                else
 | 
			
		||||
                    title = GetText(logChannel.Guild, strs.text_chan_created);
 | 
			
		||||
 | 
			
		||||
                await _sender.Response(logChannel).Embed(new EmbedBuilder()
 | 
			
		||||
                await _sender.Response(logChannel).Embed(_sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("🆕 " + title)
 | 
			
		||||
                    .WithDescription($"{ch.Name} | {ch.Id}")
 | 
			
		||||
@@ -915,7 +915,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                ITextChannel? logChannel;
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserLeft)) is null)
 | 
			
		||||
                    return;
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("❌ " + GetText(logChannel.Guild, strs.user_left))
 | 
			
		||||
                    .WithDescription(usr.ToString())
 | 
			
		||||
@@ -948,7 +948,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(usr.Guild, logSetting, LogType.UserJoined)) is null)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("✅ " + GetText(logChannel.Guild, strs.user_joined))
 | 
			
		||||
                    .WithDescription($"{usr.Mention} `{usr}`")
 | 
			
		||||
@@ -989,7 +989,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                ITextChannel? logChannel;
 | 
			
		||||
                if ((logChannel = await TryGetLogChannel(guild, logSetting, LogType.UserUnbanned)) is null)
 | 
			
		||||
                    return;
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("♻️ " + GetText(logChannel.Guild, strs.user_unbanned))
 | 
			
		||||
                    .WithDescription(usr.ToString()!)
 | 
			
		||||
@@ -1036,7 +1036,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                {
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("🚫 " + GetText(logChannel.Guild, strs.user_banned))
 | 
			
		||||
                    .WithDescription(usr.ToString()!)
 | 
			
		||||
@@ -1087,7 +1087,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var resolvedMessage = msg.Resolve(TagHandling.FullName);
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("🗑 "
 | 
			
		||||
                               + GetText(logChannel.Guild, strs.msg_del(((ITextChannel)msg.Channel).Name)))
 | 
			
		||||
@@ -1147,7 +1147,7 @@ public sealed class LogCommandService : ILogCommandService, IReadyExecutor
 | 
			
		||||
                    || logChannel.Id == after.Channel.Id)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("📝 "
 | 
			
		||||
                               + GetText(logChannel.Guild,
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public partial class Administration
 | 
			
		||||
            var usrs = settings?.LogIgnores.Where(x => x.ItemType == IgnoredItemType.User).ToList()
 | 
			
		||||
                       ?? new List<IgnoredLogItem>();
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .AddField(GetText(strs.log_ignored_channels),
 | 
			
		||||
                            chs.Count == 0
 | 
			
		||||
 
 | 
			
		||||
@@ -33,17 +33,20 @@ public partial class Administration
 | 
			
		||||
                                               if (flip)
 | 
			
		||||
                                                   return $"{offset} {Format.Code(nameStr)}";
 | 
			
		||||
                                               return $"{Format.Code(offset)} {nameStr}";
 | 
			
		||||
                                           });
 | 
			
		||||
                                           })
 | 
			
		||||
                                           .ToList();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage => new EmbedBuilder()
 | 
			
		||||
                              .WithOkColor()
 | 
			
		||||
                              .WithTitle(GetText(strs.timezones_available))
 | 
			
		||||
                              .WithDescription(string.Join("\n",
 | 
			
		||||
                                  timezoneStrings.Skip(curPage * timezonesPerPage).Take(timezonesPerPage))),
 | 
			
		||||
                timezones.Length,
 | 
			
		||||
                timezonesPerPage);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(timezoneStrings)
 | 
			
		||||
                  .PageSize(timezonesPerPage)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) => _sender.CreateEmbed()
 | 
			
		||||
                                      .WithOkColor()
 | 
			
		||||
                                      .WithTitle(GetText(strs.timezones_available))
 | 
			
		||||
                                      .WithDescription(string.Join("\n", items)))
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -66,7 +66,7 @@ public partial class Administration
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await _sender.Response(user)
 | 
			
		||||
                             .Embed(new EmbedBuilder()
 | 
			
		||||
                             .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                    .WithErrorColor()
 | 
			
		||||
                                    .WithDescription(GetText(strs.warned_on(ctx.Guild.ToString())))
 | 
			
		||||
                                    .AddField(GetText(strs.moderator), ctx.User.ToString())
 | 
			
		||||
@@ -86,7 +86,7 @@ public partial class Administration
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning(ex, "Exception occured while warning a user");
 | 
			
		||||
                var errorEmbed = new EmbedBuilder().WithErrorColor()
 | 
			
		||||
                var errorEmbed = _sender.CreateEmbed().WithErrorColor()
 | 
			
		||||
                                                   .WithDescription(GetText(strs.cant_apply_punishment));
 | 
			
		||||
 | 
			
		||||
                if (dmFailed)
 | 
			
		||||
@@ -96,7 +96,7 @@ public partial class Administration
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
            if (punishment is null)
 | 
			
		||||
                embed.WithDescription(GetText(strs.user_warned(Format.Bold(user.ToString()))));
 | 
			
		||||
            else
 | 
			
		||||
@@ -197,45 +197,46 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
            var allWarnings = _service.UserWarnings(ctx.Guild.Id, userId);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(inputPage,
 | 
			
		||||
                page =>
 | 
			
		||||
                {
 | 
			
		||||
                    var warnings = allWarnings.Skip(page * 9).Take(9).ToArray();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(allWarnings)
 | 
			
		||||
                  .PageSize(9)
 | 
			
		||||
                  .CurrentPage(inputPage)
 | 
			
		||||
                  .Page((warnings, page) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
 | 
			
		||||
                      var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
 | 
			
		||||
 | 
			
		||||
                    var user = (ctx.Guild as SocketGuild)?.GetUser(userId)?.ToString() ?? userId.ToString();
 | 
			
		||||
                    var embed = new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.warnlog_for(user)));
 | 
			
		||||
                      if (!warnings.Any())
 | 
			
		||||
                          embed.WithDescription(GetText(strs.warnings_none));
 | 
			
		||||
                      else
 | 
			
		||||
                      {
 | 
			
		||||
                          var descText = GetText(strs.warn_count(
 | 
			
		||||
                              Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
 | 
			
		||||
                              Format.Bold(warnings.Sum(x => x.Weight).ToString())));
 | 
			
		||||
 | 
			
		||||
                    if (!warnings.Any())
 | 
			
		||||
                        embed.WithDescription(GetText(strs.warnings_none));
 | 
			
		||||
                    else
 | 
			
		||||
                    {
 | 
			
		||||
                        var descText = GetText(strs.warn_count(
 | 
			
		||||
                            Format.Bold(warnings.Where(x => !x.Forgiven).Sum(x => x.Weight).ToString()),
 | 
			
		||||
                            Format.Bold(warnings.Sum(x => x.Weight).ToString())));
 | 
			
		||||
                          embed.WithDescription(descText);
 | 
			
		||||
 | 
			
		||||
                        embed.WithDescription(descText);
 | 
			
		||||
                          var i = page * 9;
 | 
			
		||||
                          foreach (var w in warnings)
 | 
			
		||||
                          {
 | 
			
		||||
                              i++;
 | 
			
		||||
                              var name = GetText(strs.warned_on_by(w.DateAdded?.ToString("dd.MM.yyy"),
 | 
			
		||||
                                  w.DateAdded?.ToString("HH:mm"),
 | 
			
		||||
                                  w.Moderator));
 | 
			
		||||
 | 
			
		||||
                        var i = page * 9;
 | 
			
		||||
                        foreach (var w in warnings)
 | 
			
		||||
                        {
 | 
			
		||||
                            i++;
 | 
			
		||||
                            var name = GetText(strs.warned_on_by(w.DateAdded?.ToString("dd.MM.yyy"),
 | 
			
		||||
                                w.DateAdded?.ToString("HH:mm"),
 | 
			
		||||
                                w.Moderator));
 | 
			
		||||
 | 
			
		||||
                            if (w.Forgiven)
 | 
			
		||||
                                name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
 | 
			
		||||
                              if (w.Forgiven)
 | 
			
		||||
                                  name = $"{Format.Strikethrough(name)} {GetText(strs.warn_cleared_by(w.ForgivenBy))}";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                            embed.AddField($"#`{i}` " + name,
 | 
			
		||||
                                Format.Code(GetText(strs.warn_weight(w.Weight))) + '\n' + w.Reason.TrimTo(1000));
 | 
			
		||||
                        }
 | 
			
		||||
                    }
 | 
			
		||||
                              embed.AddField($"#`{i}` " + name,
 | 
			
		||||
                                  Format.Code(GetText(strs.warn_weight(w.Weight))) + '\n' + w.Reason.TrimTo(1000));
 | 
			
		||||
                          }
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                allWarnings.Length,
 | 
			
		||||
                9);
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -245,31 +246,32 @@ public partial class Administration
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            var warnings = _service.WarnlogAll(ctx.Guild.Id);
 | 
			
		||||
            var allWarnings = _service.WarnlogAll(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var ws = warnings.Skip(curPage * 15)
 | 
			
		||||
                                     .Take(15)
 | 
			
		||||
                                     .ToArray()
 | 
			
		||||
                                     .Select(x =>
 | 
			
		||||
                                     {
 | 
			
		||||
                                         var all = x.Count();
 | 
			
		||||
                                         var forgiven = x.Count(y => y.Forgiven);
 | 
			
		||||
                                         var total = all - forgiven;
 | 
			
		||||
                                         var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key);
 | 
			
		||||
                                         return (usr?.ToString() ?? x.Key.ToString())
 | 
			
		||||
                                                + $" | {total} ({all} - {forgiven})";
 | 
			
		||||
                                     });
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(allWarnings)
 | 
			
		||||
                  .PageSize(15)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((warnings, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var ws = warnings
 | 
			
		||||
                          .Select(x =>
 | 
			
		||||
                          {
 | 
			
		||||
                              var all = x.Count();
 | 
			
		||||
                              var forgiven = x.Count(y => y.Forgiven);
 | 
			
		||||
                              var total = all - forgiven;
 | 
			
		||||
                              var usr = ((SocketGuild)ctx.Guild).GetUser(x.Key);
 | 
			
		||||
                              return (usr?.ToString() ?? x.Key.ToString())
 | 
			
		||||
                                     + $" | {total} ({all} - {forgiven})";
 | 
			
		||||
                          });
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(strs.warnings_list))
 | 
			
		||||
                           .WithDescription(string.Join("\n", ws));
 | 
			
		||||
                },
 | 
			
		||||
                warnings.Length,
 | 
			
		||||
                15);
 | 
			
		||||
                      return _sender.CreateEmbed()
 | 
			
		||||
                             .WithOkColor()
 | 
			
		||||
                             .WithTitle(GetText(strs.warnings_list))
 | 
			
		||||
                             .WithDescription(string.Join("\n", ws));
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -450,7 +452,7 @@ public partial class Administration
 | 
			
		||||
            var user = await ctx.Client.GetUserAsync(userId);
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            await _mute.TimedBan(ctx.Guild, userId, time.Time, (ctx.User + " | " + msg).TrimTo(512), banPrune);
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle("⛔️ " + GetText(strs.banned_user))
 | 
			
		||||
                         .AddField(GetText(strs.username), user?.ToString() ?? userId.ToString(), true)
 | 
			
		||||
@@ -478,10 +480,11 @@ public partial class Administration
 | 
			
		||||
                var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
                await ctx.Guild.AddBanAsync(userId, banPrune, (ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
 | 
			
		||||
                await ctx.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                             .WithOkColor()
 | 
			
		||||
                                             .WithTitle("⛔️ " + GetText(strs.banned_user))
 | 
			
		||||
                                             .AddField("ID", userId.ToString(), true));
 | 
			
		||||
                                             .AddField("ID", userId.ToString(), true))
 | 
			
		||||
                                .SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
            else
 | 
			
		||||
                await Ban(user, msg);
 | 
			
		||||
@@ -514,7 +517,7 @@ public partial class Administration
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            await ctx.Guild.AddBanAsync(user, banPrune, (ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle("⛔️ " + GetText(strs.banned_user))
 | 
			
		||||
                         .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
@@ -709,7 +712,7 @@ public partial class Administration
 | 
			
		||||
            try { await ctx.Guild.RemoveBanAsync(user); }
 | 
			
		||||
            catch { await ctx.Guild.RemoveBanAsync(user); }
 | 
			
		||||
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle("☣ " + GetText(strs.sb_user))
 | 
			
		||||
                         .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
@@ -764,7 +767,7 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
            await user.KickAsync((ctx.User + " | " + msg).TrimTo(512));
 | 
			
		||||
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.kicked_user))
 | 
			
		||||
                         .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
@@ -797,7 +800,7 @@ public partial class Administration
 | 
			
		||||
            {
 | 
			
		||||
                var dmMessage = GetText(strs.timeoutdm(Format.Bold(ctx.Guild.Name), msg));
 | 
			
		||||
                await _sender.Response(user)
 | 
			
		||||
                             .Embed(new EmbedBuilder()
 | 
			
		||||
                             .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                    .WithPendingColor()
 | 
			
		||||
                                    .WithDescription(dmMessage))
 | 
			
		||||
                             .SendAsync();
 | 
			
		||||
@@ -809,7 +812,7 @@ public partial class Administration
 | 
			
		||||
 | 
			
		||||
            await user.SetTimeOutAsync(time.Time);
 | 
			
		||||
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle("⏳ " + GetText(strs.timedout_user))
 | 
			
		||||
                         .AddField(GetText(strs.username), user.ToString(), true)
 | 
			
		||||
@@ -870,7 +873,7 @@ public partial class Administration
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(missStr))
 | 
			
		||||
                missStr = "-";
 | 
			
		||||
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                         .WithDescription(GetText(strs.mass_ban_in_progress(banning.Count)))
 | 
			
		||||
                         .AddField(GetText(strs.invalid(missing.Count)), missStr)
 | 
			
		||||
                         .WithPendingColor();
 | 
			
		||||
@@ -890,7 +893,7 @@ public partial class Administration
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await banningMessage.ModifyAsync(x => x.Embed = new EmbedBuilder()
 | 
			
		||||
            await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
 | 
			
		||||
                                                            .WithDescription(
 | 
			
		||||
                                                                GetText(strs.mass_ban_completed(banning.Count())))
 | 
			
		||||
                                                            .AddField(GetText(strs.invalid(missing.Count)), missStr)
 | 
			
		||||
@@ -915,11 +918,13 @@ public partial class Administration
 | 
			
		||||
                missStr = "-";
 | 
			
		||||
 | 
			
		||||
            //send a message but don't wait for it
 | 
			
		||||
            var banningMessageTask = ctx.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                                                            .WithDescription(
 | 
			
		||||
                                                                GetText(strs.mass_kill_in_progress(bans.Count())))
 | 
			
		||||
                                                            .AddField(GetText(strs.invalid(missing)), missStr)
 | 
			
		||||
                                                            .WithPendingColor());
 | 
			
		||||
            var banningMessageTask = Response()
 | 
			
		||||
                                     .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                            .WithDescription(
 | 
			
		||||
                                                GetText(strs.mass_kill_in_progress(bans.Count())))
 | 
			
		||||
                                            .AddField(GetText(strs.invalid(missing)), missStr)
 | 
			
		||||
                                            .WithPendingColor())
 | 
			
		||||
                                     .SendAsync();
 | 
			
		||||
 | 
			
		||||
            var banPrune = await _service.GetBanPruneAsync(ctx.Guild.Id) ?? 7;
 | 
			
		||||
            //do the banning
 | 
			
		||||
@@ -935,7 +940,7 @@ public partial class Administration
 | 
			
		||||
            //wait for the message and edit it
 | 
			
		||||
            var banningMessage = await banningMessageTask;
 | 
			
		||||
 | 
			
		||||
            await banningMessage.ModifyAsync(x => x.Embed = new EmbedBuilder()
 | 
			
		||||
            await banningMessage.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
 | 
			
		||||
                                                            .WithDescription(
 | 
			
		||||
                                                                GetText(strs.mass_kill_completed(bans.Count())))
 | 
			
		||||
                                                            .AddField(GetText(strs.invalid(missing)), missStr)
 | 
			
		||||
 
 | 
			
		||||
@@ -68,7 +68,7 @@ public partial class Administration
 | 
			
		||||
            else
 | 
			
		||||
                text = GetText(strs.no_vcroles);
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
            await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                            .WithOkColor()
 | 
			
		||||
                                            .WithTitle(GetText(strs.vc_role_list))
 | 
			
		||||
                                            .WithDescription(text)).SendAsync();
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ public static class NadekoExpressionExtensions
 | 
			
		||||
        IUserMessage ctx,
 | 
			
		||||
        IReplacementService repSvc,
 | 
			
		||||
        DiscordSocketClient client,
 | 
			
		||||
        bool sanitize)
 | 
			
		||||
        IMessageSenderService sender)
 | 
			
		||||
    {
 | 
			
		||||
        var channel = cr.DmResponse ? await ctx.Author.CreateDMChannelAsync() : ctx.Channel;
 | 
			
		||||
 | 
			
		||||
@@ -46,7 +46,7 @@ public static class NadekoExpressionExtensions
 | 
			
		||||
        var text = SmartText.CreateFrom(cr.Response);
 | 
			
		||||
        text = await repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
 | 
			
		||||
        return await channel.SendAsync(text, sanitize);
 | 
			
		||||
        return await sender.Response(channel).Text(text).Sanitize(false).SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
 
 | 
			
		||||
@@ -33,7 +33,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
        var ex = await _service.AddAsync(ctx.Guild?.Id, key, message);
 | 
			
		||||
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Embed(new EmbedBuilder()
 | 
			
		||||
              .Embed(_sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithTitle(GetText(strs.expr_new))
 | 
			
		||||
                     .WithDescription($"#{new kwum(ex.Id)}")
 | 
			
		||||
@@ -66,6 +66,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
        await ExprAddInternalAsync(key, message);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    public async Task ExprAdd(string key, [Leftover] string message)
 | 
			
		||||
    {
 | 
			
		||||
@@ -102,13 +103,15 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
        var ex = await _service.EditAsync(ctx.Guild?.Id, id, message);
 | 
			
		||||
        if (ex is not null)
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                                         .WithOkColor()
 | 
			
		||||
                                         .WithTitle(GetText(strs.expr_edited))
 | 
			
		||||
                                         .WithDescription($"#{id}")
 | 
			
		||||
                                         .AddField(GetText(strs.trigger), ex.Trigger)
 | 
			
		||||
                                         .AddField(GetText(strs.response),
 | 
			
		||||
                                             message.Length > 1024 ? GetText(strs.redacted_too_long) : message));
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.expr_edited))
 | 
			
		||||
                         .WithDescription($"#{id}")
 | 
			
		||||
                         .AddField(GetText(strs.trigger), ex.Trigger)
 | 
			
		||||
                         .AddField(GetText(strs.response),
 | 
			
		||||
                             message.Length > 1024 ? GetText(strs.redacted_too_long) : message))
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
@@ -152,7 +155,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
                                               : " // " + string.Join(" ", ex.GetReactions())))
 | 
			
		||||
                             .Join('\n');
 | 
			
		||||
 | 
			
		||||
                  return new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
 | 
			
		||||
                  return _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.expressions)).WithDescription(desc);
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
@@ -168,12 +171,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                                     .WithOkColor()
 | 
			
		||||
                                     .WithDescription($"#{id}")
 | 
			
		||||
                                     .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
 | 
			
		||||
                                     .AddField(GetText(strs.response),
 | 
			
		||||
                                         found.Response.TrimTo(1000).Replace("](", "]\\(")));
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Embed(_sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithDescription($"#{id}")
 | 
			
		||||
                     .AddField(GetText(strs.trigger), found.Trigger.TrimTo(1024))
 | 
			
		||||
                     .AddField(GetText(strs.response),
 | 
			
		||||
                         found.Response.TrimTo(1000).Replace("](", "]\\(")))
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ExprDeleteInternalAsync(kwum id)
 | 
			
		||||
@@ -182,12 +187,14 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
 | 
			
		||||
        if (ex is not null)
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.Channel.EmbedAsync(new EmbedBuilder()
 | 
			
		||||
                                         .WithOkColor()
 | 
			
		||||
                                         .WithTitle(GetText(strs.expr_deleted))
 | 
			
		||||
                                         .WithDescription($"#{id}")
 | 
			
		||||
                                         .AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
 | 
			
		||||
                                         .AddField(GetText(strs.response), ex.Response.TrimTo(1024)));
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.expr_deleted))
 | 
			
		||||
                         .WithDescription($"#{id}")
 | 
			
		||||
                         .AddField(GetText(strs.trigger), ex.Trigger.TrimTo(1024))
 | 
			
		||||
                         .AddField(GetText(strs.response), ex.Response.TrimTo(1024)))
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
@@ -332,7 +339,7 @@ public partial class NadekoExpressions : NadekoModule<NadekoExpressionsService>
 | 
			
		||||
    [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
    public async Task ExprClear()
 | 
			
		||||
    {
 | 
			
		||||
        if (await PromptUserConfirmAsync(new EmbedBuilder()
 | 
			
		||||
        if (await PromptUserConfirmAsync(_sender.CreateEmbed()
 | 
			
		||||
                                         .WithTitle("Expression clear")
 | 
			
		||||
                                         .WithDescription("This will delete all expressions on this server.")))
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -280,7 +280,7 @@ public sealed class NadekoExpressionsService : IExecOnMessage, IReadyExecutor
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var sentMsg = await expr.Send(msg, _repSvc, _client, false);
 | 
			
		||||
            var sentMsg = await expr.Send(msg, _repSvc, _client, _sender);
 | 
			
		||||
 | 
			
		||||
            var reactions = expr.GetReactions();
 | 
			
		||||
            foreach (var reaction in reactions)
 | 
			
		||||
 
 | 
			
		||||
@@ -128,7 +128,7 @@ public partial class Gambling
 | 
			
		||||
                raceMessage = await Response().Confirm(text).SendAsync();
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await msg.ModifyAsync(x => x.Embed = new EmbedBuilder()
 | 
			
		||||
                await msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
 | 
			
		||||
                                                        .WithTitle(GetText(strs.animal_race))
 | 
			
		||||
                                                        .WithDescription(text)
 | 
			
		||||
                                                        .WithOkColor()
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ public partial class Gambling
 | 
			
		||||
        {
 | 
			
		||||
            var bal = await _bank.GetBalanceAsync(ctx.User.Id);
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithDescription(GetText(strs.bank_balance(N(bal))));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -95,7 +95,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                var cStr = string.Concat(c.Select(x => x[..^1] + " "));
 | 
			
		||||
                cStr += "\n" + string.Concat(c.Select(x => x.Last() + " "));
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithTitle("BlackJack")
 | 
			
		||||
                               .AddField($"{dealerIcon} Dealer's Hand | Value: {bj.Dealer.GetHandValue()}", cStr);
 | 
			
		||||
 
 | 
			
		||||
@@ -150,7 +150,7 @@ public partial class Gambling
 | 
			
		||||
                else
 | 
			
		||||
                    title = GetText(strs.connect4_draw);
 | 
			
		||||
 | 
			
		||||
                return msg.ModifyAsync(x => x.Embed = new EmbedBuilder()
 | 
			
		||||
                return msg.ModifyAsync(x => x.Embed = _sender.CreateEmbed()
 | 
			
		||||
                                                         .WithTitle(title)
 | 
			
		||||
                                                         .WithDescription(GetGameStateText(game))
 | 
			
		||||
                                                         .WithOkColor()
 | 
			
		||||
@@ -160,7 +160,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
        private async Task Game_OnGameStateUpdated(Connect4Game game)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithTitle($"{game.CurrentPlayer.Username} vs {game.OtherPlayer.Username}")
 | 
			
		||||
                           .WithDescription(GetGameStateText(game))
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
            var fileName = $"dice.{format.FileExtensions.First()}";
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .AddField(GetText(strs.roll2), gen)
 | 
			
		||||
@@ -115,7 +115,7 @@ public partial class Gambling
 | 
			
		||||
                d.Dispose();
 | 
			
		||||
 | 
			
		||||
            var imageName = $"dice.{format.FileExtensions.First()}";
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .AddField(GetText(strs.rolls), values.Select(x => Format.Code(x.ToString())).Join(' '), true)
 | 
			
		||||
@@ -141,7 +141,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < n1; i++)
 | 
			
		||||
                    rolls.Add(_fateRolls[rng.Next(0, _fateRolls.Length)]);
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithAuthor(ctx.User)
 | 
			
		||||
                               .WithDescription(GetText(strs.dice_rolled_num(Format.Bold(n1.ToString()))))
 | 
			
		||||
@@ -170,7 +170,7 @@ public partial class Gambling
 | 
			
		||||
                        arr[i] = rng.Next(1, n2 + 1);
 | 
			
		||||
 | 
			
		||||
                    var sum = arr.Sum();
 | 
			
		||||
                    var embed = new EmbedBuilder()
 | 
			
		||||
                    var embed = _sender.CreateEmbed()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithAuthor(ctx.User)
 | 
			
		||||
                                   .WithDescription(GetText(strs.dice_rolled_num(n1 + $"`1 - {n2}`")))
 | 
			
		||||
 
 | 
			
		||||
@@ -55,7 +55,7 @@ public partial class Gambling
 | 
			
		||||
            foreach (var i in images)
 | 
			
		||||
                i.Dispose();
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor();
 | 
			
		||||
            
 | 
			
		||||
            var toSend = string.Empty;
 | 
			
		||||
@@ -160,7 +160,7 @@ public partial class Gambling
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .WithDescription(result.Card.GetEmoji())
 | 
			
		||||
 
 | 
			
		||||
@@ -30,12 +30,12 @@ public partial class Gambling
 | 
			
		||||
        private EmbedBuilder GetEmbed(CurrencyEvent.Type type, EventOptions opts, long currentPot)
 | 
			
		||||
            => type switch
 | 
			
		||||
            {
 | 
			
		||||
                CurrencyEvent.Type.Reaction => new EmbedBuilder()
 | 
			
		||||
                CurrencyEvent.Type.Reaction => _sender.CreateEmbed()
 | 
			
		||||
                                                  .WithOkColor()
 | 
			
		||||
                                                  .WithTitle(GetText(strs.event_title(type.ToString())))
 | 
			
		||||
                                                  .WithDescription(GetReactionDescription(opts.Amount, currentPot))
 | 
			
		||||
                                                  .WithFooter(GetText(strs.event_duration_footer(opts.Hours))),
 | 
			
		||||
                CurrencyEvent.Type.GameStatus => new EmbedBuilder()
 | 
			
		||||
                CurrencyEvent.Type.GameStatus => _sender.CreateEmbed()
 | 
			
		||||
                                                    .WithOkColor()
 | 
			
		||||
                                                    .WithTitle(GetText(strs.event_title(type.ToString())))
 | 
			
		||||
                                                    .WithDescription(GetGameStatusDescription(opts.Amount, currentPot))
 | 
			
		||||
 
 | 
			
		||||
@@ -84,7 +84,7 @@ public partial class Gambling
 | 
			
		||||
                    ? Format.Bold(GetText(strs.heads))
 | 
			
		||||
                    : Format.Bold(GetText(strs.tails))));
 | 
			
		||||
            
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .WithDescription(msg)
 | 
			
		||||
@@ -130,7 +130,7 @@ public partial class Gambling
 | 
			
		||||
                str = Format.Bold(GetText(strs.better_luck));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
            await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                                            .WithDescription(str)
 | 
			
		||||
                                            .WithOkColor()
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
    {
 | 
			
		||||
        var stats = await _gamblingTxTracker.GetAllAsync();
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
            .WithOkColor();
 | 
			
		||||
 | 
			
		||||
        var str = "` Feature `|`   Bet  `|`Paid Out`|`  RoI  `\n";
 | 
			
		||||
@@ -118,7 +118,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // [21:03] Bob Page: Kinda remids me of US economy
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithTitle(GetText(strs.economy_state))
 | 
			
		||||
                    .AddField(GetText(strs.currency_owned), N(ec.Cash - ec.Bot))
 | 
			
		||||
                    .AddField(GetText(strs.currency_one_percent), (onePercent * 100).ToString("F2") + "%")
 | 
			
		||||
@@ -310,7 +310,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            trs = await uow.Set<CurrencyTransaction>().GetPageFor(userId, page);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithTitle(GetText(strs.transactions(((SocketGuild)ctx.Guild)?.GetUser(userId)?.ToString()
 | 
			
		||||
                                                         ?? $"{userId}")))
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
@@ -360,7 +360,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder().WithOkColor();
 | 
			
		||||
        var eb = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
 | 
			
		||||
        eb.WithAuthor(ctx.User);
 | 
			
		||||
        eb.WithTitle(GetText(strs.transaction));
 | 
			
		||||
@@ -624,7 +624,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.roll_duel));
 | 
			
		||||
        var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.roll_duel));
 | 
			
		||||
 | 
			
		||||
        var description = string.Empty;
 | 
			
		||||
 | 
			
		||||
@@ -731,7 +731,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            str = GetText(strs.better_luck);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithAuthor(ctx.User)
 | 
			
		||||
                 .WithDescription(Format.Bold(str))
 | 
			
		||||
                 .AddField(GetText(strs.roll2), result.Roll.ToString(CultureInfo.InvariantCulture))
 | 
			
		||||
@@ -758,68 +758,64 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
 | 
			
		||||
        var (opts, _) = OptionsParser.ParseFrom(new LbOpts(), args);
 | 
			
		||||
 | 
			
		||||
        List<DiscordUser> cleanRichest;
 | 
			
		||||
        // List<DiscordUser> cleanRichest;
 | 
			
		||||
        // it's pointless to have clean on dm context
 | 
			
		||||
        if (ctx.Guild is null)
 | 
			
		||||
        {
 | 
			
		||||
            opts.Clean = false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (opts.Clean)
 | 
			
		||||
 | 
			
		||||
        async Task<IEnumerable<DiscordUser>> GetTopRichest(int curPage)
 | 
			
		||||
        {
 | 
			
		||||
            await using (var uow = _db.GetDbContext())
 | 
			
		||||
            if (opts.Clean)
 | 
			
		||||
            {
 | 
			
		||||
                cleanRichest = await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, 0, 10_000);
 | 
			
		||||
                await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
                await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
 | 
			
		||||
 | 
			
		||||
                await using var uow = _db.GetDbContext();
 | 
			
		||||
 | 
			
		||||
                var cleanRichest = await uow.Set<DiscordUser>()
 | 
			
		||||
                                            .GetTopRichest(_client.CurrentUser.Id, 0, 10_000);
 | 
			
		||||
 | 
			
		||||
                var sg = (SocketGuild)ctx.Guild!;
 | 
			
		||||
                return cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
            await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
 | 
			
		||||
 | 
			
		||||
            var sg = (SocketGuild)ctx.Guild!;
 | 
			
		||||
            cleanRichest = cleanRichest.Where(x => sg.GetUser(x.UserId) is not null).ToList();
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            await using var uow = _db.GetDbContext();
 | 
			
		||||
            cleanRichest = await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, page);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
            async curPage =>
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder().WithOkColor().WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
 | 
			
		||||
                await using var uow = _db.GetDbContext();
 | 
			
		||||
                return await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, curPage);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
                List<DiscordUser> toSend;
 | 
			
		||||
                if (!opts.Clean)
 | 
			
		||||
                {
 | 
			
		||||
                    await using var uow = _db.GetDbContext();
 | 
			
		||||
                    toSend = await uow.Set<DiscordUser>().GetTopRichest(_client.CurrentUser.Id, curPage);
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    toSend = cleanRichest.Skip(curPage * 9).Take(9).ToList();
 | 
			
		||||
                }
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .PageItems(GetTopRichest)
 | 
			
		||||
              .PageSize(9)
 | 
			
		||||
              .CurrentPage(page)
 | 
			
		||||
              .Page((toSend, curPage) =>
 | 
			
		||||
              {
 | 
			
		||||
                  var embed = _sender.CreateEmbed().WithOkColor()
 | 
			
		||||
                                                .WithTitle(CurrencySign + " " + GetText(strs.leaderboard));
 | 
			
		||||
 | 
			
		||||
                if (!toSend.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    embed.WithDescription(GetText(strs.no_user_on_this_page));
 | 
			
		||||
                    return embed;
 | 
			
		||||
                }
 | 
			
		||||
                  if (!toSend.Any())
 | 
			
		||||
                  {
 | 
			
		||||
                      embed.WithDescription(GetText(strs.no_user_on_this_page));
 | 
			
		||||
                      return Task.FromResult(embed);
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < toSend.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    var x = toSend[i];
 | 
			
		||||
                    var usrStr = x.ToString().TrimTo(20, true);
 | 
			
		||||
                  for (var i = 0; i < toSend.Count; i++)
 | 
			
		||||
                  {
 | 
			
		||||
                      var x = toSend[i];
 | 
			
		||||
                      var usrStr = x.ToString().TrimTo(20, true);
 | 
			
		||||
 | 
			
		||||
                    var j = i;
 | 
			
		||||
                    embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, N(x.CurrencyAmount), true);
 | 
			
		||||
                }
 | 
			
		||||
                      var j = i;
 | 
			
		||||
                      embed.AddField("#" + ((9 * curPage) + j + 1) + " " + usrStr, N(x.CurrencyAmount), true);
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                return embed;
 | 
			
		||||
            },
 | 
			
		||||
            opts.Clean ? cleanRichest.Count() : 9000,
 | 
			
		||||
            9,
 | 
			
		||||
            opts.Clean);
 | 
			
		||||
                  return Task.FromResult(embed);
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public enum InputRpsPick : byte
 | 
			
		||||
@@ -861,7 +857,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder();
 | 
			
		||||
        var embed = _sender.CreateEmbed();
 | 
			
		||||
 | 
			
		||||
        string msg;
 | 
			
		||||
        if (result.Result == RpsResultType.Draw)
 | 
			
		||||
@@ -922,7 +918,7 @@ public partial class Gambling : GamblingModule<GamblingService>
 | 
			
		||||
            sb.AppendLine();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithOkColor()
 | 
			
		||||
                 .WithDescription(sb.ToString())
 | 
			
		||||
                 .AddField(GetText(strs.multiplier), $"{result.Multiplier:0.##}x", true)
 | 
			
		||||
 
 | 
			
		||||
@@ -92,21 +92,23 @@ public partial class Gambling
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
            
 | 
			
		||||
            var enabledIn = _service.GetAllGeneratingChannels();
 | 
			
		||||
 | 
			
		||||
            return ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    var items = enabledIn.Skip(page * 9).Take(9).ToList();
 | 
			
		||||
            return Response()
 | 
			
		||||
                   .Paginated()
 | 
			
		||||
                   .Items(enabledIn.ToList())
 | 
			
		||||
                   .PageSize(9)
 | 
			
		||||
                   .CurrentPage(page)
 | 
			
		||||
                   .Page((items, _) =>
 | 
			
		||||
                   {
 | 
			
		||||
                       if (!items.Any())
 | 
			
		||||
                           return _sender.CreateEmbed().WithErrorColor().WithDescription("-");
 | 
			
		||||
 | 
			
		||||
                    if (!items.Any())
 | 
			
		||||
                        return new EmbedBuilder().WithErrorColor().WithDescription("-");
 | 
			
		||||
 | 
			
		||||
                    return items.Aggregate(new EmbedBuilder().WithOkColor(),
 | 
			
		||||
                        (eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
 | 
			
		||||
                },
 | 
			
		||||
                enabledIn.Count(),
 | 
			
		||||
                9);
 | 
			
		||||
                       return items.Aggregate(_sender.CreateEmbed().WithOkColor(),
 | 
			
		||||
                           (eb, i) => eb.AddField(i.GuildId.ToString(), i.ChannelId));
 | 
			
		||||
                   })
 | 
			
		||||
                   .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -48,27 +48,29 @@ public partial class Gambling
 | 
			
		||||
            var entries = uow.GuildConfigsForId(ctx.Guild.Id,
 | 
			
		||||
                                 set => set.Include(x => x.ShopEntries).ThenInclude(x => x.Items))
 | 
			
		||||
                             .ShopEntries.ToIndexed();
 | 
			
		||||
            return ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var theseEntries = entries.Skip(curPage * 9).Take(9).ToArray();
 | 
			
		||||
 | 
			
		||||
                    if (!theseEntries.Any())
 | 
			
		||||
                        return new EmbedBuilder().WithErrorColor().WithDescription(GetText(strs.shop_none));
 | 
			
		||||
                    var embed = new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.shop));
 | 
			
		||||
            return Response()
 | 
			
		||||
                   .Paginated()
 | 
			
		||||
                   .Items(entries.ToList())
 | 
			
		||||
                   .PageSize(9)
 | 
			
		||||
                   .CurrentPage(page)
 | 
			
		||||
                   .Page((items, curPage) =>
 | 
			
		||||
                   {
 | 
			
		||||
                       if (!items.Any())
 | 
			
		||||
                           return _sender.CreateEmbed().WithErrorColor().WithDescription(GetText(strs.shop_none));
 | 
			
		||||
                       var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.shop));
 | 
			
		||||
 | 
			
		||||
                    for (var i = 0; i < theseEntries.Length; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var entry = theseEntries[i];
 | 
			
		||||
                        embed.AddField($"#{(curPage * 9) + i + 1} - {N(entry.Price)}",
 | 
			
		||||
                            EntryToString(entry),
 | 
			
		||||
                            true);
 | 
			
		||||
                    }
 | 
			
		||||
                       for (var i = 0; i < items.Count; i++)
 | 
			
		||||
                       {
 | 
			
		||||
                           var entry = items[i];
 | 
			
		||||
                           embed.AddField($"#{(curPage * 9) + i + 1} - {N(entry.Price)}",
 | 
			
		||||
                               EntryToString(entry),
 | 
			
		||||
                               true);
 | 
			
		||||
                       }
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                entries.Count,
 | 
			
		||||
                9);
 | 
			
		||||
                       return embed;
 | 
			
		||||
                   })
 | 
			
		||||
                   .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -187,7 +189,7 @@ public partial class Gambling
 | 
			
		||||
                    {
 | 
			
		||||
                        await Response()
 | 
			
		||||
                              .User(ctx.User)
 | 
			
		||||
                              .Embed(new EmbedBuilder()
 | 
			
		||||
                              .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                     .WithOkColor()
 | 
			
		||||
                                     .WithTitle(GetText(strs.shop_purchase(ctx.Guild.Name)))
 | 
			
		||||
                                     .AddField(GetText(strs.item), item.Text)
 | 
			
		||||
@@ -246,7 +248,7 @@ public partial class Gambling
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var cmd = entry.Command.Replace("%you%", ctx.User.Id.ToString());
 | 
			
		||||
                    var eb = new EmbedBuilder()
 | 
			
		||||
                    var eb = _sender.CreateEmbed()
 | 
			
		||||
                             .WithPendingColor()
 | 
			
		||||
                             .WithTitle("Executing shop command")
 | 
			
		||||
                             .WithDescription(cmd);
 | 
			
		||||
@@ -268,10 +270,11 @@ public partial class Gambling
 | 
			
		||||
                    try
 | 
			
		||||
                    {
 | 
			
		||||
                        var pendingMsg = await msgTask;
 | 
			
		||||
                        await pendingMsg.EditAsync(SmartEmbedText.FromEmbed(eb
 | 
			
		||||
                                                                            .WithOkColor()
 | 
			
		||||
                                                                            .WithTitle("Shop command executed")
 | 
			
		||||
                                                                            .Build()));
 | 
			
		||||
                        await pendingMsg.EditAsync(
 | 
			
		||||
                            SmartEmbedText.FromEmbed(eb
 | 
			
		||||
                                                     .WithOkColor()
 | 
			
		||||
                                                     .WithTitle("Shop command executed")
 | 
			
		||||
                                                     .Build()));
 | 
			
		||||
                    }
 | 
			
		||||
                    catch
 | 
			
		||||
                    {
 | 
			
		||||
@@ -531,7 +534,7 @@ public partial class Gambling
 | 
			
		||||
 | 
			
		||||
        public EmbedBuilder EntryToEmbed(ShopEntry entry)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
 | 
			
		||||
            if (entry.Type == ShopEntryType.Role)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ public partial class Gambling
 | 
			
		||||
            await using var imgStream = await image.ToStreamAsync();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithAuthor(ctx.User)
 | 
			
		||||
                .WithDescription(Format.Bold(text))
 | 
			
		||||
                .WithImageUrl($"attachment://result.png")
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,9 @@ public partial class Gambling
 | 
			
		||||
        public async Task WaifuReset()
 | 
			
		||||
        {
 | 
			
		||||
            var price = _service.GetResetPrice(ctx.User);
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                           .WithTitle(GetText(strs.waifu_reset_confirm))
 | 
			
		||||
                           .WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithTitle(GetText(strs.waifu_reset_confirm))
 | 
			
		||||
                        .WithDescription(GetText(strs.waifu_reset_price(Format.Bold(N(price)))));
 | 
			
		||||
 | 
			
		||||
            if (!await PromptUserConfirmAsync(embed))
 | 
			
		||||
                return;
 | 
			
		||||
@@ -222,7 +222,7 @@ public partial class Gambling
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle(GetText(strs.waifus_top_waifus)).WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithTitle(GetText(strs.waifus_top_waifus)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
            var i = 0;
 | 
			
		||||
            foreach (var w in waifus)
 | 
			
		||||
@@ -306,25 +306,25 @@ public partial class Gambling
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(fansStr))
 | 
			
		||||
                fansStr = "-";
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(strs.waifu)
 | 
			
		||||
                                      + " "
 | 
			
		||||
                                      + (wi.FullName ?? name ?? targetId.ToString())
 | 
			
		||||
                                      + " - \"the "
 | 
			
		||||
                                      + _service.GetClaimTitle(wi.ClaimCount)
 | 
			
		||||
                                      + "\"")
 | 
			
		||||
                           .AddField(GetText(strs.price), N(wi.Price), true)
 | 
			
		||||
                           .AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
 | 
			
		||||
                           .AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
 | 
			
		||||
                           .AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
 | 
			
		||||
                           .AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
 | 
			
		||||
                           .AddField("\u200B", "\u200B", true)
 | 
			
		||||
                           .AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
 | 
			
		||||
                           .AddField($"Waifus ({wi.ClaimCount})",
 | 
			
		||||
                               wi.ClaimCount == 0 ? nobody : claimsStr,
 | 
			
		||||
                               true)
 | 
			
		||||
                           .AddField(GetText(strs.gifts), itemsStr, true);
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithTitle(GetText(strs.waifu)
 | 
			
		||||
                                   + " "
 | 
			
		||||
                                   + (wi.FullName ?? name ?? targetId.ToString())
 | 
			
		||||
                                   + " - \"the "
 | 
			
		||||
                                   + _service.GetClaimTitle(wi.ClaimCount)
 | 
			
		||||
                                   + "\"")
 | 
			
		||||
                        .AddField(GetText(strs.price), N(wi.Price), true)
 | 
			
		||||
                        .AddField(GetText(strs.claimed_by), wi.ClaimerName ?? nobody, true)
 | 
			
		||||
                        .AddField(GetText(strs.likes), wi.AffinityName ?? nobody, true)
 | 
			
		||||
                        .AddField(GetText(strs.changes_of_heart), $"{wi.AffinityCount} - \"the {affInfo}\"", true)
 | 
			
		||||
                        .AddField(GetText(strs.divorces), wi.DivorceCount.ToString(), true)
 | 
			
		||||
                        .AddField("\u200B", "\u200B", true)
 | 
			
		||||
                        .AddField(GetText(strs.fans(fansList.Count)), fansStr, true)
 | 
			
		||||
                        .AddField($"Waifus ({wi.ClaimCount})",
 | 
			
		||||
                            wi.ClaimCount == 0 ? nobody : claimsStr,
 | 
			
		||||
                            true)
 | 
			
		||||
                        .AddField(GetText(strs.gifts), itemsStr, true);
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(embed).SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
@@ -338,25 +338,27 @@ public partial class Gambling
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var waifuItems = _service.GetWaifuItems();
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                cur =>
 | 
			
		||||
                {
 | 
			
		||||
                    var embed = new EmbedBuilder().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(waifuItems.OrderBy(x => x.Negative)
 | 
			
		||||
                                   .ThenBy(x => x.Price)
 | 
			
		||||
                                   .ToList())
 | 
			
		||||
                  .PageSize(9)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var embed = _sender.CreateEmbed().WithTitle(GetText(strs.waifu_gift_shop)).WithOkColor();
 | 
			
		||||
                      
 | 
			
		||||
                      items
 | 
			
		||||
                          .ToList()
 | 
			
		||||
                          .ForEach(x => embed.AddField(
 | 
			
		||||
                              $"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
 | 
			
		||||
                              Format.Bold(N(x.Price)),
 | 
			
		||||
                              true));
 | 
			
		||||
 | 
			
		||||
                    waifuItems.OrderBy(x => x.Negative)
 | 
			
		||||
                              .ThenBy(x => x.Price)
 | 
			
		||||
                              .Skip(9 * cur)
 | 
			
		||||
                              .Take(9)
 | 
			
		||||
                              .ToList()
 | 
			
		||||
                              .ForEach(x => embed.AddField(
 | 
			
		||||
                                  $"{(!x.Negative ? string.Empty : "\\💔")} {x.ItemEmoji} {x.Name}",
 | 
			
		||||
                                  Format.Bold(N(x.Price)),
 | 
			
		||||
                                  true));
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                waifuItems.Count,
 | 
			
		||||
                9);
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -67,7 +67,7 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
        private Task Game_OnStarted(AcrophobiaGame game)
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(strs.acrophobia))
 | 
			
		||||
                           .WithDescription(
 | 
			
		||||
@@ -92,7 +92,7 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
            if (submissions.Length == 1)
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                                .WithOkColor()
 | 
			
		||||
                                                .WithDescription(GetText(
 | 
			
		||||
                                                    strs.acro_winner_only(
 | 
			
		||||
@@ -103,7 +103,7 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var i = 0;
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(strs.acrophobia) + " - " + GetText(strs.submissions_closed))
 | 
			
		||||
                           .WithDescription(GetText(strs.acro_nym_was(
 | 
			
		||||
@@ -127,7 +127,7 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
            var table = votes.OrderByDescending(v => v.Value);
 | 
			
		||||
            var winner = table.First();
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(strs.acrophobia))
 | 
			
		||||
                           .WithDescription(GetText(strs.acro_winner(Format.Bold(winner.Key.UserName),
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@ public partial class Games : NadekoModule<GamesService>
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var res = _service.GetEightballResponse(ctx.User.Id, question);
 | 
			
		||||
        await Response().Embed(new EmbedBuilder()
 | 
			
		||||
        await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
            .WithOkColor()
 | 
			
		||||
            .WithDescription(ctx.User.ToString())
 | 
			
		||||
            .AddField("❓ " + GetText(strs.question), question)
 | 
			
		||||
 
 | 
			
		||||
@@ -23,11 +23,11 @@ public partial class Games
 | 
			
		||||
            /-\
 | 
			
		||||
            """;
 | 
			
		||||
 | 
			
		||||
        public static EmbedBuilder GetEmbed(HangmanGame.State state)
 | 
			
		||||
        public static EmbedBuilder GetEmbed(IMessageSenderService sender, HangmanGame.State state)
 | 
			
		||||
        {
 | 
			
		||||
            if (state.Phase == HangmanGame.Phase.Running)
 | 
			
		||||
            {
 | 
			
		||||
                return new EmbedBuilder()
 | 
			
		||||
                return sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .AddField("Hangman", Draw(state))
 | 
			
		||||
                         .AddField("Guess", Format.Code(state.Word))
 | 
			
		||||
@@ -36,14 +36,14 @@ public partial class Games
 | 
			
		||||
 | 
			
		||||
            if (state.Phase == HangmanGame.Phase.Ended && state.Failed)
 | 
			
		||||
            {
 | 
			
		||||
                return new EmbedBuilder()
 | 
			
		||||
                return sender.CreateEmbed()
 | 
			
		||||
                         .WithErrorColor()
 | 
			
		||||
                         .AddField("Hangman", Draw(state))
 | 
			
		||||
                         .AddField("Guess", Format.Code(state.Word))
 | 
			
		||||
                         .WithFooter(state.MissedLetters.Join(' '));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return new EmbedBuilder()
 | 
			
		||||
            return sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .AddField("Hangman", Draw(state))
 | 
			
		||||
                     .AddField("Guess", Format.Code(state.Word))
 | 
			
		||||
@@ -60,7 +60,7 @@ public partial class Games
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var eb = GetEmbed(hangman);
 | 
			
		||||
            var eb = GetEmbed(_sender, hangman);
 | 
			
		||||
            eb.WithDescription(GetText(strs.hangman_game_started));
 | 
			
		||||
            await Response().Embed(eb).SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -116,7 +116,7 @@ public sealed class HangmanService : IHangmanService, IExecNoCommand
 | 
			
		||||
        string content,
 | 
			
		||||
        HangmanGame.State state)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = Games.HangmanCommands.GetEmbed(state);
 | 
			
		||||
        var embed = Games.HangmanCommands.GetEmbed(_sender, state);
 | 
			
		||||
        if (state.GuessResult == HangmanGame.GuessResult.Guess)
 | 
			
		||||
            embed.WithDescription($"{user} guessed the letter {content}!").WithOkColor();
 | 
			
		||||
        else if (state.GuessResult == HangmanGame.GuessResult.Incorrect && state.Failed)
 | 
			
		||||
 
 | 
			
		||||
@@ -94,7 +94,7 @@ public partial class Games
 | 
			
		||||
            if (removed is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithTitle($"Removed typing article #{index + 1}")
 | 
			
		||||
                           .WithDescription(removed.Text.TrimTo(50))
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
 
 | 
			
		||||
@@ -82,7 +82,6 @@ public class TypingGame
 | 
			
		||||
 | 
			
		||||
            do
 | 
			
		||||
            {
 | 
			
		||||
                // todo fix all modifies
 | 
			
		||||
                await Task.Delay(2000);
 | 
			
		||||
                time -= 2;
 | 
			
		||||
                try { await msg.ModifyAsync(m => m.Content = $"Starting new typing contest in **{time}**.."); }
 | 
			
		||||
@@ -145,7 +144,7 @@ public class TypingGame
 | 
			
		||||
                    var wpm = CurrentSentence.Length / WORD_VALUE / elapsed.TotalSeconds * 60;
 | 
			
		||||
                    _finishedUserIds.Add(msg.Author.Id);
 | 
			
		||||
 | 
			
		||||
                    var embed = new EmbedBuilder()
 | 
			
		||||
                    var embed = _sender.CreateEmbed()
 | 
			
		||||
                                .WithOkColor()
 | 
			
		||||
                                .WithTitle($"{msg.Author} finished the race!")
 | 
			
		||||
                                .AddField("Place", $"#{_finishedUserIds.Count}", true)
 | 
			
		||||
 
 | 
			
		||||
@@ -73,7 +73,7 @@ public class TicTacToe
 | 
			
		||||
 | 
			
		||||
    public EmbedBuilder GetEmbed(string title = null)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                       .WithOkColor()
 | 
			
		||||
                       .WithDescription(Environment.NewLine + GetState())
 | 
			
		||||
                       .WithAuthor(GetText(strs.vs(_users[0], _users[1])));
 | 
			
		||||
 
 | 
			
		||||
@@ -160,7 +160,7 @@ public partial class Games
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                questionEmbed = new EmbedBuilder()
 | 
			
		||||
                questionEmbed = _sender.CreateEmbed()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithTitle(GetText(strs.trivia_game))
 | 
			
		||||
                                   .AddField(GetText(strs.category), question.Category)
 | 
			
		||||
@@ -189,7 +189,7 @@ public partial class Games
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithErrorColor()
 | 
			
		||||
                               .WithTitle(GetText(strs.trivia_game))
 | 
			
		||||
                               .WithDescription(GetText(strs.trivia_times_up(Format.Bold(question.Answer))));
 | 
			
		||||
@@ -221,7 +221,7 @@ public partial class Games
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                    .WithOkColor()
 | 
			
		||||
                                    .WithAuthor(GetText(strs.trivia_ended))
 | 
			
		||||
                                    .WithTitle(GetText(strs.leaderboard))
 | 
			
		||||
@@ -247,7 +247,7 @@ public partial class Games
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithTitle(GetText(strs.trivia_game))
 | 
			
		||||
                               .WithDescription(GetText(strs.trivia_win(user.Name,
 | 
			
		||||
 
 | 
			
		||||
@@ -84,32 +84,32 @@ public sealed class Help : NadekoModule<HelpService>
 | 
			
		||||
                topLevelModules.Add(m);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
            cur =>
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder().WithOkColor().WithTitle(GetText(strs.list_of_modules));
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(topLevelModules)
 | 
			
		||||
              .CurrentPage(page)
 | 
			
		||||
              .AddFooter(false)
 | 
			
		||||
              .Page((items, _) =>
 | 
			
		||||
              {
 | 
			
		||||
                  var embed = _sender.CreateEmbed().WithOkColor().WithTitle(GetText(strs.list_of_modules));
 | 
			
		||||
 | 
			
		||||
                var localModules = topLevelModules.Skip(12 * cur).Take(12).ToList();
 | 
			
		||||
                  if (!items.Any())
 | 
			
		||||
                  {
 | 
			
		||||
                      embed = embed.WithOkColor().WithDescription(GetText(strs.module_page_empty));
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                if (!localModules.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    embed = embed.WithOkColor().WithDescription(GetText(strs.module_page_empty));
 | 
			
		||||
                    return embed;
 | 
			
		||||
                }
 | 
			
		||||
                  items.OrderBy(module => module.Name)
 | 
			
		||||
                       .ToList()
 | 
			
		||||
                       .ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
 | 
			
		||||
                           GetModuleDescription(module.Name)
 | 
			
		||||
                           + "\n"
 | 
			
		||||
                           + Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
 | 
			
		||||
                           true));
 | 
			
		||||
 | 
			
		||||
                localModules.OrderBy(module => module.Name)
 | 
			
		||||
                            .ToList()
 | 
			
		||||
                            .ForEach(module => embed.AddField($"{GetModuleEmoji(module.Name)} {module.Name}",
 | 
			
		||||
                                GetModuleDescription(module.Name)
 | 
			
		||||
                                + "\n"
 | 
			
		||||
                                + Format.Code(GetText(strs.module_footer(prefix, module.Name.ToLowerInvariant()))),
 | 
			
		||||
                                true));
 | 
			
		||||
 | 
			
		||||
                return embed;
 | 
			
		||||
            },
 | 
			
		||||
            topLevelModules.Count(),
 | 
			
		||||
            12,
 | 
			
		||||
            false);
 | 
			
		||||
                  return embed;
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string GetModuleDescription(string moduleName)
 | 
			
		||||
@@ -271,7 +271,7 @@ public sealed class Help : NadekoModule<HelpService>
 | 
			
		||||
 | 
			
		||||
        var cnt = 0;
 | 
			
		||||
        var groups = cmdsWithGroup.GroupBy(_ => cnt++ / 48).ToArray();
 | 
			
		||||
        var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
        var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
        foreach (var g in groups)
 | 
			
		||||
        {
 | 
			
		||||
            var last = g.Count();
 | 
			
		||||
@@ -303,9 +303,9 @@ public sealed class Help : NadekoModule<HelpService>
 | 
			
		||||
 | 
			
		||||
    private async Task Group(ModuleInfo group)
 | 
			
		||||
    {
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
                    .WithTitle(GetText(strs.cmd_group_commands(group.Name)))
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithTitle(GetText(strs.cmd_group_commands(group.Name)))
 | 
			
		||||
                 .WithOkColor();
 | 
			
		||||
 | 
			
		||||
        foreach (var cmd in group.Commands.DistinctBy(x => x.Aliases[0]))
 | 
			
		||||
        {
 | 
			
		||||
@@ -358,7 +358,8 @@ public sealed class Help : NadekoModule<HelpService>
 | 
			
		||||
                var data = await GetHelpString();
 | 
			
		||||
                if (data == default)
 | 
			
		||||
                    return;
 | 
			
		||||
                await ch.SendAsync(data);
 | 
			
		||||
                
 | 
			
		||||
                await Response().Text(data).SendAsync();
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    await ctx.OkAsync();
 | 
			
		||||
@@ -534,9 +535,9 @@ public sealed class Help : NadekoModule<HelpService>
 | 
			
		||||
                    label: "Selfhosting"),
 | 
			
		||||
                SelfhostAction));
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle("Thank you for considering to donate to the NadekoBot project!");
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithOkColor()
 | 
			
		||||
                 .WithTitle("Thank you for considering to donate to the NadekoBot project!");
 | 
			
		||||
 | 
			
		||||
        eb
 | 
			
		||||
            .WithDescription("NadekoBot relies on donations to keep the servers, services and APIs running.\n"
 | 
			
		||||
@@ -575,7 +576,12 @@ Nadeko will DM you the welcome instructions, and you may start using the patron-
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            await (await ctx.User.CreateDMChannelAsync()).EmbedAsync(eb, inter: selfhostInter);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Channel(await ctx.User.CreateDMChannelAsync())
 | 
			
		||||
                  .Embed(eb)
 | 
			
		||||
                  .Interaction(selfhostInter)
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
            
 | 
			
		||||
            _ = ctx.OkAsync();
 | 
			
		||||
        }
 | 
			
		||||
        catch
 | 
			
		||||
 
 | 
			
		||||
@@ -2,11 +2,22 @@ using NadekoBot.Common.ModuleBehaviors;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Modules.Help.Services;
 | 
			
		||||
 | 
			
		||||
public class HelpService(BotConfigService bss, IReplacementService repSvc) : IExecNoCommand, INService
 | 
			
		||||
public class HelpService : IExecNoCommand, INService
 | 
			
		||||
{
 | 
			
		||||
    private readonly BotConfigService _bss;
 | 
			
		||||
    private readonly IReplacementService _rs;
 | 
			
		||||
    private readonly IMessageSenderService _sender;
 | 
			
		||||
 | 
			
		||||
    public HelpService(BotConfigService bss, IReplacementService repSvc, IMessageSenderService sender)
 | 
			
		||||
    {
 | 
			
		||||
        _bss = bss;
 | 
			
		||||
        _rs = repSvc;
 | 
			
		||||
        _sender = sender;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task ExecOnNoCommandAsync(IGuild? guild, IUserMessage msg)
 | 
			
		||||
    {
 | 
			
		||||
        var settings = bss.Data;
 | 
			
		||||
        var settings = _bss.Data;
 | 
			
		||||
        if (guild is null)
 | 
			
		||||
        {
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(settings.DmHelpText) || settings.DmHelpText == "-")
 | 
			
		||||
@@ -14,20 +25,20 @@ public class HelpService(BotConfigService bss, IReplacementService repSvc) : IEx
 | 
			
		||||
 | 
			
		||||
            // only send dm help text if it contains one of the keywords, if they're specified
 | 
			
		||||
            // if they're not, then reply to every DM
 | 
			
		||||
            if (settings.DmHelpTextKeywords is not null &&
 | 
			
		||||
                !settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k)))
 | 
			
		||||
            if (settings.DmHelpTextKeywords is not null
 | 
			
		||||
                && !settings.DmHelpTextKeywords.Any(k => msg.Content.Contains(k)))
 | 
			
		||||
            {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var repCtx = new ReplacementContext(guild: guild, channel: msg.Channel, users: msg.Author)
 | 
			
		||||
                .WithOverride("%prefix%", () => bss.Data.Prefix)
 | 
			
		||||
                .WithOverride("%bot.prefix%", () => bss.Data.Prefix);
 | 
			
		||||
                         .WithOverride("%prefix%", () => _bss.Data.Prefix)
 | 
			
		||||
                         .WithOverride("%bot.prefix%", () => _bss.Data.Prefix);
 | 
			
		||||
 | 
			
		||||
            var text = SmartText.CreateFrom(settings.DmHelpText);
 | 
			
		||||
            text = await repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
            text = await _rs.ReplaceAsync(text, repCtx);
 | 
			
		||||
 | 
			
		||||
            await msg.Channel.SendAsync(text);
 | 
			
		||||
            await _sender.Response(msg.Channel).Text(text).SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
    {
 | 
			
		||||
        _repo = repo;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    [OwnerOnly]
 | 
			
		||||
    public async Task MedusaLoad(string? name = null)
 | 
			
		||||
@@ -21,11 +21,11 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
            var loaded = _service.GetLoadedMedusae()
 | 
			
		||||
                                 .Select(x => x.Name)
 | 
			
		||||
                                 .ToHashSet();
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            var unloaded = _service.GetAllMedusae()
 | 
			
		||||
                    .Where(x => !loaded.Contains(x))
 | 
			
		||||
                    .Select(x => Format.Code(x.ToString()))
 | 
			
		||||
                    .ToArray();
 | 
			
		||||
                                   .Where(x => !loaded.Contains(x))
 | 
			
		||||
                                   .Select(x => Format.Code(x.ToString()))
 | 
			
		||||
                                   .ToArray();
 | 
			
		||||
 | 
			
		||||
            if (unloaded.Length == 0)
 | 
			
		||||
            {
 | 
			
		||||
@@ -33,16 +33,18 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                page =>
 | 
			
		||||
                {
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                              .WithOkColor()
 | 
			
		||||
                              .WithTitle(GetText(strs.list_of_unloaded))
 | 
			
		||||
                              .WithDescription(unloaded.Skip(10 * page).Take(10).Join('\n'));
 | 
			
		||||
                },
 | 
			
		||||
                unloaded.Length,
 | 
			
		||||
                10);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(unloaded)
 | 
			
		||||
                  .PageSize(10)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      return _sender.CreateEmbed()
 | 
			
		||||
                             .WithOkColor()
 | 
			
		||||
                             .WithTitle(GetText(strs.list_of_unloaded))
 | 
			
		||||
                             .WithDescription(items.Join('\n'));
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -63,7 +65,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
            await Response().Error(locStr).SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    [OwnerOnly]
 | 
			
		||||
    public async Task MedusaUnload(string? name = null)
 | 
			
		||||
@@ -77,15 +79,17 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                                            .WithOkColor()
 | 
			
		||||
                                            .WithTitle(GetText(strs.loaded_medusae))
 | 
			
		||||
                                            .WithDescription(loaded.Select(x => x.Name)
 | 
			
		||||
                                                                   .Join("\n"))).SendAsync();
 | 
			
		||||
            
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.loaded_medusae))
 | 
			
		||||
                         .WithDescription(loaded.Select(x => x.Name)
 | 
			
		||||
                                                .Join("\n")))
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var res = await _service.UnloadMedusaAsync(name);
 | 
			
		||||
        if (res == MedusaUnloadResult.Success)
 | 
			
		||||
            await Response().Confirm(strs.medusa_unloaded(Format.Code(name))).SendAsync();
 | 
			
		||||
@@ -113,27 +117,29 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
            await Response().Pending(strs.no_medusa_available).SendAsync();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        var loaded = _service.GetLoadedMedusae()
 | 
			
		||||
                             .Select(x => x.Name)
 | 
			
		||||
                             .ToHashSet();
 | 
			
		||||
 | 
			
		||||
        var output = all
 | 
			
		||||
            .Select(m =>
 | 
			
		||||
            {
 | 
			
		||||
                var emoji = loaded.Contains(m) ? "`✅`" : "`🔴`";
 | 
			
		||||
                return $"{emoji} `{m}`";
 | 
			
		||||
            })
 | 
			
		||||
            .ToArray();
 | 
			
		||||
                     .Select(m =>
 | 
			
		||||
                     {
 | 
			
		||||
                         var emoji = loaded.Contains(m) ? "`✅`" : "`🔴`";
 | 
			
		||||
                         return $"{emoji} `{m}`";
 | 
			
		||||
                     })
 | 
			
		||||
                     .ToArray();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
            page => new EmbedBuilder()
 | 
			
		||||
                       .WithOkColor()
 | 
			
		||||
                       .WithTitle(GetText(strs.list_of_medusae))
 | 
			
		||||
                       .WithDescription(output.Skip(page * 10).Take(10).Join('\n')),
 | 
			
		||||
            output.Length,
 | 
			
		||||
            10);
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(output)
 | 
			
		||||
              .PageSize(10)
 | 
			
		||||
              .Page((items, _) => _sender.CreateEmbed()
 | 
			
		||||
                                  .WithOkColor()
 | 
			
		||||
                                  .WithTitle(GetText(strs.list_of_medusae))
 | 
			
		||||
                                  .WithDescription(items.Join('\n')))
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -147,7 +153,7 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
            var found = medusae.FirstOrDefault(x => string.Equals(x.Name,
 | 
			
		||||
                name,
 | 
			
		||||
                StringComparison.InvariantCultureIgnoreCase));
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            if (found is null)
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Error(strs.medusa_name_not_found).SendAsync();
 | 
			
		||||
@@ -156,26 +162,26 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
 | 
			
		||||
            var cmdCount = found.Sneks.Sum(x => x.Commands.Count);
 | 
			
		||||
            var cmdNames = found.Sneks
 | 
			
		||||
                                   .SelectMany(x => Format.Code(string.IsNullOrWhiteSpace(x.Prefix)
 | 
			
		||||
                                       ? x.Name
 | 
			
		||||
                                       : $"{x.Prefix} {x.Name}"))
 | 
			
		||||
                                   .Join("\n");
 | 
			
		||||
                                .SelectMany(x => Format.Code(string.IsNullOrWhiteSpace(x.Prefix)
 | 
			
		||||
                                    ? x.Name
 | 
			
		||||
                                    : $"{x.Prefix} {x.Name}"))
 | 
			
		||||
                                .Join("\n");
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithAuthor(GetText(strs.medusa_info))
 | 
			
		||||
                        .WithTitle(found.Name)
 | 
			
		||||
                        .WithDescription(found.Description)
 | 
			
		||||
                        .AddField(GetText(strs.sneks_count(found.Sneks.Count)),
 | 
			
		||||
                            found.Sneks.Count == 0
 | 
			
		||||
                                ? "-"
 | 
			
		||||
                                : found.Sneks.Select(x => x.Name).Join('\n'),
 | 
			
		||||
                            true)
 | 
			
		||||
                        .AddField(GetText(strs.commands_count(cmdCount)),
 | 
			
		||||
                            string.IsNullOrWhiteSpace(cmdNames)
 | 
			
		||||
                                ? "-"
 | 
			
		||||
                                : cmdNames,
 | 
			
		||||
                            true);
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithAuthor(GetText(strs.medusa_info))
 | 
			
		||||
                     .WithTitle(found.Name)
 | 
			
		||||
                     .WithDescription(found.Description)
 | 
			
		||||
                     .AddField(GetText(strs.sneks_count(found.Sneks.Count)),
 | 
			
		||||
                         found.Sneks.Count == 0
 | 
			
		||||
                             ? "-"
 | 
			
		||||
                             : found.Sneks.Select(x => x.Name).Join('\n'),
 | 
			
		||||
                         true)
 | 
			
		||||
                     .AddField(GetText(strs.commands_count(cmdCount)),
 | 
			
		||||
                         string.IsNullOrWhiteSpace(cmdNames)
 | 
			
		||||
                             ? "-"
 | 
			
		||||
                             : cmdNames,
 | 
			
		||||
                         true);
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(eb).SendAsync();
 | 
			
		||||
            return;
 | 
			
		||||
@@ -186,42 +192,49 @@ public partial class Medusa : NadekoModule<IMedusaLoaderService>
 | 
			
		||||
            await Response().Pending(strs.no_medusa_loaded).SendAsync();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
            page =>
 | 
			
		||||
            {
 | 
			
		||||
                var eb = new EmbedBuilder()
 | 
			
		||||
                            .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                foreach (var medusa in medusae.Skip(page * 9).Take(9))
 | 
			
		||||
                {
 | 
			
		||||
                    eb.AddField(medusa.Name,
 | 
			
		||||
                        $"""
 | 
			
		||||
                            `Sneks:` {medusa.Sneks.Count}
 | 
			
		||||
                            `Commands:` {medusa.Sneks.Sum(x => x.Commands.Count)}
 | 
			
		||||
                            --
 | 
			
		||||
                            {medusa.Description}
 | 
			
		||||
                            """);
 | 
			
		||||
                }
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(medusae)
 | 
			
		||||
              .PageSize(9)
 | 
			
		||||
              .CurrentPage(0)
 | 
			
		||||
              .Page((items, _) =>
 | 
			
		||||
              {
 | 
			
		||||
                  var eb = _sender.CreateEmbed()
 | 
			
		||||
                      .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                return eb;
 | 
			
		||||
            }, medusae.Count, 9);
 | 
			
		||||
                  foreach (var medusa in items)
 | 
			
		||||
                  {
 | 
			
		||||
                      eb.AddField(medusa.Name,
 | 
			
		||||
                          $"""
 | 
			
		||||
                           `Sneks:` {medusa.Sneks.Count}
 | 
			
		||||
                           `Commands:` {medusa.Sneks.Sum(x => x.Commands.Count)}
 | 
			
		||||
                           --
 | 
			
		||||
                           {medusa.Description}
 | 
			
		||||
                           """);
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  return eb;
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
    [OwnerOnly]
 | 
			
		||||
    public async Task MedusaSearch()
 | 
			
		||||
    {
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
                    .WithTitle(GetText(strs.list_of_medusae))
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
        
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithTitle(GetText(strs.list_of_medusae))
 | 
			
		||||
                 .WithOkColor();
 | 
			
		||||
 | 
			
		||||
        foreach (var item in await _repo.GetModuleItemsAsync())
 | 
			
		||||
        {
 | 
			
		||||
            eb.AddField(item.Name, $"""
 | 
			
		||||
                {item.Description}
 | 
			
		||||
                `{item.Command}`
 | 
			
		||||
                """, true);
 | 
			
		||||
            eb.AddField(item.Name,
 | 
			
		||||
                $"""
 | 
			
		||||
                 {item.Description}
 | 
			
		||||
                 `{item.Command}`
 | 
			
		||||
                 """,
 | 
			
		||||
                true);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Response().Embed(eb).SendAsync();
 | 
			
		||||
 
 | 
			
		||||
@@ -109,11 +109,11 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithAuthor(GetText(strs.queued_track) + " #" + (index + 1), MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ")
 | 
			
		||||
                           .WithFooter(trackInfo.Platform.ToString());
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithAuthor(GetText(strs.queued_track) + " #" + (index + 1), MUSIC_ICON_URL)
 | 
			
		||||
                        .WithDescription($"{trackInfo.PrettyName()}\n{GetText(strs.queue)} ")
 | 
			
		||||
                        .WithFooter(trackInfo.Platform.ToString());
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(trackInfo.Thumbnail))
 | 
			
		||||
                embed.WithThumbnailUrl(trackInfo.Thumbnail);
 | 
			
		||||
@@ -273,7 +273,7 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        EmbedBuilder PrintAction(int curPage)
 | 
			
		||||
        EmbedBuilder PrintAction(IReadOnlyList<IQueuedTrackInfo> tracks, int curPage)
 | 
			
		||||
        {
 | 
			
		||||
            var desc = string.Empty;
 | 
			
		||||
            var current = mp.GetCurrentTrack(out var currentIndex);
 | 
			
		||||
@@ -300,32 +300,38 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            desc += tracks.Skip(LQ_ITEMS_PER_PAGE * curPage)
 | 
			
		||||
                          .Take(LQ_ITEMS_PER_PAGE)
 | 
			
		||||
                          .Select((v, index) =>
 | 
			
		||||
                          {
 | 
			
		||||
                              index += LQ_ITEMS_PER_PAGE * curPage;
 | 
			
		||||
                              if (index == currentIndex)
 | 
			
		||||
                                  return $"**⇒**`{index + 1}.` {v.PrettyFullName()}";
 | 
			
		||||
            desc += tracks
 | 
			
		||||
                    .Select((v, index) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        index += LQ_ITEMS_PER_PAGE * curPage;
 | 
			
		||||
                        if (index == currentIndex)
 | 
			
		||||
                            return $"**⇒**`{index + 1}.` {v.PrettyFullName()}";
 | 
			
		||||
 | 
			
		||||
                              return $"`{index + 1}.` {v.PrettyFullName()}";
 | 
			
		||||
                          })
 | 
			
		||||
                          .Join('\n');
 | 
			
		||||
                        return $"`{index + 1}.` {v.PrettyFullName()}";
 | 
			
		||||
                    })
 | 
			
		||||
                    .Join('\n');
 | 
			
		||||
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(add))
 | 
			
		||||
                desc = add + "\n" + desc;
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                           .WithAuthor(GetText(strs.player_queue(curPage + 1, (tracks.Count / LQ_ITEMS_PER_PAGE) + 1)),
 | 
			
		||||
                               MUSIC_ICON_URL)
 | 
			
		||||
                           .WithDescription(desc)
 | 
			
		||||
                           .WithFooter($"  {mp.PrettyVolume()}  |  🎶 {tracks.Count}  |  ⌛ {mp.PrettyTotalTime()}  ")
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithAuthor(GetText(strs.player_queue(curPage + 1, (tracks.Count / LQ_ITEMS_PER_PAGE) + 1)),
 | 
			
		||||
                            MUSIC_ICON_URL)
 | 
			
		||||
                        .WithDescription(desc)
 | 
			
		||||
                        .WithFooter($"  {mp.PrettyVolume()}  |  🎶 {tracks.Count}  |  ⌛ {mp.PrettyTotalTime()}  ")
 | 
			
		||||
                        .WithOkColor();
 | 
			
		||||
 | 
			
		||||
            return embed;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page, PrintAction, tracks.Count, LQ_ITEMS_PER_PAGE, false);
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(tracks)
 | 
			
		||||
              .PageSize(LQ_ITEMS_PER_PAGE)
 | 
			
		||||
              .CurrentPage(page)
 | 
			
		||||
              .AddFooter(false)
 | 
			
		||||
              .Page(PrintAction)
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // search
 | 
			
		||||
@@ -408,11 +414,11 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
                       .WithAuthor(GetText(strs.removed_track) + " #" + index, MUSIC_ICON_URL)
 | 
			
		||||
                       .WithDescription(track.PrettyName())
 | 
			
		||||
                       .WithFooter(track.PrettyInfo())
 | 
			
		||||
                       .WithErrorColor();
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithAuthor(GetText(strs.removed_track) + " #" + index, MUSIC_ICON_URL)
 | 
			
		||||
                    .WithDescription(track.PrettyName())
 | 
			
		||||
                    .WithFooter(track.PrettyInfo())
 | 
			
		||||
                    .WithErrorColor();
 | 
			
		||||
 | 
			
		||||
        await _service.SendToOutputAsync(ctx.Guild.Id, embed);
 | 
			
		||||
    }
 | 
			
		||||
@@ -576,12 +582,12 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
                       .WithTitle(track.Title.TrimTo(65))
 | 
			
		||||
                       .WithAuthor(GetText(strs.track_moved), MUSIC_ICON_URL)
 | 
			
		||||
                       .AddField(GetText(strs.from_position), $"#{from + 1}", true)
 | 
			
		||||
                       .AddField(GetText(strs.to_position), $"#{to + 1}", true)
 | 
			
		||||
                       .WithOkColor();
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithTitle(track.Title.TrimTo(65))
 | 
			
		||||
                    .WithAuthor(GetText(strs.track_moved), MUSIC_ICON_URL)
 | 
			
		||||
                    .AddField(GetText(strs.from_position), $"#{from + 1}", true)
 | 
			
		||||
                    .AddField(GetText(strs.to_position), $"#{to + 1}", true)
 | 
			
		||||
                    .WithOkColor();
 | 
			
		||||
 | 
			
		||||
        if (Uri.IsWellFormedUriString(track.Url, UriKind.Absolute))
 | 
			
		||||
            embed.WithUrl(track.Url);
 | 
			
		||||
@@ -635,13 +641,13 @@ public sealed partial class Music : NadekoModule<IMusicService>
 | 
			
		||||
        if (currentTrack is null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
                       .WithOkColor()
 | 
			
		||||
                       .WithAuthor(GetText(strs.now_playing), MUSIC_ICON_URL)
 | 
			
		||||
                       .WithDescription(currentTrack.PrettyName())
 | 
			
		||||
                       .WithThumbnailUrl(currentTrack.Thumbnail)
 | 
			
		||||
                       .WithFooter(
 | 
			
		||||
                           $"{mp.PrettyVolume()} | {mp.PrettyTotalTime()} | {currentTrack.Platform} | {currentTrack.Queuer}");
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithAuthor(GetText(strs.now_playing), MUSIC_ICON_URL)
 | 
			
		||||
                    .WithDescription(currentTrack.PrettyName())
 | 
			
		||||
                    .WithThumbnailUrl(currentTrack.Thumbnail)
 | 
			
		||||
                    .WithFooter(
 | 
			
		||||
                        $"{mp.PrettyVolume()} | {mp.PrettyTotalTime()} | {currentTrack.Platform} | {currentTrack.Queuer}");
 | 
			
		||||
 | 
			
		||||
        await Response().Embed(embed).SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -50,7 +50,7 @@ public sealed partial class Music
 | 
			
		||||
                playlists = uow.Set<MusicPlaylist>().GetPlaylistsOnPage(num);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithAuthor(GetText(strs.playlists_page(num)), MUSIC_ICON_URL)
 | 
			
		||||
                        .WithDescription(string.Join("\n",
 | 
			
		||||
                            playlists.Select(r => GetText(strs.playlists(r.Id, r.Name, r.Author, r.Songs.Count)))))
 | 
			
		||||
@@ -103,20 +103,22 @@ public sealed partial class Music
 | 
			
		||||
                mpl = uow.Set<MusicPlaylist>().GetWithSongs(id);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                cur =>
 | 
			
		||||
                {
 | 
			
		||||
                    var i = 0;
 | 
			
		||||
                    var str = string.Join("\n",
 | 
			
		||||
                        mpl.Songs.Skip(cur * 20)
 | 
			
		||||
                           .Take(20)
 | 
			
		||||
                           .Select(x => $"`{++i}.` [{x.Title.TrimTo(45)}]({x.Query}) `{x.Provider}`"));
 | 
			
		||||
                    return new EmbedBuilder().WithTitle($"\"{mpl.Name}\" by {mpl.Author}")
 | 
			
		||||
                                             .WithOkColor()
 | 
			
		||||
                                             .WithDescription(str);
 | 
			
		||||
                },
 | 
			
		||||
                mpl.Songs.Count,
 | 
			
		||||
                20);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(mpl.Songs)
 | 
			
		||||
                  .PageSize(20)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var i = 0;
 | 
			
		||||
                      var str = string.Join("\n",
 | 
			
		||||
                          items
 | 
			
		||||
                              .Select(x => $"`{++i}.` [{x.Title.TrimTo(45)}]({x.Query}) `{x.Provider}`"));
 | 
			
		||||
                      return _sender.CreateEmbed().WithTitle($"\"{mpl.Name}\" by {mpl.Author}")
 | 
			
		||||
                                               .WithOkColor()
 | 
			
		||||
                                               .WithDescription(str);
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -154,7 +156,7 @@ public sealed partial class Music
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(new EmbedBuilder()
 | 
			
		||||
                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.playlist_saved))
 | 
			
		||||
                         .AddField(GetText(strs.name), name)
 | 
			
		||||
 
 | 
			
		||||
@@ -163,7 +163,9 @@ public sealed class MusicService : IMusicService
 | 
			
		||||
    {
 | 
			
		||||
        if (_outputChannels.TryGetValue(guildId, out var chan))
 | 
			
		||||
        {
 | 
			
		||||
            var msg = await (chan.Override ?? chan.Default).EmbedAsync(embed);
 | 
			
		||||
            var msg = await _sender.Response(chan.Override ?? chan.Default)
 | 
			
		||||
                                   .Embed(embed)
 | 
			
		||||
                                   .SendAsync();
 | 
			
		||||
            return msg;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -176,11 +178,11 @@ public sealed class MusicService : IMusicService
 | 
			
		||||
        return async (mp, trackInfo) =>
 | 
			
		||||
        {
 | 
			
		||||
            _ = lastFinishedMessage?.DeleteAsync();
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(GetText(guildId, strs.finished_track), Music.MUSIC_ICON_URL)
 | 
			
		||||
                .WithDescription(trackInfo.PrettyName())
 | 
			
		||||
                .WithFooter(trackInfo.PrettyTotalTime());
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithAuthor(GetText(guildId, strs.finished_track), Music.MUSIC_ICON_URL)
 | 
			
		||||
                        .WithDescription(trackInfo.PrettyName())
 | 
			
		||||
                        .WithFooter(trackInfo.PrettyTotalTime());
 | 
			
		||||
 | 
			
		||||
            lastFinishedMessage = await SendToOutputAsync(guildId, embed);
 | 
			
		||||
        };
 | 
			
		||||
@@ -192,11 +194,11 @@ public sealed class MusicService : IMusicService
 | 
			
		||||
        return async (mp, trackInfo, index) =>
 | 
			
		||||
        {
 | 
			
		||||
            _ = lastPlayingMessage?.DeleteAsync();
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithAuthor(GetText(guildId, strs.playing_track(index + 1)), Music.MUSIC_ICON_URL)
 | 
			
		||||
                .WithDescription(trackInfo.PrettyName())
 | 
			
		||||
                .WithFooter($"{mp.PrettyVolume()} | {trackInfo.PrettyInfo()}");
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithAuthor(GetText(guildId, strs.playing_track(index + 1)), Music.MUSIC_ICON_URL)
 | 
			
		||||
                        .WithDescription(trackInfo.PrettyName())
 | 
			
		||||
                        .WithFooter($"{mp.PrettyVolume()} | {trackInfo.PrettyInfo()}");
 | 
			
		||||
 | 
			
		||||
            lastPlayingMessage = await SendToOutputAsync(guildId, embed);
 | 
			
		||||
        };
 | 
			
		||||
@@ -279,9 +281,9 @@ public sealed class MusicService : IMusicService
 | 
			
		||||
        yield return ("%music.playing%", () =>
 | 
			
		||||
        {
 | 
			
		||||
            var randomPlayingTrack = _players.Select(x => x.Value.GetCurrentTrack(out _))
 | 
			
		||||
                .Where(x => x is not null)
 | 
			
		||||
                .Shuffle()
 | 
			
		||||
                .FirstOrDefault();
 | 
			
		||||
                                             .Where(x => x is not null)
 | 
			
		||||
                                             .Shuffle()
 | 
			
		||||
                                             .FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
            if (randomPlayingTrack is null)
 | 
			
		||||
                return "-";
 | 
			
		||||
 
 | 
			
		||||
@@ -171,7 +171,7 @@ public sealed class CurrencyRewardService : INService, IDisposable
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithDescription(message);
 | 
			
		||||
            
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ public partial class Patronage : NadekoModule
 | 
			
		||||
    //     
 | 
			
		||||
    //     var patron = _service.GiftPatronAsync(user, amount);
 | 
			
		||||
    //
 | 
			
		||||
    //     var eb = new EmbedBuilder();
 | 
			
		||||
    //     var eb = _sender.CreateEmbed();
 | 
			
		||||
    //
 | 
			
		||||
    //     await Response().Embed(eb.WithDescription($"Added **{days}** days of Patron benefits to {user.Mention}!")
 | 
			
		||||
    //                                    .AddField("Tier", Format.Bold(patron.Tier.ToString()), true)
 | 
			
		||||
@@ -70,7 +70,7 @@ public partial class Patronage : NadekoModule
 | 
			
		||||
        var patron = await _service.GetPatronAsync(user.Id);
 | 
			
		||||
        var quotaStats = await _service.GetUserQuotaStatistic(user.Id);
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithAuthor(user)
 | 
			
		||||
                 .WithTitle(GetText(strs.patron_info))
 | 
			
		||||
                 .WithOkColor();
 | 
			
		||||
 
 | 
			
		||||
@@ -306,7 +306,7 @@ public sealed class PatronageService
 | 
			
		||||
            _ => false,
 | 
			
		||||
            ins =>
 | 
			
		||||
            {
 | 
			
		||||
                var eb = new EmbedBuilder()
 | 
			
		||||
                var eb = _sender.CreateEmbed()
 | 
			
		||||
                         .WithPendingColor()
 | 
			
		||||
                         .WithTitle("Insufficient Patron Tier")
 | 
			
		||||
                         .AddField("For", $"{ins.FeatureType}: `{ins.Feature}`", true)
 | 
			
		||||
@@ -336,7 +336,7 @@ public sealed class PatronageService
 | 
			
		||||
            },
 | 
			
		||||
            quota =>
 | 
			
		||||
            {
 | 
			
		||||
                var eb = new EmbedBuilder()
 | 
			
		||||
                var eb = _sender.CreateEmbed()
 | 
			
		||||
                         .WithPendingColor()
 | 
			
		||||
                         .WithTitle("Quota Limit Reached");
 | 
			
		||||
 | 
			
		||||
@@ -778,7 +778,7 @@ public sealed class PatronageService
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithTitle("❤️ Thank you for supporting NadekoBot! ❤️")
 | 
			
		||||
                     .WithDescription(
 | 
			
		||||
 
 | 
			
		||||
@@ -60,12 +60,12 @@ public partial class Permissions
 | 
			
		||||
                  .Page((pageItems, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      if (pageItems.Count == 0)
 | 
			
		||||
                          return new EmbedBuilder()
 | 
			
		||||
                          return _sender.CreateEmbed()
 | 
			
		||||
                                 .WithOkColor()
 | 
			
		||||
                                 .WithTitle(title)
 | 
			
		||||
                                 .WithDescription(GetText(strs.empty_page));
 | 
			
		||||
 | 
			
		||||
                      return new EmbedBuilder()
 | 
			
		||||
                      return _sender.CreateEmbed()
 | 
			
		||||
                             .WithTitle(title)
 | 
			
		||||
                             .WithDescription(allItems.Join('\n'))
 | 
			
		||||
                             .WithOkColor();
 | 
			
		||||
 
 | 
			
		||||
@@ -61,7 +61,7 @@ public partial class Permissions
 | 
			
		||||
            else
 | 
			
		||||
                await Response().Confirm(strs.cmdcd_add(Format.Bold(name), Format.Bold(secs.ToString()))).SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [Priority(0)]
 | 
			
		||||
@@ -80,7 +80,7 @@ public partial class Permissions
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
 | 
			
		||||
            var channel = (ITextChannel)ctx.Channel;
 | 
			
		||||
            var localSet = _service.GetCommandCooldowns(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
@@ -88,17 +88,21 @@ public partial class Permissions
 | 
			
		||||
                await Response().Confirm(strs.cmdcd_none).SendAsync();
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await ctx.SendPaginatedConfirmAsync(page, curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var items = localSet.Skip(curPage * 15)
 | 
			
		||||
                        .Take(15)
 | 
			
		||||
                        .Select(x => $"{Format.Code(x.CommandName)}: {x.Seconds.Seconds().Humanize(maxUnit: TimeUnit.Second, culture: Culture)}");
 | 
			
		||||
                await Response()
 | 
			
		||||
                      .Paginated()
 | 
			
		||||
                      .Items(localSet)
 | 
			
		||||
                      .PageSize(15)
 | 
			
		||||
                      .CurrentPage(page)
 | 
			
		||||
                      .Page((items, _) =>
 | 
			
		||||
                      {
 | 
			
		||||
                          var output = items.Select(x =>
 | 
			
		||||
                              $"{Format.Code(x.CommandName)}: {x.Seconds.Seconds().Humanize(maxUnit: TimeUnit.Second, culture: Culture)}");
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithDescription(items.Join("\n"));
 | 
			
		||||
 | 
			
		||||
                }, localSet.Count, 15);
 | 
			
		||||
                          return _sender.CreateEmbed()
 | 
			
		||||
                                 .WithOkColor()
 | 
			
		||||
                                 .WithDescription(output.Join("\n"));
 | 
			
		||||
                      })
 | 
			
		||||
                      .SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -29,9 +29,9 @@ public partial class Permissions
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        public async Task FilterList()
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle("Server filter settings");
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithTitle("Server filter settings");
 | 
			
		||||
 | 
			
		||||
            var config = await _service.GetFilterSettings(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
@@ -41,14 +41,14 @@ public partial class Permissions
 | 
			
		||||
            async Task<string> GetChannelListAsync(IReadOnlyCollection<ulong> channels)
 | 
			
		||||
            {
 | 
			
		||||
                var toReturn = (await channels
 | 
			
		||||
                        .Select(async cid =>
 | 
			
		||||
                        {
 | 
			
		||||
                            var ch = await ctx.Guild.GetChannelAsync(cid);
 | 
			
		||||
                            return ch is null
 | 
			
		||||
                                ? $"{cid} *missing*"
 | 
			
		||||
                                : $"<#{cid}>";
 | 
			
		||||
                        })
 | 
			
		||||
                        .WhenAll())
 | 
			
		||||
                                      .Select(async cid =>
 | 
			
		||||
                                      {
 | 
			
		||||
                                          var ch = await ctx.Guild.GetChannelAsync(cid);
 | 
			
		||||
                                          return ch is null
 | 
			
		||||
                                              ? $"{cid} *missing*"
 | 
			
		||||
                                              : $"<#{cid}>";
 | 
			
		||||
                                      })
 | 
			
		||||
                                      .WhenAll())
 | 
			
		||||
                    .Join('\n');
 | 
			
		||||
 | 
			
		||||
                if (string.IsNullOrWhiteSpace(toReturn))
 | 
			
		||||
@@ -312,13 +312,16 @@ public partial class Permissions
 | 
			
		||||
 | 
			
		||||
            var fws = fwHash.ToArray();
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage => new EmbedBuilder()
 | 
			
		||||
                              .WithTitle(GetText(strs.filter_word_list))
 | 
			
		||||
                              .WithDescription(string.Join("\n", fws.Skip(curPage * 10).Take(10)))
 | 
			
		||||
                              .WithOkColor(),
 | 
			
		||||
                fws.Length,
 | 
			
		||||
                10);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(fws)
 | 
			
		||||
                  .PageSize(10)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) => _sender.CreateEmbed()
 | 
			
		||||
                                      .WithTitle(GetText(strs.filter_word_list))
 | 
			
		||||
                                      .WithDescription(string.Join("\n", items))
 | 
			
		||||
                                      .WithOkColor())
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -30,7 +30,7 @@ public partial class Permissions
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
 | 
			
		||||
            if (blockedModule.Any())
 | 
			
		||||
                embed.AddField(GetText(strs.blocked_modules), string.Join("\n", _service.BlockedModules));
 | 
			
		||||
 
 | 
			
		||||
@@ -24,7 +24,7 @@ public partial class Searches
 | 
			
		||||
        //         return;
 | 
			
		||||
        //     }
 | 
			
		||||
        //
 | 
			
		||||
        //     var embed = new EmbedBuilder()
 | 
			
		||||
        //     var embed = _sender.CreateEmbed()
 | 
			
		||||
        //         .WithOkColor()
 | 
			
		||||
        //         .WithDescription(novelData.Description.Replace("<br>", Environment.NewLine, StringComparison.InvariantCulture))
 | 
			
		||||
        //         .WithTitle(novelData.Title)
 | 
			
		||||
@@ -86,7 +86,7 @@ public partial class Searches
 | 
			
		||||
                                      .Select(x => x.TextContent.Split(':').Select(y => y.Trim()).ToArray())
 | 
			
		||||
                                      .ToArray();
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(strs.mal_profile(name)))
 | 
			
		||||
                           .AddField("💚 " + GetText(strs.watching), stats[0], true)
 | 
			
		||||
@@ -151,7 +151,7 @@ public partial class Searches
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithDescription(animeData.Synopsis.Replace("<br>",
 | 
			
		||||
                               Environment.NewLine,
 | 
			
		||||
@@ -183,7 +183,7 @@ public partial class Searches
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithDescription(mangaData.Synopsis.Replace("<br>",
 | 
			
		||||
                               Environment.NewLine,
 | 
			
		||||
 
 | 
			
		||||
@@ -35,7 +35,7 @@ public partial class Searches
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var symbol = symbols.First();
 | 
			
		||||
                var promptEmbed = new EmbedBuilder()
 | 
			
		||||
                var promptEmbed = _sender.CreateEmbed()
 | 
			
		||||
                                     .WithDescription(symbol.Description)
 | 
			
		||||
                                     .WithTitle(GetText(strs.did_you_mean(symbol.Symbol)));
 | 
			
		||||
                
 | 
			
		||||
@@ -79,7 +79,7 @@ public partial class Searches
 | 
			
		||||
            
 | 
			
		||||
            var price = stock.Price.ToString("C2", localCulture);
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                        .WithOkColor()
 | 
			
		||||
                        .WithAuthor(stock.Symbol)
 | 
			
		||||
                        .WithUrl($"https://www.tradingview.com/chart/?symbol={stock.Symbol}")
 | 
			
		||||
@@ -127,7 +127,7 @@ public partial class Searches
 | 
			
		||||
 | 
			
		||||
            if (nearest is not null)
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithTitle(GetText(strs.crypto_not_found))
 | 
			
		||||
                               .WithDescription(
 | 
			
		||||
                                   GetText(strs.did_you_mean(Format.Bold($"{nearest.Name} ({nearest.Symbol})"))));
 | 
			
		||||
@@ -160,7 +160,7 @@ public partial class Searches
 | 
			
		||||
            await using var sparkline = await _service.GetSparklineAsync(crypto.Id, usd.PercentChange7d >= 0);
 | 
			
		||||
            var fileName = $"{crypto.Slug}_7d.png";
 | 
			
		||||
            
 | 
			
		||||
            var toSend = new EmbedBuilder()
 | 
			
		||||
            var toSend = _sender.CreateEmbed()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithAuthor($"#{crypto.CmcRank}")
 | 
			
		||||
                            .WithTitle($"{crypto.Name} ({crypto.Symbol})")
 | 
			
		||||
 
 | 
			
		||||
@@ -111,28 +111,36 @@ public partial class Searches
 | 
			
		||||
        [Cmd]
 | 
			
		||||
        [RequireContext(ContextType.Guild)]
 | 
			
		||||
        [UserPerm(GuildPerm.ManageMessages)]
 | 
			
		||||
        public async Task FeedList()
 | 
			
		||||
        public async Task FeedList(int page = 1)
 | 
			
		||||
        {
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
            
 | 
			
		||||
            var feeds = _service.GetFeeds(ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
            if (!feeds.Any())
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Embed(new EmbedBuilder().WithOkColor().WithDescription(GetText(strs.feed_no_feed))).SendAsync();
 | 
			
		||||
                await Response()
 | 
			
		||||
                      .Embed(_sender.CreateEmbed().WithOkColor().WithDescription(GetText(strs.feed_no_feed)))
 | 
			
		||||
                      .SendAsync();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                cur =>
 | 
			
		||||
                {
 | 
			
		||||
                    var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
                    var i = 0;
 | 
			
		||||
                    var fs = string.Join("\n",
 | 
			
		||||
                        feeds.Skip(cur * 10).Take(10).Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}"));
 | 
			
		||||
 | 
			
		||||
                    return embed.WithDescription(fs);
 | 
			
		||||
                },
 | 
			
		||||
                feeds.Count,
 | 
			
		||||
                10);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(feeds)
 | 
			
		||||
                  .PageSize(10)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, cur) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
                      var i = 0;
 | 
			
		||||
                      var fs = string.Join("\n",
 | 
			
		||||
                          items.Select(x => $"`{(cur * 10) + ++i}.` <#{x.ChannelId}> {x.Url}"));
 | 
			
		||||
 | 
			
		||||
                      return embed.WithDescription(fs);
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -31,14 +31,14 @@ public class FeedsService : INService
 | 
			
		||||
        {
 | 
			
		||||
            var guildConfigIds = bot.AllGuildConfigs.Select(x => x.Id).ToList();
 | 
			
		||||
            _subs = uow.Set<GuildConfig>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(x => guildConfigIds.Contains(x.Id))
 | 
			
		||||
                .Include(x => x.FeedSubs)
 | 
			
		||||
                .ToList()
 | 
			
		||||
                .SelectMany(x => x.FeedSubs)
 | 
			
		||||
                .GroupBy(x => x.Url.ToLower())
 | 
			
		||||
                .ToDictionary(x => x.Key, x => x.ToList())
 | 
			
		||||
                .ToConcurrent();
 | 
			
		||||
                       .AsQueryable()
 | 
			
		||||
                       .Where(x => guildConfigIds.Contains(x.Id))
 | 
			
		||||
                       .Include(x => x.FeedSubs)
 | 
			
		||||
                       .ToList()
 | 
			
		||||
                       .SelectMany(x => x.FeedSubs)
 | 
			
		||||
                       .GroupBy(x => x.Url.ToLower())
 | 
			
		||||
                       .ToDictionary(x => x.Key, x => x.ToList())
 | 
			
		||||
                       .ToConcurrent();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        _client = client;
 | 
			
		||||
@@ -61,7 +61,7 @@ public class FeedsService : INService
 | 
			
		||||
                // remove from db
 | 
			
		||||
                await using var ctx = _db.GetDbContext();
 | 
			
		||||
                await ctx.GetTable<FeedSub>()
 | 
			
		||||
                    .DeleteAsync(x => ids.Contains(x.Id));
 | 
			
		||||
                         .DeleteAsync(x => ids.Contains(x.Id));
 | 
			
		||||
 | 
			
		||||
                // remove from the local cache
 | 
			
		||||
                _subs.TryRemove(url, out _);
 | 
			
		||||
@@ -95,14 +95,14 @@ public class FeedsService : INService
 | 
			
		||||
                    var feed = await FeedReader.ReadAsync(rssUrl);
 | 
			
		||||
 | 
			
		||||
                    var items = feed
 | 
			
		||||
                        .Items.Select(item => (Item: item,
 | 
			
		||||
                            LastUpdate: item.PublishingDate?.ToUniversalTime()
 | 
			
		||||
                                        ?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate?.ToUniversalTime()))
 | 
			
		||||
                        .Where(data => data.LastUpdate is not null)
 | 
			
		||||
                        .Select(data => (data.Item, LastUpdate: (DateTime)data.LastUpdate))
 | 
			
		||||
                        .OrderByDescending(data => data.LastUpdate)
 | 
			
		||||
                        .Reverse() // start from the oldest
 | 
			
		||||
                        .ToList();
 | 
			
		||||
                                .Items.Select(item => (Item: item,
 | 
			
		||||
                                    LastUpdate: item.PublishingDate?.ToUniversalTime()
 | 
			
		||||
                                                ?? (item.SpecificItem as AtomFeedItem)?.UpdatedDate?.ToUniversalTime()))
 | 
			
		||||
                                .Where(data => data.LastUpdate is not null)
 | 
			
		||||
                                .Select(data => (data.Item, LastUpdate: (DateTime)data.LastUpdate))
 | 
			
		||||
                                .OrderByDescending(data => data.LastUpdate)
 | 
			
		||||
                                .Reverse() // start from the oldest
 | 
			
		||||
                                .ToList();
 | 
			
		||||
 | 
			
		||||
                    if (!_lastPosts.TryGetValue(kvp.Key, out var lastFeedUpdate))
 | 
			
		||||
                    {
 | 
			
		||||
@@ -115,7 +115,7 @@ public class FeedsService : INService
 | 
			
		||||
                        if (itemUpdateDate <= lastFeedUpdate)
 | 
			
		||||
                            continue;
 | 
			
		||||
 | 
			
		||||
                        var embed = new EmbedBuilder().WithFooter(rssUrl);
 | 
			
		||||
                        var embed = _sender.CreateEmbed().WithFooter(rssUrl);
 | 
			
		||||
 | 
			
		||||
                        _lastPosts[kvp.Key] = itemUpdateDate;
 | 
			
		||||
 | 
			
		||||
@@ -141,12 +141,12 @@ public class FeedsService : INService
 | 
			
		||||
                        if (!gotImage && feedItem.SpecificItem is AtomFeedItem afi)
 | 
			
		||||
                        {
 | 
			
		||||
                            var previewElement = afi.Element.Elements()
 | 
			
		||||
                                .FirstOrDefault(x => x.Name.LocalName == "preview");
 | 
			
		||||
                                                    .FirstOrDefault(x => x.Name.LocalName == "preview");
 | 
			
		||||
 | 
			
		||||
                            if (previewElement is null)
 | 
			
		||||
                            {
 | 
			
		||||
                                previewElement = afi.Element.Elements()
 | 
			
		||||
                                    .FirstOrDefault(x => x.Name.LocalName == "thumbnail");
 | 
			
		||||
                                                    .FirstOrDefault(x => x.Name.LocalName == "thumbnail");
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            if (previewElement is not null)
 | 
			
		||||
@@ -170,11 +170,23 @@ public class FeedsService : INService
 | 
			
		||||
 | 
			
		||||
                        //send the created embed to all subscribed channels
 | 
			
		||||
                        var feedSendTasks = kvp.Value
 | 
			
		||||
                            .Where(x => x.GuildConfig is not null)
 | 
			
		||||
                            .Select(x => _client.GetGuild(x.GuildConfig.GuildId)
 | 
			
		||||
                                ?.GetTextChannel(x.ChannelId)
 | 
			
		||||
                                ?.EmbedAsync(embed, string.IsNullOrWhiteSpace(x.Message) ? "" : x.Message))
 | 
			
		||||
                            .Where(x => x is not null);
 | 
			
		||||
                                               .Where(x => x.GuildConfig is not null)
 | 
			
		||||
                                               .Select(x =>
 | 
			
		||||
                                               {
 | 
			
		||||
                                                   var ch = _client.GetGuild(x.GuildConfig.GuildId)
 | 
			
		||||
                                                                   ?.GetTextChannel(x.ChannelId);
 | 
			
		||||
 | 
			
		||||
                                                   if (ch is null)
 | 
			
		||||
                                                       return null;
 | 
			
		||||
 | 
			
		||||
                                                   return _sender.Response(ch)
 | 
			
		||||
                                                                 .Embed(embed)
 | 
			
		||||
                                                                 .Text(string.IsNullOrWhiteSpace(x.Message)
 | 
			
		||||
                                                                     ? string.Empty
 | 
			
		||||
                                                                     : x.Message)
 | 
			
		||||
                                                                 .SendAsync();
 | 
			
		||||
                                               })
 | 
			
		||||
                                               .Where(x => x is not null);
 | 
			
		||||
 | 
			
		||||
                        allSendTasks.Add(feedSendTasks.WhenAll());
 | 
			
		||||
 | 
			
		||||
@@ -202,11 +214,15 @@ public class FeedsService : INService
 | 
			
		||||
    {
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        return uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs))
 | 
			
		||||
            .FeedSubs.OrderBy(x => x.Id)
 | 
			
		||||
            .ToList();
 | 
			
		||||
                  .FeedSubs.OrderBy(x => x.Id)
 | 
			
		||||
                  .ToList();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public FeedAddResult AddFeed(ulong guildId, ulong channelId, string rssFeed, string message)
 | 
			
		||||
    public FeedAddResult AddFeed(
 | 
			
		||||
        ulong guildId,
 | 
			
		||||
        ulong channelId,
 | 
			
		||||
        string rssFeed,
 | 
			
		||||
        string message)
 | 
			
		||||
    {
 | 
			
		||||
        ArgumentNullException.ThrowIfNull(rssFeed, nameof(rssFeed));
 | 
			
		||||
 | 
			
		||||
@@ -252,8 +268,8 @@ public class FeedsService : INService
 | 
			
		||||
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var items = uow.GuildConfigsForId(guildId, set => set.Include(x => x.FeedSubs))
 | 
			
		||||
            .FeedSubs.OrderBy(x => x.Id)
 | 
			
		||||
            .ToList();
 | 
			
		||||
                       .FeedSubs.OrderBy(x => x.Id)
 | 
			
		||||
                       .ToList();
 | 
			
		||||
 | 
			
		||||
        if (items.Count <= index)
 | 
			
		||||
            return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -40,18 +40,21 @@ public partial class Searches
 | 
			
		||||
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<List<MemegenTemplate>>(rawJson)!;
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var templates = string.Empty;
 | 
			
		||||
                    foreach (var template in data.Skip(curPage * 15).Take(15))
 | 
			
		||||
                        templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
 | 
			
		||||
                    var embed = new EmbedBuilder().WithOkColor().WithDescription(templates);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(data)
 | 
			
		||||
                  .PageSize(15)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, curPage) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var templates = string.Empty;
 | 
			
		||||
                      foreach (var template in items)
 | 
			
		||||
                          templates += $"**{template.Name}:**\n key: `{template.Id}`\n";
 | 
			
		||||
                      var embed = _sender.CreateEmbed().WithOkColor().WithDescription(templates);
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                data.Count,
 | 
			
		||||
                15);
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -49,7 +49,7 @@ public partial class Searches
 | 
			
		||||
                var obj = objs[0];
 | 
			
		||||
                var userId = obj.UserId;
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                                .WithOkColor()
 | 
			
		||||
                                                .WithTitle($"osu! {smode} profile for {user}")
 | 
			
		||||
                                                .WithThumbnailUrl($"https://a.ppy.sh/{userId}")
 | 
			
		||||
@@ -95,7 +95,7 @@ public partial class Searches
 | 
			
		||||
            var userData = JsonConvert.DeserializeObject<GatariUserResponse>(usrResString).Users[0];
 | 
			
		||||
            var userStats = statsResponse.Stats;
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle($"osu!Gatari {modeStr} profile for {user}")
 | 
			
		||||
                           .WithThumbnailUrl($"https://a.gatari.pw/{userStats.Id}")
 | 
			
		||||
@@ -166,7 +166,7 @@ public partial class Searches
 | 
			
		||||
                return (title, desc);
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder().WithOkColor().WithTitle($"Top 5 plays for {user}");
 | 
			
		||||
            var eb = _sender.CreateEmbed().WithOkColor().WithTitle($"Top 5 plays for {user}");
 | 
			
		||||
 | 
			
		||||
            var mapData = await mapTasks.WhenAll();
 | 
			
		||||
            foreach (var (title, desc) in mapData.Where(x => x != default))
 | 
			
		||||
 
 | 
			
		||||
@@ -135,7 +135,7 @@ public partial class Searches
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder().WithDescription(GetText(strs.account_not_found)).WithErrorColor();
 | 
			
		||||
                var embed = _sender.CreateEmbed().WithDescription(GetText(strs.account_not_found)).WithErrorColor();
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(embed).SendAsync();
 | 
			
		||||
                return;
 | 
			
		||||
@@ -144,37 +144,38 @@ public partial class Searches
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(league))
 | 
			
		||||
                characters.RemoveAll(c => c.League != league);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    var embed = new EmbedBuilder()
 | 
			
		||||
                                   .WithAuthor($"Characters on {usr}'s account",
 | 
			
		||||
                                       "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                                       $"{PROFILE_URL}{usr}")
 | 
			
		||||
                                   .WithOkColor();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(characters)
 | 
			
		||||
                  .PageSize(9)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, curPage) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var embed = _sender.CreateEmbed()
 | 
			
		||||
                                  .WithAuthor($"Characters on {usr}'s account",
 | 
			
		||||
                                      "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                                      $"{PROFILE_URL}{usr}")
 | 
			
		||||
                                  .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    var tempList = characters.Skip(curPage * 9).Take(9).ToList();
 | 
			
		||||
                      if (characters.Count == 0)
 | 
			
		||||
                          return embed.WithDescription("This account has no characters.");
 | 
			
		||||
 | 
			
		||||
                    if (characters.Count == 0)
 | 
			
		||||
                        return embed.WithDescription("This account has no characters.");
 | 
			
		||||
                      var sb = new StringBuilder();
 | 
			
		||||
                      sb.AppendLine($"```{"#",-5}{"Character Name",-23}{"League",-10}{"Class",-13}{"Level",-3}");
 | 
			
		||||
                      for (var i = 0; i < items.Count; i++)
 | 
			
		||||
                      {
 | 
			
		||||
                          var character = items[i];
 | 
			
		||||
 | 
			
		||||
                    var sb = new StringBuilder();
 | 
			
		||||
                    sb.AppendLine($"```{"#",-5}{"Character Name",-23}{"League",-10}{"Class",-13}{"Level",-3}");
 | 
			
		||||
                    for (var i = 0; i < tempList.Count; i++)
 | 
			
		||||
                    {
 | 
			
		||||
                        var character = tempList[i];
 | 
			
		||||
                          sb.AppendLine(
 | 
			
		||||
                              $"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}");
 | 
			
		||||
                      }
 | 
			
		||||
 | 
			
		||||
                        sb.AppendLine(
 | 
			
		||||
                            $"#{i + 1 + (curPage * 9),-4}{character.Name,-23}{ShortLeagueName(character.League),-10}{character.Class,-13}{character.Level,-3}");
 | 
			
		||||
                    }
 | 
			
		||||
                      sb.AppendLine("```");
 | 
			
		||||
                      embed.WithDescription(sb.ToString());
 | 
			
		||||
 | 
			
		||||
                    sb.AppendLine("```");
 | 
			
		||||
                    embed.WithDescription(sb.ToString());
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                characters.Count,
 | 
			
		||||
                9);
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -190,17 +191,17 @@ public partial class Searches
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                var eembed = new EmbedBuilder().WithDescription(GetText(strs.leagues_not_found)).WithErrorColor();
 | 
			
		||||
                var eembed = _sender.CreateEmbed().WithDescription(GetText(strs.leagues_not_found)).WithErrorColor();
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(eembed).SendAsync();
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
                           .WithAuthor("Path of Exile Leagues",
 | 
			
		||||
                               "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                               "https://www.pathofexile.com")
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                        .WithAuthor("Path of Exile Leagues",
 | 
			
		||||
                            "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                            "https://www.pathofexile.com")
 | 
			
		||||
                        .WithOkColor();
 | 
			
		||||
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            sb.AppendLine($"```{"#",-5}{"League Name",-23}");
 | 
			
		||||
@@ -273,19 +274,19 @@ public partial class Searches
 | 
			
		||||
                        CultureInfo.InvariantCulture);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                               .WithAuthor($"{leagueName} Currency Exchange",
 | 
			
		||||
                                   "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                                   "http://poe.ninja")
 | 
			
		||||
                               .AddField("Currency Type", cleanCurrency, true)
 | 
			
		||||
                               .AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
 | 
			
		||||
                               .WithOkColor();
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                            .WithAuthor($"{leagueName} Currency Exchange",
 | 
			
		||||
                                "https://web.poecdn.com/image/favicon/ogimage.png",
 | 
			
		||||
                                "http://poe.ninja")
 | 
			
		||||
                            .AddField("Currency Type", cleanCurrency, true)
 | 
			
		||||
                            .AddField($"{cleanConvert} Equivalent", chaosEquivalent / conversionEquivalent, true)
 | 
			
		||||
                            .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(embed).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
            catch
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder().WithDescription(GetText(strs.ninja_not_found)).WithErrorColor();
 | 
			
		||||
                var embed = _sender.CreateEmbed().WithDescription(GetText(strs.ninja_not_found)).WithErrorColor();
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(embed).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ public partial class Searches
 | 
			
		||||
                if (kvp.Key.ToUpperInvariant() == pokemon.ToUpperInvariant())
 | 
			
		||||
                {
 | 
			
		||||
                    var p = kvp.Value;
 | 
			
		||||
                    await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                    await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                                    .WithOkColor()
 | 
			
		||||
                                                    .WithTitle(kvp.Key.ToTitleCase())
 | 
			
		||||
                                                    .WithDescription(p.BaseStats.ToString())
 | 
			
		||||
@@ -55,7 +55,7 @@ public partial class Searches
 | 
			
		||||
            {
 | 
			
		||||
                if (kvp.Key.ToUpperInvariant() == ability)
 | 
			
		||||
                {
 | 
			
		||||
                    await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                    await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                                    .WithOkColor()
 | 
			
		||||
                                                    .WithTitle(kvp.Value.Name)
 | 
			
		||||
                                                    .WithDescription(string.IsNullOrWhiteSpace(kvp.Value.Desc)
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ public partial class Searches
 | 
			
		||||
 | 
			
		||||
            descStr = descStr.TrimTo(4096);
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithAuthor(ctx.User)
 | 
			
		||||
                           .WithTitle(query.TrimTo(64)!)
 | 
			
		||||
@@ -98,7 +98,7 @@ public partial class Searches
 | 
			
		||||
 | 
			
		||||
            EmbedBuilder CreateEmbed(IImageSearchResultEntry entry)
 | 
			
		||||
            {
 | 
			
		||||
                return new EmbedBuilder()
 | 
			
		||||
                return _sender.CreateEmbed()
 | 
			
		||||
                          .WithOkColor()
 | 
			
		||||
                          .WithAuthor(ctx.User)
 | 
			
		||||
                          .WithTitle(query)
 | 
			
		||||
@@ -190,7 +190,7 @@ public partial class Searches
 | 
			
		||||
//
 | 
			
		||||
//         var descStr = string.Join("\n\n", desc);
 | 
			
		||||
//
 | 
			
		||||
//         var embed = new EmbedBuilder()
 | 
			
		||||
//         var embed = _sender.CreateEmbed()
 | 
			
		||||
//                        .WithAuthor(ctx.User.ToString(),
 | 
			
		||||
//                            "https://upload.wikimedia.org/wikipedia/en/9/90/The_DuckDuckGo_Duck.png")
 | 
			
		||||
//                        .WithDescription($"{GetText(strs.search_for)} **{query}**\n\n" + descStr)
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        if (!await ValidateQuery(query))
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder();
 | 
			
		||||
        var embed = _sender.CreateEmbed();
 | 
			
		||||
        var data = await _service.GetWeatherDataAsync(query);
 | 
			
		||||
 | 
			
		||||
        if (data is null)
 | 
			
		||||
@@ -134,7 +134,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
                 .WithOkColor()
 | 
			
		||||
                 .WithTitle(GetText(strs.time_new))
 | 
			
		||||
                 .WithDescription(Format.Code(data.Time.ToString(Culture)))
 | 
			
		||||
@@ -160,7 +160,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Embed(new EmbedBuilder()
 | 
			
		||||
              .Embed(_sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithTitle(movie.Title)
 | 
			
		||||
                     .WithUrl($"https://www.imdb.com/title/{movie.ImdbId}/")
 | 
			
		||||
@@ -191,7 +191,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
    private Task InternalRandomImage(SearchesService.ImageTag tag)
 | 
			
		||||
    {
 | 
			
		||||
        var url = _service.GetRandomImageUrl(tag);
 | 
			
		||||
        return Response().Embed(new EmbedBuilder().WithOkColor().WithImageUrl(url)).SendAsync();
 | 
			
		||||
        return Response().Embed(_sender.CreateEmbed().WithOkColor().WithImageUrl(url)).SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -242,7 +242,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Embed(new EmbedBuilder()
 | 
			
		||||
              .Embed(_sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .AddField(GetText(strs.original_url), $"<{query}>")
 | 
			
		||||
                     .AddField(GetText(strs.short_url), $"<{shortLink}>"))
 | 
			
		||||
@@ -264,7 +264,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithOkColor()
 | 
			
		||||
                    .WithTitle(card.Name)
 | 
			
		||||
                    .WithDescription(card.Description)
 | 
			
		||||
@@ -297,7 +297,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder().WithOkColor().WithImageUrl(card.Img);
 | 
			
		||||
        var embed = _sender.CreateEmbed().WithOkColor().WithImageUrl(card.Img);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(card.Flavor))
 | 
			
		||||
            embed.WithDescription(card.Flavor);
 | 
			
		||||
@@ -318,21 +318,24 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
                $"https://api.urbandictionary.com/v0/define?term={Uri.EscapeDataString(query)}");
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                var items = JsonConvert.DeserializeObject<UrbanResponse>(res).List;
 | 
			
		||||
                if (items.Any())
 | 
			
		||||
                var allItems = JsonConvert.DeserializeObject<UrbanResponse>(res).List;
 | 
			
		||||
                if (allItems.Any())
 | 
			
		||||
                {
 | 
			
		||||
                    await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                        p =>
 | 
			
		||||
                        {
 | 
			
		||||
                            var item = items[p];
 | 
			
		||||
                            return new EmbedBuilder()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithUrl(item.Permalink)
 | 
			
		||||
                                   .WithTitle(item.Word)
 | 
			
		||||
                                   .WithDescription(item.Definition);
 | 
			
		||||
                        },
 | 
			
		||||
                        items.Length,
 | 
			
		||||
                        1);
 | 
			
		||||
                    await Response()
 | 
			
		||||
                          .Paginated()
 | 
			
		||||
                          .Items(allItems)
 | 
			
		||||
                          .PageSize(1)
 | 
			
		||||
                          .CurrentPage(0)
 | 
			
		||||
                          .Page((items, _) =>
 | 
			
		||||
                          {
 | 
			
		||||
                              var item = items[0];
 | 
			
		||||
                              return _sender.CreateEmbed()
 | 
			
		||||
                                     .WithOkColor()
 | 
			
		||||
                                     .WithUrl(item.Permalink)
 | 
			
		||||
                                     .WithTitle(item.Word)
 | 
			
		||||
                                     .WithDescription(item.Definition);
 | 
			
		||||
                          })
 | 
			
		||||
                          .SendAsync();
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
@@ -362,52 +365,54 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
                                               + WebUtility.UrlEncode(word));
 | 
			
		||||
                });
 | 
			
		||||
 | 
			
		||||
            var data = JsonConvert.DeserializeObject<DefineModel>(res);
 | 
			
		||||
            var responseModel = JsonConvert.DeserializeObject<DefineModel>(res);
 | 
			
		||||
 | 
			
		||||
            var datas = data.Results
 | 
			
		||||
                            .Where(x => x.Senses is not null
 | 
			
		||||
                                        && x.Senses.Count > 0
 | 
			
		||||
                                        && x.Senses[0].Definition is not null)
 | 
			
		||||
                            .Select(x => (Sense: x.Senses[0], x.PartOfSpeech))
 | 
			
		||||
                            .ToList();
 | 
			
		||||
            var data = responseModel.Results
 | 
			
		||||
                                    .Where(x => x.Senses is not null
 | 
			
		||||
                                                && x.Senses.Count > 0
 | 
			
		||||
                                                && x.Senses[0].Definition is not null)
 | 
			
		||||
                                    .Select(x => (Sense: x.Senses[0], x.PartOfSpeech))
 | 
			
		||||
                                    .ToList();
 | 
			
		||||
 | 
			
		||||
            if (!datas.Any())
 | 
			
		||||
            if (!data.Any())
 | 
			
		||||
            {
 | 
			
		||||
                Log.Warning("Definition not found: {Word}", word);
 | 
			
		||||
                await Response().Error(strs.define_unknown).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            var col = datas.Select(x => (
 | 
			
		||||
                               Definition: x.Sense.Definition is string
 | 
			
		||||
                                   ? x.Sense.Definition.ToString()
 | 
			
		||||
                                   : ((JArray)JToken.Parse(x.Sense.Definition.ToString())).First.ToString(),
 | 
			
		||||
                               Example: x.Sense.Examples is null || x.Sense.Examples.Count == 0
 | 
			
		||||
                                   ? string.Empty
 | 
			
		||||
                                   : x.Sense.Examples[0].Text, Word: word,
 | 
			
		||||
                               WordType: string.IsNullOrWhiteSpace(x.PartOfSpeech) ? "-" : x.PartOfSpeech))
 | 
			
		||||
                           .ToList();
 | 
			
		||||
            var col = data.Select(x => (
 | 
			
		||||
                              Definition: x.Sense.Definition is string
 | 
			
		||||
                                  ? x.Sense.Definition.ToString()
 | 
			
		||||
                                  : ((JArray)JToken.Parse(x.Sense.Definition.ToString())).First.ToString(),
 | 
			
		||||
                              Example: x.Sense.Examples is null || x.Sense.Examples.Count == 0
 | 
			
		||||
                                  ? string.Empty
 | 
			
		||||
                                  : x.Sense.Examples[0].Text, Word: word,
 | 
			
		||||
                              WordType: string.IsNullOrWhiteSpace(x.PartOfSpeech) ? "-" : x.PartOfSpeech))
 | 
			
		||||
                          .ToList();
 | 
			
		||||
 | 
			
		||||
            Log.Information("Sending {Count} definition for: {Word}", col.Count, word);
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                page =>
 | 
			
		||||
                {
 | 
			
		||||
                    var model = col.Skip(page).First();
 | 
			
		||||
                    var embed = new EmbedBuilder()
 | 
			
		||||
                                .WithDescription(ctx.User.Mention)
 | 
			
		||||
                                .AddField(GetText(strs.word), model.Word, true)
 | 
			
		||||
                                .AddField(GetText(strs._class), model.WordType, true)
 | 
			
		||||
                                .AddField(GetText(strs.definition), model.Definition)
 | 
			
		||||
                                .WithOkColor();
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(col)
 | 
			
		||||
                  .PageSize(1)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var model = items.First();
 | 
			
		||||
                      var embed = _sender.CreateEmbed()
 | 
			
		||||
                                  .WithDescription(ctx.User.Mention)
 | 
			
		||||
                                  .AddField(GetText(strs.word), model.Word, true)
 | 
			
		||||
                                  .AddField(GetText(strs._class), model.WordType, true)
 | 
			
		||||
                                  .AddField(GetText(strs.definition), model.Definition)
 | 
			
		||||
                                  .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    if (!string.IsNullOrWhiteSpace(model.Example))
 | 
			
		||||
                        embed.AddField(GetText(strs.example), model.Example);
 | 
			
		||||
                      if (!string.IsNullOrWhiteSpace(model.Example))
 | 
			
		||||
                          embed.AddField(GetText(strs.example), model.Example);
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                col.Count,
 | 
			
		||||
                1);
 | 
			
		||||
                      return embed;
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
        catch (Exception ex)
 | 
			
		||||
        {
 | 
			
		||||
@@ -474,7 +479,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Embed(
 | 
			
		||||
                  new EmbedBuilder()
 | 
			
		||||
                  _sender.CreateEmbed()
 | 
			
		||||
                      .WithOkColor()
 | 
			
		||||
                      .AddField("Username", usr.ToString())
 | 
			
		||||
                      .AddField("Avatar Url", avatarUrl)
 | 
			
		||||
@@ -542,7 +547,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
        {
 | 
			
		||||
            var v = obj.Verses[0];
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(new EmbedBuilder()
 | 
			
		||||
                  .Embed(_sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle($"{v.BookName} {v.Chapter}:{v.Verse}")
 | 
			
		||||
                         .WithDescription(v.Text))
 | 
			
		||||
@@ -565,7 +570,7 @@ public partial class Searches : NadekoModule<SearchesService>
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        //var embed = new EmbedBuilder()
 | 
			
		||||
        //var embed = _sender.CreateEmbed()
 | 
			
		||||
        //    .WithOkColor()
 | 
			
		||||
        //    .WithDescription(gameData.ShortDescription)
 | 
			
		||||
        //    .WithTitle(gameData.Name)
 | 
			
		||||
 
 | 
			
		||||
@@ -95,9 +95,9 @@ public partial class Searches
 | 
			
		||||
                  .Page((elements, cur) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      if (elements.Count == 0)
 | 
			
		||||
                          return new EmbedBuilder().WithDescription(GetText(strs.streams_none)).WithErrorColor();
 | 
			
		||||
                          return _sender.CreateEmbed().WithDescription(GetText(strs.streams_none)).WithErrorColor();
 | 
			
		||||
 | 
			
		||||
                      var eb = new EmbedBuilder().WithTitle(GetText(strs.streams_follow_title)).WithOkColor();
 | 
			
		||||
                      var eb = _sender.CreateEmbed().WithTitle(GetText(strs.streams_follow_title)).WithOkColor();
 | 
			
		||||
                      for (var index = 0; index < elements.Count; index++)
 | 
			
		||||
                      {
 | 
			
		||||
                          var elem = elements[index];
 | 
			
		||||
 
 | 
			
		||||
@@ -73,34 +73,34 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
        {
 | 
			
		||||
            var ids = client.GetGuildIds();
 | 
			
		||||
            var guildConfigs = uow.Set<GuildConfig>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Include(x => x.FollowedStreams)
 | 
			
		||||
                .Where(x => ids.Contains(x.GuildId))
 | 
			
		||||
                .ToList();
 | 
			
		||||
                                  .AsQueryable()
 | 
			
		||||
                                  .Include(x => x.FollowedStreams)
 | 
			
		||||
                                  .Where(x => ids.Contains(x.GuildId))
 | 
			
		||||
                                  .ToList();
 | 
			
		||||
 | 
			
		||||
            _offlineNotificationServers = new(guildConfigs
 | 
			
		||||
                .Where(gc => gc.NotifyStreamOffline)
 | 
			
		||||
                .Select(x => x.GuildId)
 | 
			
		||||
                .ToList());
 | 
			
		||||
                                              .Where(gc => gc.NotifyStreamOffline)
 | 
			
		||||
                                              .Select(x => x.GuildId)
 | 
			
		||||
                                              .ToList());
 | 
			
		||||
 | 
			
		||||
            _deleteOnOfflineServers = new(guildConfigs
 | 
			
		||||
                .Where(gc => gc.DeleteStreamOnlineMessage)
 | 
			
		||||
                .Select(x => x.GuildId)
 | 
			
		||||
                .ToList());
 | 
			
		||||
                                          .Where(gc => gc.DeleteStreamOnlineMessage)
 | 
			
		||||
                                          .Select(x => x.GuildId)
 | 
			
		||||
                                          .ToList());
 | 
			
		||||
 | 
			
		||||
            var followedStreams = guildConfigs.SelectMany(x => x.FollowedStreams).ToList();
 | 
			
		||||
 | 
			
		||||
            _shardTrackedStreams = followedStreams.GroupBy(x => new
 | 
			
		||||
                {
 | 
			
		||||
                    x.Type,
 | 
			
		||||
                    Name = x.Username.ToLower()
 | 
			
		||||
                })
 | 
			
		||||
                .ToList()
 | 
			
		||||
                .ToDictionary(
 | 
			
		||||
                    x => new StreamDataKey(x.Key.Type, x.Key.Name.ToLower()),
 | 
			
		||||
                    x => x.GroupBy(y => y.GuildId)
 | 
			
		||||
                        .ToDictionary(y => y.Key,
 | 
			
		||||
                            y => y.AsEnumerable().ToHashSet()));
 | 
			
		||||
                                                  {
 | 
			
		||||
                                                      x.Type,
 | 
			
		||||
                                                      Name = x.Username.ToLower()
 | 
			
		||||
                                                  })
 | 
			
		||||
                                                  .ToList()
 | 
			
		||||
                                                  .ToDictionary(
 | 
			
		||||
                                                      x => new StreamDataKey(x.Key.Type, x.Key.Name.ToLower()),
 | 
			
		||||
                                                      x => x.GroupBy(y => y.GuildId)
 | 
			
		||||
                                                            .ToDictionary(y => y.Key,
 | 
			
		||||
                                                                y => y.AsEnumerable().ToHashSet()));
 | 
			
		||||
 | 
			
		||||
            // shard 0 will keep track of when there are no more guilds which track a stream
 | 
			
		||||
            if (client.ShardId == 0)
 | 
			
		||||
@@ -111,12 +111,12 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
                    _streamTracker.AddLastData(fs.CreateKey(), null, false);
 | 
			
		||||
 | 
			
		||||
                _trackCounter = allFollowedStreams.GroupBy(x => new
 | 
			
		||||
                    {
 | 
			
		||||
                        x.Type,
 | 
			
		||||
                        Name = x.Username.ToLower()
 | 
			
		||||
                    })
 | 
			
		||||
                    .ToDictionary(x => new StreamDataKey(x.Key.Type, x.Key.Name),
 | 
			
		||||
                        x => x.Select(fs => fs.GuildId).ToHashSet());
 | 
			
		||||
                                                  {
 | 
			
		||||
                                                      x.Type,
 | 
			
		||||
                                                      Name = x.Username.ToLower()
 | 
			
		||||
                                                  })
 | 
			
		||||
                                                  .ToDictionary(x => new StreamDataKey(x.Key.Type, x.Key.Name),
 | 
			
		||||
                                                      x => x.Select(fs => fs.GuildId).ToHashSet());
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +156,7 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
                    continue;
 | 
			
		||||
 | 
			
		||||
                var deleteGroups = failingStreams.GroupBy(x => x.Type)
 | 
			
		||||
                    .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList());
 | 
			
		||||
                                                 .ToDictionary(x => x.Key, x => x.Select(y => y.Name).ToList());
 | 
			
		||||
 | 
			
		||||
                await using var uow = _db.GetDbContext();
 | 
			
		||||
                foreach (var kvp in deleteGroups)
 | 
			
		||||
@@ -169,9 +169,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
                        string.Join(", ", kvp.Value));
 | 
			
		||||
 | 
			
		||||
                    var toDelete = uow.Set<FollowedStream>()
 | 
			
		||||
                        .AsQueryable()
 | 
			
		||||
                        .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username))
 | 
			
		||||
                        .ToList();
 | 
			
		||||
                                      .AsQueryable()
 | 
			
		||||
                                      .Where(x => x.Type == kvp.Key && kvp.Value.Contains(x.Username))
 | 
			
		||||
                                      .ToList();
 | 
			
		||||
 | 
			
		||||
                    uow.RemoveRange(toDelete);
 | 
			
		||||
                    await uow.SaveChangesAsync();
 | 
			
		||||
@@ -250,13 +250,20 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
            if (_shardTrackedStreams.TryGetValue(key, out var fss))
 | 
			
		||||
            {
 | 
			
		||||
                await fss
 | 
			
		||||
                    // send offline stream notifications only to guilds which enable it with .stoff
 | 
			
		||||
                    .SelectMany(x => x.Value)
 | 
			
		||||
                    .Where(x => _offlineNotificationServers.Contains(x.GuildId))
 | 
			
		||||
                    .Select(fs => _client.GetGuild(fs.GuildId)
 | 
			
		||||
                        ?.GetTextChannel(fs.ChannelId)
 | 
			
		||||
                        ?.EmbedAsync(GetEmbed(fs.GuildId, stream)))
 | 
			
		||||
                    .WhenAll();
 | 
			
		||||
                      // send offline stream notifications only to guilds which enable it with .stoff
 | 
			
		||||
                      .SelectMany(x => x.Value)
 | 
			
		||||
                      .Where(x => _offlineNotificationServers.Contains(x.GuildId))
 | 
			
		||||
                      .Select(fs =>
 | 
			
		||||
                      {
 | 
			
		||||
                          var ch = _client.GetGuild(fs.GuildId)
 | 
			
		||||
                                          ?.GetTextChannel(fs.ChannelId);
 | 
			
		||||
                          
 | 
			
		||||
                          if (ch is null)
 | 
			
		||||
                              return Task.CompletedTask;
 | 
			
		||||
 | 
			
		||||
                          return _sender.Response(ch).Embed(GetEmbed(fs.GuildId, stream)).SendAsync();
 | 
			
		||||
                      })
 | 
			
		||||
                      .WhenAll();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
@@ -270,33 +277,35 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
            if (_shardTrackedStreams.TryGetValue(key, out var fss))
 | 
			
		||||
            {
 | 
			
		||||
                var messages = await fss.SelectMany(x => x.Value)
 | 
			
		||||
                    .Select(async fs =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var textChannel = _client.GetGuild(fs.GuildId)?.GetTextChannel(fs.ChannelId);
 | 
			
		||||
                                        .Select(async fs =>
 | 
			
		||||
                                        {
 | 
			
		||||
                                            var textChannel = _client.GetGuild(fs.GuildId)
 | 
			
		||||
                                                                     ?.GetTextChannel(fs.ChannelId);
 | 
			
		||||
 | 
			
		||||
                        if (textChannel is null)
 | 
			
		||||
                            return default;
 | 
			
		||||
                                            if (textChannel is null)
 | 
			
		||||
                                                return default;
 | 
			
		||||
 | 
			
		||||
                        var repCtx = new ReplacementContext(guild: textChannel.Guild, client: _client)
 | 
			
		||||
                            .WithOverride("%platform%", () => fs.Type.ToString());
 | 
			
		||||
                                            var repCtx = new ReplacementContext(guild: textChannel.Guild,
 | 
			
		||||
                                                    client: _client)
 | 
			
		||||
                                                .WithOverride("%platform%", () => fs.Type.ToString());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                        var message = string.IsNullOrWhiteSpace(fs.Message)
 | 
			
		||||
                            ? ""
 | 
			
		||||
                            : await _repSvc.ReplaceAsync(fs.Message, repCtx);
 | 
			
		||||
                                            var message = string.IsNullOrWhiteSpace(fs.Message)
 | 
			
		||||
                                                ? ""
 | 
			
		||||
                                                : await _repSvc.ReplaceAsync(fs.Message, repCtx);
 | 
			
		||||
 | 
			
		||||
                        var msg = await _sender.Response(textChannel)
 | 
			
		||||
                                               .Embed(GetEmbed(fs.GuildId, stream, false))
 | 
			
		||||
                                               .Text(message)
 | 
			
		||||
                                               .SendAsync();
 | 
			
		||||
                                            var msg = await _sender.Response(textChannel)
 | 
			
		||||
                                                                   .Embed(GetEmbed(fs.GuildId, stream, false))
 | 
			
		||||
                                                                   .Text(message)
 | 
			
		||||
                                                                   .SendAsync();
 | 
			
		||||
 | 
			
		||||
                        // only cache the ids of channel/message pairs 
 | 
			
		||||
                        if (_deleteOnOfflineServers.Contains(fs.GuildId))
 | 
			
		||||
                            return (textChannel.Id, msg.Id);
 | 
			
		||||
                        else
 | 
			
		||||
                            return default;
 | 
			
		||||
                    })
 | 
			
		||||
                    .WhenAll();
 | 
			
		||||
                                            // only cache the ids of channel/message pairs 
 | 
			
		||||
                                            if (_deleteOnOfflineServers.Contains(fs.GuildId))
 | 
			
		||||
                                                return (textChannel.Id, msg.Id);
 | 
			
		||||
                                            else
 | 
			
		||||
                                                return default;
 | 
			
		||||
                                        })
 | 
			
		||||
                                        .WhenAll();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                // push online stream messages to redis
 | 
			
		||||
@@ -306,9 +315,9 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
                try
 | 
			
		||||
                {
 | 
			
		||||
                    var pairs = messages
 | 
			
		||||
                        .Where(x => x != default)
 | 
			
		||||
                        .Select(x => (x.Item1, x.Item2))
 | 
			
		||||
                        .ToList();
 | 
			
		||||
                                .Where(x => x != default)
 | 
			
		||||
                                .Select(x => (x.Item1, x.Item2))
 | 
			
		||||
                                .ToList();
 | 
			
		||||
 | 
			
		||||
                    if (pairs.Count > 0)
 | 
			
		||||
                        await OnlineMessagesSent(key.Type, key.Name, pairs);
 | 
			
		||||
@@ -330,9 +339,10 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
    {
 | 
			
		||||
        using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var gc = uow.Set<GuildConfig>().AsQueryable()
 | 
			
		||||
                .Include(x => x.FollowedStreams)
 | 
			
		||||
                .FirstOrDefault(x => x.GuildId == guildConfig.GuildId);
 | 
			
		||||
            var gc = uow.Set<GuildConfig>()
 | 
			
		||||
                        .AsQueryable()
 | 
			
		||||
                        .Include(x => x.FollowedStreams)
 | 
			
		||||
                        .FirstOrDefault(x => x.GuildId == guildConfig.GuildId);
 | 
			
		||||
 | 
			
		||||
            if (gc is null)
 | 
			
		||||
                return Task.CompletedTask;
 | 
			
		||||
@@ -392,10 +402,10 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
        await using (var uow = _db.GetDbContext())
 | 
			
		||||
        {
 | 
			
		||||
            var fss = uow.Set<FollowedStream>()
 | 
			
		||||
                .AsQueryable()
 | 
			
		||||
                .Where(x => x.GuildId == guildId)
 | 
			
		||||
                .OrderBy(x => x.Id)
 | 
			
		||||
                .ToList();
 | 
			
		||||
                         .AsQueryable()
 | 
			
		||||
                         .Where(x => x.GuildId == guildId)
 | 
			
		||||
                         .OrderBy(x => x.Id)
 | 
			
		||||
                         .ToList();
 | 
			
		||||
 | 
			
		||||
            // out of range
 | 
			
		||||
            if (fss.Count <= index)
 | 
			
		||||
@@ -484,11 +494,11 @@ public sealed class StreamNotificationService : INService, IReadyExecutor
 | 
			
		||||
 | 
			
		||||
    public EmbedBuilder GetEmbed(ulong guildId, StreamData status, bool showViewers = true)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
            .WithTitle(status.Name)
 | 
			
		||||
            .WithUrl(status.StreamUrl)
 | 
			
		||||
            .WithDescription(status.StreamUrl)
 | 
			
		||||
            .AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true);
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                    .WithTitle(status.Name)
 | 
			
		||||
                    .WithUrl(status.StreamUrl)
 | 
			
		||||
                    .WithDescription(status.StreamUrl)
 | 
			
		||||
                    .AddField(GetText(guildId, strs.status), status.IsLive ? "🟢 Online" : "🔴 Offline", true);
 | 
			
		||||
 | 
			
		||||
        if (showViewers)
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,7 @@ public sealed class TranslateService : ITranslateService, IExecNoCommand, IReady
 | 
			
		||||
                || msg.Content.Equals(output, StringComparison.InvariantCultureIgnoreCase))
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
 | 
			
		||||
            if (autoDelete)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,7 @@ public partial class Searches
 | 
			
		||||
                await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
                var translation = await _service.Translate(from, to, text);
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder().WithOkColor().AddField(from, text).AddField(to, translation);
 | 
			
		||||
                var embed = _sender.CreateEmbed().WithOkColor().AddField(from, text).AddField(to, translation);
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(embed).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
@@ -80,7 +80,7 @@ public partial class Searches
 | 
			
		||||
        {
 | 
			
		||||
            var langs = _service.GetLanguages().ToList();
 | 
			
		||||
            
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                        .WithTitle(GetText(strs.supported_languages))
 | 
			
		||||
                        .WithOkColor();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -25,7 +25,7 @@ public partial class Searches
 | 
			
		||||
                    using var http = _httpFactory.CreateClient();
 | 
			
		||||
                    var res = await http.GetStringAsync($"{XKCD_URL}/info.0.json");
 | 
			
		||||
                    var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                    var embed = new EmbedBuilder()
 | 
			
		||||
                    var embed = _sender.CreateEmbed()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                                   .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{XKCD_URL}/{comic.Num}")
 | 
			
		||||
@@ -60,7 +60,7 @@ public partial class Searches
 | 
			
		||||
                var res = await http.GetStringAsync($"{XKCD_URL}/{num}/info.0.json");
 | 
			
		||||
 | 
			
		||||
                var comic = JsonConvert.DeserializeObject<XkcdComic>(res);
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithImageUrl(comic.ImageLink)
 | 
			
		||||
                               .WithAuthor(comic.Title, "https://xkcd.com/s/919f27.ico", $"{XKCD_URL}/{num}")
 | 
			
		||||
 
 | 
			
		||||
@@ -121,17 +121,19 @@ public partial class Utility
 | 
			
		||||
 | 
			
		||||
            var arr = maps.ToArray();
 | 
			
		||||
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                curPage =>
 | 
			
		||||
                {
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                              .WithOkColor()
 | 
			
		||||
                              .WithTitle(GetText(strs.alias_list))
 | 
			
		||||
                              .WithDescription(string.Join("\n",
 | 
			
		||||
                                  arr.Skip(curPage * 10).Take(10).Select(x => $"`{x.Key}` => `{x.Value}`")));
 | 
			
		||||
                },
 | 
			
		||||
                arr.Length,
 | 
			
		||||
                10);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(arr)
 | 
			
		||||
                  .PageSize(10)
 | 
			
		||||
                  .CurrentPage(page)
 | 
			
		||||
                  .Page((items, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      return _sender.CreateEmbed()
 | 
			
		||||
                             .WithOkColor()
 | 
			
		||||
                             .WithTitle(GetText(strs.alias_list))
 | 
			
		||||
                             .WithDescription(string.Join("\n", items.Select(x => $"`{x.Key}` => `{x.Value}`")));
 | 
			
		||||
                  })
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -20,7 +20,7 @@ public partial class Utility
 | 
			
		||||
            if (setting is null)
 | 
			
		||||
            {
 | 
			
		||||
                var configNames = _settingServices.Select(x => x.Name);
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithErrorColor()
 | 
			
		||||
                               .WithDescription(GetText(strs.config_not_found(Format.Code(name))))
 | 
			
		||||
                               .AddField(GetText(strs.config_list), string.Join("\n", configNames));
 | 
			
		||||
@@ -43,7 +43,7 @@ public partial class Utility
 | 
			
		||||
            name = name?.ToLowerInvariant();
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(name))
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .WithTitle(GetText(strs.config_list))
 | 
			
		||||
                               .WithDescription(string.Join("\n", configNames));
 | 
			
		||||
@@ -58,7 +58,7 @@ public partial class Utility
 | 
			
		||||
            // if config name is not found, print error and the list of configs
 | 
			
		||||
            if (setting is null)
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithErrorColor()
 | 
			
		||||
                               .WithDescription(GetText(strs.config_not_found(Format.Code(name))))
 | 
			
		||||
                               .AddField(GetText(strs.config_list), string.Join("\n", configNames));
 | 
			
		||||
@@ -75,7 +75,7 @@ public partial class Utility
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(prop))
 | 
			
		||||
            {
 | 
			
		||||
                var propStrings = GetPropsAndValuesString(setting, propNames);
 | 
			
		||||
                var embed = new EmbedBuilder().WithOkColor().WithTitle($"⚙️ {setting.Name}").WithDescription(propStrings);
 | 
			
		||||
                var embed = _sender.CreateEmbed().WithOkColor().WithTitle($"⚙️ {setting.Name}").WithDescription(propStrings);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                await Response().Embed(embed).SendAsync();
 | 
			
		||||
@@ -88,7 +88,7 @@ public partial class Utility
 | 
			
		||||
            if (!exists)
 | 
			
		||||
            {
 | 
			
		||||
                var propStrings = GetPropsAndValuesString(setting, propNames);
 | 
			
		||||
                var propErrorEmbed = new EmbedBuilder()
 | 
			
		||||
                var propErrorEmbed = _sender.CreateEmbed()
 | 
			
		||||
                                        .WithErrorColor()
 | 
			
		||||
                                        .WithDescription(GetText(
 | 
			
		||||
                                            strs.config_prop_not_found(Format.Code(prop), Format.Code(name))))
 | 
			
		||||
@@ -110,7 +110,7 @@ public partial class Utility
 | 
			
		||||
                if (prop != "currency.sign")
 | 
			
		||||
                    value = Format.Code(Format.Sanitize(value.TrimTo(1000)), "json");
 | 
			
		||||
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                var embed = _sender.CreateEmbed()
 | 
			
		||||
                               .WithOkColor()
 | 
			
		||||
                               .AddField("Config", Format.Code(setting.Name), true)
 | 
			
		||||
                               .AddField("Prop", Format.Code(prop), true)
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ public partial class Utility
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithPendingColor()
 | 
			
		||||
                .WithTitle(GetText(strs.giveaway_starting))
 | 
			
		||||
                .WithDescription(message);
 | 
			
		||||
@@ -103,7 +103,7 @@ public partial class Utility
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                .WithTitle(GetText(strs.giveaway_list))
 | 
			
		||||
                .WithOkColor();
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -317,7 +317,7 @@ public sealed class GiveawayService : INService, IReadyExecutor
 | 
			
		||||
               {Format.Code(winner.UserId.ToString())}
 | 
			
		||||
               """;
 | 
			
		||||
 | 
			
		||||
        var eb = new EmbedBuilder()
 | 
			
		||||
        var eb = _sender.CreateEmbed()
 | 
			
		||||
            .WithOkColor()
 | 
			
		||||
            .WithTitle(GetText(strs.giveaway_ended))
 | 
			
		||||
            .WithDescription(ga.Message)
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,7 @@ public partial class Utility
 | 
			
		||||
            if (string.IsNullOrWhiteSpace(features))
 | 
			
		||||
                features = "-";
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithAuthor(GetText(strs.server_info))
 | 
			
		||||
                           .WithTitle(guild.Name)
 | 
			
		||||
                           .AddField(GetText(strs.id), guild.Id.ToString(), true)
 | 
			
		||||
@@ -87,7 +87,7 @@ public partial class Utility
 | 
			
		||||
                return;
 | 
			
		||||
            var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddMilliseconds(ch.Id >> 22);
 | 
			
		||||
            var usercount = (await ch.GetUsersAsync().FlattenAsync()).Count();
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithTitle(ch.Name)
 | 
			
		||||
                           .WithDescription(ch.Topic?.SanitizeMentions(true))
 | 
			
		||||
                           .AddField(GetText(strs.id), ch.Id.ToString(), true)
 | 
			
		||||
@@ -107,7 +107,7 @@ public partial class Utility
 | 
			
		||||
            var createdAt = new DateTime(2015, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)
 | 
			
		||||
                .AddMilliseconds(role.Id >> 22);
 | 
			
		||||
            var usercount = role.Members.LongCount();
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                .WithTitle(role.Name.TrimTo(128))
 | 
			
		||||
                .WithDescription(role.Permissions.ToList().Join(" | "))
 | 
			
		||||
                .AddField(GetText(strs.id), role.Id.ToString(), true)
 | 
			
		||||
@@ -133,7 +133,7 @@ public partial class Utility
 | 
			
		||||
            if (user is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().AddField(GetText(strs.name), $"**{user.Username}**#{user.Discriminator}", true);
 | 
			
		||||
            var embed = _sender.CreateEmbed().AddField(GetText(strs.name), $"**{user.Username}**#{user.Discriminator}", true);
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(user.Nickname))
 | 
			
		||||
                embed.AddField(GetText(strs.nickname), user.Nickname, true);
 | 
			
		||||
 | 
			
		||||
@@ -204,7 +204,7 @@ public partial class Utility
 | 
			
		||||
                    kvp.Value)));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
            await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                                            .WithTitle(GetText(strs.activity_page(page + 1)))
 | 
			
		||||
                                            .WithOkColor()
 | 
			
		||||
                                            .WithFooter(GetText(
 | 
			
		||||
 
 | 
			
		||||
@@ -47,9 +47,9 @@ public partial class Utility
 | 
			
		||||
                      var i = 1;
 | 
			
		||||
 | 
			
		||||
                      if (!invs.Any())
 | 
			
		||||
                          return new EmbedBuilder().WithErrorColor().WithDescription(GetText(strs.no_invites));
 | 
			
		||||
                          return _sender.CreateEmbed().WithErrorColor().WithDescription(GetText(strs.no_invites));
 | 
			
		||||
 | 
			
		||||
                      var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
                      var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
                      foreach (var inv in invs)
 | 
			
		||||
                      {
 | 
			
		||||
                          var expiryString = inv.MaxAge is null or 0 || inv.CreatedAt is null
 | 
			
		||||
 
 | 
			
		||||
@@ -106,7 +106,10 @@ public partial class Utility
 | 
			
		||||
            var text = SmartText.CreateFrom(quote.Text);
 | 
			
		||||
            text = await repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
 | 
			
		||||
            await ctx.Channel.SendAsync($"`#{quote.Id}` 📣 " + text, true, replyTo: ctx.Message);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Text($"`#{quote.Id}` 📣 " + text)
 | 
			
		||||
                  .Sanitize()
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -132,13 +135,13 @@ public partial class Utility
 | 
			
		||||
 | 
			
		||||
        private async Task ShowQuoteData(Quote data)
 | 
			
		||||
        {
 | 
			
		||||
            var eb = new EmbedBuilder()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithTitle($"{GetText(strs.quote_id($"#{data.Id}"))} | {GetText(strs.response)}:")
 | 
			
		||||
                     .WithDescription(Format.Sanitize(data.Text).Replace("](", "]\\(").TrimTo(4096))
 | 
			
		||||
                     .AddField(GetText(strs.trigger), data.Keyword)
 | 
			
		||||
                     .WithFooter(
 | 
			
		||||
                         GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")));
 | 
			
		||||
            var eb = _sender.CreateEmbed()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle($"{GetText(strs.quote_id($"#{data.Id}"))} | {GetText(strs.response)}:")
 | 
			
		||||
                            .WithDescription(Format.Sanitize(data.Text).Replace("](", "]\\(").TrimTo(4096))
 | 
			
		||||
                            .AddField(GetText(strs.trigger), data.Keyword)
 | 
			
		||||
                            .WithFooter(
 | 
			
		||||
                                GetText(strs.created_by($"{data.AuthorName} ({data.AuthorId})")));
 | 
			
		||||
 | 
			
		||||
            if (!(data.Text.Length > 4096))
 | 
			
		||||
            {
 | 
			
		||||
@@ -146,10 +149,12 @@ public partial class Utility
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // todo all send files should go through response system too
 | 
			
		||||
            await ctx.Channel.SendFileAsync(
 | 
			
		||||
                attachment: new FileAttachment(await data.Text.ToStream(), "quote.txt"),
 | 
			
		||||
                embed: eb.Build());
 | 
			
		||||
            await using var textStream = await data.Text.ToStream();
 | 
			
		||||
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Embed(eb)
 | 
			
		||||
                  .File(textStream, "quote.txt")
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        private async Task QuoteSearchinternalAsync(string? keyword, string textOrAuthor)
 | 
			
		||||
@@ -217,7 +222,10 @@ public partial class Utility
 | 
			
		||||
 | 
			
		||||
            var text = SmartText.CreateFrom(quote.Text);
 | 
			
		||||
            text = await repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
            await ctx.Channel.SendAsync(infoText + text, true, replyTo: ctx.Message);
 | 
			
		||||
            await Response()
 | 
			
		||||
                  .Text(infoText + text)
 | 
			
		||||
                  .Sanitize()
 | 
			
		||||
                  .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -96,7 +96,7 @@ public partial class Utility
 | 
			
		||||
            if (--page < 0)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .WithTitle(GetText(isServer ? strs.reminder_server_list : strs.reminder_list));
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -208,7 +208,7 @@ public class RemindService : INService, IReadyExecutor, IRemindService
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await _sender.Response(ch)
 | 
			
		||||
                             .Embed(new EmbedBuilder()
 | 
			
		||||
                             .Embed(_sender.CreateEmbed()
 | 
			
		||||
                                    .WithOkColor()
 | 
			
		||||
                                    .WithTitle("Reminder")
 | 
			
		||||
                                    .AddField("Created At",
 | 
			
		||||
 
 | 
			
		||||
@@ -64,7 +64,7 @@ public partial class Utility
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var description = GetRepeaterInfoString(removed);
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
            await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle(GetText(strs.repeater_removed(index + 1)))
 | 
			
		||||
                .WithDescription(description)).SendAsync();
 | 
			
		||||
@@ -187,7 +187,7 @@ public partial class Utility
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var description = GetRepeaterInfoString(runner);
 | 
			
		||||
            await Response().Embed(new EmbedBuilder()
 | 
			
		||||
            await Response().Embed(_sender.CreateEmbed()
 | 
			
		||||
                .WithOkColor()
 | 
			
		||||
                .WithTitle(GetText(strs.repeater_created))
 | 
			
		||||
                .WithDescription(description)).SendAsync();
 | 
			
		||||
@@ -205,7 +205,7 @@ public partial class Utility
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle(GetText(strs.list_of_repeaters)).WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithTitle(GetText(strs.list_of_repeaters)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
            var i = 0;
 | 
			
		||||
            foreach (var runner in repeaters.OrderBy(r => r.Repeater.Id))
 | 
			
		||||
 
 | 
			
		||||
@@ -19,24 +19,27 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
    private readonly ConcurrentHashSet<int> _skipNext = new();
 | 
			
		||||
 | 
			
		||||
    private readonly object _queueLocker = new();
 | 
			
		||||
    private readonly IMessageSenderService _sender;
 | 
			
		||||
 | 
			
		||||
    public RepeaterService(
 | 
			
		||||
        DiscordSocketClient client,
 | 
			
		||||
        DbService db,
 | 
			
		||||
        IReplacementService repSvc,
 | 
			
		||||
        IBotCredentials creds)
 | 
			
		||||
        IBotCredentials creds,
 | 
			
		||||
        IMessageSenderService sender)
 | 
			
		||||
    {
 | 
			
		||||
        _db = db;
 | 
			
		||||
        _repSvc = repSvc;
 | 
			
		||||
        _creds = creds;
 | 
			
		||||
        _client = client;
 | 
			
		||||
        _sender = sender;
 | 
			
		||||
 | 
			
		||||
        using var uow = _db.GetDbContext();
 | 
			
		||||
        var shardRepeaters = uow.Set<Repeater>()
 | 
			
		||||
            .Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards
 | 
			
		||||
                        == _client.ShardId)
 | 
			
		||||
            .AsNoTracking()
 | 
			
		||||
            .ToList();
 | 
			
		||||
                                .Where(x => (int)(x.GuildId / Math.Pow(2, 22)) % _creds.TotalShards
 | 
			
		||||
                                            == _client.ShardId)
 | 
			
		||||
                                .AsNoTracking()
 | 
			
		||||
                                .ToList();
 | 
			
		||||
 | 
			
		||||
        _noRedundant = new(shardRepeaters.Where(x => x.NoRedundant).Select(x => x.Id));
 | 
			
		||||
 | 
			
		||||
@@ -125,10 +128,11 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
 | 
			
		||||
        var toTrigger = await uow.Set<Repeater>().AsNoTracking()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .Skip(index)
 | 
			
		||||
            .FirstOrDefaultAsyncEF();
 | 
			
		||||
        var toTrigger = await uow.Set<Repeater>()
 | 
			
		||||
                                 .AsNoTracking()
 | 
			
		||||
                                 .Where(x => x.GuildId == guildId)
 | 
			
		||||
                                 .Skip(index)
 | 
			
		||||
                                 .FirstOrDefaultAsyncEF();
 | 
			
		||||
 | 
			
		||||
        if (toTrigger is null)
 | 
			
		||||
            return false;
 | 
			
		||||
@@ -265,7 +269,7 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
            var text = SmartText.CreateFrom(repeater.Message);
 | 
			
		||||
            text = await _repSvc.ReplaceAsync(text, repCtx);
 | 
			
		||||
 | 
			
		||||
            var newMsg = await channel.SendAsync(text);
 | 
			
		||||
            var newMsg = await _sender.Response(channel).Text(text).SendAsync();
 | 
			
		||||
            _ = newMsg.AddReactionAsync(new Emoji("🔄"));
 | 
			
		||||
 | 
			
		||||
            if (_noRedundant.Contains(repeater.Id))
 | 
			
		||||
@@ -308,12 +312,13 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
    private async Task SetRepeaterLastMessageInternal(int repeaterId, ulong lastMsgId)
 | 
			
		||||
    {
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        await uow.Set<Repeater>().AsQueryable()
 | 
			
		||||
            .Where(x => x.Id == repeaterId)
 | 
			
		||||
            .UpdateAsync(rep => new()
 | 
			
		||||
            {
 | 
			
		||||
                LastMessageId = lastMsgId
 | 
			
		||||
            });
 | 
			
		||||
        await uow.Set<Repeater>()
 | 
			
		||||
                 .AsQueryable()
 | 
			
		||||
                 .Where(x => x.Id == repeaterId)
 | 
			
		||||
                 .UpdateAsync(rep => new()
 | 
			
		||||
                 {
 | 
			
		||||
                     LastMessageId = lastMsgId
 | 
			
		||||
                 });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<RunningRepeater?> AddRepeaterAsync(
 | 
			
		||||
@@ -358,10 +363,11 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
            throw new ArgumentOutOfRangeException(nameof(index));
 | 
			
		||||
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var toRemove = await uow.Set<Repeater>().AsNoTracking()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .Skip(index)
 | 
			
		||||
            .FirstOrDefaultAsyncEF();
 | 
			
		||||
        var toRemove = await uow.Set<Repeater>()
 | 
			
		||||
                                .AsNoTracking()
 | 
			
		||||
                                .Where(x => x.GuildId == guildId)
 | 
			
		||||
                                .Skip(index)
 | 
			
		||||
                                .FirstOrDefaultAsyncEF();
 | 
			
		||||
 | 
			
		||||
        if (toRemove is null)
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -389,10 +395,11 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
    public async Task<bool?> ToggleRedundantAsync(ulong guildId, int index)
 | 
			
		||||
    {
 | 
			
		||||
        await using var uow = _db.GetDbContext();
 | 
			
		||||
        var toToggle = await uow.Set<Repeater>().AsQueryable()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .Skip(index)
 | 
			
		||||
            .FirstOrDefaultAsyncEF();
 | 
			
		||||
        var toToggle = await uow.Set<Repeater>()
 | 
			
		||||
                                .AsQueryable()
 | 
			
		||||
                                .Where(x => x.GuildId == guildId)
 | 
			
		||||
                                .Skip(index)
 | 
			
		||||
                                .FirstOrDefaultAsyncEF();
 | 
			
		||||
 | 
			
		||||
        if (toToggle is null)
 | 
			
		||||
            return null;
 | 
			
		||||
@@ -411,9 +418,9 @@ public sealed class RepeaterService : IReadyExecutor, INService
 | 
			
		||||
    {
 | 
			
		||||
        await using var ctx = _db.GetDbContext();
 | 
			
		||||
        var toSkip = await ctx.Set<Repeater>()
 | 
			
		||||
            .Where(x => x.GuildId == guildId)
 | 
			
		||||
            .Skip(index)
 | 
			
		||||
            .FirstOrDefaultAsyncEF();
 | 
			
		||||
                              .Where(x => x.GuildId == guildId)
 | 
			
		||||
                              .Skip(index)
 | 
			
		||||
                              .FirstOrDefaultAsyncEF();
 | 
			
		||||
 | 
			
		||||
        if (toSkip is null)
 | 
			
		||||
            return null;
 | 
			
		||||
 
 | 
			
		||||
@@ -81,27 +81,27 @@ public partial class Utility
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        private async Task ShowTodosAsync(TodoModel[] todos)
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                (curPage) =>
 | 
			
		||||
                {
 | 
			
		||||
                    var eb = new EmbedBuilder()
 | 
			
		||||
                             .WithOkColor()
 | 
			
		||||
                             .WithTitle(GetText(strs.todo_list));
 | 
			
		||||
        private Task ShowTodosAsync(TodoModel[] todos)
 | 
			
		||||
            => Response()
 | 
			
		||||
               .Paginated()
 | 
			
		||||
               .Items(todos)
 | 
			
		||||
               .PageSize(9)
 | 
			
		||||
               .Page((items, _) =>
 | 
			
		||||
               {
 | 
			
		||||
                   var eb = _sender.CreateEmbed()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle(GetText(strs.todo_list));
 | 
			
		||||
 | 
			
		||||
                    ShowTodoItem(todos, curPage, eb);
 | 
			
		||||
                   ShowTodoItem(items, eb);
 | 
			
		||||
 | 
			
		||||
                    return eb;
 | 
			
		||||
                },
 | 
			
		||||
                todos.Length,
 | 
			
		||||
                9);
 | 
			
		||||
        }
 | 
			
		||||
                   return eb;
 | 
			
		||||
               })
 | 
			
		||||
               .SendAsync();
 | 
			
		||||
 | 
			
		||||
        private static void ShowTodoItem(IReadOnlyCollection<TodoModel> todos, int curPage, EmbedBuilder eb)
 | 
			
		||||
        private static void ShowTodoItem(IReadOnlyCollection<TodoModel> todos, EmbedBuilder eb)
 | 
			
		||||
        {
 | 
			
		||||
            var sb = new StringBuilder();
 | 
			
		||||
            foreach (var todo in todos.Skip(curPage * 9).Take(9))
 | 
			
		||||
            foreach (var todo in todos)
 | 
			
		||||
            {
 | 
			
		||||
                sb.AppendLine($"{(todo.IsDone ? "✔" : "□")} {Format.Code(new kwum(todo.Id).ToString())} {todo.Todo}");
 | 
			
		||||
 | 
			
		||||
@@ -147,23 +147,25 @@ public partial class Utility
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                    (curPage) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var eb = new EmbedBuilder()
 | 
			
		||||
                                 .WithTitle(GetText(strs.todo_archive_list))
 | 
			
		||||
                                 .WithOkColor();
 | 
			
		||||
                await Response()
 | 
			
		||||
                      .Paginated()
 | 
			
		||||
                      .Items(archivedTodoLists)
 | 
			
		||||
                      .PageSize(9)
 | 
			
		||||
                      .CurrentPage(page)
 | 
			
		||||
                      .Page((items, _) =>
 | 
			
		||||
                      {
 | 
			
		||||
                          var eb = _sender.CreateEmbed()
 | 
			
		||||
                                   .WithTitle(GetText(strs.todo_archive_list))
 | 
			
		||||
                                   .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                        foreach (var archivedList in archivedTodoLists.Skip(curPage * 9).Take(9))
 | 
			
		||||
                        {
 | 
			
		||||
                            eb.AddField($"id: {archivedList.Id.ToString()}", archivedList.Name, true);
 | 
			
		||||
                        }
 | 
			
		||||
                          foreach (var archivedList in items)
 | 
			
		||||
                          {
 | 
			
		||||
                              eb.AddField($"id: {archivedList.Id.ToString()}", archivedList.Name, true);
 | 
			
		||||
                          }
 | 
			
		||||
 | 
			
		||||
                        return eb;
 | 
			
		||||
                    },
 | 
			
		||||
                    archivedTodoLists.Count,
 | 
			
		||||
                    9,
 | 
			
		||||
                    true);
 | 
			
		||||
                          return eb;
 | 
			
		||||
                      })
 | 
			
		||||
                      .SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
@@ -176,19 +178,21 @@ public partial class Utility
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
                    (curPage) =>
 | 
			
		||||
                    {
 | 
			
		||||
                        var eb = new EmbedBuilder()
 | 
			
		||||
                                 .WithOkColor()
 | 
			
		||||
                                 .WithTitle(GetText(strs.todo_list));
 | 
			
		||||
                await Response()
 | 
			
		||||
                      .Paginated()
 | 
			
		||||
                      .Items(list.Items)
 | 
			
		||||
                      .PageSize(9)
 | 
			
		||||
                      .Page((items, _) =>
 | 
			
		||||
                      {
 | 
			
		||||
                          var eb = _sender.CreateEmbed()
 | 
			
		||||
                                   .WithOkColor()
 | 
			
		||||
                                   .WithTitle(GetText(strs.todo_list));
 | 
			
		||||
 | 
			
		||||
                        ShowTodoItem(list.Items, curPage, eb);
 | 
			
		||||
                          ShowTodoItem(items, eb);
 | 
			
		||||
 | 
			
		||||
                        return eb;
 | 
			
		||||
                    },
 | 
			
		||||
                    list.Items.Count,
 | 
			
		||||
                    9);
 | 
			
		||||
                          return eb;
 | 
			
		||||
                      })
 | 
			
		||||
                      .SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -13,7 +13,7 @@ public partial class Utility
 | 
			
		||||
        {
 | 
			
		||||
            var units = await _service.GetUnitsAsync();
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle(GetText(strs.convertlist)).WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithTitle(GetText(strs.convertlist)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
            foreach (var g in units.GroupBy(x => x.UnitType))
 | 
			
		||||
 
 | 
			
		||||
@@ -83,9 +83,10 @@ public partial class Utility : NadekoModule
 | 
			
		||||
        var repCtx = new ReplacementContext(Context);
 | 
			
		||||
        message = await repSvc.ReplaceAsync(message, repCtx);
 | 
			
		||||
 | 
			
		||||
        await channel.SendAsync(message,
 | 
			
		||||
            !((IGuildUser)ctx.User).GuildPermissions.MentionEveryone,
 | 
			
		||||
            replyTo: ctx.Message);
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Text(message)
 | 
			
		||||
              .UserBasedMentions()
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -154,21 +155,22 @@ public partial class Utility : NadekoModule
 | 
			
		||||
                             .Select(u => $"`{u.Id,18}` {u}")
 | 
			
		||||
                             .ToArray();
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
            cur =>
 | 
			
		||||
            {
 | 
			
		||||
                var pageUsers = roleUsers.Skip(cur * 20).Take(20).ToList();
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(roleUsers)
 | 
			
		||||
              .PageSize(20)
 | 
			
		||||
              .CurrentPage(page)
 | 
			
		||||
              .Page((pageUsers, _) =>
 | 
			
		||||
              {
 | 
			
		||||
                  if (pageUsers.Count == 0)
 | 
			
		||||
                      return _sender.CreateEmbed().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page));
 | 
			
		||||
 | 
			
		||||
                if (pageUsers.Count == 0)
 | 
			
		||||
                    return new EmbedBuilder().WithOkColor().WithDescription(GetText(strs.no_user_on_this_page));
 | 
			
		||||
 | 
			
		||||
                return new EmbedBuilder()
 | 
			
		||||
                          .WithOkColor()
 | 
			
		||||
                          .WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length)))
 | 
			
		||||
                          .WithDescription(string.Join("\n", pageUsers));
 | 
			
		||||
            },
 | 
			
		||||
            roleUsers.Length,
 | 
			
		||||
            20);
 | 
			
		||||
                  return _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .WithTitle(GetText(strs.inrole_list(Format.Bold(role?.Name ?? "No Role"), roleUsers.Length)))
 | 
			
		||||
                         .WithDescription(string.Join("\n", pageUsers));
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -301,30 +303,32 @@ public partial class Utility : NadekoModule
 | 
			
		||||
        if (string.IsNullOrWhiteSpace(ownerIds))
 | 
			
		||||
            ownerIds = "-";
 | 
			
		||||
 | 
			
		||||
        await Response().Embed(new EmbedBuilder()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithAuthor($"NadekoBot v{StatsService.BotVersion}",
 | 
			
		||||
                                "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png",
 | 
			
		||||
                                "https://nadekobot.readthedocs.io/en/latest/")
 | 
			
		||||
                            .AddField(GetText(strs.author), _stats.Author, true)
 | 
			
		||||
                            .AddField(GetText(strs.botid), _client.CurrentUser.Id.ToString(), true)
 | 
			
		||||
                            .AddField(GetText(strs.shard),
 | 
			
		||||
                                $"#{_client.ShardId} / {_creds.TotalShards}",
 | 
			
		||||
                                true)
 | 
			
		||||
                            .AddField(GetText(strs.commands_ran), _stats.CommandsRan.ToString(), true)
 | 
			
		||||
                            .AddField(GetText(strs.messages),
 | 
			
		||||
                                $"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)",
 | 
			
		||||
                                true)
 | 
			
		||||
                            .AddField(GetText(strs.memory),
 | 
			
		||||
                                FormattableString.Invariant($"{_stats.GetPrivateMemoryMegabytes():F2} MB"),
 | 
			
		||||
                                true)
 | 
			
		||||
                            .AddField(GetText(strs.owner_ids), ownerIds, true)
 | 
			
		||||
                            .AddField(GetText(strs.uptime), _stats.GetUptimeString("\n"), true)
 | 
			
		||||
                            .AddField(GetText(strs.presence),
 | 
			
		||||
                                GetText(strs.presence_txt(_coord.GetGuildCount(),
 | 
			
		||||
                                    _stats.TextChannels,
 | 
			
		||||
                                    _stats.VoiceChannels)),
 | 
			
		||||
                                true)).SendAsync();
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Embed(_sender.CreateEmbed()
 | 
			
		||||
                     .WithOkColor()
 | 
			
		||||
                     .WithAuthor($"NadekoBot v{StatsService.BotVersion}",
 | 
			
		||||
                         "https://nadeko-pictures.nyc3.digitaloceanspaces.com/other/avatar.png",
 | 
			
		||||
                         "https://nadekobot.readthedocs.io/en/latest/")
 | 
			
		||||
                     .AddField(GetText(strs.author), _stats.Author, true)
 | 
			
		||||
                     .AddField(GetText(strs.botid), _client.CurrentUser.Id.ToString(), true)
 | 
			
		||||
                     .AddField(GetText(strs.shard),
 | 
			
		||||
                         $"#{_client.ShardId} / {_creds.TotalShards}",
 | 
			
		||||
                         true)
 | 
			
		||||
                     .AddField(GetText(strs.commands_ran), _stats.CommandsRan.ToString(), true)
 | 
			
		||||
                     .AddField(GetText(strs.messages),
 | 
			
		||||
                         $"{_stats.MessageCounter} ({_stats.MessagesPerSecond:F2}/sec)",
 | 
			
		||||
                         true)
 | 
			
		||||
                     .AddField(GetText(strs.memory),
 | 
			
		||||
                         FormattableString.Invariant($"{_stats.GetPrivateMemoryMegabytes():F2} MB"),
 | 
			
		||||
                         true)
 | 
			
		||||
                     .AddField(GetText(strs.owner_ids), ownerIds, true)
 | 
			
		||||
                     .AddField(GetText(strs.uptime), _stats.GetUptimeString("\n"), true)
 | 
			
		||||
                     .AddField(GetText(strs.presence),
 | 
			
		||||
                         GetText(strs.presence_txt(_coord.GetGuildCount(),
 | 
			
		||||
                             _stats.TextChannels,
 | 
			
		||||
                             _stats.VoiceChannels)),
 | 
			
		||||
                         true))
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -517,7 +521,7 @@ public partial class Utility : NadekoModule
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder().WithOkColor();
 | 
			
		||||
        var embed = _sender.CreateEmbed().WithOkColor();
 | 
			
		||||
        foreach (var guild in guilds)
 | 
			
		||||
            embed.AddField(guild.Name, GetText(strs.listservers(guild.Id, guild.MemberCount, guild.OwnerId)));
 | 
			
		||||
 | 
			
		||||
@@ -660,6 +664,7 @@ public partial class Utility : NadekoModule
 | 
			
		||||
                         .WithReferences(this.GetType().Assembly)
 | 
			
		||||
                         .WithImports(
 | 
			
		||||
                             "System",
 | 
			
		||||
                             "System.Linq",
 | 
			
		||||
                             "NadekoBot",
 | 
			
		||||
                             "NadekoBot.Extensions",
 | 
			
		||||
                             "Microsoft.Extensions.DependencyInjection",
 | 
			
		||||
@@ -683,10 +688,10 @@ public partial class Utility : NadekoModule
 | 
			
		||||
            var output = result.ReturnValue?.ToString();
 | 
			
		||||
            if (!string.IsNullOrWhiteSpace(output))
 | 
			
		||||
            {
 | 
			
		||||
                var eb = new EmbedBuilder()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .AddField("Code", scriptText)
 | 
			
		||||
                            .AddField("Output", output.TrimTo(512)!);
 | 
			
		||||
                var eb = _sender.CreateEmbed()
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
                         .AddField("Code", scriptText)
 | 
			
		||||
                         .AddField("Output", output.TrimTo(512)!);
 | 
			
		||||
 | 
			
		||||
                _ = Response().Embed(eb).SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
 
 | 
			
		||||
@@ -127,10 +127,9 @@ public partial class Xp
 | 
			
		||||
                  .Paginated()
 | 
			
		||||
                  .Items(allUsers)
 | 
			
		||||
                  .PageSize(10)
 | 
			
		||||
                  .CurrentPage(0)
 | 
			
		||||
                  .Page((users, _) =>
 | 
			
		||||
                  {
 | 
			
		||||
                      var embed = new EmbedBuilder()
 | 
			
		||||
                      var embed = _sender.CreateEmbed()
 | 
			
		||||
                                  .WithOkColor()
 | 
			
		||||
                                  .WithTitle($"{club}")
 | 
			
		||||
                                  .WithDescription(GetText(strs.level_x(lvl.Level + $" ({club.Xp} xp)")))
 | 
			
		||||
@@ -206,22 +205,21 @@ public partial class Xp
 | 
			
		||||
 | 
			
		||||
            var bans = club.Bans.Select(x => x.User).ToArray();
 | 
			
		||||
 | 
			
		||||
            return ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    var toShow = string.Join("\n",
 | 
			
		||||
                        bans
 | 
			
		||||
                            .Skip(page * 10)
 | 
			
		||||
                            .Take(10)
 | 
			
		||||
                            .Select(x => x.ToString()));
 | 
			
		||||
            return Response()
 | 
			
		||||
                   .Paginated()
 | 
			
		||||
                   .Items(bans)
 | 
			
		||||
                   .PageSize(10)
 | 
			
		||||
                   .CurrentPage(page)
 | 
			
		||||
                   .Page((items, _) =>
 | 
			
		||||
                   {
 | 
			
		||||
                       var toShow = string.Join("\n", items.Select(x => x.ToString()));
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                           .WithTitle(GetText(strs.club_bans_for(club.ToString())))
 | 
			
		||||
                           .WithDescription(toShow)
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
                },
 | 
			
		||||
                bans.Length,
 | 
			
		||||
                10);
 | 
			
		||||
                       return _sender.CreateEmbed()
 | 
			
		||||
                              .WithTitle(GetText(strs.club_bans_for(club.ToString())))
 | 
			
		||||
                              .WithDescription(toShow)
 | 
			
		||||
                              .WithOkColor();
 | 
			
		||||
                   })
 | 
			
		||||
                   .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -236,18 +234,21 @@ public partial class Xp
 | 
			
		||||
 | 
			
		||||
            var apps = club.Applicants.Select(x => x.User).ToArray();
 | 
			
		||||
 | 
			
		||||
            return ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                _ =>
 | 
			
		||||
                {
 | 
			
		||||
                    var toShow = string.Join("\n", apps.Skip(page * 10).Take(10).Select(x => x.ToString()));
 | 
			
		||||
            return Response()
 | 
			
		||||
                   .Paginated()
 | 
			
		||||
                   .Items(apps)
 | 
			
		||||
                   .PageSize(10)
 | 
			
		||||
                   .CurrentPage(page)
 | 
			
		||||
                   .Page((items, _) =>
 | 
			
		||||
                   {
 | 
			
		||||
                       var toShow = string.Join("\n", items.Select(x => x.ToString()));
 | 
			
		||||
 | 
			
		||||
                    return new EmbedBuilder()
 | 
			
		||||
                           .WithTitle(GetText(strs.club_apps_for(club.ToString())))
 | 
			
		||||
                           .WithDescription(toShow)
 | 
			
		||||
                           .WithOkColor();
 | 
			
		||||
                },
 | 
			
		||||
                apps.Length,
 | 
			
		||||
                10);
 | 
			
		||||
                       return _sender.CreateEmbed()
 | 
			
		||||
                              .WithTitle(GetText(strs.club_apps_for(club.ToString())))
 | 
			
		||||
                              .WithDescription(toShow)
 | 
			
		||||
                              .WithOkColor();
 | 
			
		||||
                   })
 | 
			
		||||
                   .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -412,7 +413,7 @@ public partial class Xp
 | 
			
		||||
                    ? "-"
 | 
			
		||||
                    : desc;
 | 
			
		||||
 | 
			
		||||
                var eb = new EmbedBuilder()
 | 
			
		||||
                var eb = _sender.CreateEmbed()
 | 
			
		||||
                         .WithAuthor(ctx.User)
 | 
			
		||||
                         .WithTitle(GetText(strs.club_desc_update))
 | 
			
		||||
                         .WithOkColor()
 | 
			
		||||
@@ -443,7 +444,7 @@ public partial class Xp
 | 
			
		||||
 | 
			
		||||
            var clubs = _service.GetClubLeaderboardPage(page);
 | 
			
		||||
 | 
			
		||||
            var embed = new EmbedBuilder().WithTitle(GetText(strs.club_leaderboard(page + 1))).WithOkColor();
 | 
			
		||||
            var embed = _sender.CreateEmbed().WithTitle(GetText(strs.club_leaderboard(page + 1))).WithOkColor();
 | 
			
		||||
 | 
			
		||||
            var i = page * 9;
 | 
			
		||||
            foreach (var club in clubs)
 | 
			
		||||
@@ -464,7 +465,7 @@ public partial class Xp
 | 
			
		||||
                    return;
 | 
			
		||||
                case ClubRenameResult.Success:
 | 
			
		||||
                    {
 | 
			
		||||
                        var embed = new EmbedBuilder().WithTitle(GetText(strs.club_renamed(clubName))).WithOkColor();
 | 
			
		||||
                        var embed = _sender.CreateEmbed().WithTitle(GetText(strs.club_renamed(clubName))).WithOkColor();
 | 
			
		||||
                        await Response().Embed(embed).SendAsync();
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
 
 | 
			
		||||
@@ -59,10 +59,10 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
        var globalSetting = _service.GetNotificationType(ctx.User);
 | 
			
		||||
        var serverSetting = _service.GetNotificationType(ctx.User.Id, ctx.Guild.Id);
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
                       .WithOkColor()
 | 
			
		||||
                       .AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting))
 | 
			
		||||
                       .AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting));
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithOkColor()
 | 
			
		||||
                           .AddField(GetText(strs.xpn_setting_global), GetNotifLocationString(globalSetting))
 | 
			
		||||
                           .AddField(GetText(strs.xpn_setting_server), GetNotifLocationString(serverSetting));
 | 
			
		||||
 | 
			
		||||
        await Response().Embed(embed).SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
@@ -147,18 +147,21 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
        desc += "\n\n" + rolesStr + chansStr;
 | 
			
		||||
 | 
			
		||||
        var lines = desc.Split('\n');
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(0,
 | 
			
		||||
            curpage =>
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder()
 | 
			
		||||
                               .WithTitle(GetText(strs.exclusion_list))
 | 
			
		||||
                               .WithDescription(string.Join('\n', lines.Skip(15 * curpage).Take(15)))
 | 
			
		||||
                               .WithOkColor();
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(lines)
 | 
			
		||||
              .PageSize(15)
 | 
			
		||||
              .CurrentPage(0)
 | 
			
		||||
              .Page((items, _) =>
 | 
			
		||||
              {
 | 
			
		||||
                  var embed = _sender.CreateEmbed()
 | 
			
		||||
                                     .WithTitle(GetText(strs.exclusion_list))
 | 
			
		||||
                                     .WithDescription(string.Join('\n', items))
 | 
			
		||||
                                     .WithOkColor();
 | 
			
		||||
 | 
			
		||||
                return embed;
 | 
			
		||||
            },
 | 
			
		||||
            lines.Length,
 | 
			
		||||
            15);
 | 
			
		||||
                  return embed;
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -182,53 +185,54 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
        await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
 | 
			
		||||
        var socketGuild = (SocketGuild)ctx.Guild;
 | 
			
		||||
        var allUsers = new List<UserXpStats>();
 | 
			
		||||
        var allCleanUsers = new List<UserXpStats>();
 | 
			
		||||
        if (opts.Clean)
 | 
			
		||||
        {
 | 
			
		||||
            await ctx.Channel.TriggerTypingAsync();
 | 
			
		||||
            await _tracker.EnsureUsersDownloadedAsync(ctx.Guild);
 | 
			
		||||
 | 
			
		||||
            allUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000)
 | 
			
		||||
                               .Where(user => socketGuild.GetUser(user.UserId) is not null)
 | 
			
		||||
                               .ToList();
 | 
			
		||||
            allCleanUsers = _service.GetTopUserXps(ctx.Guild.Id, 1000)
 | 
			
		||||
                                    .Where(user => socketGuild.GetUser(user.UserId) is not null)
 | 
			
		||||
                                    .ToList();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync(page,
 | 
			
		||||
            curPage =>
 | 
			
		||||
            {
 | 
			
		||||
                var embed = new EmbedBuilder().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .PageItems<UserXpStats>(opts.Clean
 | 
			
		||||
                  ? (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(allCleanUsers.Skip(curPage * 9)
 | 
			
		||||
                      .Take(9)
 | 
			
		||||
                      .ToList())
 | 
			
		||||
                  : (curPage) => Task.FromResult<IEnumerable<UserXpStats>>(_service.GetUserXps(ctx.Guild.Id, curPage)))
 | 
			
		||||
              .PageSize(9)
 | 
			
		||||
              .CurrentPage(page)
 | 
			
		||||
              .AddFooter(false)
 | 
			
		||||
              .Page((users, curPage) =>
 | 
			
		||||
              {
 | 
			
		||||
                  var embed = _sender.CreateEmbed().WithTitle(GetText(strs.server_leaderboard)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
                List<UserXpStats> users;
 | 
			
		||||
                if (opts.Clean)
 | 
			
		||||
                    users = allUsers.Skip(curPage * 9).Take(9).ToList();
 | 
			
		||||
                else
 | 
			
		||||
                    users = _service.GetUserXps(ctx.Guild.Id, curPage);
 | 
			
		||||
                  if (!users.Any())
 | 
			
		||||
                      return embed.WithDescription("-");
 | 
			
		||||
 | 
			
		||||
                if (!users.Any())
 | 
			
		||||
                    return embed.WithDescription("-");
 | 
			
		||||
                  for (var i = 0; i < users.Count; i++)
 | 
			
		||||
                  {
 | 
			
		||||
                      var levelStats = new LevelStats(users[i].Xp + users[i].AwardedXp);
 | 
			
		||||
                      var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
 | 
			
		||||
 | 
			
		||||
                for (var i = 0; i < users.Count; i++)
 | 
			
		||||
                {
 | 
			
		||||
                    var levelStats = new LevelStats(users[i].Xp + users[i].AwardedXp);
 | 
			
		||||
                    var user = ((SocketGuild)ctx.Guild).GetUser(users[i].UserId);
 | 
			
		||||
                      var userXpData = users[i];
 | 
			
		||||
 | 
			
		||||
                    var userXpData = users[i];
 | 
			
		||||
                      var awardStr = string.Empty;
 | 
			
		||||
                      if (userXpData.AwardedXp > 0)
 | 
			
		||||
                          awardStr = $"(+{userXpData.AwardedXp})";
 | 
			
		||||
                      else if (userXpData.AwardedXp < 0)
 | 
			
		||||
                          awardStr = $"({userXpData.AwardedXp})";
 | 
			
		||||
 | 
			
		||||
                    var awardStr = string.Empty;
 | 
			
		||||
                    if (userXpData.AwardedXp > 0)
 | 
			
		||||
                        awardStr = $"(+{userXpData.AwardedXp})";
 | 
			
		||||
                    else if (userXpData.AwardedXp < 0)
 | 
			
		||||
                        awardStr = $"({userXpData.AwardedXp})";
 | 
			
		||||
                      embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}",
 | 
			
		||||
                          $"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                    embed.AddField($"#{i + 1 + (curPage * 9)} {user?.ToString() ?? users[i].UserId.ToString()}",
 | 
			
		||||
                        $"{GetText(strs.level_x(levelStats.Level))} - {levelStats.TotalXp}xp {awardStr}");
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return embed;
 | 
			
		||||
            },
 | 
			
		||||
            900,
 | 
			
		||||
            9,
 | 
			
		||||
            false);
 | 
			
		||||
                  return embed;
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
@@ -239,7 +243,7 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
            return;
 | 
			
		||||
        var users = _service.GetUserXps(page);
 | 
			
		||||
 | 
			
		||||
        var embed = new EmbedBuilder().WithTitle(GetText(strs.global_leaderboard)).WithOkColor();
 | 
			
		||||
        var embed = _sender.CreateEmbed().WithTitle(GetText(strs.global_leaderboard)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
        if (!users.Any())
 | 
			
		||||
            embed.WithDescription("-");
 | 
			
		||||
@@ -317,7 +321,9 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
    [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
    public async Task XpReset(ulong userId)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder().WithTitle(GetText(strs.reset)).WithDescription(GetText(strs.reset_user_confirm));
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithTitle(GetText(strs.reset))
 | 
			
		||||
                           .WithDescription(GetText(strs.reset_user_confirm));
 | 
			
		||||
 | 
			
		||||
        if (!await PromptUserConfirmAsync(embed))
 | 
			
		||||
            return;
 | 
			
		||||
@@ -332,7 +338,9 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
    [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
    public async Task XpReset()
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder().WithTitle(GetText(strs.reset)).WithDescription(GetText(strs.reset_server_confirm));
 | 
			
		||||
        var embed = _sender.CreateEmbed()
 | 
			
		||||
                           .WithTitle(GetText(strs.reset))
 | 
			
		||||
                           .WithDescription(GetText(strs.reset_server_confirm));
 | 
			
		||||
 | 
			
		||||
        if (!await PromptUserConfirmAsync(embed))
 | 
			
		||||
            return;
 | 
			
		||||
@@ -383,94 +391,102 @@ public partial class Xp : NadekoModule<XpService>
 | 
			
		||||
        if (page < 0)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var items = type == XpShopInputType.Backgrounds
 | 
			
		||||
        var allItems = type == XpShopInputType.Backgrounds
 | 
			
		||||
            ? await _service.GetShopBgs()
 | 
			
		||||
            : await _service.GetShopFrames();
 | 
			
		||||
 | 
			
		||||
        if (items is null)
 | 
			
		||||
        if (allItems is null)
 | 
			
		||||
        {
 | 
			
		||||
            await Response().Error(strs.xp_shop_disabled).SendAsync();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (items.Count == 0)
 | 
			
		||||
        if (allItems.Count == 0)
 | 
			
		||||
        {
 | 
			
		||||
            await Response().Error(strs.not_found).SendAsync();
 | 
			
		||||
            return;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await ctx.SendPaginatedConfirmAsync<(string, XpShopItemType)?>(page,
 | 
			
		||||
            current =>
 | 
			
		||||
            {
 | 
			
		||||
                var (key, item) = items.Skip(current).First();
 | 
			
		||||
        await Response()
 | 
			
		||||
              .Paginated()
 | 
			
		||||
              .Items(allItems)
 | 
			
		||||
              .PageSize(1)
 | 
			
		||||
              .CurrentPage(page)
 | 
			
		||||
              .AddFooter(false)
 | 
			
		||||
              .Page((items, _) =>
 | 
			
		||||
              {
 | 
			
		||||
                  if (!items.Any())
 | 
			
		||||
                      return _sender.CreateEmbed()
 | 
			
		||||
                                    .WithDescription(GetText(strs.not_found))
 | 
			
		||||
                                    .WithErrorColor();
 | 
			
		||||
 | 
			
		||||
                var eb = new EmbedBuilder()
 | 
			
		||||
                            .WithOkColor()
 | 
			
		||||
                            .WithTitle(item.Name)
 | 
			
		||||
                            .AddField(GetText(strs.price),
 | 
			
		||||
                                CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
 | 
			
		||||
                                true)
 | 
			
		||||
                            .WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
 | 
			
		||||
                                ? item.Url
 | 
			
		||||
                                : item.Preview);
 | 
			
		||||
                  var (key, item) = items.FirstOrDefault();
 | 
			
		||||
 | 
			
		||||
                if (!string.IsNullOrWhiteSpace(item.Desc))
 | 
			
		||||
                    eb.AddField(GetText(strs.desc), item.Desc);
 | 
			
		||||
                  var eb = _sender.CreateEmbed()
 | 
			
		||||
                                  .WithOkColor()
 | 
			
		||||
                                  .WithTitle(item.Name)
 | 
			
		||||
                                  .AddField(GetText(strs.price),
 | 
			
		||||
                                      CurrencyHelper.N(item.Price, Culture, _gss.GetCurrencySign()),
 | 
			
		||||
                                      true)
 | 
			
		||||
                                  .WithImageUrl(string.IsNullOrWhiteSpace(item.Preview)
 | 
			
		||||
                                      ? item.Url
 | 
			
		||||
                                      : item.Preview);
 | 
			
		||||
 | 
			
		||||
                if (key == "default")
 | 
			
		||||
                    eb.WithDescription(GetText(strs.xpshop_website));
 | 
			
		||||
                  if (!string.IsNullOrWhiteSpace(item.Desc))
 | 
			
		||||
                      eb.AddField(GetText(strs.desc), item.Desc);
 | 
			
		||||
 | 
			
		||||
                  if (key == "default")
 | 
			
		||||
                      eb.WithDescription(GetText(strs.xpshop_website));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
                var tier = _service.GetXpShopTierRequirement(type);
 | 
			
		||||
                if (tier != PatronTier.None)
 | 
			
		||||
                {
 | 
			
		||||
                    eb.WithFooter(GetText(strs.xp_shop_buy_required_tier(tier.ToString())));
 | 
			
		||||
                }
 | 
			
		||||
                  var tier = _service.GetXpShopTierRequirement(type);
 | 
			
		||||
                  if (tier != PatronTier.None)
 | 
			
		||||
                  {
 | 
			
		||||
                      eb.WithFooter(GetText(strs.xp_shop_buy_required_tier(tier.ToString())));
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                return Task.FromResult(eb);
 | 
			
		||||
            },
 | 
			
		||||
            async current =>
 | 
			
		||||
            {
 | 
			
		||||
                var (key, _) = items.Skip(current).First();
 | 
			
		||||
                  return eb;
 | 
			
		||||
              })
 | 
			
		||||
              .Interaction(async current =>
 | 
			
		||||
              {
 | 
			
		||||
                  var (key, _) = allItems.Skip(current).First();
 | 
			
		||||
 | 
			
		||||
                var itemType = type == XpShopInputType.Backgrounds
 | 
			
		||||
                    ? XpShopItemType.Background
 | 
			
		||||
                    : XpShopItemType.Frame;
 | 
			
		||||
                  var itemType = type == XpShopInputType.Backgrounds
 | 
			
		||||
                      ? XpShopItemType.Background
 | 
			
		||||
                      : XpShopItemType.Frame;
 | 
			
		||||
 | 
			
		||||
                var ownedItem = await _service.GetUserItemAsync(ctx.User.Id, itemType, key);
 | 
			
		||||
                if (ownedItem is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    var button = new ButtonBuilder(ownedItem.IsUsing
 | 
			
		||||
                            ? GetText(strs.in_use)
 | 
			
		||||
                            : GetText(strs.use),
 | 
			
		||||
                        "xpshop:use",
 | 
			
		||||
                        emote: Emoji.Parse("👐"),
 | 
			
		||||
                        isDisabled: ownedItem.IsUsing);
 | 
			
		||||
                  var ownedItem = await _service.GetUserItemAsync(ctx.User.Id, itemType, key);
 | 
			
		||||
                  if (ownedItem is not null)
 | 
			
		||||
                  {
 | 
			
		||||
                      var button = new ButtonBuilder(ownedItem.IsUsing
 | 
			
		||||
                              ? GetText(strs.in_use)
 | 
			
		||||
                              : GetText(strs.use),
 | 
			
		||||
                          "xpshop:use",
 | 
			
		||||
                          emote: Emoji.Parse("👐"),
 | 
			
		||||
                          isDisabled: ownedItem.IsUsing);
 | 
			
		||||
 | 
			
		||||
                    var inter = new SimpleInteraction<(string key, XpShopItemType type)?>(
 | 
			
		||||
                        button,
 | 
			
		||||
                        OnShopUse,
 | 
			
		||||
                        (key, itemType));
 | 
			
		||||
                      var inter = new SimpleInteraction<(string key, XpShopItemType type)?>(
 | 
			
		||||
                          button,
 | 
			
		||||
                          OnShopUse,
 | 
			
		||||
                          (key, itemType));
 | 
			
		||||
 | 
			
		||||
                    return inter;
 | 
			
		||||
                }
 | 
			
		||||
                else
 | 
			
		||||
                {
 | 
			
		||||
                    var button = new ButtonBuilder(GetText(strs.buy),
 | 
			
		||||
                        "xpshop:buy",
 | 
			
		||||
                        emote: Emoji.Parse("💰"));
 | 
			
		||||
                      return inter;
 | 
			
		||||
                  }
 | 
			
		||||
                  else
 | 
			
		||||
                  {
 | 
			
		||||
                      var button = new ButtonBuilder(GetText(strs.buy),
 | 
			
		||||
                          "xpshop:buy",
 | 
			
		||||
                          emote: Emoji.Parse("💰"));
 | 
			
		||||
 | 
			
		||||
                    var inter = new SimpleInteraction<(string key, XpShopItemType type)?>(
 | 
			
		||||
                        button,
 | 
			
		||||
                        OnShopBuy,
 | 
			
		||||
                        (key, itemType));
 | 
			
		||||
                      var inter = new SimpleInteraction<(string key, XpShopItemType type)?>(
 | 
			
		||||
                          button,
 | 
			
		||||
                          OnShopBuy,
 | 
			
		||||
                          (key, itemType));
 | 
			
		||||
 | 
			
		||||
                    return inter;
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
            items.Count,
 | 
			
		||||
            1,
 | 
			
		||||
            addPaginatedFooter: false);
 | 
			
		||||
                      return inter;
 | 
			
		||||
                  }
 | 
			
		||||
              })
 | 
			
		||||
              .SendAsync();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    [Cmd]
 | 
			
		||||
 
 | 
			
		||||
@@ -16,10 +16,10 @@ public partial class Xp
 | 
			
		||||
        [UserPerm(GuildPerm.Administrator)]
 | 
			
		||||
        public async Task XpRewsReset()
 | 
			
		||||
        {
 | 
			
		||||
            var promptEmbed = new EmbedBuilder()
 | 
			
		||||
                                 .WithPendingColor()
 | 
			
		||||
                                 .WithDescription(GetText(strs.xprewsreset_confirm));
 | 
			
		||||
            
 | 
			
		||||
            var promptEmbed = _sender.CreateEmbed()
 | 
			
		||||
                              .WithPendingColor()
 | 
			
		||||
                              .WithDescription(GetText(strs.xprewsreset_confirm));
 | 
			
		||||
 | 
			
		||||
            var reply = await PromptUserConfirmAsync(promptEmbed);
 | 
			
		||||
 | 
			
		||||
            if (!reply)
 | 
			
		||||
@@ -66,24 +66,25 @@ public partial class Xp
 | 
			
		||||
                                     .OrderBy(x => x.Key)
 | 
			
		||||
                                     .ToList();
 | 
			
		||||
 | 
			
		||||
            return Context.SendPaginatedConfirmAsync(page,
 | 
			
		||||
                cur =>
 | 
			
		||||
                {
 | 
			
		||||
                    var embed = new EmbedBuilder().WithTitle(GetText(strs.level_up_rewards)).WithOkColor();
 | 
			
		||||
            return Response()
 | 
			
		||||
                   .Paginated()
 | 
			
		||||
                   .Items(allRewards)
 | 
			
		||||
                   .PageSize(9)
 | 
			
		||||
                   .CurrentPage(page)
 | 
			
		||||
                   .Page((items, _) =>
 | 
			
		||||
                   {
 | 
			
		||||
                       var embed = _sender.CreateEmbed().WithTitle(GetText(strs.level_up_rewards)).WithOkColor();
 | 
			
		||||
 | 
			
		||||
                    var localRewards = allRewards.Skip(cur * 9).Take(9).ToList();
 | 
			
		||||
                       if (!items.Any())
 | 
			
		||||
                           return embed.WithDescription(GetText(strs.no_level_up_rewards));
 | 
			
		||||
 | 
			
		||||
                    if (!localRewards.Any())
 | 
			
		||||
                        return embed.WithDescription(GetText(strs.no_level_up_rewards));
 | 
			
		||||
                       foreach (var reward in items)
 | 
			
		||||
                           embed.AddField(GetText(strs.level_x(reward.Key)),
 | 
			
		||||
                               string.Join("\n", reward.Select(y => y.Item2)));
 | 
			
		||||
 | 
			
		||||
                    foreach (var reward in localRewards)
 | 
			
		||||
                        embed.AddField(GetText(strs.level_x(reward.Key)),
 | 
			
		||||
                            string.Join("\n", reward.Select(y => y.Item2)));
 | 
			
		||||
 | 
			
		||||
                    return embed;
 | 
			
		||||
                },
 | 
			
		||||
                allRewards.Count,
 | 
			
		||||
                9);
 | 
			
		||||
                       return embed;
 | 
			
		||||
                   })
 | 
			
		||||
                   .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        [Cmd]
 | 
			
		||||
@@ -112,8 +113,10 @@ public partial class Xp
 | 
			
		||||
                await Response().Confirm(strs.xp_role_reward_add_role(level, Format.Bold(role.ToString()))).SendAsync();
 | 
			
		||||
            else
 | 
			
		||||
            {
 | 
			
		||||
                await Response().Confirm(strs.xp_role_reward_remove_role(Format.Bold(level.ToString()),
 | 
			
		||||
                    Format.Bold(role.ToString()))).SendAsync();
 | 
			
		||||
                await Response()
 | 
			
		||||
                      .Confirm(strs.xp_role_reward_remove_role(Format.Bold(level.ToString()),
 | 
			
		||||
                          Format.Bold(role.ToString())))
 | 
			
		||||
                      .SendAsync();
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@@ -129,8 +132,10 @@ public partial class Xp
 | 
			
		||||
            if (amount == 0)
 | 
			
		||||
                await Response().Confirm(strs.cur_reward_cleared(level, _cp.GetCurrencySign())).SendAsync();
 | 
			
		||||
            else
 | 
			
		||||
                await Response().Confirm(strs.cur_reward_added(level,
 | 
			
		||||
                    Format.Bold(amount + _cp.GetCurrencySign()))).SendAsync();
 | 
			
		||||
                await Response()
 | 
			
		||||
                      .Confirm(strs.cur_reward_added(level,
 | 
			
		||||
                          Format.Bold(amount + _cp.GetCurrencySign())))
 | 
			
		||||
                      .SendAsync();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,7 +7,7 @@ public abstract class CleanupModuleBase : NadekoModule
 | 
			
		||||
    {
 | 
			
		||||
        try
 | 
			
		||||
        {
 | 
			
		||||
            var embed = new EmbedBuilder()
 | 
			
		||||
            var embed = _sender.CreateEmbed()
 | 
			
		||||
                .WithTitle(GetText(strs.sql_confirm_exec))
 | 
			
		||||
                .WithDescription(name);
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,20 @@
 | 
			
		||||
namespace NadekoBot;
 | 
			
		||||
 | 
			
		||||
public class SimpleInteraction<T>
 | 
			
		||||
public static class InteractionHelpers
 | 
			
		||||
{
 | 
			
		||||
    public ButtonBuilder Button { get; }
 | 
			
		||||
    public static readonly IEmote ArrowLeft = Emote.Parse("<:x:1232256519844790302>");
 | 
			
		||||
    public static readonly IEmote ArrowRight = Emote.Parse("<:x:1232256515298295838>");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public abstract class SimpleInteractionBase
 | 
			
		||||
{
 | 
			
		||||
    public abstract Task TriggerAsync(SocketMessageComponent smc);
 | 
			
		||||
    public abstract ButtonBuilder Button { get; }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class SimpleInteraction<T> : SimpleInteractionBase
 | 
			
		||||
{
 | 
			
		||||
    public override ButtonBuilder Button { get; }
 | 
			
		||||
    private readonly Func<SocketMessageComponent, T, Task> _onClick;
 | 
			
		||||
    private readonly T? _state;
 | 
			
		||||
 | 
			
		||||
@@ -13,7 +25,7 @@ public class SimpleInteraction<T>
 | 
			
		||||
        _state = state;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task TriggerAsync(SocketMessageComponent smc)
 | 
			
		||||
    public override async Task TriggerAsync(SocketMessageComponent smc)
 | 
			
		||||
    {
 | 
			
		||||
        await _onClick(smc, _state!);
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ public abstract class NadekoModule : ModuleBase
 | 
			
		||||
    public INadekoInteractionService _inter { get; set; }
 | 
			
		||||
    public IReplacementService repSvc { get; set; }
 | 
			
		||||
    public IMessageSenderService _sender { get; set; }
 | 
			
		||||
    public BotConfigService _bcs { get; set; }
 | 
			
		||||
 | 
			
		||||
    protected string prefix
 | 
			
		||||
        => _cmdHandler.GetPrefix(ctx.Guild);
 | 
			
		||||
@@ -27,7 +28,7 @@ public abstract class NadekoModule : ModuleBase
 | 
			
		||||
        => Context;
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Response()
 | 
			
		||||
        => new ResponseBuilder(Strings)
 | 
			
		||||
        => new ResponseBuilder(Strings, _bcs, (DiscordSocketClient)ctx.Client)
 | 
			
		||||
            .Context(ctx);
 | 
			
		||||
 | 
			
		||||
    protected override void BeforeExecute(CommandInfo command)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,4 +7,6 @@ public interface IMessageSenderService
 | 
			
		||||
    ResponseBuilder Response(IUser user);
 | 
			
		||||
 | 
			
		||||
    ResponseBuilder Response(SocketMessageComponent smc);
 | 
			
		||||
 | 
			
		||||
    NadekoEmbedBuilder CreateEmbed();
 | 
			
		||||
}
 | 
			
		||||
@@ -1,280 +0,0 @@
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class MessageChannelExtensions
 | 
			
		||||
{
 | 
			
		||||
    // main overload that all other send methods reduce to
 | 
			
		||||
    public static Task<IUserMessage> SendAsync(
 | 
			
		||||
        this IMessageChannel channel,
 | 
			
		||||
        string? plainText,
 | 
			
		||||
        Embed? embed = null,
 | 
			
		||||
        IReadOnlyCollection<Embed>? embeds = null,
 | 
			
		||||
        bool sanitizeAll = false,
 | 
			
		||||
        MessageComponent? components = null,
 | 
			
		||||
        IUserMessage? replyTo = null)
 | 
			
		||||
    {
 | 
			
		||||
        plainText = sanitizeAll
 | 
			
		||||
            ? plainText?.SanitizeAllMentions() ?? ""
 | 
			
		||||
            : plainText?.SanitizeMentions() ?? "";
 | 
			
		||||
 | 
			
		||||
        var msgReference = CreateMessageReference(channel, replyTo);
 | 
			
		||||
        return channel.SendMessageAsync(plainText,
 | 
			
		||||
            embed: embed,
 | 
			
		||||
            embeds: embeds is null
 | 
			
		||||
                ? null
 | 
			
		||||
                : embeds as Embed[] ?? embeds.ToArray(),
 | 
			
		||||
            components: components,
 | 
			
		||||
            messageReference: msgReference);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static MessageReference? CreateMessageReference(IChannel source, IMessage? replyTo)
 | 
			
		||||
    {
 | 
			
		||||
        if (replyTo is null)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        if (replyTo.Channel.Id != source.Id)
 | 
			
		||||
            return null;
 | 
			
		||||
 | 
			
		||||
        return new(replyTo.Id,
 | 
			
		||||
            replyTo.Channel.Id,
 | 
			
		||||
            (replyTo.Channel as ITextChannel)?.GuildId,
 | 
			
		||||
            failIfNotExists: false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static async Task<IUserMessage> SendAsync(
 | 
			
		||||
        this IMessageChannel channel,
 | 
			
		||||
        string? plainText,
 | 
			
		||||
        NadekoInteraction? inter,
 | 
			
		||||
        Embed? embed = null,
 | 
			
		||||
        IReadOnlyCollection<Embed>? embeds = null,
 | 
			
		||||
        bool sanitizeAll = false,
 | 
			
		||||
        IUserMessage? replyTo = null)
 | 
			
		||||
    {
 | 
			
		||||
        var msg = await channel.SendAsync(plainText,
 | 
			
		||||
            embed,
 | 
			
		||||
            embeds,
 | 
			
		||||
            sanitizeAll,
 | 
			
		||||
            inter?.CreateComponent(),
 | 
			
		||||
            replyTo);
 | 
			
		||||
 | 
			
		||||
        if (inter is not null)
 | 
			
		||||
            await inter.RunAsync(msg);
 | 
			
		||||
 | 
			
		||||
        return msg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task<IUserMessage> SendAsync(
 | 
			
		||||
        this IMessageChannel channel,
 | 
			
		||||
        SmartText text,
 | 
			
		||||
        bool sanitizeAll = false,
 | 
			
		||||
        IUserMessage? replyTo = null)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
            SmartEmbedText set => channel.SendAsync(set.PlainText,
 | 
			
		||||
                set.IsValid ? set.GetEmbed().Build() : null,
 | 
			
		||||
                sanitizeAll: sanitizeAll,
 | 
			
		||||
                replyTo: replyTo),
 | 
			
		||||
            SmartPlainText st => channel.SendAsync(st.Text,
 | 
			
		||||
                default(Embed),
 | 
			
		||||
                sanitizeAll: sanitizeAll,
 | 
			
		||||
                replyTo: replyTo),
 | 
			
		||||
            SmartEmbedTextArray arr => channel.SendAsync(arr.Content,
 | 
			
		||||
                embeds: arr.GetEmbedBuilders().Map(e => e.Build()),
 | 
			
		||||
                sanitizeAll: sanitizeAll,
 | 
			
		||||
                replyTo: replyTo),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public static Task<IUserMessage> EmbedAsync(
 | 
			
		||||
        this IMessageChannel ch,
 | 
			
		||||
        EmbedBuilder? embed,
 | 
			
		||||
        string plainText = "",
 | 
			
		||||
        IReadOnlyCollection<EmbedBuilder>? embeds = null,
 | 
			
		||||
        NadekoInteraction? inter = null,
 | 
			
		||||
        IUserMessage? replyTo = null)
 | 
			
		||||
        => ch.SendAsync(plainText,
 | 
			
		||||
            inter,
 | 
			
		||||
            embed: embed?.Build(),
 | 
			
		||||
            embeds: embeds?.Map(x => x.Build()),
 | 
			
		||||
            replyTo: replyTo);
 | 
			
		||||
 | 
			
		||||
    // embed title and optional footer overloads
 | 
			
		||||
 | 
			
		||||
    public static Task SendPaginatedConfirmAsync(
 | 
			
		||||
        this ICommandContext ctx,
 | 
			
		||||
        int currentPage,
 | 
			
		||||
        Func<int, EmbedBuilder> pageFunc,
 | 
			
		||||
        int totalElements,
 | 
			
		||||
        int itemsPerPage,
 | 
			
		||||
        bool addPaginatedFooter = true)
 | 
			
		||||
        => ctx.SendPaginatedConfirmAsync(currentPage,
 | 
			
		||||
            x => Task.FromResult(pageFunc(x)),
 | 
			
		||||
            totalElements,
 | 
			
		||||
            itemsPerPage,
 | 
			
		||||
            addPaginatedFooter);
 | 
			
		||||
 | 
			
		||||
    private const string BUTTON_LEFT = "BUTTON_LEFT";
 | 
			
		||||
    private const string BUTTON_RIGHT = "BUTTON_RIGHT";
 | 
			
		||||
 | 
			
		||||
    private static readonly IEmote _arrowLeft = Emote.Parse("<:x:1232256519844790302>");
 | 
			
		||||
    private static readonly IEmote _arrowRight = Emote.Parse("<:x:1232256515298295838>");
 | 
			
		||||
 | 
			
		||||
    public static Task SendPaginatedConfirmAsync(
 | 
			
		||||
        this ICommandContext ctx,
 | 
			
		||||
        int currentPage,
 | 
			
		||||
        Func<int, Task<EmbedBuilder>> pageFunc,
 | 
			
		||||
        int totalElements,
 | 
			
		||||
        int itemsPerPage,
 | 
			
		||||
        bool addPaginatedFooter = true)
 | 
			
		||||
        => ctx.SendPaginatedConfirmAsync(currentPage,
 | 
			
		||||
            pageFunc,
 | 
			
		||||
            default(Func<int, ValueTask<SimpleInteraction<object>?>>),
 | 
			
		||||
            totalElements,
 | 
			
		||||
            itemsPerPage,
 | 
			
		||||
            addPaginatedFooter);
 | 
			
		||||
 | 
			
		||||
    public static async Task SendPaginatedConfirmAsync<T>(
 | 
			
		||||
        this ICommandContext ctx,
 | 
			
		||||
        int currentPage,
 | 
			
		||||
        Func<int, Task<EmbedBuilder>> pageFunc,
 | 
			
		||||
        Func<int, ValueTask<SimpleInteraction<T>?>>? interFactory,
 | 
			
		||||
        int totalElements,
 | 
			
		||||
        int itemsPerPage,
 | 
			
		||||
        bool addPaginatedFooter = true)
 | 
			
		||||
    {
 | 
			
		||||
        var lastPage = (totalElements - 1) / itemsPerPage;
 | 
			
		||||
 | 
			
		||||
        var embed = await pageFunc(currentPage);
 | 
			
		||||
 | 
			
		||||
        if (addPaginatedFooter)
 | 
			
		||||
            embed.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
 | 
			
		||||
        SimpleInteraction<T>? maybeInter = null;
 | 
			
		||||
 | 
			
		||||
        async Task<ComponentBuilder> GetComponentBuilder()
 | 
			
		||||
        {
 | 
			
		||||
            var cb = new ComponentBuilder();
 | 
			
		||||
 | 
			
		||||
            cb.WithButton(new ButtonBuilder()
 | 
			
		||||
                          .WithStyle(ButtonStyle.Primary)
 | 
			
		||||
                          .WithCustomId(BUTTON_LEFT)
 | 
			
		||||
                          .WithDisabled(lastPage == 0)
 | 
			
		||||
                          .WithEmote(_arrowLeft)
 | 
			
		||||
                          .WithDisabled(currentPage <= 0));
 | 
			
		||||
 | 
			
		||||
            if (interFactory is not null)
 | 
			
		||||
            {
 | 
			
		||||
                maybeInter = await interFactory(currentPage);
 | 
			
		||||
 | 
			
		||||
                if (maybeInter is not null)
 | 
			
		||||
                    cb.WithButton(maybeInter.Button);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            cb.WithButton(new ButtonBuilder()
 | 
			
		||||
                          .WithStyle(ButtonStyle.Primary)
 | 
			
		||||
                          .WithCustomId(BUTTON_RIGHT)
 | 
			
		||||
                          .WithDisabled(lastPage == 0 || currentPage >= lastPage)
 | 
			
		||||
                          .WithEmote(_arrowRight));
 | 
			
		||||
 | 
			
		||||
            return cb;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        async Task UpdatePageAsync(SocketMessageComponent smc)
 | 
			
		||||
        {
 | 
			
		||||
            var toSend = await pageFunc(currentPage);
 | 
			
		||||
            if (addPaginatedFooter)
 | 
			
		||||
                toSend.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
 | 
			
		||||
            var component = (await GetComponentBuilder()).Build();
 | 
			
		||||
 | 
			
		||||
            await smc.ModifyOriginalResponseAsync(x =>
 | 
			
		||||
            {
 | 
			
		||||
                x.Embed = toSend.Build();
 | 
			
		||||
                x.Components = component;
 | 
			
		||||
            });
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        var component = (await GetComponentBuilder()).Build();
 | 
			
		||||
        var msg = await ctx.Channel.SendAsync(null, embed: embed.Build(), components: component, replyTo: ctx.Message);
 | 
			
		||||
 | 
			
		||||
        async Task OnInteractionAsync(SocketInteraction si)
 | 
			
		||||
        {
 | 
			
		||||
            try
 | 
			
		||||
            {
 | 
			
		||||
                if (si is not SocketMessageComponent smc)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (smc.Message.Id != msg.Id)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                await si.DeferAsync();
 | 
			
		||||
                if (smc.User.Id != ctx.User.Id)
 | 
			
		||||
                    return;
 | 
			
		||||
 | 
			
		||||
                if (smc.Data.CustomId == BUTTON_LEFT)
 | 
			
		||||
                {
 | 
			
		||||
                    if (currentPage == 0)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    --currentPage;
 | 
			
		||||
                    _ = UpdatePageAsync(smc);
 | 
			
		||||
                }
 | 
			
		||||
                else if (smc.Data.CustomId == BUTTON_RIGHT)
 | 
			
		||||
                {
 | 
			
		||||
                    if (currentPage >= lastPage)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    ++currentPage;
 | 
			
		||||
                    _ = UpdatePageAsync(smc);
 | 
			
		||||
                }
 | 
			
		||||
                else if (maybeInter is { } inter && inter.Button.CustomId == smc.Data.CustomId)
 | 
			
		||||
                {
 | 
			
		||||
                    await inter.TriggerAsync(smc);
 | 
			
		||||
                    _ = UpdatePageAsync(smc);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            catch (Exception ex)
 | 
			
		||||
            {
 | 
			
		||||
                Log.Error(ex, "Error in pagination: {ErrorMessage}", ex.Message);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (lastPage == 0 && interFactory is null)
 | 
			
		||||
            return;
 | 
			
		||||
 | 
			
		||||
        var client = (DiscordSocketClient)ctx.Client;
 | 
			
		||||
 | 
			
		||||
        client.InteractionCreated += OnInteractionAsync;
 | 
			
		||||
 | 
			
		||||
        await Task.Delay(30_000);
 | 
			
		||||
 | 
			
		||||
        client.InteractionCreated -= OnInteractionAsync;
 | 
			
		||||
 | 
			
		||||
        await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private static readonly Emoji _okEmoji = new Emoji("✅");
 | 
			
		||||
    private static readonly Emoji _warnEmoji = new Emoji("⚠️");
 | 
			
		||||
    private static readonly Emoji _errorEmoji = new Emoji("❌");
 | 
			
		||||
 | 
			
		||||
    public static Task ReactAsync(this ICommandContext ctx, MsgType type)
 | 
			
		||||
    {
 | 
			
		||||
        var emoji = type switch
 | 
			
		||||
        {
 | 
			
		||||
            MsgType.Error => _errorEmoji,
 | 
			
		||||
            MsgType.Pending => _warnEmoji,
 | 
			
		||||
            MsgType.Ok => _okEmoji,
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(type)),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ctx.Message.AddReactionAsync(emoji);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task OkAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.ReactAsync(MsgType.Ok);
 | 
			
		||||
 | 
			
		||||
    public static Task ErrorAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.ReactAsync(MsgType.Error);
 | 
			
		||||
 | 
			
		||||
    public static Task WarningAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.ReactAsync(MsgType.Pending);
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
using NadekoBot.Common.Configs;
 | 
			
		||||
using System.Diagnostics.CodeAnalysis;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
@@ -5,27 +6,52 @@ namespace NadekoBot.Extensions;
 | 
			
		||||
public sealed class MessageSenderService : IMessageSenderService, INService
 | 
			
		||||
{
 | 
			
		||||
    private readonly IBotStrings _bs;
 | 
			
		||||
    private readonly BotConfigService _bcs;
 | 
			
		||||
    private readonly DiscordSocketClient _client;
 | 
			
		||||
 | 
			
		||||
    public MessageSenderService(IBotStrings bs)
 | 
			
		||||
    public MessageSenderService(IBotStrings bs, BotConfigService bcs, DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        _bs = bs;
 | 
			
		||||
        _bcs = bcs;
 | 
			
		||||
        _client = client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Response(IMessageChannel channel)
 | 
			
		||||
        => new ResponseBuilder(_bs)
 | 
			
		||||
        => new ResponseBuilder(_bs, _bcs, _client)
 | 
			
		||||
            .Channel(channel);
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Response(ICommandContext ctx)
 | 
			
		||||
        => new ResponseBuilder(_bs)
 | 
			
		||||
        => new ResponseBuilder(_bs, _bcs, _client)
 | 
			
		||||
            .Context(ctx);
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Response(IUser user)
 | 
			
		||||
        => new ResponseBuilder(_bs)
 | 
			
		||||
        => new ResponseBuilder(_bs, _bcs, _client)
 | 
			
		||||
            .User(user);
 | 
			
		||||
    
 | 
			
		||||
    // todo fix interactions
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Response(SocketMessageComponent smc)
 | 
			
		||||
        => new ResponseBuilder(_bs)
 | 
			
		||||
        => new ResponseBuilder(_bs, _bcs, _client)
 | 
			
		||||
            .Channel(smc.Channel);
 | 
			
		||||
 | 
			
		||||
    public NadekoEmbedBuilder CreateEmbed()
 | 
			
		||||
        => new NadekoEmbedBuilder(_bcs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public class NadekoEmbedBuilder : EmbedBuilder
 | 
			
		||||
{
 | 
			
		||||
    private readonly BotConfig _bc;
 | 
			
		||||
 | 
			
		||||
    public NadekoEmbedBuilder(BotConfigService bcs)
 | 
			
		||||
    {
 | 
			
		||||
        _bc = bcs.Data;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public EmbedBuilder WithOkColor()
 | 
			
		||||
        => WithColor(_bc.Color.Ok.ToDiscordColor());
 | 
			
		||||
 | 
			
		||||
    public EmbedBuilder WithErrorColor()
 | 
			
		||||
        => WithColor(_bc.Color.Error.ToDiscordColor());
 | 
			
		||||
 | 
			
		||||
    public EmbedBuilder WithPendingColor()
 | 
			
		||||
        => WithColor(_bc.Color.Pending.ToDiscordColor());
 | 
			
		||||
}
 | 
			
		||||
@@ -7,24 +7,20 @@ public partial class ResponseBuilder
 | 
			
		||||
        private const string BUTTON_LEFT = "BUTTON_LEFT";
 | 
			
		||||
        private const string BUTTON_RIGHT = "BUTTON_RIGHT";
 | 
			
		||||
 | 
			
		||||
        private static readonly IEmote _arrowLeft = Emote.Parse("<:x:1232256519844790302>");
 | 
			
		||||
        private static readonly IEmote _arrowRight = Emote.Parse("<:x:1232256515298295838>");
 | 
			
		||||
 | 
			
		||||
        private readonly SourcedPaginatedResponseBuilder<T> _paginationBuilder;
 | 
			
		||||
        private readonly ResponseBuilder builder;
 | 
			
		||||
        private readonly DiscordSocketClient client;
 | 
			
		||||
        private readonly ResponseBuilder _builder;
 | 
			
		||||
        private readonly DiscordSocketClient _client;
 | 
			
		||||
        private int currentPage;
 | 
			
		||||
 | 
			
		||||
        public PaginationSender(
 | 
			
		||||
            SourcedPaginatedResponseBuilder<T> paginationBuilder,
 | 
			
		||||
            ResponseBuilder builder
 | 
			
		||||
        )
 | 
			
		||||
            ResponseBuilder builder)
 | 
			
		||||
        {
 | 
			
		||||
            this._paginationBuilder = paginationBuilder;
 | 
			
		||||
            this.builder = builder;
 | 
			
		||||
            _paginationBuilder = paginationBuilder;
 | 
			
		||||
            _builder = builder;
 | 
			
		||||
 | 
			
		||||
            client = (DiscordSocketClient)builder.ctx.Client;
 | 
			
		||||
            currentPage = 0;
 | 
			
		||||
            _client = builder.Client;
 | 
			
		||||
            currentPage = paginationBuilder.InitialPage;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        public async Task SendAsync(bool ephemeral = false)
 | 
			
		||||
@@ -38,7 +34,7 @@ public partial class ResponseBuilder
 | 
			
		||||
            if (_paginationBuilder.AddPaginatedFooter)
 | 
			
		||||
                embed.AddPaginatedFooter(currentPage, lastPage);
 | 
			
		||||
 | 
			
		||||
            SimpleInteraction<T>? maybeInter = null;
 | 
			
		||||
            SimpleInteractionBase? maybeInter = null;
 | 
			
		||||
 | 
			
		||||
            async Task<ComponentBuilder> GetComponentBuilder()
 | 
			
		||||
            {
 | 
			
		||||
@@ -48,22 +44,22 @@ public partial class ResponseBuilder
 | 
			
		||||
                              .WithStyle(ButtonStyle.Primary)
 | 
			
		||||
                              .WithCustomId(BUTTON_LEFT)
 | 
			
		||||
                              .WithDisabled(lastPage == 0)
 | 
			
		||||
                              .WithEmote(_arrowLeft)
 | 
			
		||||
                              .WithEmote(InteractionHelpers.ArrowLeft)
 | 
			
		||||
                              .WithDisabled(currentPage <= 0));
 | 
			
		||||
                // todo
 | 
			
		||||
                // if (interFactory is not null)
 | 
			
		||||
                // {
 | 
			
		||||
                //     maybeInter = await interFactory(currentPage);
 | 
			
		||||
                //
 | 
			
		||||
                //     if (maybeInter is not null)
 | 
			
		||||
                //         cb.WithButton(maybeInter.Button);
 | 
			
		||||
                // }
 | 
			
		||||
 | 
			
		||||
                if (_paginationBuilder.InteractionFunc is not null)
 | 
			
		||||
                {
 | 
			
		||||
                    maybeInter = await _paginationBuilder.InteractionFunc(currentPage);
 | 
			
		||||
 | 
			
		||||
                    if (maybeInter is not null)
 | 
			
		||||
                        cb.WithButton(maybeInter.Button);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                cb.WithButton(new ButtonBuilder()
 | 
			
		||||
                              .WithStyle(ButtonStyle.Primary)
 | 
			
		||||
                              .WithCustomId(BUTTON_RIGHT)
 | 
			
		||||
                              .WithDisabled(lastPage == 0 || currentPage >= lastPage)
 | 
			
		||||
                              .WithEmote(_arrowRight));
 | 
			
		||||
                              .WithEmote(InteractionHelpers.ArrowRight));
 | 
			
		||||
 | 
			
		||||
                return cb;
 | 
			
		||||
            }
 | 
			
		||||
@@ -84,7 +80,7 @@ public partial class ResponseBuilder
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var model = builder.Build(ephemeral);
 | 
			
		||||
            var model = await _builder.BuildAsync(ephemeral);
 | 
			
		||||
 | 
			
		||||
            var component = (await GetComponentBuilder()).Build();
 | 
			
		||||
            var msg = await model.TargetChannel
 | 
			
		||||
@@ -104,7 +100,8 @@ public partial class ResponseBuilder
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    await si.DeferAsync();
 | 
			
		||||
                    if (smc.User.Id != model.User.Id)
 | 
			
		||||
 | 
			
		||||
                    if (smc.User.Id != model.User?.Id)
 | 
			
		||||
                        return;
 | 
			
		||||
 | 
			
		||||
                    if (smc.Data.CustomId == BUTTON_LEFT)
 | 
			
		||||
@@ -134,20 +131,15 @@ public partial class ResponseBuilder
 | 
			
		||||
                    Log.Error(ex, "Error in pagination: {ErrorMessage}", ex.Message);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
            // todo re-add
 | 
			
		||||
            // if (lastPage == 0 && interFactory is null)
 | 
			
		||||
            //     return;
 | 
			
		||||
 | 
			
		||||
            if (lastPage == 0)
 | 
			
		||||
            if (lastPage == 0 && _paginationBuilder.InteractionFunc is null)
 | 
			
		||||
                return;
 | 
			
		||||
 | 
			
		||||
            var client = this.client;
 | 
			
		||||
 | 
			
		||||
            client.InteractionCreated += OnInteractionAsync;
 | 
			
		||||
            _client.InteractionCreated += OnInteractionAsync;
 | 
			
		||||
 | 
			
		||||
            await Task.Delay(30_000);
 | 
			
		||||
 | 
			
		||||
            client.InteractionCreated -= OnInteractionAsync;
 | 
			
		||||
            _client.InteractionCreated -= OnInteractionAsync;
 | 
			
		||||
 | 
			
		||||
            await msg.ModifyAsync(mp => mp.Components = new ComponentBuilder().Build());
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +1,39 @@
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
using NadekoBot.Common.Configs;
 | 
			
		||||
using NadekoBot.Db.Models;
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public sealed partial class ResponseBuilder
 | 
			
		||||
{
 | 
			
		||||
    private ICommandContext? ctx = null;
 | 
			
		||||
    private IMessageChannel? channel = null;
 | 
			
		||||
    private Embed? embed = null;
 | 
			
		||||
    private string? plainText = null;
 | 
			
		||||
    private IReadOnlyCollection<EmbedBuilder>? embeds = null;
 | 
			
		||||
    private IUserMessage? msg = null;
 | 
			
		||||
    private IUser? user = null;
 | 
			
		||||
    private ICommandContext? ctx;
 | 
			
		||||
    private IMessageChannel? channel;
 | 
			
		||||
    private string? plainText;
 | 
			
		||||
    private IReadOnlyCollection<EmbedBuilder>? embeds;
 | 
			
		||||
    private IUserMessage? msg;
 | 
			
		||||
    private IUser? user;
 | 
			
		||||
    private bool sanitizeMentions = true;
 | 
			
		||||
    private LocStr? locTxt;
 | 
			
		||||
    private object[] locParams = [];
 | 
			
		||||
    private bool shouldReply = true;
 | 
			
		||||
    private readonly IBotStrings _bs;
 | 
			
		||||
    private EmbedBuilder? embedBuilder = null;
 | 
			
		||||
    private readonly BotConfigService _bcs;
 | 
			
		||||
    private EmbedBuilder? embedBuilder;
 | 
			
		||||
    private NadekoInteraction? inter;
 | 
			
		||||
    private Stream? fileStream = null;
 | 
			
		||||
    private string? fileName = null;
 | 
			
		||||
    private Stream? fileStream;
 | 
			
		||||
    private string? fileName;
 | 
			
		||||
    private EmbedColor color = EmbedColor.Ok;
 | 
			
		||||
    private LocStr? embedLocDesc;
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder(IBotStrings bs)
 | 
			
		||||
    public DiscordSocketClient Client { get; set; }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder(IBotStrings bs, BotConfigService bcs, DiscordSocketClient client)
 | 
			
		||||
    {
 | 
			
		||||
        _bs = bs;
 | 
			
		||||
        _bcs = bcs;
 | 
			
		||||
        Client = client;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
    private MessageReference? CreateMessageReference(IMessageChannel targetChannel)
 | 
			
		||||
    {
 | 
			
		||||
        if (!shouldReply)
 | 
			
		||||
@@ -44,72 +54,116 @@ public sealed partial class ResponseBuilder
 | 
			
		||||
            failIfNotExists: false);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseMessageModel Build(bool ephemeral = false)
 | 
			
		||||
    public async Task<ResponseMessageModel> BuildAsync(bool ephemeral)
 | 
			
		||||
    {
 | 
			
		||||
        // todo use ephemeral in interactions
 | 
			
		||||
        var targetChannel = InternalResolveChannel() ?? throw new ArgumentNullException(nameof(channel));
 | 
			
		||||
        var targetChannel = await InternalResolveChannel() ?? throw new ArgumentNullException(nameof(channel));
 | 
			
		||||
        var msgReference = CreateMessageReference(targetChannel);
 | 
			
		||||
 | 
			
		||||
        var txt = GetText(locTxt);
 | 
			
		||||
        // todo check message  sanitization
 | 
			
		||||
        var txt = GetText(locTxt, targetChannel);
 | 
			
		||||
 | 
			
		||||
        if (embedLocDesc is LocStr ls)
 | 
			
		||||
        {
 | 
			
		||||
            InternalCreateEmbed(null, GetText(ls, targetChannel));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (embedBuilder is not null)
 | 
			
		||||
            PaintEmbedInternal(embedBuilder);
 | 
			
		||||
 | 
			
		||||
        var finalEmbed = embedBuilder?.Build();
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        var buildModel = new ResponseMessageModel()
 | 
			
		||||
        {
 | 
			
		||||
            TargetChannel = targetChannel,
 | 
			
		||||
            MessageReference = msgReference,
 | 
			
		||||
            Text = txt,
 | 
			
		||||
            User = ctx?.User,
 | 
			
		||||
            Embed = embed ?? embedBuilder?.Build(),
 | 
			
		||||
            User = user ?? ctx?.User,
 | 
			
		||||
            Embed = finalEmbed,
 | 
			
		||||
            Embeds = embeds?.Map(x => x.Build()),
 | 
			
		||||
            SanitizeMentions = sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All
 | 
			
		||||
            SanitizeMentions = sanitizeMentions ? new(AllowedMentionTypes.Users) : AllowedMentions.All,
 | 
			
		||||
            Ephemeral = ephemeral,
 | 
			
		||||
            Interaction = inter
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return buildModel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public Task<IUserMessage> SendAsync(bool ephemeral = false)
 | 
			
		||||
    public async Task<IUserMessage> SendAsync(bool ephemeral = false)
 | 
			
		||||
    {
 | 
			
		||||
        var model = Build(ephemeral);
 | 
			
		||||
        return SendAsync(model);
 | 
			
		||||
        var model = await BuildAsync(ephemeral);
 | 
			
		||||
        var sentMsg = await SendAsync(model);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
        return sentMsg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public async Task<IUserMessage> SendAsync(ResponseMessageModel model)
 | 
			
		||||
    {
 | 
			
		||||
        if (this.fileStream is Stream stream)
 | 
			
		||||
            return await model.TargetChannel.SendFileAsync(stream,
 | 
			
		||||
        IUserMessage sentMsg;
 | 
			
		||||
        if (fileStream is Stream stream)
 | 
			
		||||
        {
 | 
			
		||||
            sentMsg = await model.TargetChannel.SendFileAsync(stream,
 | 
			
		||||
                filename: fileName,
 | 
			
		||||
                model.Text,
 | 
			
		||||
                embed: model.Embed,
 | 
			
		||||
                components: null,
 | 
			
		||||
                components: inter?.CreateComponent(),
 | 
			
		||||
                allowedMentions: model.SanitizeMentions,
 | 
			
		||||
                messageReference: model.MessageReference);
 | 
			
		||||
        }
 | 
			
		||||
        else
 | 
			
		||||
        {
 | 
			
		||||
            sentMsg = await model.TargetChannel.SendMessageAsync(
 | 
			
		||||
                model.Text,
 | 
			
		||||
                embed: model.Embed,
 | 
			
		||||
                embeds: model.Embeds,
 | 
			
		||||
                components: inter?.CreateComponent(),
 | 
			
		||||
                allowedMentions: model.SanitizeMentions,
 | 
			
		||||
                messageReference: model.MessageReference);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return await model.TargetChannel.SendMessageAsync(
 | 
			
		||||
            model.Text,
 | 
			
		||||
            embed: model.Embed,
 | 
			
		||||
            embeds: model.Embeds,
 | 
			
		||||
            components: null,
 | 
			
		||||
            allowedMentions: model.SanitizeMentions,
 | 
			
		||||
            messageReference: model.MessageReference);
 | 
			
		||||
        if (model.Interaction is not null)
 | 
			
		||||
        {
 | 
			
		||||
            await model.Interaction.RunAsync(sentMsg);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return sentMsg;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private EmbedBuilder PaintEmbedInternal(EmbedBuilder eb)
 | 
			
		||||
        => color switch
 | 
			
		||||
        {
 | 
			
		||||
            EmbedColor.Ok => eb.WithOkColor(),
 | 
			
		||||
            EmbedColor.Pending => eb.WithPendingColor(),
 | 
			
		||||
            EmbedColor.Error => eb.WithErrorColor(),
 | 
			
		||||
            _ => throw new NotSupportedException()
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    private ulong? InternalResolveGuildId(IMessageChannel? targetChannel)
 | 
			
		||||
        => ctx?.Guild?.Id ?? (targetChannel as ITextChannel)?.GuildId;
 | 
			
		||||
 | 
			
		||||
    // todo not good, has to go to the user
 | 
			
		||||
    private IMessageChannel? InternalResolveChannel()
 | 
			
		||||
        => channel ?? ctx?.Channel ?? msg?.Channel;
 | 
			
		||||
 | 
			
		||||
    private string? GetText(LocStr? locStr)
 | 
			
		||||
    private async Task<IMessageChannel?> InternalResolveChannel()
 | 
			
		||||
    {
 | 
			
		||||
        if (user is not null)
 | 
			
		||||
        {
 | 
			
		||||
            var ch = await user.CreateDMChannelAsync();
 | 
			
		||||
 | 
			
		||||
            if (ch is not null)
 | 
			
		||||
            {
 | 
			
		||||
                return ch;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return channel ?? ctx?.Channel ?? msg?.Channel;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string? GetText(LocStr? locStr, IMessageChannel targetChannel)
 | 
			
		||||
    {
 | 
			
		||||
        var targetChannel = InternalResolveChannel();
 | 
			
		||||
        var guildId = InternalResolveGuildId(targetChannel);
 | 
			
		||||
        return locStr is LocStr ls ? _bs.GetText(ls.Key, guildId, locParams) : plainText;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private string GetText(LocStr locStr)
 | 
			
		||||
    private string GetText(LocStr locStr, IMessageChannel targetChannel)
 | 
			
		||||
    {
 | 
			
		||||
        var targetChannel = InternalResolveChannel();
 | 
			
		||||
        var guildId = InternalResolveGuildId(targetChannel);
 | 
			
		||||
        return _bs.GetText(locStr.Key, guildId, locStr.Params);
 | 
			
		||||
    }
 | 
			
		||||
@@ -125,91 +179,108 @@ public sealed partial class ResponseBuilder
 | 
			
		||||
        if (text is SmartPlainText spt)
 | 
			
		||||
            plainText = spt.Text;
 | 
			
		||||
        else if (text is SmartEmbedText set)
 | 
			
		||||
            embed = set.GetEmbed().Build();
 | 
			
		||||
            embedBuilder = set.GetEmbed();
 | 
			
		||||
        else if (text is SmartEmbedTextArray ser)
 | 
			
		||||
            embeds = ser.GetEmbedBuilders();
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private ResponseBuilder InternalColoredText(string text, EmbedColor color)
 | 
			
		||||
    {
 | 
			
		||||
        embed = new EmbedBuilder()
 | 
			
		||||
                .WithColor(color)
 | 
			
		||||
                .WithDescription(text)
 | 
			
		||||
                .Build();
 | 
			
		||||
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private EmbedBuilder CreateEmbedInternal(
 | 
			
		||||
    private void InternalCreateEmbed(
 | 
			
		||||
        string? title,
 | 
			
		||||
        string? text,
 | 
			
		||||
        string? url,
 | 
			
		||||
        string? footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder()
 | 
			
		||||
                    .WithTitle(title)
 | 
			
		||||
                    .WithDescription(text);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(url))
 | 
			
		||||
            embed = embed.WithUrl(url);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(footer))
 | 
			
		||||
            embed = embed.WithFooter(footer);
 | 
			
		||||
 | 
			
		||||
        return embed;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private EmbedBuilder PaintEmbedInternal(EmbedBuilder eb, EmbedColor color)
 | 
			
		||||
        => color switch
 | 
			
		||||
        {
 | 
			
		||||
            EmbedColor.Ok => eb.WithOkColor(),
 | 
			
		||||
            EmbedColor.Pending => eb.WithPendingColor(),
 | 
			
		||||
            EmbedColor.Error => eb.WithErrorColor(),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Error(
 | 
			
		||||
        string? title,
 | 
			
		||||
        string? text,
 | 
			
		||||
        string text,
 | 
			
		||||
        string? url = null,
 | 
			
		||||
        string? footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        var eb = CreateEmbedInternal(title, text, url, footer);
 | 
			
		||||
        embed = PaintEmbedInternal(eb, EmbedColor.Error).Build();
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
        var eb = new NadekoEmbedBuilder(_bcs)
 | 
			
		||||
            .WithDescription(text);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(title))
 | 
			
		||||
            eb.WithTitle(title);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(url))
 | 
			
		||||
            eb = eb.WithUrl(url);
 | 
			
		||||
 | 
			
		||||
        if (!string.IsNullOrWhiteSpace(footer))
 | 
			
		||||
            eb = eb.WithFooter(footer);
 | 
			
		||||
 | 
			
		||||
        embedBuilder = eb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Confirm(
 | 
			
		||||
        string? title,
 | 
			
		||||
        string? text,
 | 
			
		||||
        string text,
 | 
			
		||||
        string? url = null,
 | 
			
		||||
        string? footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        var eb = CreateEmbedInternal(title, text, url, footer);
 | 
			
		||||
        embed = PaintEmbedInternal(eb, EmbedColor.Error).Build();
 | 
			
		||||
        InternalCreateEmbed(title, text, url, footer);
 | 
			
		||||
        color = EmbedColor.Ok;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Error(
 | 
			
		||||
        string? title,
 | 
			
		||||
        string text,
 | 
			
		||||
        string? url = null,
 | 
			
		||||
        string? footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        InternalCreateEmbed(title, text, url, footer);
 | 
			
		||||
        color = EmbedColor.Error;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Pending(
 | 
			
		||||
        string? title,
 | 
			
		||||
        string text,
 | 
			
		||||
        string? url = null,
 | 
			
		||||
        string? footer = null)
 | 
			
		||||
    {
 | 
			
		||||
        InternalCreateEmbed(title, text, url, footer);
 | 
			
		||||
        color = EmbedColor.Pending;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Confirm(string text)
 | 
			
		||||
        => InternalColoredText(text, EmbedColor.Ok);
 | 
			
		||||
    {
 | 
			
		||||
        InternalCreateEmbed(null, text);
 | 
			
		||||
        color = EmbedColor.Ok;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Confirm(LocStr str)
 | 
			
		||||
        => Confirm(GetText(str));
 | 
			
		||||
    {
 | 
			
		||||
        embedLocDesc = str;
 | 
			
		||||
        color = EmbedColor.Ok;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Pending(string text)
 | 
			
		||||
        => InternalColoredText(text, EmbedColor.Ok);
 | 
			
		||||
    {
 | 
			
		||||
        InternalCreateEmbed(null, text);
 | 
			
		||||
        color = EmbedColor.Pending;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Pending(LocStr str)
 | 
			
		||||
        => Pending(GetText(str));
 | 
			
		||||
    {
 | 
			
		||||
        embedLocDesc = str;
 | 
			
		||||
        color = EmbedColor.Pending;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Error(string text)
 | 
			
		||||
        => InternalColoredText(text, EmbedColor.Error);
 | 
			
		||||
    {
 | 
			
		||||
        InternalCreateEmbed(null, text);
 | 
			
		||||
        color = EmbedColor.Error;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Error(LocStr str)
 | 
			
		||||
        => Error(GetText(str));
 | 
			
		||||
 | 
			
		||||
    {
 | 
			
		||||
        embedLocDesc = str;
 | 
			
		||||
        color = EmbedColor.Error;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder UserBasedMentions()
 | 
			
		||||
    {
 | 
			
		||||
@@ -220,17 +291,15 @@ public sealed partial class ResponseBuilder
 | 
			
		||||
    private IUser? InternalResolveUser()
 | 
			
		||||
        => ctx?.User ?? user ?? msg?.Author;
 | 
			
		||||
 | 
			
		||||
    // todo embed colors
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Embed(EmbedBuilder eb)
 | 
			
		||||
    {
 | 
			
		||||
        embedBuilder = eb;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Channel(IMessageChannel channel)
 | 
			
		||||
    public ResponseBuilder Channel(IMessageChannel ch)
 | 
			
		||||
    {
 | 
			
		||||
        this.channel = channel;
 | 
			
		||||
        channel = ch;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -240,21 +309,21 @@ public sealed partial class ResponseBuilder
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Context(ICommandContext ctx)
 | 
			
		||||
    public ResponseBuilder Context(ICommandContext context)
 | 
			
		||||
    {
 | 
			
		||||
        this.ctx = ctx;
 | 
			
		||||
        ctx = context;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Message(IUserMessage msg)
 | 
			
		||||
    public ResponseBuilder Message(IUserMessage message)
 | 
			
		||||
    {
 | 
			
		||||
        this.msg = msg;
 | 
			
		||||
        msg = message;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder User(IUser user)
 | 
			
		||||
    public ResponseBuilder User(IUser usr)
 | 
			
		||||
    {
 | 
			
		||||
        this.user = user;
 | 
			
		||||
        user = usr;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -266,7 +335,6 @@ public sealed partial class ResponseBuilder
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder Interaction(NadekoInteraction? interaction)
 | 
			
		||||
    {
 | 
			
		||||
        // todo implement
 | 
			
		||||
        inter = interaction;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
@@ -277,10 +345,10 @@ public sealed partial class ResponseBuilder
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public ResponseBuilder FileName(Stream fileStream, string fileName)
 | 
			
		||||
    public ResponseBuilder File(Stream stream, string name)
 | 
			
		||||
    {
 | 
			
		||||
        this.fileStream = fileStream;
 | 
			
		||||
        this.fileName = fileName;
 | 
			
		||||
        fileStream = stream;
 | 
			
		||||
        fileName = name;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -300,27 +368,51 @@ public class PaginatedResponseBuilder
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> Items<T>(IReadOnlyCollection<T> items)
 | 
			
		||||
        => new SourcedPaginatedResponseBuilder<T>(_builder)
 | 
			
		||||
            .Items(items);
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> PageItems<T>(Func<int, Task<IEnumerable<T>>> items)
 | 
			
		||||
        => new SourcedPaginatedResponseBuilder<T>(_builder)
 | 
			
		||||
            .PageItems(items);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilder
 | 
			
		||||
{
 | 
			
		||||
    private IReadOnlyCollection<T>? items;
 | 
			
		||||
    public Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> PageFunc { get; private set; }
 | 
			
		||||
    public Func<int, Task<IEnumerable<T>>> ItemsFunc { get; set; }
 | 
			
		||||
 | 
			
		||||
    public Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> PageFunc { get; private set; } = static delegate
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult<EmbedBuilder>(new());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public Func<int, Task<IEnumerable<T>>> ItemsFunc { get; set; } = static delegate
 | 
			
		||||
    {
 | 
			
		||||
        return Task.FromResult(Enumerable.Empty<T>());
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    public Func<int, Task<SimpleInteractionBase>>? InteractionFunc { get; private set; }
 | 
			
		||||
 | 
			
		||||
    public int TotalElements { get; private set; } = 1;
 | 
			
		||||
    public int ItemsPerPage { get; private set; } = 9;
 | 
			
		||||
    public bool AddPaginatedFooter { get; private set; } = true;
 | 
			
		||||
    public bool IsEphemeral { get; private set; }
 | 
			
		||||
 | 
			
		||||
    public int InitialPage { get; set; }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder(ResponseBuilder builder)
 | 
			
		||||
        : base(builder)
 | 
			
		||||
    {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> Items(IReadOnlyCollection<T> items)
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> Items(IReadOnlyCollection<T> col)
 | 
			
		||||
    {
 | 
			
		||||
        this.items = items;
 | 
			
		||||
        ItemsFunc = (i) => Task.FromResult(this.items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
 | 
			
		||||
        items = col;
 | 
			
		||||
        TotalElements = col.Count;
 | 
			
		||||
        ItemsFunc = (i) => Task.FromResult(items.Skip(i * ItemsPerPage).Take(ItemsPerPage));
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> PageItems(Func<int, Task<IEnumerable<T>>> func)
 | 
			
		||||
    {
 | 
			
		||||
        ItemsFunc = func;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -337,24 +429,22 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // todo use it
 | 
			
		||||
    public int InitialPage { get; set; }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> Page(Func<IReadOnlyList<T>, int, EmbedBuilder> pageFunc)
 | 
			
		||||
    {
 | 
			
		||||
        this.PageFunc = (xs, x) => Task.FromResult(pageFunc(xs, x));
 | 
			
		||||
        PageFunc = (xs, x) => Task.FromResult(pageFunc(xs, x));
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> Page(Func<IReadOnlyList<T>, int, Task<EmbedBuilder>> pageFunc)
 | 
			
		||||
    {
 | 
			
		||||
        this.PageFunc = pageFunc;
 | 
			
		||||
        PageFunc = pageFunc;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> AddFooter()
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> AddFooter(bool addFooter = true)
 | 
			
		||||
    {
 | 
			
		||||
        AddPaginatedFooter = true;
 | 
			
		||||
        AddPaginatedFooter = addFooter;
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -373,4 +463,10 @@ public sealed class SourcedPaginatedResponseBuilder<T> : PaginatedResponseBuilde
 | 
			
		||||
 | 
			
		||||
        return paginationSender.SendAsync(IsEphemeral);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public SourcedPaginatedResponseBuilder<T> Interaction(Func<int, Task<SimpleInteractionBase>> func)
 | 
			
		||||
    {
 | 
			
		||||
        InteractionFunc = async (i) => await func(i);
 | 
			
		||||
        return this;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -2,17 +2,27 @@
 | 
			
		||||
 | 
			
		||||
public static class ResponseBuilderExtensions
 | 
			
		||||
{
 | 
			
		||||
    // todo delete this
 | 
			
		||||
 | 
			
		||||
    public static EmbedBuilder WithColor(this EmbedBuilder eb, EmbedColor color)
 | 
			
		||||
        => eb;
 | 
			
		||||
 | 
			
		||||
    public static EmbedBuilder WithPendingColor(this EmbedBuilder eb)
 | 
			
		||||
        => eb.WithColor(EmbedColor.Error);
 | 
			
		||||
    {
 | 
			
		||||
        if (eb is NadekoEmbedBuilder neb)
 | 
			
		||||
            return neb.WithPendingColor();
 | 
			
		||||
 | 
			
		||||
        return eb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static EmbedBuilder WithOkColor(this EmbedBuilder eb)
 | 
			
		||||
        => eb.WithColor(EmbedColor.Ok);
 | 
			
		||||
    {
 | 
			
		||||
        if (eb is NadekoEmbedBuilder neb)
 | 
			
		||||
            return neb.WithOkColor();
 | 
			
		||||
 | 
			
		||||
        return eb;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static EmbedBuilder WithErrorColor(this EmbedBuilder eb)
 | 
			
		||||
        => eb.WithColor(EmbedColor.Error);
 | 
			
		||||
    {
 | 
			
		||||
        if (eb is NadekoEmbedBuilder neb)
 | 
			
		||||
            return neb.WithErrorColor();
 | 
			
		||||
 | 
			
		||||
        return eb;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,10 +1,12 @@
 | 
			
		||||
public class ResponseMessageModel
 | 
			
		||||
{
 | 
			
		||||
    public IMessageChannel TargetChannel { get; set; }
 | 
			
		||||
    public MessageReference MessageReference { get; set; }
 | 
			
		||||
    public string Text { get; set; }
 | 
			
		||||
    public Embed Embed { get; set; }
 | 
			
		||||
    public Embed[] Embeds { get; set; }
 | 
			
		||||
    public AllowedMentions SanitizeMentions { get; set; }
 | 
			
		||||
    public IUser User { get; set; }
 | 
			
		||||
    public required IMessageChannel TargetChannel { get; set; }
 | 
			
		||||
    public MessageReference? MessageReference { get; set; }
 | 
			
		||||
    public string? Text { get; set; }
 | 
			
		||||
    public Embed? Embed { get; set; }
 | 
			
		||||
    public Embed[]? Embeds { get; set; }
 | 
			
		||||
    public required AllowedMentions SanitizeMentions { get; set; }
 | 
			
		||||
    public IUser? User { get; set; }
 | 
			
		||||
    public bool Ephemeral { get; set; }
 | 
			
		||||
    public NadekoInteraction? Interaction { get; set; }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,17 +0,0 @@
 | 
			
		||||
#nullable disable
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Services;
 | 
			
		||||
 | 
			
		||||
// todo remove 
 | 
			
		||||
public sealed class DiscordEmbedBuilderWrapper 
 | 
			
		||||
{
 | 
			
		||||
    // public EmbedBuilder WithColor(EmbedColor color)
 | 
			
		||||
    //     => color switch
 | 
			
		||||
    //     {
 | 
			
		||||
    //         EmbedColor.Ok => Wrap(embed.WithColor(_botConfig.Color.Ok.ToDiscordColor())),
 | 
			
		||||
    //         EmbedColor.Pending => Wrap(embed.WithColor(_botConfig.Color.Pending.ToDiscordColor())),
 | 
			
		||||
    //         EmbedColor.Error => Wrap(embed.WithColor(_botConfig.Color.Error.ToDiscordColor())),
 | 
			
		||||
    //         _ => throw new ArgumentOutOfRangeException(nameof(color), "Unsupported EmbedColor type")
 | 
			
		||||
    //     };
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -39,7 +39,7 @@ public sealed class CommandsUtilityService : ICommandsUtilityService, INService
 | 
			
		||||
 | 
			
		||||
        var culture = _loc.GetCultureInfo(guild);
 | 
			
		||||
 | 
			
		||||
        var em = new EmbedBuilder()
 | 
			
		||||
        var em = _sender.CreateEmbed()
 | 
			
		||||
                    .AddField(str, $"{com.RealSummary(_strings, _medusae, culture, prefix)}", true);
 | 
			
		||||
 | 
			
		||||
        _dpos.TryGetOverrides(guild?.Id ?? 0, com.Name, out var overrides);
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,30 @@
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class CommandContextExtensions
 | 
			
		||||
{
 | 
			
		||||
    private static readonly Emoji _okEmoji = new Emoji("✅");
 | 
			
		||||
    private static readonly Emoji _warnEmoji = new Emoji("⚠️");
 | 
			
		||||
    private static readonly Emoji _errorEmoji = new Emoji("❌");
 | 
			
		||||
 | 
			
		||||
    public static Task ReactAsync(this ICommandContext ctx, MsgType type)
 | 
			
		||||
    {
 | 
			
		||||
        var emoji = type switch
 | 
			
		||||
        {
 | 
			
		||||
            MsgType.Error => _errorEmoji,
 | 
			
		||||
            MsgType.Pending => _warnEmoji,
 | 
			
		||||
            MsgType.Ok => _okEmoji,
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(type)),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        return ctx.Message.AddReactionAsync(emoji);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task OkAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.ReactAsync(MsgType.Ok);
 | 
			
		||||
 | 
			
		||||
    public static Task ErrorAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.ReactAsync(MsgType.Error);
 | 
			
		||||
 | 
			
		||||
    public static Task WarningAsync(this ICommandContext ctx)
 | 
			
		||||
        => ctx.ReactAsync(MsgType.Pending);
 | 
			
		||||
}
 | 
			
		||||
@@ -9,18 +9,25 @@ namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class Extensions
 | 
			
		||||
{
 | 
			
		||||
    private static readonly Regex _urlRegex =
 | 
			
		||||
        new(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
 | 
			
		||||
    
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Converts <see cref="DateTime"/> to <see cref="DateOnly"/>
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="dateTime"> The <see cref="DateTime"/> to convert. </param>
 | 
			
		||||
    /// <returns> The <see cref="DateOnly"/>. </returns>
 | 
			
		||||
    public static DateOnly ToDateOnly(this DateTime dateTime)
 | 
			
		||||
        => DateOnly.FromDateTime(dateTime);
 | 
			
		||||
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    ///     Determines if <see cref="DateTime"/> is before today
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <param name="date"> The <see cref="DateTime"/> to check. </param>
 | 
			
		||||
    /// <returns> True if <see cref="DateTime"/> is before today. </returns>
 | 
			
		||||
    public static bool IsBeforeToday(this DateTime date)
 | 
			
		||||
        => date < DateTime.UtcNow.Date;
 | 
			
		||||
 | 
			
		||||
    private static readonly Regex _urlRegex =
 | 
			
		||||
        new(@"^(https?|ftp)://(?<path>[^\s/$.?#].[^\s]*)$", RegexOptions.Compiled);
 | 
			
		||||
 | 
			
		||||
    // public static EmbedBuilder WithAuthor(this EmbedBuilder eb, IUser author)
 | 
			
		||||
        // => eb.WithAuthor(author.ToString()!, author.RealAvatarUrl().ToString());
 | 
			
		||||
 | 
			
		||||
    public static Task EditAsync(this IUserMessage msg, SmartText text)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,72 +1,15 @@
 | 
			
		||||
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
namespace NadekoBot.Extensions;
 | 
			
		||||
 | 
			
		||||
public static class SocketMessageComponentExtensions
 | 
			
		||||
{
 | 
			
		||||
    public static Task RespondAsync(
 | 
			
		||||
        this SocketMessageComponent smc,
 | 
			
		||||
        string? plainText,
 | 
			
		||||
        Embed? embed = null,
 | 
			
		||||
        IReadOnlyCollection<Embed>? embeds = null,
 | 
			
		||||
        bool sanitizeAll = false,
 | 
			
		||||
        MessageComponent? components = null,
 | 
			
		||||
        bool ephemeral = true)
 | 
			
		||||
    {
 | 
			
		||||
        plainText = sanitizeAll
 | 
			
		||||
            ? plainText?.SanitizeAllMentions() ?? ""
 | 
			
		||||
            : plainText?.SanitizeMentions() ?? "";
 | 
			
		||||
 | 
			
		||||
        return smc.RespondAsync(plainText,
 | 
			
		||||
            embed: embed,
 | 
			
		||||
            embeds: embeds is null
 | 
			
		||||
                ? null
 | 
			
		||||
                : embeds as Embed[] ?? embeds.ToArray(),
 | 
			
		||||
            components: components,
 | 
			
		||||
            ephemeral: ephemeral);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static Task RespondAsync(
 | 
			
		||||
        this SocketMessageComponent smc,
 | 
			
		||||
        SmartText text,
 | 
			
		||||
        bool sanitizeAll = false,
 | 
			
		||||
        bool ephemeral = true)
 | 
			
		||||
        => text switch
 | 
			
		||||
        {
 | 
			
		||||
            SmartEmbedText set => smc.RespondAsync(set.PlainText,
 | 
			
		||||
                set.IsValid ? set.GetEmbed().Build() : null,
 | 
			
		||||
                sanitizeAll: sanitizeAll,
 | 
			
		||||
                ephemeral: ephemeral),
 | 
			
		||||
            SmartPlainText st => smc.RespondAsync(st.Text,
 | 
			
		||||
                default(Embed),
 | 
			
		||||
                sanitizeAll: sanitizeAll,
 | 
			
		||||
                ephemeral: ephemeral),
 | 
			
		||||
            SmartEmbedTextArray arr => smc.RespondAsync(arr.Content,
 | 
			
		||||
                embeds: arr.GetEmbedBuilders().Map(e => e.Build()),
 | 
			
		||||
                ephemeral: ephemeral),
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(text))
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
    public static Task EmbedAsync(
 | 
			
		||||
        this SocketMessageComponent smc,
 | 
			
		||||
        EmbedBuilder? embed,
 | 
			
		||||
        string plainText = "",
 | 
			
		||||
        IReadOnlyCollection<EmbedBuilder>? embeds = null,
 | 
			
		||||
        NadekoInteraction? inter = null,
 | 
			
		||||
        bool ephemeral = false)
 | 
			
		||||
        => smc.RespondAsync(plainText,
 | 
			
		||||
            embed: embed?.Build(),
 | 
			
		||||
            embeds: embeds?.Map(x => x.Build()),
 | 
			
		||||
            ephemeral: ephemeral);
 | 
			
		||||
    
 | 
			
		||||
    public static Task RespondAsync(
 | 
			
		||||
    public static async Task RespondAsync(
 | 
			
		||||
        this SocketMessageComponent ch,
 | 
			
		||||
        IMessageSenderService sender,
 | 
			
		||||
        string text,
 | 
			
		||||
        MsgType type,
 | 
			
		||||
        bool ephemeral = false,
 | 
			
		||||
        NadekoInteraction? inter = null)
 | 
			
		||||
        bool ephemeral = false)
 | 
			
		||||
    {
 | 
			
		||||
        var embed = new EmbedBuilder().WithDescription(text);
 | 
			
		||||
        var embed = sender.CreateEmbed().WithDescription(text);
 | 
			
		||||
 | 
			
		||||
        embed = (type switch
 | 
			
		||||
        {
 | 
			
		||||
@@ -76,12 +19,9 @@ public static class SocketMessageComponentExtensions
 | 
			
		||||
            _ => throw new ArgumentOutOfRangeException(nameof(type))
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return sender.Response(ch)
 | 
			
		||||
                     .Embed(embed)
 | 
			
		||||
                     .Interaction(inter)
 | 
			
		||||
                     .SendAsync(ephemeral: ephemeral);
 | 
			
		||||
        await ch.RespondAsync(embeds: [embed.Build()], ephemeral: ephemeral);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    // embed title and optional footer overloads
 | 
			
		||||
 | 
			
		||||
    public static Task RespondConfirmAsync(
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user