Converted enough to send the system card
This commit is contained in:
parent
a6fbd869be
commit
05334f0d25
@ -17,12 +17,12 @@ namespace Myriad.Cache
|
||||
public ValueTask RemoveUser(ulong userId);
|
||||
public ValueTask RemoveRole(ulong guildId, ulong roleId);
|
||||
|
||||
public ValueTask<Guild?> GetGuild(ulong guildId);
|
||||
public ValueTask<Channel?> GetChannel(ulong channelId);
|
||||
public ValueTask<User?> GetUser(ulong userId);
|
||||
public ValueTask<Role?> GetRole(ulong roleId);
|
||||
public bool TryGetGuild(ulong guildId, out Guild guild);
|
||||
public bool TryGetChannel(ulong channelId, out Channel channel);
|
||||
public bool TryGetUser(ulong userId, out User user);
|
||||
public bool TryGetRole(ulong roleId, out Role role);
|
||||
|
||||
public IAsyncEnumerable<Guild> GetAllGuilds();
|
||||
public ValueTask<IEnumerable<Channel>> GetGuildChannels(ulong guildId);
|
||||
public IEnumerable<Channel> GetGuildChannels(ulong guildId);
|
||||
}
|
||||
}
|
@ -110,13 +110,26 @@ namespace Myriad.Cache
|
||||
return default;
|
||||
}
|
||||
|
||||
public ValueTask<Guild?> GetGuild(ulong guildId) => new(_guilds.GetValueOrDefault(guildId)?.Guild);
|
||||
public bool TryGetGuild(ulong guildId, out Guild guild)
|
||||
{
|
||||
if (_guilds.TryGetValue(guildId, out var cg))
|
||||
{
|
||||
guild = cg.Guild;
|
||||
return true;
|
||||
}
|
||||
|
||||
public ValueTask<Channel?> GetChannel(ulong channelId) => new(_channels.GetValueOrDefault(channelId));
|
||||
guild = null!;
|
||||
return false;
|
||||
}
|
||||
|
||||
public ValueTask<User?> GetUser(ulong userId) => new(_users.GetValueOrDefault(userId));
|
||||
public bool TryGetChannel(ulong channelId, out Channel channel) =>
|
||||
_channels.TryGetValue(channelId, out channel!);
|
||||
|
||||
public ValueTask<Role?> GetRole(ulong roleId) => new(_roles.GetValueOrDefault(roleId));
|
||||
public bool TryGetUser(ulong userId, out User user) =>
|
||||
_users.TryGetValue(userId, out user!);
|
||||
|
||||
public bool TryGetRole(ulong roleId, out Role role) =>
|
||||
_roles.TryGetValue(roleId, out role!);
|
||||
|
||||
public async IAsyncEnumerable<Guild> GetAllGuilds()
|
||||
{
|
||||
@ -124,12 +137,12 @@ namespace Myriad.Cache
|
||||
yield return guild.Guild;
|
||||
}
|
||||
|
||||
public ValueTask<IEnumerable<Channel>> GetGuildChannels(ulong guildId)
|
||||
public IEnumerable<Channel> GetGuildChannels(ulong guildId)
|
||||
{
|
||||
if (!_guilds.TryGetValue(guildId, out var guild))
|
||||
throw new ArgumentException("Guild not found", nameof(guildId));
|
||||
|
||||
return new ValueTask<IEnumerable<Channel>>(guild.Channels.Keys.Select(c => _channels[c]));
|
||||
return guild.Channels.Keys.Select(c => _channels[c]);
|
||||
}
|
||||
|
||||
private CachedGuild SaveGuildRaw(Guild guild) =>
|
||||
|
45
Myriad/Extensions/CacheExtensions.cs
Normal file
45
Myriad/Extensions/CacheExtensions.cs
Normal file
@ -0,0 +1,45 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
using Myriad.Cache;
|
||||
using Myriad.Types;
|
||||
|
||||
namespace Myriad.Extensions
|
||||
{
|
||||
public static class CacheExtensions
|
||||
{
|
||||
public static Guild GetGuild(this IDiscordCache cache, ulong guildId)
|
||||
{
|
||||
if (!cache.TryGetGuild(guildId, out var guild))
|
||||
throw new KeyNotFoundException($"Guild {guildId} not found in cache");
|
||||
return guild;
|
||||
}
|
||||
|
||||
public static Channel GetChannel(this IDiscordCache cache, ulong channelId)
|
||||
{
|
||||
if (!cache.TryGetChannel(channelId, out var channel))
|
||||
throw new KeyNotFoundException($"Channel {channelId} not found in cache");
|
||||
return channel;
|
||||
}
|
||||
|
||||
public static Channel? GetChannelOrNull(this IDiscordCache cache, ulong channelId)
|
||||
{
|
||||
if (cache.TryGetChannel(channelId, out var channel))
|
||||
return channel;
|
||||
return null;
|
||||
}
|
||||
|
||||
public static User GetUser(this IDiscordCache cache, ulong userId)
|
||||
{
|
||||
if (!cache.TryGetUser(userId, out var user))
|
||||
throw new KeyNotFoundException($"User {userId} not found in cache");
|
||||
return user;
|
||||
}
|
||||
|
||||
public static Role GetRole(this IDiscordCache cache, ulong roleId)
|
||||
{
|
||||
if (!cache.TryGetRole(roleId, out var role))
|
||||
throw new KeyNotFoundException($"User {roleId} not found in cache");
|
||||
return role;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
namespace Myriad.Extensions
|
||||
using Myriad.Types;
|
||||
|
||||
namespace Myriad.Extensions
|
||||
{
|
||||
public static class ChannelExtensions
|
||||
{
|
||||
|
||||
public static string Mention(this Channel channel) => $"<#{channel.Id}>";
|
||||
}
|
||||
}
|
7
Myriad/Extensions/GuildExtensions.cs
Normal file
7
Myriad/Extensions/GuildExtensions.cs
Normal file
@ -0,0 +1,7 @@
|
||||
namespace Myriad.Extensions
|
||||
{
|
||||
public static class GuildExtensions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
namespace Myriad.Extensions
|
||||
{
|
||||
public class MessageExtensions
|
||||
public static class MessageExtensions
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Myriad.Cache;
|
||||
using Myriad.Gateway;
|
||||
using Myriad.Types;
|
||||
|
||||
@ -9,17 +11,39 @@ namespace Myriad.Extensions
|
||||
{
|
||||
public static class PermissionExtensions
|
||||
{
|
||||
public static PermissionSet PermissionsFor(this IDiscordCache cache, MessageCreateEvent message) =>
|
||||
PermissionsFor(cache, message.ChannelId, message.Author.Id, message.Member?.Roles);
|
||||
|
||||
public static PermissionSet PermissionsFor(this IDiscordCache cache, ulong channelId, GuildMember member) =>
|
||||
PermissionsFor(cache, channelId, member.User.Id, member.Roles);
|
||||
|
||||
public static PermissionSet PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId, GuildMemberPartial member) =>
|
||||
PermissionsFor(cache, channelId, userId, member.Roles);
|
||||
|
||||
public static PermissionSet PermissionsFor(this IDiscordCache cache, ulong channelId, ulong userId, ICollection<ulong>? userRoles)
|
||||
{
|
||||
var channel = cache.GetChannel(channelId);
|
||||
if (channel.GuildId == null)
|
||||
return PermissionSet.Dm;
|
||||
|
||||
var guild = cache.GetGuild(channel.GuildId.Value);
|
||||
return PermissionsFor(guild, channel, userId, userRoles);
|
||||
}
|
||||
|
||||
public static PermissionSet EveryonePermissions(this Guild guild) =>
|
||||
guild.Roles.FirstOrDefault(r => r.Id == guild.Id)?.Permissions ?? PermissionSet.Dm;
|
||||
|
||||
public static PermissionSet PermissionsFor(Guild guild, Channel channel, MessageCreateEvent msg) =>
|
||||
PermissionsFor(guild, channel, msg.Author.Id, msg.Member!.Roles);
|
||||
PermissionsFor(guild, channel, msg.Author.Id, msg.Member?.Roles);
|
||||
|
||||
public static PermissionSet PermissionsFor(Guild guild, Channel channel, ulong userId,
|
||||
ICollection<ulong> roleIds)
|
||||
ICollection<ulong>? roleIds)
|
||||
{
|
||||
if (channel.Type == Channel.ChannelType.Dm)
|
||||
return PermissionSet.Dm;
|
||||
|
||||
if (roleIds == null)
|
||||
throw new ArgumentException($"User roles must be specified for guild channels");
|
||||
|
||||
var perms = GuildPermissions(guild, userId, roleIds);
|
||||
perms = ApplyChannelOverwrites(perms, channel, userId, roleIds);
|
||||
@ -36,9 +60,6 @@ namespace Myriad.Extensions
|
||||
return perms;
|
||||
}
|
||||
|
||||
public static bool Has(this PermissionSet value, PermissionSet flag) =>
|
||||
(value & flag) == flag;
|
||||
|
||||
public static PermissionSet GuildPermissions(this Guild guild, ulong userId, ICollection<ulong> roleIds)
|
||||
{
|
||||
if (guild.OwnerId == userId)
|
||||
@ -51,7 +72,7 @@ namespace Myriad.Extensions
|
||||
perms |= role.Permissions;
|
||||
}
|
||||
|
||||
if (perms.Has(PermissionSet.Administrator))
|
||||
if (perms.HasFlag(PermissionSet.Administrator))
|
||||
return PermissionSet.All;
|
||||
|
||||
return perms;
|
||||
|
@ -4,6 +4,8 @@ namespace Myriad.Extensions
|
||||
{
|
||||
public static class UserExtensions
|
||||
{
|
||||
public static string Mention(this User user) => $"<@{user.Id}>";
|
||||
|
||||
public static string AvatarUrl(this User user) =>
|
||||
$"https://cdn.discordapp.com/avatars/{user.Id}/{user.Avatar}.png";
|
||||
}
|
||||
|
@ -11,9 +11,9 @@ namespace Myriad.Rest.Types
|
||||
Everyone
|
||||
}
|
||||
|
||||
public List<ParseType>? Parse { get; set; }
|
||||
public List<ulong>? Users { get; set; }
|
||||
public List<ulong>? Roles { get; set; }
|
||||
public ParseType[]? Parse { get; set; }
|
||||
public ulong[]? Users { get; set; }
|
||||
public ulong[]? Roles { get; set; }
|
||||
public bool RepliedUser { get; set; }
|
||||
}
|
||||
}
|
@ -8,6 +8,6 @@ namespace Myriad.Rest.Types.Requests
|
||||
public object? Nonce { get; set; }
|
||||
public bool Tts { get; set; }
|
||||
public AllowedMentions AllowedMentions { get; set; }
|
||||
public Embed? Embeds { get; set; }
|
||||
public Embed? Embed { get; set; }
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@
|
||||
public string? Name { get; init; }
|
||||
public string? Topic { get; init; }
|
||||
public bool? Nsfw { get; init; }
|
||||
public long? ParentId { get; init; }
|
||||
public ulong? ParentId { get; init; }
|
||||
public Overwrite[]? PermissionOverwrites { get; init; }
|
||||
|
||||
public record Overwrite
|
||||
|
@ -8,14 +8,17 @@ using Autofac;
|
||||
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
using DSharpPlus.Net;
|
||||
|
||||
using Myriad.Cache;
|
||||
using Myriad.Extensions;
|
||||
using Myriad.Gateway;
|
||||
using Myriad.Rest.Types.Requests;
|
||||
using Myriad.Types;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
using Permissions = DSharpPlus.Permissions;
|
||||
using DiscordApiClient = Myriad.Rest.DiscordApiClient;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
{
|
||||
@ -24,6 +27,7 @@ namespace PluralKit.Bot
|
||||
private readonly ILifetimeScope _provider;
|
||||
|
||||
private readonly DiscordRestClient _rest;
|
||||
private readonly DiscordApiClient _newRest;
|
||||
private readonly DiscordShardedClient _client;
|
||||
private readonly DiscordClient _shard = null;
|
||||
private readonly Shard _shardNew;
|
||||
@ -42,6 +46,7 @@ namespace PluralKit.Bot
|
||||
private readonly PKSystem _senderSystem;
|
||||
private readonly IMetrics _metrics;
|
||||
private readonly CommandMessageService _commandMessageService;
|
||||
private readonly IDiscordCache _cache;
|
||||
|
||||
private Command _currentCommand;
|
||||
|
||||
@ -57,24 +62,25 @@ namespace PluralKit.Bot
|
||||
_senderSystem = senderSystem;
|
||||
_messageContext = messageContext;
|
||||
_botMember = botMember;
|
||||
_cache = provider.Resolve<IDiscordCache>();
|
||||
_db = provider.Resolve<IDatabase>();
|
||||
_repo = provider.Resolve<ModelRepository>();
|
||||
_metrics = provider.Resolve<IMetrics>();
|
||||
_provider = provider;
|
||||
_commandMessageService = provider.Resolve<CommandMessageService>();
|
||||
_parameters = new Parameters(message.Content.Substring(commandParseOffset));
|
||||
_newRest = provider.Resolve<DiscordApiClient>();
|
||||
|
||||
_botPermissions = message.GuildId != null
|
||||
? PermissionExtensions.PermissionsFor(guild!, channel, shard.User?.Id ?? default, botMember!.Roles)
|
||||
: PermissionSet.Dm;
|
||||
_userPermissions = message.GuildId != null
|
||||
? PermissionExtensions.PermissionsFor(guild!, channel, message.Author.Id, message.Member!.Roles)
|
||||
: PermissionSet.Dm;
|
||||
_botPermissions = _cache.PermissionsFor(message.ChannelId, shard.User!.Id, botMember!);
|
||||
_userPermissions = _cache.PermissionsFor(message);
|
||||
}
|
||||
|
||||
public IDiscordCache Cache => _cache;
|
||||
|
||||
public DiscordUser Author => _message.Author;
|
||||
public DiscordChannel Channel => _message.Channel;
|
||||
public Channel ChannelNew => _channel;
|
||||
public User AuthorNew => _messageNew.Author;
|
||||
public DiscordMessage Message => _message;
|
||||
public Message MessageNew => _messageNew;
|
||||
public DiscordGuild Guild => _message.Channel.Guild;
|
||||
@ -95,24 +101,44 @@ namespace PluralKit.Bot
|
||||
internal IDatabase Database => _db;
|
||||
internal ModelRepository Repository => _repo;
|
||||
|
||||
public async Task<DiscordMessage> Reply(string text = null, DiscordEmbed embed = null, IEnumerable<IMention> mentions = null)
|
||||
public Task<DiscordMessage> Reply(string text, DiscordEmbed embed,
|
||||
IEnumerable<IMention>? mentions = null)
|
||||
{
|
||||
if (!this.BotHasAllPermissions(Permissions.SendMessages))
|
||||
return Reply(text, (DiscordEmbed) null, mentions);
|
||||
}
|
||||
|
||||
public Task<DiscordMessage> Reply(DiscordEmbed embed,
|
||||
IEnumerable<IMention>? mentions = null)
|
||||
{
|
||||
return Reply(null, (DiscordEmbed) null, mentions);
|
||||
}
|
||||
|
||||
public async Task<DiscordMessage> Reply(string text = null, Embed embed = null, IEnumerable<IMention>? mentions = null)
|
||||
{
|
||||
if (!BotPermissions.HasFlag(PermissionSet.SendMessages))
|
||||
// Will be "swallowed" during the error handler anyway, this message is never shown.
|
||||
throw new PKError("PluralKit does not have permission to send messages in this channel.");
|
||||
|
||||
if (embed != null && !this.BotHasAllPermissions(Permissions.EmbedLinks))
|
||||
if (embed != null && !BotPermissions.HasFlag(PermissionSet.EmbedLinks))
|
||||
throw new PKError("PluralKit does not have permission to send embeds in this channel. Please ensure I have the **Embed Links** permission enabled.");
|
||||
var msg = await Channel.SendMessageFixedAsync(text, embed: embed, mentions: mentions);
|
||||
|
||||
var msg = await _newRest.CreateMessage(_channel.Id, new MessageRequest
|
||||
{
|
||||
Content = text,
|
||||
Embed = embed
|
||||
});
|
||||
// TODO: embeds/mentions
|
||||
// var msg = await Channel.SendMessageFixedAsync(text, embed: embed, mentions: mentions);
|
||||
|
||||
if (embed != null)
|
||||
{
|
||||
// Sensitive information that might want to be deleted by :x: reaction is typically in an embed format (member cards, for example)
|
||||
// This may need to be changed at some point but works well enough for now
|
||||
await _commandMessageService.RegisterMessage(msg.Id, Author.Id);
|
||||
await _commandMessageService.RegisterMessage(msg.Id, AuthorNew.Id);
|
||||
}
|
||||
|
||||
return msg;
|
||||
// return msg;
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task Execute<T>(Command commandDef, Func<T, Task> handler)
|
||||
|
@ -1,5 +1,7 @@
|
||||
using DSharpPlus;
|
||||
|
||||
using Myriad.Types;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
@ -8,7 +10,7 @@ namespace PluralKit.Bot
|
||||
{
|
||||
public static Context CheckGuildContext(this Context ctx)
|
||||
{
|
||||
if (ctx.Channel.Guild != null) return ctx;
|
||||
if (ctx.ChannelNew.GuildId != null) return ctx;
|
||||
throw new PKError("This command can not be run in a DM.");
|
||||
}
|
||||
|
||||
@ -46,12 +48,9 @@ namespace PluralKit.Bot
|
||||
return ctx;
|
||||
}
|
||||
|
||||
public static Context CheckAuthorPermission(this Context ctx, Permissions neededPerms, string permissionName)
|
||||
public static Context CheckAuthorPermission(this Context ctx, PermissionSet neededPerms, string permissionName)
|
||||
{
|
||||
// TODO: can we always assume Author is a DiscordMember? I would think so, given they always come from a
|
||||
// message received event...
|
||||
var hasPerms = ctx.Channel.PermissionsInSync(ctx.Author);
|
||||
if ((hasPerms & neededPerms) != neededPerms)
|
||||
if ((ctx.UserPermissions & neededPerms) != neededPerms)
|
||||
throw new PKError($"You must have the \"{permissionName}\" permission in this server to use this command.");
|
||||
return ctx;
|
||||
}
|
||||
|
@ -3,6 +3,8 @@
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
using Myriad.Types;
|
||||
|
||||
using PluralKit.Bot.Utils;
|
||||
using PluralKit.Core;
|
||||
|
||||
@ -153,13 +155,16 @@ namespace PluralKit.Bot
|
||||
return $"Group not found. Note that a group ID is 5 characters long.";
|
||||
}
|
||||
|
||||
public static async Task<DiscordChannel> MatchChannel(this Context ctx)
|
||||
public static async Task<Channel> MatchChannel(this Context ctx)
|
||||
{
|
||||
if (!MentionUtils.TryParseChannel(ctx.PeekArgument(), out var id))
|
||||
return null;
|
||||
|
||||
if (!ctx.Cache.TryGetChannel(id, out var channel))
|
||||
return null;
|
||||
|
||||
var channel = await ctx.Shard.GetChannel(id);
|
||||
if (channel == null || !(channel.Type == ChannelType.Text || channel.Type == ChannelType.News)) return null;
|
||||
if (!(channel.Type == Channel.ChannelType.GuildText || channel.Type == Channel.ChannelType.GuildText))
|
||||
return null;
|
||||
|
||||
ctx.PopArgument();
|
||||
return channel;
|
||||
|
@ -2,7 +2,6 @@ using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Exceptions;
|
||||
|
||||
using Humanizer;
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace PluralKit.Bot
|
||||
if (location == AvatarLocation.Server)
|
||||
{
|
||||
if (target.AvatarUrl != null)
|
||||
await ctx.Reply($"{Emojis.Success} Member server avatar cleared. This member will now use the global avatar in this server (**{ctx.Guild.Name}**).");
|
||||
await ctx.Reply($"{Emojis.Success} Member server avatar cleared. This member will now use the global avatar in this server (**{ctx.GuildNew.Name}**).");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Member server avatar cleared. This member now has no avatar.");
|
||||
}
|
||||
@ -55,7 +55,7 @@ namespace PluralKit.Bot
|
||||
throw new PKError($"This member does not have a server avatar set. Type `pk;member {target.Reference()} avatar` to see their global avatar.");
|
||||
}
|
||||
|
||||
var field = location == AvatarLocation.Server ? $"server avatar (for {ctx.Guild.Name})" : "avatar";
|
||||
var field = location == AvatarLocation.Server ? $"server avatar (for {ctx.GuildNew.Name})" : "avatar";
|
||||
var cmd = location == AvatarLocation.Server ? "serveravatar" : "avatar";
|
||||
|
||||
var eb = new DiscordEmbedBuilder()
|
||||
@ -69,14 +69,14 @@ namespace PluralKit.Bot
|
||||
public async Task ServerAvatar(Context ctx, PKMember target)
|
||||
{
|
||||
ctx.CheckGuildContext();
|
||||
var guildData = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
|
||||
var guildData = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.GuildNew.Id, target.Id));
|
||||
await AvatarCommandTree(AvatarLocation.Server, ctx, target, guildData);
|
||||
}
|
||||
|
||||
public async Task Avatar(Context ctx, PKMember target)
|
||||
{
|
||||
var guildData = ctx.Guild != null ?
|
||||
await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id))
|
||||
var guildData = ctx.GuildNew != null ?
|
||||
await _db.Execute(c => _repo.GetMemberGuild(c, ctx.GuildNew.Id, target.Id))
|
||||
: null;
|
||||
|
||||
await AvatarCommandTree(AvatarLocation.Member, ctx, target, guildData);
|
||||
@ -119,8 +119,8 @@ namespace PluralKit.Bot
|
||||
|
||||
var serverFrag = location switch
|
||||
{
|
||||
AvatarLocation.Server => $" This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).",
|
||||
AvatarLocation.Member when targetGuildData?.AvatarUrl != null => $"\n{Emojis.Note} Note that this member *also* has a server-specific avatar set in this server (**{ctx.Guild.Name}**), and thus changing the global avatar will have no effect here.",
|
||||
AvatarLocation.Server => $" This avatar will now be used when proxying in this server (**{ctx.GuildNew.Name}**).",
|
||||
AvatarLocation.Member when targetGuildData?.AvatarUrl != null => $"\n{Emojis.Note} Note that this member *also* has a server-specific avatar set in this server (**{ctx.GuildNew.Name}**), and thus changing the global avatar will have no effect here.",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
@ -145,7 +145,7 @@ namespace PluralKit.Bot
|
||||
{
|
||||
case AvatarLocation.Server:
|
||||
var serverPatch = new MemberGuildPatch { AvatarUrl = url };
|
||||
return _db.Execute(c => _repo.UpsertMemberGuild(c, target.Id, ctx.Guild.Id, serverPatch));
|
||||
return _db.Execute(c => _repo.UpsertMemberGuild(c, target.Id, ctx.GuildNew.Id, serverPatch));
|
||||
case AvatarLocation.Member:
|
||||
var memberPatch = new MemberPatch { AvatarUrl = url };
|
||||
return _db.Execute(c => _repo.UpdateMember(c, target.Id, memberPatch));
|
||||
|
@ -2,9 +2,6 @@ using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using System;
|
||||
|
||||
|
||||
using Dapper;
|
||||
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
using NodaTime;
|
||||
@ -49,11 +46,11 @@ namespace PluralKit.Bot
|
||||
if (newName.Contains(" ")) await ctx.Reply($"{Emojis.Note} Note that this member's name now contains spaces. You will need to surround it with \"double quotes\" when using commands referring to it.");
|
||||
if (target.DisplayName != null) await ctx.Reply($"{Emojis.Note} Note that this member has a display name set ({target.DisplayName}), and will be proxied using that name instead.");
|
||||
|
||||
if (ctx.Guild != null)
|
||||
if (ctx.GuildNew != null)
|
||||
{
|
||||
var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
|
||||
var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.GuildNew.Id, target.Id));
|
||||
if (memberGuildConfig.DisplayName != null)
|
||||
await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName}) in this server ({ctx.Guild.Name}), and will be proxied using that name here.");
|
||||
await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName}) in this server ({ctx.GuildNew.Name}), and will be proxied using that name here.");
|
||||
}
|
||||
}
|
||||
|
||||
@ -229,8 +226,8 @@ namespace PluralKit.Bot
|
||||
var lcx = ctx.LookupContextFor(target);
|
||||
|
||||
MemberGuildSettings memberGuildConfig = null;
|
||||
if (ctx.Guild != null)
|
||||
memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
|
||||
if (ctx.GuildNew != null)
|
||||
memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.GuildNew.Id, target.Id));
|
||||
|
||||
var eb = new DiscordEmbedBuilder().WithTitle($"Member names")
|
||||
.WithFooter($"Member ID: {target.Hid} | Active name in bold. Server name overrides display name, which overrides base name.");
|
||||
@ -248,12 +245,12 @@ namespace PluralKit.Bot
|
||||
eb.AddField("Display Name", target.DisplayName ?? "*(none)*");
|
||||
}
|
||||
|
||||
if (ctx.Guild != null)
|
||||
if (ctx.GuildNew != null)
|
||||
{
|
||||
if (memberGuildConfig?.DisplayName != null)
|
||||
eb.AddField($"Server Name (in {ctx.Guild.Name})", $"**{memberGuildConfig.DisplayName}**");
|
||||
eb.AddField($"Server Name (in {ctx.GuildNew.Name})", $"**{memberGuildConfig.DisplayName}**");
|
||||
else
|
||||
eb.AddField($"Server Name (in {ctx.Guild.Name})", memberGuildConfig?.DisplayName ?? "*(none)*");
|
||||
eb.AddField($"Server Name (in {ctx.GuildNew.Name})", memberGuildConfig?.DisplayName ?? "*(none)*");
|
||||
}
|
||||
|
||||
return eb;
|
||||
@ -264,11 +261,11 @@ namespace PluralKit.Bot
|
||||
async Task PrintSuccess(string text)
|
||||
{
|
||||
var successStr = text;
|
||||
if (ctx.Guild != null)
|
||||
if (ctx.GuildNew != null)
|
||||
{
|
||||
var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
|
||||
var memberGuildConfig = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.GuildNew.Id, target.Id));
|
||||
if (memberGuildConfig.DisplayName != null)
|
||||
successStr += $" However, this member has a server name set in this server ({ctx.Guild.Name}), and will be proxied using that name, \"{memberGuildConfig.DisplayName}\", here.";
|
||||
successStr += $" However, this member has a server name set in this server ({ctx.GuildNew.Name}), and will be proxied using that name, \"{memberGuildConfig.DisplayName}\", here.";
|
||||
}
|
||||
|
||||
await ctx.Reply(successStr);
|
||||
@ -313,12 +310,12 @@ namespace PluralKit.Bot
|
||||
ctx.CheckOwnMember(target);
|
||||
|
||||
var patch = new MemberGuildPatch {DisplayName = null};
|
||||
await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch));
|
||||
await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.GuildNew.Id, patch));
|
||||
|
||||
if (target.DisplayName != null)
|
||||
await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName}\" in this server ({ctx.Guild.Name}).");
|
||||
await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their global display name \"{target.DisplayName}\" in this server ({ctx.GuildNew.Name}).");
|
||||
else
|
||||
await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\" in this server ({ctx.Guild.Name}).");
|
||||
await ctx.Reply($"{Emojis.Success} Member server name cleared. This member will now be proxied using their member name \"{target.NameFor(ctx)}\" in this server ({ctx.GuildNew.Name}).");
|
||||
}
|
||||
else if (!ctx.HasNext())
|
||||
{
|
||||
@ -335,9 +332,9 @@ namespace PluralKit.Bot
|
||||
var newServerName = ctx.RemainderOrNull();
|
||||
|
||||
var patch = new MemberGuildPatch {DisplayName = newServerName};
|
||||
await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.Guild.Id, patch));
|
||||
await _db.Execute(conn => _repo.UpsertMemberGuild(conn, target.Id, ctx.GuildNew.Id, patch));
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Member server name changed. This member will now be proxied using the name \"{newServerName}\" in this server ({ctx.Guild.Name}).");
|
||||
await ctx.Reply($"{Emojis.Success} Member server name changed. This member will now be proxied using the name \"{newServerName}\" in this server ({ctx.GuildNew.Name}).");
|
||||
}
|
||||
}
|
||||
|
||||
@ -417,8 +414,8 @@ namespace PluralKit.Bot
|
||||
|
||||
// Get guild settings (mostly for warnings and such)
|
||||
MemberGuildSettings guildSettings = null;
|
||||
if (ctx.Guild != null)
|
||||
guildSettings = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.Guild.Id, target.Id));
|
||||
if (ctx.GuildNew != null)
|
||||
guildSettings = await _db.Execute(c => _repo.GetMemberGuild(c, ctx.GuildNew.Id, target.Id));
|
||||
|
||||
async Task SetAll(PrivacyLevel level)
|
||||
{
|
||||
|
@ -6,29 +6,37 @@ using System.Threading.Tasks;
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
using Myriad.Cache;
|
||||
using Myriad.Extensions;
|
||||
using Myriad.Types;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
using Permissions = DSharpPlus.Permissions;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
{
|
||||
public class ServerConfig
|
||||
{
|
||||
private readonly IDatabase _db;
|
||||
private readonly ModelRepository _repo;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly LoggerCleanService _cleanService;
|
||||
public ServerConfig(LoggerCleanService cleanService, IDatabase db, ModelRepository repo)
|
||||
public ServerConfig(LoggerCleanService cleanService, IDatabase db, ModelRepository repo, IDiscordCache cache)
|
||||
{
|
||||
_cleanService = cleanService;
|
||||
_db = db;
|
||||
_repo = repo;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task SetLogChannel(Context ctx)
|
||||
{
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
if (await ctx.MatchClear("the server log channel"))
|
||||
{
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, new GuildPatch {LogChannel = null}));
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.GuildNew.Id, new GuildPatch {LogChannel = null}));
|
||||
await ctx.Reply($"{Emojis.Success} Proxy logging channel cleared.");
|
||||
return;
|
||||
}
|
||||
@ -36,36 +44,36 @@ namespace PluralKit.Bot
|
||||
if (!ctx.HasNext())
|
||||
throw new PKSyntaxError("You must pass a #channel to set, or `clear` to clear it.");
|
||||
|
||||
DiscordChannel channel = null;
|
||||
Channel channel = null;
|
||||
var channelString = ctx.PeekArgument();
|
||||
channel = await ctx.MatchChannel();
|
||||
if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString);
|
||||
if (channel == null || channel.GuildId != ctx.GuildNew.Id) throw Errors.ChannelNotFound(channelString);
|
||||
|
||||
var patch = new GuildPatch {LogChannel = channel.Id};
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, patch));
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.GuildNew.Id, patch));
|
||||
await ctx.Reply($"{Emojis.Success} Proxy logging channel set to #{channel.Name}.");
|
||||
}
|
||||
|
||||
public async Task SetLogEnabled(Context ctx, bool enable)
|
||||
{
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var affectedChannels = new List<DiscordChannel>();
|
||||
var affectedChannels = new List<Channel>();
|
||||
if (ctx.Match("all"))
|
||||
affectedChannels = (await ctx.Guild.GetChannelsAsync()).Where(x => x.Type == ChannelType.Text).ToList();
|
||||
affectedChannels = _cache.GetGuildChannels(ctx.GuildNew.Id).Where(x => x.Type == Channel.ChannelType.GuildText).ToList();
|
||||
else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels.");
|
||||
else while (ctx.HasNext())
|
||||
{
|
||||
var channelString = ctx.PeekArgument();
|
||||
var channel = await ctx.MatchChannel();
|
||||
if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString);
|
||||
if (channel == null || channel.GuildId != ctx.GuildNew.Id) throw Errors.ChannelNotFound(channelString);
|
||||
affectedChannels.Add(channel);
|
||||
}
|
||||
|
||||
ulong? logChannel = null;
|
||||
await using (var conn = await _db.Obtain())
|
||||
{
|
||||
var config = await _repo.GetGuild(conn, ctx.Guild.Id);
|
||||
var config = await _repo.GetGuild(conn, ctx.GuildNew.Id);
|
||||
logChannel = config.LogChannel;
|
||||
var blacklist = config.LogBlacklist.ToHashSet();
|
||||
if (enable)
|
||||
@ -74,7 +82,7 @@ namespace PluralKit.Bot
|
||||
blacklist.UnionWith(affectedChannels.Select(c => c.Id));
|
||||
|
||||
var patch = new GuildPatch {LogBlacklist = blacklist.ToArray()};
|
||||
await _repo.UpsertGuild(conn, ctx.Guild.Id, patch);
|
||||
await _repo.UpsertGuild(conn, ctx.GuildNew.Id, patch);
|
||||
}
|
||||
|
||||
await ctx.Reply(
|
||||
@ -84,13 +92,13 @@ namespace PluralKit.Bot
|
||||
|
||||
public async Task ShowBlacklisted(Context ctx)
|
||||
{
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var blacklist = await _db.Execute(c => _repo.GetGuild(c, ctx.Guild.Id));
|
||||
var blacklist = await _db.Execute(c => _repo.GetGuild(c, ctx.GuildNew.Id));
|
||||
|
||||
// Resolve all channels from the cache and order by position
|
||||
var channels = blacklist.Blacklist
|
||||
.Select(id => ctx.Guild.GetChannel(id))
|
||||
.Select(id => _cache.GetChannelOrNull(id))
|
||||
.Where(c => c != null)
|
||||
.OrderBy(c => c.Position)
|
||||
.ToList();
|
||||
@ -102,26 +110,29 @@ namespace PluralKit.Bot
|
||||
}
|
||||
|
||||
await ctx.Paginate(channels.ToAsyncEnumerable(), channels.Count, 25,
|
||||
$"Blacklisted channels for {ctx.Guild.Name}",
|
||||
$"Blacklisted channels for {ctx.GuildNew.Name}",
|
||||
(eb, l) =>
|
||||
{
|
||||
DiscordChannel lastCategory = null;
|
||||
string CategoryName(ulong? id) =>
|
||||
id != null ? _cache.GetChannel(id.Value).Name : "(no category)";
|
||||
|
||||
ulong? lastCategory = null;
|
||||
|
||||
var fieldValue = new StringBuilder();
|
||||
foreach (var channel in l)
|
||||
{
|
||||
if (lastCategory != channel.Parent && fieldValue.Length > 0)
|
||||
if (lastCategory != channel!.ParentId && fieldValue.Length > 0)
|
||||
{
|
||||
eb.AddField(lastCategory?.Name ?? "(no category)", fieldValue.ToString());
|
||||
eb.AddField(CategoryName(lastCategory), fieldValue.ToString());
|
||||
fieldValue.Clear();
|
||||
}
|
||||
else fieldValue.Append("\n");
|
||||
|
||||
fieldValue.Append(channel.Mention);
|
||||
lastCategory = channel.Parent;
|
||||
fieldValue.Append(channel.Mention());
|
||||
lastCategory = channel.ParentId;
|
||||
}
|
||||
|
||||
eb.AddField(lastCategory?.Name ?? "(no category)", fieldValue.ToString());
|
||||
eb.AddField(CategoryName(lastCategory), fieldValue.ToString());
|
||||
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
@ -129,23 +140,23 @@ namespace PluralKit.Bot
|
||||
|
||||
public async Task SetBlacklisted(Context ctx, bool shouldAdd)
|
||||
{
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var affectedChannels = new List<DiscordChannel>();
|
||||
var affectedChannels = new List<Channel>();
|
||||
if (ctx.Match("all"))
|
||||
affectedChannels = (await ctx.Guild.GetChannelsAsync()).Where(x => x.Type == ChannelType.Text).ToList();
|
||||
affectedChannels = _cache.GetGuildChannels(ctx.GuildNew.Id).Where(x => x.Type == Channel.ChannelType.GuildText).ToList();
|
||||
else if (!ctx.HasNext()) throw new PKSyntaxError("You must pass one or more #channels.");
|
||||
else while (ctx.HasNext())
|
||||
{
|
||||
var channelString = ctx.PeekArgument();
|
||||
var channel = await ctx.MatchChannel();
|
||||
if (channel == null || channel.GuildId != ctx.Guild.Id) throw Errors.ChannelNotFound(channelString);
|
||||
if (channel == null || channel.GuildId != ctx.GuildNew.Id) throw Errors.ChannelNotFound(channelString);
|
||||
affectedChannels.Add(channel);
|
||||
}
|
||||
|
||||
await using (var conn = await _db.Obtain())
|
||||
{
|
||||
var guild = await _repo.GetGuild(conn, ctx.Guild.Id);
|
||||
var guild = await _repo.GetGuild(conn, ctx.GuildNew.Id);
|
||||
var blacklist = guild.Blacklist.ToHashSet();
|
||||
if (shouldAdd)
|
||||
blacklist.UnionWith(affectedChannels.Select(c => c.Id));
|
||||
@ -153,7 +164,7 @@ namespace PluralKit.Bot
|
||||
blacklist.ExceptWith(affectedChannels.Select(c => c.Id));
|
||||
|
||||
var patch = new GuildPatch {Blacklist = blacklist.ToArray()};
|
||||
await _repo.UpsertGuild(conn, ctx.Guild.Id, patch);
|
||||
await _repo.UpsertGuild(conn, ctx.GuildNew.Id, patch);
|
||||
}
|
||||
|
||||
await ctx.Reply($"{Emojis.Success} Channels {(shouldAdd ? "added to" : "removed from")} the proxy blacklist.");
|
||||
@ -161,7 +172,7 @@ namespace PluralKit.Bot
|
||||
|
||||
public async Task SetLogCleanup(Context ctx)
|
||||
{
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(Permissions.ManageGuild, "Manage Server");
|
||||
ctx.CheckGuildContext().CheckAuthorPermission(PermissionSet.ManageGuild, "Manage Server");
|
||||
|
||||
var botList = string.Join(", ", _cleanService.Bots.Select(b => b.Name).OrderBy(x => x.ToLowerInvariant()));
|
||||
|
||||
@ -176,7 +187,7 @@ namespace PluralKit.Bot
|
||||
.WithTitle("Log cleanup settings")
|
||||
.AddField("Supported bots", botList);
|
||||
|
||||
var guildCfg = await _db.Execute(c => _repo.GetGuild(c, ctx.Guild.Id));
|
||||
var guildCfg = await _db.Execute(c => _repo.GetGuild(c, ctx.GuildNew.Id));
|
||||
if (guildCfg.LogCleanupEnabled)
|
||||
eb.WithDescription("Log cleanup is currently **on** for this server. To disable it, type `pk;logclean off`.");
|
||||
else
|
||||
@ -186,7 +197,7 @@ namespace PluralKit.Bot
|
||||
}
|
||||
|
||||
var patch = new GuildPatch {LogCleanupEnabled = newValue};
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.Guild.Id, patch));
|
||||
await _db.Execute(conn => _repo.UpsertGuild(conn, ctx.GuildNew.Id, patch));
|
||||
|
||||
if (newValue)
|
||||
await ctx.Reply($"{Emojis.Success} Log cleanup has been **enabled** for this server. Messages deleted by PluralKit will now be cleaned up from logging channels managed by the following bots:\n- **{botList}**\n\n{Emojis.Note} Make sure PluralKit has the **Manage Messages** permission in the channels in question.\n{Emojis.Note} Also, make sure to blacklist the logging channel itself from the bots in question to prevent conflicts.");
|
||||
|
@ -2,8 +2,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
using NodaTime;
|
||||
using NodaTime.TimeZones;
|
||||
|
||||
|
@ -2,9 +2,6 @@ using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Dapper;
|
||||
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
|
||||
using NodaTime;
|
||||
@ -13,8 +10,6 @@ using NodaTime.TimeZones;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
using Sentry.Protocol;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
{
|
||||
public class SystemEdit
|
||||
@ -196,7 +191,7 @@ namespace PluralKit.Bot
|
||||
public async Task SystemProxy(Context ctx)
|
||||
{
|
||||
ctx.CheckSystem().CheckGuildContext();
|
||||
var gs = await _db.Execute(c => _repo.GetSystemGuild(c, ctx.Guild.Id, ctx.System.Id));
|
||||
var gs = await _db.Execute(c => _repo.GetSystemGuild(c, ctx.GuildNew.Id, ctx.System.Id));
|
||||
|
||||
bool newValue;
|
||||
if (ctx.Match("on", "enabled", "true", "yes")) newValue = true;
|
||||
@ -212,12 +207,12 @@ namespace PluralKit.Bot
|
||||
}
|
||||
|
||||
var patch = new SystemGuildPatch {ProxyEnabled = newValue};
|
||||
await _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, ctx.Guild.Id, patch));
|
||||
await _db.Execute(conn => _repo.UpsertSystemGuild(conn, ctx.System.Id, ctx.GuildNew.Id, patch));
|
||||
|
||||
if (newValue)
|
||||
await ctx.Reply($"Message proxying in this server ({ctx.Guild.Name.EscapeMarkdown()}) is now **enabled** for your system.");
|
||||
await ctx.Reply($"Message proxying in this server ({ctx.GuildNew.Name.EscapeMarkdown()}) is now **enabled** for your system.");
|
||||
else
|
||||
await ctx.Reply($"Message proxying in this server ({ctx.Guild.Name.EscapeMarkdown()}) is now **disabled** for your system.");
|
||||
await ctx.Reply($"Message proxying in this server ({ctx.GuildNew.Name.EscapeMarkdown()}) is now **disabled** for your system.");
|
||||
}
|
||||
|
||||
public async Task SystemTimezone(Context ctx)
|
||||
|
@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
|
@ -1,8 +1,6 @@
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
namespace PluralKit.Bot
|
||||
|
@ -61,8 +61,8 @@ namespace PluralKit.Bot
|
||||
if (evt.Type != Message.MessageType.Default) return;
|
||||
if (IsDuplicateMessage(evt)) return;
|
||||
|
||||
var guild = evt.GuildId != null ? await _cache.GetGuild(evt.GuildId.Value) : null;
|
||||
var channel = await _cache.GetChannel(evt.ChannelId);
|
||||
var guild = evt.GuildId != null ? _cache.GetGuild(evt.GuildId.Value) : null;
|
||||
var channel = _cache.GetChannel(evt.ChannelId);
|
||||
|
||||
// Log metrics and message info
|
||||
_metrics.Measure.Meter.Mark(BotMetrics.MessagesReceived);
|
||||
@ -89,8 +89,8 @@ namespace PluralKit.Bot
|
||||
|
||||
private async ValueTask<bool> TryHandleLogClean(MessageCreateEvent evt, MessageContext ctx)
|
||||
{
|
||||
var channel = await _cache.GetChannel(evt.ChannelId);
|
||||
if (!evt.Author.Bot || channel!.Type != Channel.ChannelType.GuildText ||
|
||||
var channel = _cache.GetChannel(evt.ChannelId);
|
||||
if (!evt.Author.Bot || channel.Type != Channel.ChannelType.GuildText ||
|
||||
!ctx.LogCleanupEnabled) return false;
|
||||
|
||||
await _loggerClean.HandleLoggerBotCleanup(evt);
|
||||
|
@ -5,9 +5,13 @@ using System.Threading.Tasks;
|
||||
|
||||
using DSharpPlus;
|
||||
using DSharpPlus.Entities;
|
||||
using DSharpPlus.Exceptions;
|
||||
|
||||
using Humanizer;
|
||||
|
||||
using Myriad.Cache;
|
||||
using Myriad.Rest;
|
||||
using Myriad.Types;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Core;
|
||||
@ -18,54 +22,79 @@ namespace PluralKit.Bot {
|
||||
private readonly IDatabase _db;
|
||||
private readonly ModelRepository _repo;
|
||||
private readonly DiscordShardedClient _client;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly DiscordApiClient _rest;
|
||||
|
||||
public EmbedService(DiscordShardedClient client, IDatabase db, ModelRepository repo)
|
||||
public EmbedService(DiscordShardedClient client, IDatabase db, ModelRepository repo, IDiscordCache cache, DiscordApiClient rest)
|
||||
{
|
||||
_client = client;
|
||||
_db = db;
|
||||
_repo = repo;
|
||||
_cache = cache;
|
||||
_rest = rest;
|
||||
}
|
||||
|
||||
private Task<(ulong Id, User? User)[]> GetUsers(IEnumerable<ulong> ids)
|
||||
{
|
||||
async Task<(ulong Id, User? User)> Inner(ulong id)
|
||||
{
|
||||
if (_cache.TryGetUser(id, out var cachedUser))
|
||||
return (id, cachedUser);
|
||||
|
||||
var user = await _rest.GetUser(id);
|
||||
if (user == null)
|
||||
return (id, null);
|
||||
// todo: move to "GetUserCached" helper
|
||||
await _cache.SaveUser(user);
|
||||
return (id, user);
|
||||
}
|
||||
|
||||
return Task.WhenAll(ids.Select(Inner));
|
||||
}
|
||||
|
||||
public async Task<DiscordEmbed> CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx)
|
||||
public async Task<Embed> CreateSystemEmbed(Context cctx, PKSystem system, LookupContext ctx)
|
||||
{
|
||||
await using var conn = await _db.Obtain();
|
||||
|
||||
// Fetch/render info for all accounts simultaneously
|
||||
var accounts = await _repo.GetSystemAccounts(conn, system.Id);
|
||||
var users = await Task.WhenAll(accounts.Select(async uid => (await cctx.Shard.GetUser(uid))?.NameAndMention() ?? $"(deleted account {uid})"));
|
||||
var users = (await GetUsers(accounts)).Select(x => x.User?.NameAndMention() ?? $"(deleted account {x.Id})");
|
||||
|
||||
var memberCount = cctx.MatchPrivateFlag(ctx) ? await _repo.GetSystemMemberCount(conn, system.Id, PrivacyLevel.Public) : await _repo.GetSystemMemberCount(conn, system.Id);
|
||||
|
||||
var eb = new DiscordEmbedBuilder()
|
||||
.WithColor(DiscordUtils.Gray)
|
||||
.WithTitle(system.Name ?? null)
|
||||
.WithThumbnail(system.AvatarUrl)
|
||||
.WithFooter($"System ID: {system.Hid} | Created on {system.Created.FormatZoned(system)}");
|
||||
var embed = new Embed
|
||||
{
|
||||
Title = system.Name,
|
||||
Thumbnail = new(system.AvatarUrl),
|
||||
Footer = new($"System ID: {system.Hid} | Created on {system.Created.FormatZoned(system)}"),
|
||||
Color = (uint?) DiscordUtils.Gray.Value
|
||||
};
|
||||
var fields = new List<Embed.Field>();
|
||||
|
||||
var latestSwitch = await _repo.GetLatestSwitch(conn, system.Id);
|
||||
if (latestSwitch != null && system.FrontPrivacy.CanAccess(ctx))
|
||||
{
|
||||
var switchMembers = await _repo.GetSwitchMembers(conn, latestSwitch.Id).ToListAsync();
|
||||
if (switchMembers.Count > 0)
|
||||
eb.AddField("Fronter".ToQuantity(switchMembers.Count(), ShowQuantityAs.None),
|
||||
string.Join(", ", switchMembers.Select(m => m.NameFor(ctx))));
|
||||
if (switchMembers.Count > 0)
|
||||
fields.Add(new("Fronter".ToQuantity(switchMembers.Count, ShowQuantityAs.None), string.Join(", ", switchMembers.Select(m => m.NameFor(ctx)))));
|
||||
}
|
||||
|
||||
if (system.Tag != null) eb.AddField("Tag", system.Tag.EscapeMarkdown());
|
||||
eb.AddField("Linked accounts", string.Join("\n", users).Truncate(1000), true);
|
||||
if (system.Tag != null)
|
||||
fields.Add(new("Tag", system.Tag.EscapeMarkdown()));
|
||||
fields.Add(new("Linked accounts", string.Join("\n", users).Truncate(1000), true));
|
||||
|
||||
if (system.MemberListPrivacy.CanAccess(ctx))
|
||||
{
|
||||
if (memberCount > 0)
|
||||
eb.AddField($"Members ({memberCount})", $"(see `pk;system {system.Hid} list` or `pk;system {system.Hid} list full`)", true);
|
||||
fields.Add(new($"Members ({memberCount})", $"(see `pk;system {system.Hid} list` or `pk;system {system.Hid} list full`)", true));
|
||||
else
|
||||
eb.AddField($"Members ({memberCount})", "Add one with `pk;member new`!", true);
|
||||
fields.Add(new($"Members ({memberCount})", "Add one with `pk;member new`!", true));
|
||||
}
|
||||
|
||||
if (system.DescriptionFor(ctx) is { } desc)
|
||||
eb.AddField("Description", desc.NormalizeLineEndSpacing().Truncate(1024), false);
|
||||
fields.Add(new("Description", desc.NormalizeLineEndSpacing().Truncate(1024), false));
|
||||
|
||||
return eb.Build();
|
||||
return embed with { Fields = fields.ToArray() };
|
||||
}
|
||||
|
||||
public DiscordEmbed CreateLoggedMessageEmbed(PKSystem system, PKMember member, ulong messageId, ulong originalMsgId, DiscordUser sender, string content, DiscordChannel channel) {
|
||||
|
@ -60,18 +60,16 @@ namespace PluralKit.Bot {
|
||||
private async Task<Channel?> FindLogChannel(ulong guildId, ulong channelId)
|
||||
{
|
||||
// TODO: fetch it directly on cache miss?
|
||||
var channel = await _cache.GetChannel(channelId);
|
||||
if (_cache.TryGetChannel(channelId, out var channel))
|
||||
return channel;
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
// Channel doesn't exist or we don't have permission to access it, let's remove it from the database too
|
||||
_logger.Warning("Attempted to fetch missing log channel {LogChannel} for guild {Guild}, removing from database", channelId, guildId);
|
||||
await using var conn = await _db.Obtain();
|
||||
await conn.ExecuteAsync("update servers set log_channel = null where id = @Guild",
|
||||
new {Guild = guildId});
|
||||
}
|
||||
// Channel doesn't exist or we don't have permission to access it, let's remove it from the database too
|
||||
_logger.Warning("Attempted to fetch missing log channel {LogChannel} for guild {Guild}, removing from database", channelId, guildId);
|
||||
await using var conn = await _db.Obtain();
|
||||
await conn.ExecuteAsync("update servers set log_channel = null where id = @Guild",
|
||||
new {Guild = guildId});
|
||||
|
||||
return channel;
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -9,6 +9,7 @@ using App.Metrics;
|
||||
using Humanizer;
|
||||
|
||||
using Myriad.Cache;
|
||||
using Myriad.Extensions;
|
||||
using Myriad.Rest;
|
||||
using Myriad.Rest.Types;
|
||||
using Myriad.Rest.Types.Requests;
|
||||
@ -77,20 +78,22 @@ namespace PluralKit.Bot
|
||||
|
||||
private async Task<Message> ExecuteWebhookInner(Webhook webhook, ProxyRequest req, bool hasRetried = false)
|
||||
{
|
||||
var guild = await _cache.GetGuild(req.GuildId)!;
|
||||
var guild = _cache.GetGuild(req.GuildId);
|
||||
var content = req.Content.Truncate(2000);
|
||||
|
||||
var allowedMentions = content.ParseMentions();
|
||||
if (!req.AllowEveryone)
|
||||
allowedMentions = allowedMentions.RemoveUnmentionableRoles(guild);
|
||||
|
||||
var webhookReq = new ExecuteWebhookRequest
|
||||
{
|
||||
Username = FixClyde(req.Name).Truncate(80),
|
||||
Content = content,
|
||||
AllowedMentions = null, // todo
|
||||
AllowedMentions = allowedMentions,
|
||||
AvatarUrl = !string.IsNullOrWhiteSpace(req.AvatarUrl) ? req.AvatarUrl : null,
|
||||
Embeds = req.Embeds
|
||||
};
|
||||
|
||||
// dwb.AddMentions(content.ParseAllMentions(guild, req.AllowEveryone));
|
||||
|
||||
MultipartFile[] files = null;
|
||||
var attachmentChunks = ChunkAttachmentsOrThrow(req.Attachments, 8 * 1024 * 1024);
|
||||
if (attachmentChunks.Count > 0)
|
||||
|
@ -11,10 +11,14 @@ using DSharpPlus.Entities;
|
||||
using DSharpPlus.EventArgs;
|
||||
using DSharpPlus.Exceptions;
|
||||
|
||||
using Myriad.Types;
|
||||
|
||||
using NodaTime;
|
||||
|
||||
using PluralKit.Core;
|
||||
|
||||
using Permissions = DSharpPlus.Permissions;
|
||||
|
||||
namespace PluralKit.Bot {
|
||||
public static class ContextUtils {
|
||||
public static async Task<bool> ConfirmClear(this Context ctx, string toClear)
|
||||
@ -149,7 +153,8 @@ namespace PluralKit.Bot {
|
||||
if (currentPage < 0) currentPage += pageCount;
|
||||
|
||||
// If we can, remove the user's reaction (so they can press again quickly)
|
||||
if (ctx.BotHasAllPermissions(Permissions.ManageMessages)) await msg.DeleteReactionAsync(reaction.Emoji, reaction.User);
|
||||
if (ctx.BotPermissions.HasFlag(PermissionSet.ManageMessages))
|
||||
await msg.DeleteReactionAsync(reaction.Emoji, reaction.User);
|
||||
|
||||
// Edit the embed with the new page
|
||||
var embed = await MakeEmbedForPage(currentPage);
|
||||
@ -159,7 +164,8 @@ namespace PluralKit.Bot {
|
||||
// "escape hatch", clean up as if we hit X
|
||||
}
|
||||
|
||||
if (ctx.BotHasAllPermissions(Permissions.ManageMessages)) await msg.DeleteAllReactionsAsync();
|
||||
if (ctx.BotPermissions.HasFlag(PermissionSet.ManageMessages))
|
||||
await msg.DeleteAllReactionsAsync();
|
||||
}
|
||||
// If we get a "NotFound" error, the message has been deleted and thus not our problem
|
||||
catch (NotFoundException) { }
|
||||
@ -245,12 +251,7 @@ namespace PluralKit.Bot {
|
||||
return items[Array.IndexOf(indicators, reaction.Emoji.Name)];
|
||||
}
|
||||
}
|
||||
|
||||
public static Permissions BotPermissions(this Context ctx) => ctx.Channel.BotPermissions();
|
||||
|
||||
public static bool BotHasAllPermissions(this Context ctx, Permissions permission) =>
|
||||
ctx.Channel.BotHasAllPermissions(permission);
|
||||
|
||||
|
||||
public static async Task BusyIndicator(this Context ctx, Func<Task> f, string emoji = "\u23f3" /* hourglass */)
|
||||
{
|
||||
await ctx.BusyIndicator<object>(async () =>
|
||||
@ -265,8 +266,8 @@ namespace PluralKit.Bot {
|
||||
var task = f();
|
||||
|
||||
// If we don't have permission to add reactions, don't bother, and just await the task normally.
|
||||
var neededPermissions = Permissions.AddReactions | Permissions.ReadMessageHistory;
|
||||
if ((ctx.BotPermissions() & neededPermissions) != neededPermissions) return await task;
|
||||
var neededPermissions = PermissionSet.AddReactions | PermissionSet.ReadMessageHistory;
|
||||
if ((ctx.BotPermissions & neededPermissions) != neededPermissions) return await task;
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -12,6 +12,8 @@ using DSharpPlus.Entities;
|
||||
using DSharpPlus.EventArgs;
|
||||
using DSharpPlus.Exceptions;
|
||||
|
||||
using Myriad.Extensions;
|
||||
using Myriad.Rest.Types;
|
||||
using Myriad.Types;
|
||||
|
||||
using NodaTime;
|
||||
@ -50,6 +52,11 @@ namespace PluralKit.Bot
|
||||
{
|
||||
return $"{user.Username}#{user.Discriminator} ({user.Mention})";
|
||||
}
|
||||
|
||||
public static string NameAndMention(this User user)
|
||||
{
|
||||
return $"{user.Username}#{user.Discriminator} ({user.Mention()})";
|
||||
}
|
||||
|
||||
// We funnel all "permissions from DiscordMember" calls through here
|
||||
// This way we can ensure we do the read permission correction everywhere
|
||||
@ -74,20 +81,7 @@ namespace PluralKit.Bot
|
||||
var invalidRoleIds = roleIdCache.Where(x => !currentRoleIds.Contains(x)).ToList();
|
||||
roleIdCache.RemoveAll(x => invalidRoleIds.Contains(x));
|
||||
}
|
||||
|
||||
public static async Task<Permissions> PermissionsIn(this DiscordChannel channel, DiscordUser user)
|
||||
{
|
||||
// Just delegates to PermissionsInSync, but handles the case of a non-member User in a guild properly
|
||||
// This is a separate method because it requires an async call
|
||||
if (channel.Guild != null && !(user is DiscordMember))
|
||||
{
|
||||
var member = await channel.Guild.GetMember(user.Id);
|
||||
if (member != null)
|
||||
return PermissionsInSync(channel, member);
|
||||
}
|
||||
|
||||
return PermissionsInSync(channel, user);
|
||||
}
|
||||
|
||||
|
||||
// Same as PermissionsIn, but always synchronous. DiscordUser must be a DiscordMember if channel is in guild.
|
||||
public static Permissions PermissionsInSync(this DiscordChannel channel, DiscordUser user)
|
||||
@ -194,23 +188,27 @@ namespace PluralKit.Bot
|
||||
return false;
|
||||
}
|
||||
|
||||
public static IEnumerable<IMention> ParseAllMentions(this string input, Guild guild, bool allowEveryone = false)
|
||||
public static AllowedMentions ParseMentions(this string input)
|
||||
{
|
||||
var mentions = new List<IMention>();
|
||||
mentions.AddRange(USER_MENTION.Matches(input)
|
||||
.Select(x => new UserMention(ulong.Parse(x.Groups[1].Value)) as IMention));
|
||||
var users = USER_MENTION.Matches(input).Select(x => ulong.Parse(x.Groups[1].Value));
|
||||
var roles = ROLE_MENTION.Matches(input).Select(x => ulong.Parse(x.Groups[1].Value));
|
||||
var everyone = EVERYONE_HERE_MENTION.IsMatch(input);
|
||||
|
||||
return new AllowedMentions
|
||||
{
|
||||
Users = users.ToArray(),
|
||||
Roles = roles.ToArray(),
|
||||
Parse = everyone ? new[] {AllowedMentions.ParseType.Everyone} : null
|
||||
};
|
||||
}
|
||||
|
||||
// Only allow role mentions through where the role is actually listed as *mentionable*
|
||||
// (ie. any user can @ them, regardless of permissions)
|
||||
// Still let the allowEveryone flag override this though (privileged users can @ *any* role)
|
||||
// Original fix by Gwen
|
||||
mentions.AddRange(ROLE_MENTION.Matches(input)
|
||||
.Select(x => ulong.Parse(x.Groups[1].Value))
|
||||
.Where(x => allowEveryone || guild != null && (guild.Roles.FirstOrDefault(g => g.Id == x)?.Mentionable ?? false))
|
||||
.Select(x => new RoleMention(x) as IMention));
|
||||
if (EVERYONE_HERE_MENTION.IsMatch(input) && allowEveryone)
|
||||
mentions.Add(new EveryoneMention());
|
||||
return mentions;
|
||||
public static AllowedMentions RemoveUnmentionableRoles(this AllowedMentions mentions, Guild guild)
|
||||
{
|
||||
return mentions with {
|
||||
Roles = mentions.Roles
|
||||
?.Where(id => guild.Roles.FirstOrDefault(r => r.Id == id)?.Mentionable == true)
|
||||
.ToArray()
|
||||
};
|
||||
}
|
||||
|
||||
public static string EscapeMarkdown(this string input)
|
||||
|
Loading…
Reference in New Issue
Block a user