Rework caching

This does a *lot* of things. Essentially, it replaces the existing
individual proxy- and autoproxy caches on the bot end with a
global cache (in Core) that handles all the caching at once, and
automatically invalidates the cache once something changes in the
datastore.

This allows us to do proxying and autoproxying with *zero database
queries* (best-case).
This commit is contained in:
Ske
2020-02-01 14:40:57 +01:00
parent 125ea81ec3
commit 82dfe43d5a
16 changed files with 254 additions and 255 deletions

View File

@@ -194,8 +194,6 @@ namespace PluralKit.Bot
private Task HandleEvent(Func<PKEventHandler, Task> handler)
{
_logger.Debug("Received event");
// Inner function so we can await the handler without stalling the entire pipeline
async Task Inner()
{
@@ -245,6 +243,7 @@ namespace PluralKit.Bot
private ILifetimeScope _services;
private CommandTree _tree;
private Scope _sentryScope;
private ProxyCache _cache;
// We're defining in the Autofac module that this class is instantiated with one instance per event
// This means that the HandleMessage function will either be called once, or not at all
@@ -252,7 +251,7 @@ namespace PluralKit.Bot
// hence, we just store it in a local variable, ignoring it entirely if it's null.
private IUserMessage _msg = null;
public PKEventHandler(ProxyService proxy, ILogger logger, IMetrics metrics, DiscordShardedClient client, DbConnectionFactory connectionFactory, ILifetimeScope services, CommandTree tree, Scope sentryScope)
public PKEventHandler(ProxyService proxy, ILogger logger, IMetrics metrics, DiscordShardedClient client, DbConnectionFactory connectionFactory, ILifetimeScope services, CommandTree tree, Scope sentryScope, ProxyCache cache)
{
_proxy = proxy;
_logger = logger;
@@ -262,6 +261,7 @@ namespace PluralKit.Bot
_services = services;
_tree = tree;
_sentryScope = sentryScope;
_cache = cache;
}
public async Task HandleMessage(SocketMessage arg)
@@ -288,6 +288,12 @@ namespace PluralKit.Bot
{"message", msg.Id.ToString()},
});
// We fetch information about the sending account *and* guild from the cache
GuildConfig cachedGuild = default; // todo: is this default correct?
if (msg.Channel is ITextChannel textChannel) cachedGuild = await _cache.GetGuildDataCached(textChannel.GuildId);
var cachedAccount = await _cache.GetAccountDataCached(msg.Author.Id);
// this ^ may be null, do remember that down the line
int argPos = -1;
// Check if message starts with the command prefix
if (msg.Content.StartsWith("pk;", StringComparison.InvariantCultureIgnoreCase)) argPos = 3;
@@ -306,23 +312,16 @@ namespace PluralKit.Bot
msg.Content.Substring(argPos).TrimStart().Length;
argPos += trimStartLengthDiff;
// If it does, fetch the sender's system (because most commands need that) into the context,
// and start command execution
// Note system may be null if user has no system, hence `OrDefault`
PKSystem system;
using (var conn = await _connectionFactory.Obtain())
system = await conn.QueryFirstOrDefaultAsync<PKSystem>(
"select systems.* from systems, accounts where accounts.uid = @Id and systems.id = accounts.system",
new {Id = msg.Author.Id});
await _tree.ExecuteCommand(new Context(_services, msg, argPos, system));
await _tree.ExecuteCommand(new Context(_services, msg, argPos, cachedAccount?.System));
}
else
else if (cachedAccount != null)
{
// If not, try proxying anyway
// but only if the account data we got before is present
// no data = no account = no system = no proxy!
try
{
await _proxy.HandleMessageAsync(msg);
await _proxy.HandleMessageAsync(cachedGuild, cachedAccount, msg);
}
catch (PKError e)
{

View File

@@ -11,12 +11,10 @@ namespace PluralKit.Bot.Commands
public class Autoproxy
{
private IDataStore _data;
private AutoproxyCacheService _cache;
public Autoproxy(IDataStore data, AutoproxyCacheService cache)
public Autoproxy(IDataStore data)
{
_data = data;
_cache = cache;
}
public async Task AutoproxyRoot(Context ctx)
@@ -51,7 +49,6 @@ namespace PluralKit.Bot.Commands
settings.AutoproxyMode = AutoproxyMode.Off;
settings.AutoproxyMember = null;
await _data.SetSystemGuildSettings(ctx.System, ctx.Guild.Id, settings);
await _cache.FlushCacheForSystem(ctx.System, ctx.Guild.Id);
await ctx.Reply($"{Emojis.Success} Autoproxy turned off in this server.");
}
}
@@ -68,7 +65,6 @@ namespace PluralKit.Bot.Commands
settings.AutoproxyMode = AutoproxyMode.Latch;
settings.AutoproxyMember = null;
await _data.SetSystemGuildSettings(ctx.System, ctx.Guild.Id, settings);
await _cache.FlushCacheForSystem(ctx.System, ctx.Guild.Id);
await ctx.Reply($"{Emojis.Success} Autoproxy set to latch mode in this server. Messages will now be autoproxied using the *last-proxied member* in this server.");
}
}
@@ -85,7 +81,6 @@ namespace PluralKit.Bot.Commands
settings.AutoproxyMode = AutoproxyMode.Front;
settings.AutoproxyMember = null;
await _data.SetSystemGuildSettings(ctx.System, ctx.Guild.Id, settings);
await _cache.FlushCacheForSystem(ctx.System, ctx.Guild.Id);
await ctx.Reply($"{Emojis.Success} Autoproxy set to front mode in this server. Messages will now be autoproxied using the *current first fronter*, if any.");
}
}
@@ -98,7 +93,6 @@ namespace PluralKit.Bot.Commands
settings.AutoproxyMode = AutoproxyMode.Member;
settings.AutoproxyMember = member.Id;
await _data.SetSystemGuildSettings(ctx.System, ctx.Guild.Id, settings);
await _cache.FlushCacheForSystem(ctx.System, ctx.Guild.Id);
await ctx.Reply($"{Emojis.Success} Autoproxy set to **{member.Name}** in this server.");
}

View File

@@ -10,14 +10,11 @@ namespace PluralKit.Bot.Commands
{
private IDataStore _data;
private EmbedService _embeds;
private ProxyCacheService _proxyCache;
public Member(IDataStore data, EmbedService embeds, ProxyCacheService proxyCache)
public Member(IDataStore data, EmbedService embeds)
{
_data = data;
_embeds = embeds;
_proxyCache = proxyCache;
}
public async Task NewMember(Context ctx) {
@@ -51,8 +48,6 @@ namespace PluralKit.Bot.Commands
await ctx.Reply($"{Emojis.Warn} You have reached the per-system member limit ({Limits.MaxMemberCount}). You will be unable to create additional members until existing members are deleted.");
else if (memberCount >= Limits.MaxMembersWarnThreshold)
await ctx.Reply($"{Emojis.Warn} You are approaching the per-system member limit ({memberCount} / {Limits.MaxMemberCount} members). Please review your member list for unused or duplicate members.");
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task MemberRandom(Context ctx)
@@ -68,11 +63,8 @@ namespace PluralKit.Bot.Commands
throw Errors.NoMembersError;
var randInt = randGen.Next(members.Count);
await ctx.Reply(embed: await _embeds.CreateMemberEmbed(ctx.System, members[randInt], ctx.Guild, ctx.LookupContextFor(ctx.System)));
}
public async Task ViewMember(Context ctx, PKMember target)
{
var system = await _data.GetSystemById(target.System);

View File

@@ -10,12 +10,10 @@ namespace PluralKit.Bot.Commands
public class MemberAvatar
{
private IDataStore _data;
private ProxyCacheService _proxyCache;
public MemberAvatar(IDataStore data, ProxyCacheService proxyCache)
public MemberAvatar(IDataStore data)
{
_data = data;
_proxyCache = proxyCache;
}
public async Task Avatar(Context ctx, PKMember target)
@@ -80,8 +78,6 @@ namespace PluralKit.Bot.Commands
await ctx.Reply($"{Emojis.Success} Member avatar changed to attached image. Please note that if you delete the message containing the attachment, the avatar will stop working.");
}
// No-arguments no-attachment case covered by conditional at the very top
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
}
}

View File

@@ -11,12 +11,10 @@ namespace PluralKit.Bot.Commands
public class MemberEdit
{
private IDataStore _data;
private ProxyCacheService _proxyCache;
public MemberEdit(IDataStore data, ProxyCacheService proxyCache)
public MemberEdit(IDataStore data)
{
_data = data;
_proxyCache = proxyCache;
}
public async Task Name(Context ctx, PKMember target) {
@@ -50,8 +48,6 @@ namespace PluralKit.Bot.Commands
if (memberGuildConfig.DisplayName != null)
await ctx.Reply($"{Emojis.Note} Note that this member has a server name set ({memberGuildConfig.DisplayName.SanitizeMentions()}) in this server ({ctx.Guild.Name.SanitizeMentions()}), and will be proxied using that name here.");
}
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task Description(Context ctx, PKMember target) {
@@ -141,8 +137,6 @@ namespace PluralKit.Bot.Commands
}
await ctx.Reply(successStr);
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task ServerName(Context ctx, PKMember target)
@@ -168,8 +162,6 @@ namespace PluralKit.Bot.Commands
successStr += $"Member server name cleared. This member will now be proxied using their member name \"{target.Name.SanitizeMentions()}\" in this server ({ctx.Guild.Name.SanitizeMentions()}).";
await ctx.Reply(successStr);
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task KeepProxy(Context ctx, PKMember target)
@@ -190,7 +182,6 @@ namespace PluralKit.Bot.Commands
await ctx.Reply($"{Emojis.Success} Member proxy tags will now be included in the resulting message when proxying.");
else
await ctx.Reply($"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task Privacy(Context ctx, PKMember target)
@@ -222,8 +213,6 @@ namespace PluralKit.Bot.Commands
if (!await ctx.ConfirmWithReply(target.Hid)) throw Errors.MemberDeleteCancelled;
await _data.DeleteMember(target);
await ctx.Reply($"{Emojis.Success} Member deleted.");
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
}
}

View File

@@ -8,12 +8,10 @@ namespace PluralKit.Bot.Commands
public class MemberProxy
{
private IDataStore _data;
private ProxyCacheService _proxyCache;
public MemberProxy(IDataStore data, ProxyCacheService proxyCache)
public MemberProxy(IDataStore data)
{
_data = data;
_proxyCache = proxyCache;
}
public async Task Proxy(Context ctx, PKMember target)
@@ -117,9 +115,6 @@ namespace PluralKit.Bot.Commands
await _data.SaveMember(target);
await ctx.Reply($"{Emojis.Success} Member proxy tags set to `{requestedTag.ProxyString.SanitizeMentions()}`.");
}
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
}
}

View File

@@ -17,13 +17,11 @@ namespace PluralKit.Bot.Commands
{
private IDataStore _data;
private EmbedService _embeds;
private ProxyCacheService _proxyCache;
public SystemEdit(IDataStore data, EmbedService embeds, ProxyCacheService proxyCache)
public SystemEdit(IDataStore data, EmbedService embeds)
{
_data = data;
_embeds = embeds;
_proxyCache = proxyCache;
}
public async Task Name(Context ctx)
@@ -62,8 +60,6 @@ namespace PluralKit.Bot.Commands
await _data.SaveSystem(ctx.System);
await ctx.Reply($"{Emojis.Success} System tag {(newTag != null ? $"changed. Member names will now end with `{newTag.SanitizeMentions()}` when proxied" : "cleared")}.");
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task Avatar(Context ctx)
@@ -115,8 +111,6 @@ namespace PluralKit.Bot.Commands
var embed = url != null ? new EmbedBuilder().WithImageUrl(url).Build() : null;
await ctx.Reply($"{Emojis.Success} System avatar changed.", embed: embed);
}
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task Delete(Context ctx) {
@@ -128,8 +122,6 @@ namespace PluralKit.Bot.Commands
await _data.DeleteSystem(ctx.System);
await ctx.Reply($"{Emojis.Success} System deleted.");
await _proxyCache.InvalidateResultsForSystem(ctx.System);
}
public async Task SystemProxy(Context ctx)

View File

@@ -63,9 +63,7 @@ namespace PluralKit.Bot
builder.RegisterType<LogChannelService>().AsSelf().SingleInstance();
builder.RegisterType<DataFileService>().AsSelf().SingleInstance();
builder.RegisterType<WebhookExecutorService>().AsSelf().SingleInstance();
builder.RegisterType<ProxyCacheService>().AsSelf().SingleInstance();
builder.RegisterType<WebhookCacheService>().AsSelf().SingleInstance();
builder.RegisterType<AutoproxyCacheService>().AsSelf().SingleInstance();
builder.RegisterType<ShardInfoService>().AsSelf().SingleInstance();
builder.RegisterType<CpuStatService>().AsSelf().SingleInstance();
builder.RegisterType<PeriodicStatCollector>().AsSelf().SingleInstance();
@@ -73,9 +71,6 @@ namespace PluralKit.Bot
// Sentry stuff
builder.Register(_ => new Scope(null)).AsSelf().InstancePerLifetimeScope();
// .NET stuff
builder.Populate(new ServiceCollection()
.AddMemoryCache());
// Utils
builder.Register(c => new HttpClient

View File

@@ -12,7 +12,6 @@
<ItemGroup>
<PackageReference Include="Humanizer.Core" Version="2.7.9" />
<PackageReference Include="Microsoft.Extensions.Caching.Memory" Version="3.1.0" />
<PackageReference Include="Sentry" Version="2.0.0-beta7" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.0-beta0007" />
</ItemGroup>

View File

@@ -1,73 +0,0 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Extensions.Caching.Memory;
namespace PluralKit.Bot
{
public class AutoproxyCacheResult
{
public SystemGuildSettings GuildSettings;
public PKSystem System;
public PKMember AutoproxyMember;
}
public class AutoproxyCacheService
{
private IMemoryCache _cache;
private IDataStore _data;
private DbConnectionFactory _conn;
public AutoproxyCacheService(IMemoryCache cache, DbConnectionFactory conn, IDataStore data)
{
_cache = cache;
_conn = conn;
_data = data;
}
public async Task<AutoproxyCacheResult> GetGuildSettings(ulong account, ulong guild) =>
await _cache.GetOrCreateAsync(GetKey(account, guild), entry => FetchSettings(account, guild, entry));
public async Task FlushCacheForSystem(PKSystem system, ulong guild)
{
foreach (var account in await _data.GetSystemAccounts(system))
FlushCacheFor(account, guild);
}
public void FlushCacheFor(ulong account, ulong guild) =>
_cache.Remove(GetKey(account, guild));
private async Task<AutoproxyCacheResult> FetchSettings(ulong account, ulong guild, ICacheEntry entry)
{
using var conn = await _conn.Obtain();
var data = (await conn.QueryAsync<SystemGuildSettings, PKSystem, PKMember, AutoproxyCacheResult>(
"select system_guild.*, systems.*, members.* from accounts inner join systems on systems.id = accounts.system inner join system_guild on system_guild.system = systems.id left join members on system_guild.autoproxy_member = members.id where accounts.uid = @Uid and system_guild.guild = @Guild",
(guildSettings, system, autoproxyMember) => new AutoproxyCacheResult
{
GuildSettings = guildSettings,
System = system,
AutoproxyMember = autoproxyMember
},
new {Uid = account, Guild = guild})).FirstOrDefault();
if (data != null)
{
// Long expiry for accounts with no system/settings registered
entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
entry.SetAbsoluteExpiration(TimeSpan.FromHours(1));
}
else
{
// Shorter expiry if they already have settings
entry.SetSlidingExpiration(TimeSpan.FromMinutes(1));
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(5));
}
return data;
}
private string GetKey(ulong account, ulong guild) => $"_system_guild_{account}_{guild}";
}
}

View File

@@ -1,82 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Dapper;
using Microsoft.Extensions.Caching.Memory;
using Serilog;
namespace PluralKit.Bot
{
public class ProxyCacheService
{
public class ProxyDatabaseResult
{
public PKSystem System;
public PKMember Member;
}
private DbConnectionFactory _conn;
private IMemoryCache _cache;
private ILogger _logger;
public ProxyCacheService(DbConnectionFactory conn, IMemoryCache cache, ILogger logger)
{
_conn = conn;
_cache = cache;
_logger = logger;
}
public Task<IEnumerable<ProxyDatabaseResult>> GetResultsFor(ulong account)
{
_logger.Verbose("Looking up members for account {Account} in cache...", account);
return _cache.GetOrCreateAsync(GetKey(account), (entry) => FetchResults(account, entry));
}
public void InvalidateResultsFor(ulong account)
{
_logger.Information("Invalidating proxy cache for account {Account}", account);
_cache.Remove(GetKey(account));
}
public async Task InvalidateResultsForSystem(PKSystem system)
{
_logger.Debug("Invalidating proxy cache for system {System}", system.Id);
using (var conn = await _conn.Obtain())
foreach (var accountId in await conn.QueryAsync<ulong>("select uid from accounts where system = @Id", system))
_cache.Remove(GetKey(accountId));
}
private async Task<IEnumerable<ProxyDatabaseResult>> FetchResults(ulong account, ICacheEntry entry)
{
_logger.Debug("Members for account {Account} not in cache, fetching", account);
using (var conn = await _conn.Obtain())
{
var results = (await conn.QueryAsync<PKMember, PKSystem, ProxyDatabaseResult>(
"select members.*, systems.* from members, systems, accounts where members.system = systems.id and accounts.system = systems.id and accounts.uid = @Uid",
(member, system) =>
new ProxyDatabaseResult {Member = member, System = system}, new {Uid = account})).ToList();
if (results.Count == 0)
{
// Long expiry for accounts with no system registered
entry.SetSlidingExpiration(TimeSpan.FromMinutes(5));
entry.SetAbsoluteExpiration(TimeSpan.FromHours(1));
}
else
{
// Shorter expiry if they already have a system
entry.SetSlidingExpiration(TimeSpan.FromMinutes(1));
entry.SetAbsoluteExpiration(TimeSpan.FromMinutes(5));
}
return results;
}
}
private object GetKey(ulong account)
{
return $"_proxy_account_{account}";
}
}
}

View File

@@ -33,23 +33,21 @@ namespace PluralKit.Bot
private EmbedService _embeds;
private ILogger _logger;
private WebhookExecutorService _webhookExecutor;
private ProxyCacheService _cache;
private AutoproxyCacheService _autoproxyCache;
private ProxyCache _cache;
public ProxyService(IDiscordClient client, LogChannelService logChannel, IDataStore data, EmbedService embeds, ILogger logger, ProxyCacheService cache, WebhookExecutorService webhookExecutor, DbConnectionFactory conn, AutoproxyCacheService autoproxyCache)
public ProxyService(IDiscordClient client, LogChannelService logChannel, IDataStore data, EmbedService embeds, ILogger logger, WebhookExecutorService webhookExecutor, DbConnectionFactory conn, ProxyCache cache)
{
_client = client;
_logChannel = logChannel;
_data = data;
_embeds = embeds;
_cache = cache;
_webhookExecutor = webhookExecutor;
_conn = conn;
_autoproxyCache = autoproxyCache;
_cache = cache;
_logger = logger.ForContext<ProxyService>();
}
private ProxyMatch GetProxyTagMatch(string message, IEnumerable<ProxyCacheService.ProxyDatabaseResult> potentialMembers)
private ProxyMatch GetProxyTagMatch(string message, PKSystem system, IEnumerable<PKMember> potentialMembers)
{
// If the message starts with a @mention, and then proceeds to have proxy tags,
// extract the mention and place it inside the inner message
@@ -63,7 +61,7 @@ namespace PluralKit.Bot
}
// Flatten and sort by specificity (ProxyString length desc = prefix+suffix length desc = inner message asc = more specific proxy first!)
var ordered = potentialMembers.SelectMany(m => m.Member.ProxyTags.Select(tag => (tag, m))).OrderByDescending(p => p.Item1.ProxyString.Length);
var ordered = potentialMembers.SelectMany(m => m.ProxyTags.Select(tag => (tag, m))).OrderByDescending(p => p.Item1.ProxyString.Length);
foreach (var (tag, match) in ordered)
{
if (tag.Prefix == null && tag.Suffix == null) continue;
@@ -84,36 +82,35 @@ namespace PluralKit.Bot
if (isMatch) {
var inner = message.Substring(prefix.Length, message.Length - prefix.Length - suffix.Length);
if (leadingMention != null) inner = $"{leadingMention} {inner}";
return new ProxyMatch { Member = match.Member, System = match.System, InnerText = inner, ProxyTags = tag};
return new ProxyMatch { Member = match, System = system, InnerText = inner, ProxyTags = tag};
}
}
return null;
}
public async Task HandleMessageAsync(IMessage message)
public async Task HandleMessageAsync(GuildConfig guild, CachedAccount account, IMessage message)
{
// Bail early if this isn't in a guild channel
if (!(message.Channel is ITextChannel channel)) return;
// Find a member with proxy tags matching the message
var results = (await _cache.GetResultsFor(message.Author.Id)).ToList();
var match = GetProxyTagMatch(message.Content, results);
var match = GetProxyTagMatch(message.Content, account.System, account.Members);
// O(n) lookup since n is small (max ~100 in prod) and we're more constrained by memory (for a dictionary) here
var systemSettingsForGuild = account.SettingsForGuild(channel.GuildId);
// If we didn't get a match by proxy tags, try to get one by autoproxy
if (match == null) match = await GetAutoproxyMatch(message, channel);
if (match == null) match = await GetAutoproxyMatch(account, systemSettingsForGuild, message, channel);
// If we still haven't found any, just yeet
if (match == null) return;
// Gather all "extra" data from DB at once
var aux = await _data.GetAuxillaryProxyInformation(channel.GuildId, match.System, match.Member);
// And make sure the channel's not blacklisted from proxying.
if (aux.Guild.Blacklist.Contains(channel.Id)) return;
if (guild.Blacklist.Contains(channel.Id)) return;
// Make sure the system hasn't blacklisted the guild either
if (!aux.SystemGuild.ProxyEnabled) return;
if (!systemSettingsForGuild.ProxyEnabled) return;
// We know message.Channel can only be ITextChannel as PK doesn't work in DMs/groups
// Afterwards we ensure the bot has the right permissions, otherwise bail early
@@ -122,9 +119,11 @@ namespace PluralKit.Bot
// Can't proxy a message with no content and no attachment
if (match.InnerText.Trim().Length == 0 && message.Attachments.Count == 0)
return;
var memberSettingsForGuild = account.SettingsForMemberGuild(match.Member.Id, channel.GuildId);
// Get variables in order and all
var proxyName = match.Member.ProxyName(match.System.Tag, aux.MemberGuild.DisplayName);
var proxyName = match.Member.ProxyName(match.System.Tag, memberSettingsForGuild.DisplayName);
var avatarUrl = match.Member.AvatarUrl ?? match.System.AvatarUrl;
// If the name's too long (or short), bail
@@ -150,7 +149,7 @@ namespace PluralKit.Bot
// Store the message in the database, and log it in the log channel (if applicable)
await _data.AddMessage(message.Author.Id, hookMessageId, channel.GuildId, message.Channel.Id, message.Id, match.Member);
await _logChannel.LogMessage(match.System, match.Member, hookMessageId, message.Id, message.Channel as IGuildChannel, message.Author, match.InnerText, aux.Guild);
await _logChannel.LogMessage(match.System, match.Member, hookMessageId, message.Id, message.Channel as IGuildChannel, message.Author, match.InnerText, guild);
// Wait a second or so before deleting the original message
await Task.Delay(1000);
@@ -166,25 +165,21 @@ namespace PluralKit.Bot
}
}
private async Task<ProxyMatch> GetAutoproxyMatch(IMessage message, IGuildChannel channel)
private async Task<ProxyMatch> GetAutoproxyMatch(CachedAccount account, SystemGuildSettings guildSettings, IMessage message, IGuildChannel channel)
{
// For now we use a backslash as an "escape character", subject to change later
if ((message.Content ?? "").TrimStart().StartsWith("\\")) return null;
// Fetch info from the cache, bail if we don't have anything (either no system or no autoproxy settings - AP defaults to off so this works)
var autoproxyCache = await _autoproxyCache.GetGuildSettings(message.Author.Id, channel.GuildId);
if (autoproxyCache == null) return null;
PKMember member = null;
// Figure out which member to proxy as
switch (autoproxyCache.GuildSettings.AutoproxyMode)
switch (guildSettings.AutoproxyMode)
{
case AutoproxyMode.Off:
// Autoproxy off, bail
return null;
case AutoproxyMode.Front:
// Front mode: just use the current first fronter
member = await _data.GetFirstFronter(autoproxyCache.System);
member = await _data.GetFirstFronter(account.System);
break;
case AutoproxyMode.Latch:
// Latch mode: find last proxied message, use *that* member
@@ -201,7 +196,8 @@ namespace PluralKit.Bot
break;
case AutoproxyMode.Member:
// Member mode: just use that member
member = autoproxyCache.AutoproxyMember;
// O(n) lookup since n is small (max 1000 de jure) and we're more constrained by memory (for a dictionary) here
member = account.Members.FirstOrDefault(m => m.Id == guildSettings.AutoproxyMember);
break;
}
@@ -209,7 +205,7 @@ namespace PluralKit.Bot
if (member == null) return null;
return new ProxyMatch
{
System = autoproxyCache.System,
System = account.System,
Member = member,
// Autoproxying members with no proxy tags is possible, return the correct result
ProxyTags = member.ProxyTags.Count > 0 ? member.ProxyTags.First() : (ProxyTag?) null,