refactor(bot): remove saving own user ID from ready event, rely on ID in config
This commit is contained in:
parent
aeb6411b6c
commit
9303dbb91e
@ -10,8 +10,6 @@ public static class DiscordCacheExtensions
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
case ReadyEvent ready:
|
||||
return cache.SaveOwnUser(ready.User.Id);
|
||||
case GuildCreateEvent gc:
|
||||
return cache.SaveGuildCreate(gc);
|
||||
case GuildUpdateEvent gu:
|
||||
@ -108,7 +106,7 @@ public static class DiscordCacheExtensions
|
||||
|
||||
if (channel.GuildId != null)
|
||||
{
|
||||
var userId = await cache.GetOwnUser();
|
||||
var userId = cache.GetOwnUser();
|
||||
var member = await cache.TryGetSelfMember(channel.GuildId.Value);
|
||||
return await cache.PermissionsFor(channelId, userId, member);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ namespace Myriad.Cache;
|
||||
|
||||
public interface IDiscordCache
|
||||
{
|
||||
public ValueTask SaveOwnUser(ulong userId);
|
||||
public ValueTask SaveGuild(Guild guild);
|
||||
public ValueTask SaveChannel(Channel channel);
|
||||
public ValueTask SaveUser(User user);
|
||||
@ -17,7 +16,7 @@ public interface IDiscordCache
|
||||
public ValueTask RemoveUser(ulong userId);
|
||||
public ValueTask RemoveRole(ulong guildId, ulong roleId);
|
||||
|
||||
public Task<ulong> GetOwnUser();
|
||||
internal ulong GetOwnUser();
|
||||
public Task<Guild?> TryGetGuild(ulong guildId);
|
||||
public Task<Channel?> TryGetChannel(ulong channelId);
|
||||
public Task<User?> TryGetUser(ulong userId);
|
||||
|
@ -11,7 +11,12 @@ public class MemoryDiscordCache: IDiscordCache
|
||||
private readonly ConcurrentDictionary<ulong, CachedGuild> _guilds = new();
|
||||
private readonly ConcurrentDictionary<ulong, Role> _roles = new();
|
||||
private readonly ConcurrentDictionary<ulong, User> _users = new();
|
||||
private ulong? _ownUserId { get; set; }
|
||||
private readonly ulong _ownUserId;
|
||||
|
||||
public MemoryDiscordCache(ulong ownUserId)
|
||||
{
|
||||
_ownUserId = ownUserId;
|
||||
}
|
||||
|
||||
public ValueTask SaveGuild(Guild guild)
|
||||
{
|
||||
@ -48,15 +53,6 @@ public class MemoryDiscordCache: IDiscordCache
|
||||
await SaveUser(recipient);
|
||||
}
|
||||
|
||||
public ValueTask SaveOwnUser(ulong userId)
|
||||
{
|
||||
// this (hopefully) never changes at runtime, so we skip out on re-assigning it
|
||||
if (_ownUserId == null)
|
||||
_ownUserId = userId;
|
||||
|
||||
return default;
|
||||
}
|
||||
|
||||
public ValueTask SaveUser(User user)
|
||||
{
|
||||
_users[user.Id] = user;
|
||||
@ -127,7 +123,7 @@ public class MemoryDiscordCache: IDiscordCache
|
||||
return default;
|
||||
}
|
||||
|
||||
public Task<ulong> GetOwnUser() => Task.FromResult(_ownUserId!.Value);
|
||||
public ulong GetOwnUser() => _ownUserId;
|
||||
|
||||
public ValueTask RemoveRole(ulong guildId, ulong roleId)
|
||||
{
|
||||
|
@ -13,18 +13,18 @@ namespace Myriad.Cache;
|
||||
public class RedisDiscordCache: IDiscordCache
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
public RedisDiscordCache(ILogger logger)
|
||||
private readonly ulong _ownUserId;
|
||||
public RedisDiscordCache(ILogger logger, ulong ownUserId)
|
||||
{
|
||||
_logger = logger;
|
||||
_ownUserId = ownUserId;
|
||||
}
|
||||
|
||||
private ConnectionMultiplexer _redis { get; set; }
|
||||
private ulong _ownUserId { get; set; }
|
||||
|
||||
public async Task InitAsync(string addr, ulong ownUserId)
|
||||
public async Task InitAsync(string addr)
|
||||
{
|
||||
_redis = await ConnectionMultiplexer.ConnectAsync(addr);
|
||||
_ownUserId = ownUserId;
|
||||
}
|
||||
|
||||
private IDatabase db => _redis.GetDatabase().WithKeyPrefix("discord:");
|
||||
@ -78,12 +78,6 @@ public class RedisDiscordCache: IDiscordCache
|
||||
await SaveUser(recipient);
|
||||
}
|
||||
|
||||
public ValueTask SaveOwnUser(ulong userId)
|
||||
{
|
||||
// we get the own user ID in InitAsync, so no need to save it here
|
||||
return default;
|
||||
}
|
||||
|
||||
public async ValueTask SaveUser(User user)
|
||||
{
|
||||
_logger.Verbose("Saving user {UserId} to redis", user.Id);
|
||||
@ -161,8 +155,7 @@ public class RedisDiscordCache: IDiscordCache
|
||||
public async ValueTask RemoveUser(ulong userId)
|
||||
=> await db.HashDeleteAsync("users", userId);
|
||||
|
||||
// todo: try getting this from redis if we don't have it yet
|
||||
public Task<ulong> GetOwnUser() => Task.FromResult(_ownUserId);
|
||||
public ulong GetOwnUser() => _ownUserId;
|
||||
|
||||
public async ValueTask RemoveRole(ulong guildId, ulong roleId)
|
||||
{
|
||||
|
@ -94,8 +94,7 @@ public class Bot
|
||||
// we HandleGatewayEvent **before** getting the own user, because the own user is set in HandleGatewayEvent for ReadyEvent
|
||||
await _cache.HandleGatewayEvent(evt);
|
||||
|
||||
var userId = await _cache.GetOwnUser();
|
||||
await _cache.TryUpdateSelfMember(userId, evt);
|
||||
await _cache.TryUpdateSelfMember(_config.ClientId, evt);
|
||||
|
||||
await OnEventReceivedInner(shardId, evt);
|
||||
}
|
||||
@ -200,13 +199,11 @@ public class Bot
|
||||
{
|
||||
_metrics.Measure.Meter.Mark(BotMetrics.BotErrors, exc.GetType().FullName);
|
||||
|
||||
var ourUserId = await _cache.GetOwnUser();
|
||||
|
||||
// Make this beforehand so we can access the event ID for logging
|
||||
var sentryEvent = new SentryEvent(exc);
|
||||
|
||||
// If the event is us responding to our own error messages, don't bother logging
|
||||
if (evt is MessageCreateEvent mc && mc.Author.Id == ourUserId)
|
||||
if (evt is MessageCreateEvent mc && mc.Author.Id == _config.ClientId)
|
||||
return;
|
||||
|
||||
var shouldReport = exc.IsOurProblem();
|
||||
@ -235,7 +232,7 @@ public class Bot
|
||||
return;
|
||||
|
||||
// Once we've sent it to Sentry, report it to the user (if we have permission to)
|
||||
var reportChannel = handler.ErrorChannelFor(evt, ourUserId);
|
||||
var reportChannel = handler.ErrorChannelFor(evt, _config.ClientId);
|
||||
if (reportChannel == null)
|
||||
return;
|
||||
|
||||
|
@ -5,7 +5,7 @@ public class BotConfig
|
||||
public static readonly string[] DefaultPrefixes = { "pk;", "pk!" };
|
||||
|
||||
public string Token { get; set; }
|
||||
public ulong? ClientId { get; set; }
|
||||
public ulong ClientId { get; set; }
|
||||
|
||||
// ASP.NET configuration merges arrays with defaults, so we leave this field nullable
|
||||
// and fall back to the separate default array at the use site :)
|
||||
|
@ -14,8 +14,6 @@ namespace PluralKit.Bot;
|
||||
public class Checks
|
||||
{
|
||||
private readonly BotConfig _botConfig;
|
||||
// this must ONLY be used to get the bot's user ID
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly ProxyMatcher _matcher;
|
||||
private readonly ProxyService _proxy;
|
||||
private readonly DiscordApiClient _rest;
|
||||
@ -28,10 +26,9 @@ public class Checks
|
||||
};
|
||||
|
||||
// todo: make sure everything uses the minimum amount of REST calls necessary
|
||||
public Checks(DiscordApiClient rest, IDiscordCache cache, BotConfig botConfig, ProxyService proxy, ProxyMatcher matcher)
|
||||
public Checks(DiscordApiClient rest, BotConfig botConfig, ProxyService proxy, ProxyMatcher matcher)
|
||||
{
|
||||
_rest = rest;
|
||||
_cache = cache;
|
||||
_botConfig = botConfig;
|
||||
_proxy = proxy;
|
||||
_matcher = matcher;
|
||||
@ -69,14 +66,14 @@ public class Checks
|
||||
throw Errors.GuildNotFound(guildId);
|
||||
}
|
||||
|
||||
var guildMember = await _rest.GetGuildMember(guild.Id, await _cache.GetOwnUser());
|
||||
var guildMember = await _rest.GetGuildMember(guild.Id, _botConfig.ClientId);
|
||||
|
||||
// Loop through every channel and group them by sets of permissions missing
|
||||
var permissionsMissing = new Dictionary<ulong, List<Channel>>();
|
||||
var hiddenChannels = false;
|
||||
foreach (var channel in await _rest.GetGuildChannels(guild.Id))
|
||||
{
|
||||
var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember);
|
||||
var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, _botConfig.ClientId, guildMember);
|
||||
var userPermissions = PermissionExtensions.PermissionsFor(guild, channel, ctx.Author.Id, senderGuildUser);
|
||||
|
||||
if ((userPermissions & PermissionSet.ViewChannel) == 0)
|
||||
@ -152,12 +149,12 @@ public class Checks
|
||||
if (guild == null)
|
||||
throw new PKError(error);
|
||||
|
||||
var guildMember = await _rest.GetGuildMember(channel.GuildId.Value, await _cache.GetOwnUser());
|
||||
var guildMember = await _rest.GetGuildMember(channel.GuildId.Value, _botConfig.ClientId);
|
||||
|
||||
if (!await ctx.CheckPermissionsInGuildChannel(channel, PermissionSet.ViewChannel))
|
||||
throw new PKError(error);
|
||||
|
||||
var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember);
|
||||
var botPermissions = PermissionExtensions.PermissionsFor(guild, channel, _botConfig.ClientId, guildMember);
|
||||
|
||||
// We use a bitfield so we can set individual permission bits
|
||||
ulong missingPermissions = 0;
|
||||
|
@ -18,26 +18,22 @@ namespace PluralKit.Bot;
|
||||
public class Misc
|
||||
{
|
||||
private readonly BotConfig _botConfig;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly CpuStatService _cpu;
|
||||
private readonly IMetrics _metrics;
|
||||
private readonly ShardInfoService _shards;
|
||||
private readonly ModelRepository _repo;
|
||||
|
||||
public Misc(BotConfig botConfig, IMetrics metrics, CpuStatService cpu, ModelRepository repo, ShardInfoService shards, IDiscordCache cache)
|
||||
public Misc(BotConfig botConfig, IMetrics metrics, CpuStatService cpu, ModelRepository repo, ShardInfoService shards)
|
||||
{
|
||||
_botConfig = botConfig;
|
||||
_metrics = metrics;
|
||||
_cpu = cpu;
|
||||
_repo = repo;
|
||||
_shards = shards;
|
||||
_cache = cache;
|
||||
}
|
||||
|
||||
public async Task Invite(Context ctx)
|
||||
{
|
||||
var clientId = _botConfig.ClientId ?? await _cache.GetOwnUser();
|
||||
|
||||
var permissions =
|
||||
PermissionSet.AddReactions |
|
||||
PermissionSet.AttachFiles |
|
||||
@ -48,7 +44,7 @@ public class Misc
|
||||
PermissionSet.SendMessages;
|
||||
|
||||
var invite =
|
||||
$"https://discord.com/oauth2/authorize?client_id={clientId}&scope=bot%20applications.commands&permissions={(ulong)permissions}";
|
||||
$"https://discord.com/oauth2/authorize?client_id={_botConfig.ClientId}&scope=bot%20applications.commands&permissions={(ulong)permissions}";
|
||||
|
||||
var botName = _botConfig.IsBetaBot ? "PluralKit Beta" : "PluralKit";
|
||||
await ctx.Reply($"{Emojis.Success} Use this link to add {botName} to your server:\n<{invite}>");
|
||||
|
@ -59,7 +59,7 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
||||
|
||||
public async Task Handle(int shardId, MessageCreateEvent evt)
|
||||
{
|
||||
if (evt.Author.Id == await _cache.GetOwnUser()) return;
|
||||
if (evt.Author.Id == _config.ClientId) return;
|
||||
if (evt.Type != Message.MessageType.Default && evt.Type != Message.MessageType.Reply) return;
|
||||
if (IsDuplicateMessage(evt)) return;
|
||||
|
||||
@ -109,10 +109,8 @@ public class MessageCreated: IEventHandler<MessageCreateEvent>
|
||||
var content = evt.Content;
|
||||
if (content == null) return false;
|
||||
|
||||
var ourUserId = await _cache.GetOwnUser();
|
||||
|
||||
// Check for command prefix
|
||||
if (!HasCommandPrefix(content, ourUserId, out var cmdStart) || cmdStart == content.Length)
|
||||
if (!HasCommandPrefix(content, _config.ClientId, out var cmdStart) || cmdStart == content.Length)
|
||||
return false;
|
||||
|
||||
// Trim leading whitespace from command without actually modifying the string
|
||||
|
@ -15,6 +15,7 @@ namespace PluralKit.Bot;
|
||||
public class MessageEdited: IEventHandler<MessageUpdateEvent>
|
||||
{
|
||||
private readonly Bot _bot;
|
||||
private readonly BotConfig _config;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly Cluster _client;
|
||||
private readonly IDatabase _db;
|
||||
@ -27,7 +28,7 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
|
||||
|
||||
public MessageEdited(LastMessageCacheService lastMessageCache, ProxyService proxy, IDatabase db,
|
||||
IMetrics metrics, ModelRepository repo, Cluster client, IDiscordCache cache, Bot bot,
|
||||
DiscordApiClient rest, ILogger logger)
|
||||
BotConfig config, DiscordApiClient rest, ILogger logger)
|
||||
{
|
||||
_lastMessageCache = lastMessageCache;
|
||||
_proxy = proxy;
|
||||
@ -37,13 +38,14 @@ public class MessageEdited: IEventHandler<MessageUpdateEvent>
|
||||
_client = client;
|
||||
_cache = cache;
|
||||
_bot = bot;
|
||||
_config = config;
|
||||
_rest = rest;
|
||||
_logger = logger.ForContext<MessageEdited>();
|
||||
}
|
||||
|
||||
public async Task Handle(int shardId, MessageUpdateEvent evt)
|
||||
{
|
||||
if (evt.Author.Value?.Id == await _cache.GetOwnUser()) return;
|
||||
if (evt.Author.Value?.Id == _config.ClientId) return;
|
||||
|
||||
// Edit message events sometimes arrive with missing data; double-check it's all there
|
||||
if (!evt.Content.HasValue || !evt.Author.HasValue || !evt.Member.HasValue)
|
||||
|
@ -18,6 +18,7 @@ namespace PluralKit.Bot;
|
||||
public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
||||
{
|
||||
private readonly Bot _bot;
|
||||
private readonly BotConfig _config;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly Cluster _cluster;
|
||||
private readonly CommandMessageService _commandMessageService;
|
||||
@ -30,13 +31,14 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
||||
|
||||
public ReactionAdded(ILogger logger, IDatabase db, ModelRepository repo,
|
||||
CommandMessageService commandMessageService, IDiscordCache cache, Bot bot, Cluster cluster,
|
||||
DiscordApiClient rest, EmbedService embeds, PrivateChannelService dmCache)
|
||||
BotConfig config, DiscordApiClient rest, EmbedService embeds, PrivateChannelService dmCache)
|
||||
{
|
||||
_db = db;
|
||||
_repo = repo;
|
||||
_commandMessageService = commandMessageService;
|
||||
_cache = cache;
|
||||
_bot = bot;
|
||||
_config = config;
|
||||
_cluster = cluster;
|
||||
_rest = rest;
|
||||
_embeds = embeds;
|
||||
@ -52,7 +54,7 @@ public class ReactionAdded: IEventHandler<MessageReactionAddEvent>
|
||||
private async ValueTask TryHandleProxyMessageReactions(MessageReactionAddEvent evt)
|
||||
{
|
||||
// ignore any reactions added by *us*
|
||||
if (evt.UserId == await _cache.GetOwnUser())
|
||||
if (evt.UserId == _config.ClientId)
|
||||
return;
|
||||
|
||||
// Ignore reactions from bots (we can't DM them anyway)
|
||||
|
@ -58,7 +58,7 @@ public class Init
|
||||
|
||||
var cache = services.Resolve<IDiscordCache>();
|
||||
if (cache is RedisDiscordCache)
|
||||
await (cache as RedisDiscordCache).InitAsync(coreConfig.RedisAddr, config.ClientId!.Value);
|
||||
await (cache as RedisDiscordCache).InitAsync(coreConfig.RedisAddr);
|
||||
|
||||
if (config.Cluster == null)
|
||||
{
|
||||
|
@ -49,8 +49,8 @@ public class BotModule: Module
|
||||
var botConfig = c.Resolve<BotConfig>();
|
||||
|
||||
if (botConfig.UseRedisCache)
|
||||
return new RedisDiscordCache(c.Resolve<ILogger>());
|
||||
return new MemoryDiscordCache();
|
||||
return new RedisDiscordCache(c.Resolve<ILogger>(), botConfig.ClientId);
|
||||
return new MemoryDiscordCache(botConfig.ClientId);
|
||||
}).AsSelf().SingleInstance();
|
||||
builder.RegisterType<PrivateChannelService>().AsSelf().SingleInstance();
|
||||
|
||||
|
@ -15,6 +15,7 @@ namespace PluralKit.Bot;
|
||||
public class LogChannelService
|
||||
{
|
||||
private readonly Bot _bot;
|
||||
private readonly BotConfig _config;
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly IDatabase _db;
|
||||
private readonly EmbedService _embed;
|
||||
@ -23,7 +24,7 @@ public class LogChannelService
|
||||
private readonly DiscordApiClient _rest;
|
||||
|
||||
public LogChannelService(EmbedService embed, ILogger logger, IDatabase db, ModelRepository repo,
|
||||
IDiscordCache cache, DiscordApiClient rest, Bot bot)
|
||||
IDiscordCache cache, DiscordApiClient rest, Bot bot, BotConfig config)
|
||||
{
|
||||
_embed = embed;
|
||||
_db = db;
|
||||
@ -31,6 +32,7 @@ public class LogChannelService
|
||||
_cache = cache;
|
||||
_rest = rest;
|
||||
_bot = bot;
|
||||
_config = config;
|
||||
_logger = logger.ForContext<LogChannelService>();
|
||||
}
|
||||
|
||||
@ -97,9 +99,9 @@ public class LogChannelService
|
||||
|
||||
var guildMember = await _cache.TryGetSelfMember(channel.GuildId.Value);
|
||||
if (guildMember == null)
|
||||
guildMember = await _rest.GetGuildMember(channel.GuildId.Value, await _cache.GetOwnUser());
|
||||
guildMember = await _rest.GetGuildMember(channel.GuildId.Value, _config.ClientId);
|
||||
|
||||
var perms = PermissionExtensions.PermissionsFor(guild, channel, await _cache.GetOwnUser(), guildMember);
|
||||
var perms = PermissionExtensions.PermissionsFor(guild, channel, _config.ClientId, guildMember);
|
||||
return perms;
|
||||
}
|
||||
|
||||
|
@ -17,16 +17,18 @@ public class WebhookCacheService
|
||||
private readonly IDiscordCache _cache;
|
||||
private readonly ILogger _logger;
|
||||
private readonly IMetrics _metrics;
|
||||
private readonly BotConfig _config;
|
||||
|
||||
|
||||
private readonly DiscordApiClient _rest;
|
||||
private readonly ConcurrentDictionary<ulong, Lazy<Task<Webhook>>> _webhooks;
|
||||
|
||||
public WebhookCacheService(ILogger logger, IMetrics metrics, DiscordApiClient rest, IDiscordCache cache)
|
||||
public WebhookCacheService(ILogger logger, IMetrics metrics, DiscordApiClient rest, IDiscordCache cache, BotConfig config)
|
||||
{
|
||||
_metrics = metrics;
|
||||
_rest = rest;
|
||||
_cache = cache;
|
||||
_config = config;
|
||||
_logger = logger.ForContext<WebhookCacheService>();
|
||||
_webhooks = new ConcurrentDictionary<ulong, Lazy<Task<Webhook>>>();
|
||||
}
|
||||
@ -86,8 +88,7 @@ public class WebhookCacheService
|
||||
var webhooks = await FetchChannelWebhooks(channelId);
|
||||
|
||||
// If the channel has a webhook created by PK, just return that one
|
||||
var ourUserId = await _cache.GetOwnUser();
|
||||
var ourWebhook = webhooks.FirstOrDefault(hook => IsWebhookMine(ourUserId, hook));
|
||||
var ourWebhook = webhooks.FirstOrDefault(hook => IsWebhookMine(hook));
|
||||
if (ourWebhook != null)
|
||||
return ourWebhook;
|
||||
|
||||
@ -122,5 +123,5 @@ public class WebhookCacheService
|
||||
return await _rest.CreateWebhook(channelId, new CreateWebhookRequest(WebhookName));
|
||||
}
|
||||
|
||||
private bool IsWebhookMine(ulong userId, Webhook arg) => arg.User?.Id == userId && arg.Name == WebhookName;
|
||||
private bool IsWebhookMine(Webhook arg) => arg.User?.Id == _config.ClientId && arg.Name == WebhookName;
|
||||
}
|
@ -32,7 +32,7 @@ The bot can also take configuration from environment variables, which will overr
|
||||
The easiest way to get the bot running is with Docker. The repository contains a `docker-compose.yml` file ready to use.
|
||||
|
||||
* Clone this repository: `git clone https://github.com/PluralKit/PluralKit`
|
||||
* Create a `pluralkit.conf` file in the same directory as `docker-compose.yml` containing at least a `PluralKit.Bot.Token` field
|
||||
* Create a `pluralkit.conf` file in the same directory as `docker-compose.yml` containing at least `PluralKit.Bot.Token` and `PluralKit.Bot.ClientId` fields
|
||||
* (`PluralKit.Database` is overridden in `docker-compose.yml` to point to the Postgres container)
|
||||
* Build the bot: `docker-compose build`
|
||||
* Run the bot: `docker-compose up`
|
||||
|
@ -1,7 +1,8 @@
|
||||
{
|
||||
"PluralKit": {
|
||||
"Bot": {
|
||||
"Token": "BOT_TOKEN_GOES_HERE"
|
||||
"Token": "BOT_TOKEN_GOES_HERE",
|
||||
"ClientId": 466707357099884544,
|
||||
},
|
||||
"Database": "Host=localhost;Port=5432;Username=myusername;Password=mypassword;Database=mydatabasename"
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user