feat: rewrite database schema for localized autoproxy

This commit is contained in:
spiral 2022-03-21 23:43:33 -04:00
parent ca108813b7
commit 982812333b
No known key found for this signature in database
GPG Key ID: 244A11E4B0BCF40E
16 changed files with 245 additions and 188 deletions

View File

@ -24,11 +24,7 @@ public class DiscordControllerV2: PKControllerBase
if (settings == null)
throw Errors.SystemGuildNotFound;
PKMember member = null;
if (settings.AutoproxyMember != null)
member = await _repo.GetMember(settings.AutoproxyMember.Value);
return Ok(settings.ToJson(member?.Uuid.ToString()));
return Ok(settings.ToJson());
}
[HttpPatch("systems/{systemRef}/guilds/{guild_id}")]
@ -42,61 +38,14 @@ public class DiscordControllerV2: PKControllerBase
if (settings == null)
throw Errors.SystemGuildNotFound;
MemberId? memberId = null;
if (data.ContainsKey("autoproxy_member"))
{
if (data["autoproxy_member"].Type != JTokenType.Null)
{
var member = await ResolveMember(data.Value<string>("autoproxy_member"));
if (member == null)
throw Errors.MemberNotFound;
memberId = member.Id;
}
}
else
{
memberId = settings.AutoproxyMember;
}
var patch = SystemGuildPatch.FromJson(data, memberId);
var patch = SystemGuildPatch.FromJson(data);
patch.AssertIsValid();
if (patch.Errors.Count > 0)
throw new ModelParseError(patch.Errors);
// this is less than great, but at least it's legible
if (patch.AutoproxyMember.Value == null)
{
if (patch.AutoproxyMode.IsPresent)
{
if (patch.AutoproxyMode.Value == AutoproxyMode.Member)
throw Errors.MissingAutoproxyMember;
}
else if (settings.AutoproxyMode == AutoproxyMode.Member)
{
throw Errors.MissingAutoproxyMember;
}
}
else
{
if (patch.AutoproxyMode.IsPresent)
{
if (patch.AutoproxyMode.Value == AutoproxyMode.Latch)
throw Errors.PatchLatchMemberError;
}
else if (settings.AutoproxyMode == AutoproxyMode.Latch)
{
throw Errors.PatchLatchMemberError;
}
}
var newSettings = await _repo.UpdateSystemGuild(system.Id, guild_id, patch);
PKMember? newMember = null;
if (newSettings.AutoproxyMember != null)
newMember = await _repo.GetMember(newSettings.AutoproxyMember.Value);
return Ok(newSettings.ToJson(newMember?.Hid));
return Ok(newSettings.ToJson());
}
[HttpGet("members/{memberRef}/guilds/{guild_id}")]

View File

@ -16,25 +16,29 @@ public class Autoproxy
// no need to check account here, it's already done at CommandTree
ctx.CheckGuildContext();
// for now, just for guild
// this also creates settings if there are none present
var settings = await ctx.Repository.GetAutoproxySettings(ctx.System.Id, ctx.Guild.Id, null);
if (ctx.Match("off", "stop", "cancel", "no", "disable", "remove"))
await AutoproxyOff(ctx);
await AutoproxyOff(ctx, settings);
else if (ctx.Match("latch", "last", "proxy", "stick", "sticky"))
await AutoproxyLatch(ctx);
await AutoproxyLatch(ctx, settings);
else if (ctx.Match("front", "fronter", "switch"))
await AutoproxyFront(ctx);
await AutoproxyFront(ctx, settings);
else if (ctx.Match("member"))
throw new PKSyntaxError("Member-mode autoproxy must target a specific member. Use the `pk;autoproxy <member>` command, where `member` is the name or ID of a member in your system.");
else if (await ctx.MatchMember() is PKMember member)
await AutoproxyMember(ctx, member);
else if (!ctx.HasNext())
await ctx.Reply(embed: await CreateAutoproxyStatusEmbed(ctx));
await ctx.Reply(embed: await CreateAutoproxyStatusEmbed(ctx, settings));
else
throw new PKSyntaxError($"Invalid autoproxy mode {ctx.PopArgument().AsCode()}.");
}
private async Task AutoproxyOff(Context ctx)
private async Task AutoproxyOff(Context ctx, AutoproxySettings settings)
{
if (ctx.MessageContext.AutoproxyMode == AutoproxyMode.Off)
if (settings.AutoproxyMode == AutoproxyMode.Off)
{
await ctx.Reply($"{Emojis.Note} Autoproxy is already off in this server.");
}
@ -45,9 +49,9 @@ public class Autoproxy
}
}
private async Task AutoproxyLatch(Context ctx)
private async Task AutoproxyLatch(Context ctx, AutoproxySettings settings)
{
if (ctx.MessageContext.AutoproxyMode == AutoproxyMode.Latch)
if (settings.AutoproxyMode == AutoproxyMode.Latch)
{
await ctx.Reply($"{Emojis.Note} Autoproxy is already set to latch mode in this server. If you want to disable autoproxying, use `pk;autoproxy off`.");
}
@ -58,9 +62,9 @@ public class Autoproxy
}
}
private async Task AutoproxyFront(Context ctx)
private async Task AutoproxyFront(Context ctx, AutoproxySettings settings)
{
if (ctx.MessageContext.AutoproxyMode == AutoproxyMode.Front)
if (settings.AutoproxyMode == AutoproxyMode.Front)
{
await ctx.Reply($"{Emojis.Note} Autoproxy is already set to front mode in this server. If you want to disable autoproxying, use `pk;autoproxy off`.");
}
@ -75,11 +79,13 @@ public class Autoproxy
{
ctx.CheckOwnMember(member);
// todo: why does this not throw an error if the member is already set
await UpdateAutoproxy(ctx, AutoproxyMode.Member, member.Id);
await ctx.Reply($"{Emojis.Success} Autoproxy set to **{member.NameFor(ctx)}** in this server.");
}
private async Task<Embed> CreateAutoproxyStatusEmbed(Context ctx)
private async Task<Embed> CreateAutoproxyStatusEmbed(Context ctx, AutoproxySettings settings)
{
var commandList = "**pk;autoproxy latch** - Autoproxies as last-proxied member"
+ "\n**pk;autoproxy front** - Autoproxies as current (first) fronter"
@ -88,14 +94,16 @@ public class Autoproxy
.Title($"Current autoproxy status (for {ctx.Guild.Name.EscapeMarkdown()})");
var fronters = ctx.MessageContext.LastSwitchMembers;
var relevantMember = ctx.MessageContext.AutoproxyMode switch
var relevantMember = settings.AutoproxyMode switch
{
AutoproxyMode.Front => fronters.Length > 0 ? await ctx.Repository.GetMember(fronters[0]) : null,
AutoproxyMode.Member when ctx.MessageContext.AutoproxyMember.HasValue => await ctx.Repository.GetMember(ctx.MessageContext.AutoproxyMember.Value),
AutoproxyMode.Member when settings.AutoproxyMember.HasValue => await ctx.Repository.GetMember(settings.AutoproxyMember.Value),
_ => null
};
switch (ctx.MessageContext.AutoproxyMode)
Console.WriteLine(settings.AutoproxyMode);
switch (settings.AutoproxyMode)
{
case AutoproxyMode.Off:
eb.Description($"Autoproxy is currently **off** in this server. To enable it, use one of the following commands:\n{commandList}");
@ -136,9 +144,7 @@ public class Autoproxy
private async Task UpdateAutoproxy(Context ctx, AutoproxyMode autoproxyMode, MemberId? autoproxyMember)
{
await ctx.Repository.GetSystemGuild(ctx.Guild.Id, ctx.System.Id);
var patch = new SystemGuildPatch { AutoproxyMode = autoproxyMode, AutoproxyMember = autoproxyMember };
await ctx.Repository.UpdateSystemGuild(ctx.System.Id, ctx.Guild.Id, patch);
var patch = new AutoproxyPatch { AutoproxyMode = autoproxyMode, AutoproxyMember = autoproxyMember };
await ctx.Repository.UpdateAutoproxy(ctx.System.Id, ctx.Guild.Id, null, patch);
}
}

View File

@ -259,11 +259,16 @@ public class Checks
var context = await ctx.Repository.GetMessageContext(msg.Author.Id, channel.GuildId.Value, msg.ChannelId);
var members = (await ctx.Repository.GetProxyMembers(msg.Author.Id, channel.GuildId.Value)).ToList();
// for now this is just server
var autoproxySettings = await ctx.Repository.GetAutoproxySettings(ctx.System.Id, channel.GuildId.Value, null);
// todo: match unlatch
// Run everything through the checks, catch the ProxyCheckFailedException, and reply with the error message.
try
{
_proxy.ShouldProxy(channel, msg, context);
_matcher.TryMatch(context, members, out var match, msg.Content, msg.Attachments.Length > 0,
_matcher.TryMatch(context, autoproxySettings, members, out var match, msg.Content, msg.Attachments.Length > 0,
context.AllowAutoproxy);
await ctx.Reply("I'm not sure why this message was not proxied, sorry.");

View File

@ -18,12 +18,12 @@ public class ProxyMatcher
_clock = clock;
}
public bool TryMatch(MessageContext ctx, IReadOnlyCollection<ProxyMember> members, out ProxyMatch match,
public bool TryMatch(MessageContext ctx, AutoproxySettings settings, IReadOnlyCollection<ProxyMember> members, out ProxyMatch match,
string messageContent,
bool hasAttachments, bool allowAutoproxy)
{
if (TryMatchTags(members, messageContent, hasAttachments, out match)) return true;
if (allowAutoproxy && TryMatchAutoproxy(ctx, members, messageContent, out match)) return true;
if (allowAutoproxy && TryMatchAutoproxy(ctx, settings, members, messageContent, out match)) return true;
return false;
}
@ -37,7 +37,7 @@ public class ProxyMatcher
return hasAttachments || match.Content.Trim().Length > 0;
}
private bool TryMatchAutoproxy(MessageContext ctx, IReadOnlyCollection<ProxyMember> members,
private bool TryMatchAutoproxy(MessageContext ctx, AutoproxySettings settings, IReadOnlyCollection<ProxyMember> members,
string messageContent,
out ProxyMatch match)
{
@ -49,41 +49,41 @@ public class ProxyMatcher
"This message matches none of your proxy tags, and it was not autoproxied because it starts with a backslash (`\\`).");
// Find the member we should autoproxy (null if none)
var member = ctx.AutoproxyMode switch
var member = settings.AutoproxyMode switch
{
AutoproxyMode.Member when ctx.AutoproxyMember != null =>
members.FirstOrDefault(m => m.Id == ctx.AutoproxyMember),
AutoproxyMode.Member when settings.AutoproxyMember != null =>
members.FirstOrDefault(m => m.Id == settings.AutoproxyMember),
AutoproxyMode.Front when ctx.LastSwitchMembers.Length > 0 =>
members.FirstOrDefault(m => m.Id == ctx.LastSwitchMembers[0]),
AutoproxyMode.Latch when ctx.LastMessageMember != null =>
members.FirstOrDefault(m => m.Id == ctx.LastMessageMember.Value),
AutoproxyMode.Latch when settings.AutoproxyMember != null =>
members.FirstOrDefault(m => m.Id == settings.AutoproxyMember.Value),
_ => null
};
// Throw an error if the member is null, message varies depending on autoproxy mode
if (member == null)
{
if (ctx.AutoproxyMode == AutoproxyMode.Front)
if (settings.AutoproxyMode == AutoproxyMode.Front)
throw new ProxyService.ProxyChecksFailedException(
"You are using autoproxy front, but no members are currently registered as fronting. Please use `pk;switch <member>` to log a new switch.");
if (ctx.AutoproxyMode == AutoproxyMode.Member)
if (settings.AutoproxyMode == AutoproxyMode.Member)
throw new ProxyService.ProxyChecksFailedException(
"You are using member-specific autoproxy with an invalid member. Was this member deleted?");
if (ctx.AutoproxyMode == AutoproxyMode.Latch)
if (settings.AutoproxyMode == AutoproxyMode.Latch)
throw new ProxyService.ProxyChecksFailedException(
"You are using autoproxy latch, but have not sent any messages yet in this server. Please send a message using proxy tags first.");
throw new ProxyService.ProxyChecksFailedException(
"This message matches none of your proxy tags and autoproxy is not enabled.");
}
if (ctx.AutoproxyMode != AutoproxyMode.Member && !member.AllowAutoproxy)
if (settings.AutoproxyMode != AutoproxyMode.Member && !member.AllowAutoproxy)
throw new ProxyService.ProxyChecksFailedException(
"This member has autoproxy disabled. To enable it, use `pk;m <member> autoproxy on`.");
// Moved the IsLatchExpired() check to here, so that an expired latch and a latch without any previous messages throw different errors
if (ctx.AutoproxyMode == AutoproxyMode.Latch && IsLatchExpired(ctx))
if (settings.AutoproxyMode == AutoproxyMode.Latch && IsLatchExpired(ctx, settings))
throw new ProxyService.ProxyChecksFailedException(
"Latch-mode autoproxy has timed out. Please send a new message using proxy tags.");
@ -99,16 +99,14 @@ public class ProxyMatcher
return true;
}
private bool IsLatchExpired(MessageContext ctx)
private bool IsLatchExpired(MessageContext ctx, AutoproxySettings settings)
{
if (ctx.LastMessage == null) return true;
if (ctx.LatchTimeout == 0) return false;
var timeout = ctx.LatchTimeout.HasValue
? Duration.FromSeconds(ctx.LatchTimeout.Value)
: DefaultLatchExpiryTime;
var timestamp = DiscordUtils.SnowflakeToInstant(ctx.LastMessage.Value);
return _clock.GetCurrentInstant() - timestamp > timeout;
return _clock.GetCurrentInstant() - settings.LastLatchTimestamp > timeout;
}
}

View File

@ -32,10 +32,11 @@ public class ProxyService
private readonly ModelRepository _repo;
private readonly DiscordApiClient _rest;
private readonly WebhookExecutorService _webhookExecutor;
private readonly NodaTime.IClock _clock;
public ProxyService(LogChannelService logChannel, ILogger logger, WebhookExecutorService webhookExecutor,
DispatchService dispatch, IDatabase db, ProxyMatcher matcher, IMetrics metrics, ModelRepository repo,
IDiscordCache cache, DiscordApiClient rest, LastMessageCacheService lastMessage)
NodaTime.IClock clock, IDiscordCache cache, DiscordApiClient rest, LastMessageCacheService lastMessage)
{
_logChannel = logChannel;
_webhookExecutor = webhookExecutor;
@ -47,6 +48,7 @@ public class ProxyService
_cache = cache;
_lastMessage = lastMessage;
_rest = rest;
_clock = clock;
_logger = logger.ForContext<ProxyService>();
}
@ -56,6 +58,17 @@ public class ProxyService
if (!ShouldProxy(channel, message, ctx))
return false;
var autoproxySettings = await _repo.GetAutoproxySettings(ctx.SystemId.Value, guild.Id, null);
if (autoproxySettings.AutoproxyMode == AutoproxyMode.Latch && IsUnlatch(message))
{
// "unlatch"
await _repo.UpdateAutoproxy(ctx.SystemId.Value, guild.Id, null, new() {
AutoproxyMember = null
});
return false;
}
var rootChannel = await _cache.GetRootChannel(message.ChannelId);
List<ProxyMember> members;
@ -63,7 +76,7 @@ public class ProxyService
using (_metrics.Measure.Timer.Time(BotMetrics.ProxyMembersQueryTime))
members = (await _repo.GetProxyMembers(message.Author.Id, message.GuildId!.Value)).ToList();
if (!_matcher.TryMatch(ctx, members, out var match, message.Content, message.Attachments.Length > 0,
if (!_matcher.TryMatch(ctx, autoproxySettings, members, out var match, message.Content, message.Attachments.Length > 0,
allowAutoproxy)) return false;
// this is hopefully temporary, so not putting it into a separate method
@ -84,7 +97,7 @@ public class ProxyService
var allowEmbeds = senderPermissions.HasFlag(PermissionSet.EmbedLinks);
// Everything's in order, we can execute the proxy!
await ExecuteProxy(message, ctx, match, allowEveryone, allowEmbeds);
await ExecuteProxy(message, ctx, autoproxySettings, match, allowEveryone, allowEmbeds);
return true;
}
@ -129,7 +142,7 @@ public class ProxyService
return true;
}
private async Task ExecuteProxy(Message trigger, MessageContext ctx,
private async Task ExecuteProxy(Message trigger, MessageContext ctx, AutoproxySettings autoproxySettings,
ProxyMatch match, bool allowEveryone, bool allowEmbeds)
{
// Create reply embed
@ -171,7 +184,7 @@ public class ProxyService
Stickers = trigger.StickerItems,
AllowEveryone = allowEveryone
});
await HandleProxyExecutedActions(ctx, trigger, proxyMessage, match);
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
}
private async Task<(string?, string?)> FetchReferencedMessageAuthorInfo(Message trigger, Message referenced)
@ -290,7 +303,11 @@ public class ProxyService
private string FixSameNameInner(string name)
=> $"{name}\u17b5";
private async Task HandleProxyExecutedActions(MessageContext ctx, Message triggerMessage, Message proxyMessage, ProxyMatch match)
public static bool IsUnlatch(Message message)
=> message.Content.StartsWith(@"\\") || message.Content.StartsWith("\\\u200b\\");
private async Task HandleProxyExecutedActions(MessageContext ctx, AutoproxySettings autoproxySettings,
Message triggerMessage, Message proxyMessage, ProxyMatch match)
{
var sentMessage = new PKMessage
{
@ -308,6 +325,14 @@ public class ProxyService
Task LogMessageToChannel() =>
_logChannel.LogMessage(ctx, sentMessage, triggerMessage, proxyMessage).AsTask();
Task SaveLatchAutoproxy() => autoproxySettings.AutoproxyMode == AutoproxyMode.Latch
? _repo.UpdateAutoproxy(ctx.SystemId.Value, triggerMessage.GuildId, null, new()
{
AutoproxyMember = match.Member.Id,
LastLatchTimestamp = _clock.GetCurrentInstant(),
})
: Task.CompletedTask;
Task DispatchWebhook() => _dispatch.Dispatch(ctx.SystemId.Value, sentMessage);
async Task DeleteProxyTriggerMessage()
@ -333,6 +358,7 @@ public class ProxyService
DeleteProxyTriggerMessage(),
SaveMessageInDatabase(),
LogMessageToChannel(),
SaveLatchAutoproxy(),
DispatchWebhook()
);
}

View File

@ -15,10 +15,6 @@ public class MessageContext
public bool InLogBlacklist { get; }
public bool LogCleanupEnabled { get; }
public bool ProxyEnabled { get; }
public AutoproxyMode AutoproxyMode { get; }
public MemberId? AutoproxyMember { get; }
public ulong? LastMessage { get; }
public MemberId? LastMessageMember { get; }
public SwitchId? LastSwitch { get; }
public MemberId[] LastSwitchMembers { get; } = new MemberId[0];
public Instant? LastSwitchTimestamp { get; }

View File

@ -6,10 +6,6 @@
in_log_blacklist bool,
log_cleanup_enabled bool,
proxy_enabled bool,
autoproxy_mode int,
autoproxy_member int,
last_message bigint,
last_message_member int,
last_switch int,
last_switch_members int[],
last_switch_timestamp timestamp,
@ -28,8 +24,7 @@ as $$
left join system_config on system_config.system = accounts.system
left join system_guild on system_guild.system = accounts.system and system_guild.guild = guild_id
where accounts.uid = account_id),
guild as (select * from servers where id = guild_id),
last_message as (select * from messages where messages.guild = guild_id and messages.sender = account_id order by mid desc limit 1)
guild as (select * from servers where id = guild_id)
select
system.id as system_id,
guild.log_channel,
@ -37,10 +32,6 @@ as $$
(channel_id = any(guild.log_blacklist)) as in_log_blacklist,
coalesce(guild.log_cleanup_enabled, false),
coalesce(system_guild.proxy_enabled, true) as proxy_enabled,
coalesce(system_guild.autoproxy_mode, 1) as autoproxy_mode,
system_guild.autoproxy_member,
last_message.mid as last_message,
last_message.member as last_message_member,
system_last_switch.switch as last_switch,
system_last_switch.members as last_switch_members,
system_last_switch.timestamp as last_switch_timestamp,
@ -55,7 +46,6 @@ as $$
from (select 1) as _placeholder
left join system on true
left join guild on true
left join last_message on true
left join system_last_switch on system_last_switch.system = system.id
left join system_guild on system_guild.system = system.id and system_guild.guild = guild_id
$$ language sql stable rows 1;

View File

@ -0,0 +1,36 @@
-- schema version 27
-- autoproxy locations
-- mode pseudo-enum: (copied from 3.sql)
-- 1 = autoproxy off
-- 2 = front mode (first fronter)
-- 3 = latch mode (last proxyer)
-- 4 = member mode (specific member)
create table autoproxy (
system int references systems(id) on delete cascade,
channel_id bigint,
guild_id bigint,
autoproxy_mode int check (mode in (1, 2, 3, 4)) not null default 1,
autoproxy_member int references members(id) on delete set null,
last_latch_timestamp timestamp,
check (
(channel_id = 0 and guild_id = 0)
or (channel_id != 0 and guild_id = 0)
or (channel_id = 0 and guild_id != 0)
),
primary key (system, channel_id, guild_id)
);
insert into autoproxy select
system,
0 as channel_id,
guild as guild_id,
autoproxy_mode,
autoproxy_member
from system_guild;
alter table system_guild drop column autoproxy_mode;
alter table system_guild drop column autoproxy_member;
update info set schema_version = 27;

View File

@ -0,0 +1,35 @@
using Dapper;
using SqlKata;
namespace PluralKit.Core;
public partial class ModelRepository
{
public async Task UpdateAutoproxy(SystemId system, ulong? guildId, ulong? channelId, AutoproxyPatch patch)
{
var locationStr = guildId != null ? "guild" : (channelId != null ? "channel" : "global");
_logger.Information("Updated autoproxy for {SystemId} in location {location}: {@AutoproxyPatch}", system, locationStr, patch);
var query = patch.Apply(new Query("autoproxy")
.Where("system", system)
.Where("guild_id", guildId ?? 0)
.Where("channel_id", channelId ?? 0)
);
_ = _dispatch.Dispatch(system, guildId, channelId, patch);
await _db.ExecuteQuery(query);
}
// todo: this might break with differently scoped autoproxy
public async Task<AutoproxySettings> GetAutoproxySettings(SystemId system, ulong? guildId, ulong? channelId)
=> await _db.QueryFirst<AutoproxySettings>(new Query("autoproxy").AsInsert(new {
system = system,
guild_id = guildId ?? 0,
channel_id = channelId ?? 0,
})
.Where("system", system)
.Where("guild_id", guildId ?? 0)
.Where("channel_id", channelId ?? 0),
"on conflict (system, guild_id, channel_id) do update set system = $1 returning *"
);
}

View File

@ -9,7 +9,7 @@ namespace PluralKit.Core;
internal class DatabaseMigrator
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 26;
private const int TargetSchemaVersion = 27;
private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger)

View File

@ -38,6 +38,12 @@ public class DispatchService
}
}
public Task Dispatch(SystemId systemId, ulong? guildId, ulong? channelId, AutoproxyPatch patch)
{
// todo
return Task.CompletedTask;
}
public async Task Dispatch(SystemId systemId, UpdateDispatchData data)
{
if (data.EventData != null && data.EventData.Count == 0)
@ -159,18 +165,11 @@ public class DispatchService
if (system.WebhookUrl == null)
return;
string memberRef = null;
if (patch.AutoproxyMember.Value != null)
{
var member = await repo.GetMember(patch.AutoproxyMember.Value.Value);
memberRef = member.Uuid.ToString();
}
var data = new UpdateDispatchData();
data.Event = DispatchEvent.UPDATE_SYSTEM_GUILD;
data.SigningToken = system.WebhookToken;
data.SystemId = system.Uuid.ToString();
data.EventData = patch.ToJson(memberRef, guild_id);
data.EventData = patch.ToJson(guild_id);
_logger.Debug("Dispatching webhook for system {SystemId} in guild {GuildId}", system.Id, guild_id);
await DoPostRequest(system.Id, system.WebhookUrl, data.GetPayloadBody());

View File

@ -0,0 +1,59 @@
using Newtonsoft.Json.Linq;
using NodaTime;
namespace PluralKit.Core;
public enum AutoproxyMode
{
Off = 1,
Front = 2,
Latch = 3,
Member = 4
}
public class AutoproxySettings
{
public AutoproxyMode AutoproxyMode { get; }
public MemberId? AutoproxyMember { get; }
public Instant LastLatchTimestamp { get; }
}
public static class AutoproxyExt
{
public static JObject ToJson(this AutoproxySettings settings, string? memberHid = null)
{
var o = new JObject();
// tbd
o.Add("autoproxy_mode", settings.AutoproxyMode.ToString().ToLower());
o.Add("autoproxy_member", memberHid);
return o;
}
public static (AutoproxyMode?, ValidationError?) ParseAutoproxyMode(this JToken o)
{
if (o.Type == JTokenType.Null)
return (AutoproxyMode.Off, null);
if (o.Type != JTokenType.String)
return (null, new ValidationError("autoproxy_mode"));
var value = o.Value<string>();
switch (value)
{
case "off":
return (AutoproxyMode.Off, null);
case "front":
return (AutoproxyMode.Front, null);
case "latch":
return (AutoproxyMode.Latch, null);
case "member":
return (AutoproxyMode.Member, null);
default:
return (null,
new ValidationError("autoproxy_mode", $"Value '{value}' is not a valid autoproxy mode."));
}
}
}

View File

@ -0,0 +1,21 @@
using Newtonsoft.Json.Linq;
using NodaTime;
using SqlKata;
namespace PluralKit.Core;
public class AutoproxyPatch : PatchObject
{
public Partial<AutoproxyMode> AutoproxyMode { get; set; }
public Partial<MemberId?> AutoproxyMember { get; set; }
public Partial<Instant> LastLatchTimestamp { get; set; }
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
.With("autoproxy_mode", AutoproxyMode)
.With("autoproxy_member", AutoproxyMember)
.With("last_latch_timestamp", LastLatchTimestamp)
);
}

View File

@ -9,15 +9,11 @@ namespace PluralKit.Core;
public class SystemGuildPatch: PatchObject
{
public Partial<bool> ProxyEnabled { get; set; }
public Partial<AutoproxyMode> AutoproxyMode { get; set; }
public Partial<MemberId?> AutoproxyMember { get; set; }
public Partial<string?> Tag { get; set; }
public Partial<bool?> TagEnabled { get; set; }
public override Query Apply(Query q) => q.ApplyPatch(wrapper => wrapper
.With("proxy_enabled", ProxyEnabled)
.With("autoproxy_mode", AutoproxyMode)
.With("autoproxy_member", AutoproxyMember)
.With("tag", Tag)
.With("tag_enabled", TagEnabled)
);
@ -29,24 +25,13 @@ public class SystemGuildPatch: PatchObject
}
#nullable disable
public static SystemGuildPatch FromJson(JObject o, MemberId? memberId)
public static SystemGuildPatch FromJson(JObject o)
{
var patch = new SystemGuildPatch();
if (o.ContainsKey("proxying_enabled") && o["proxying_enabled"].Type != JTokenType.Null)
patch.ProxyEnabled = o.Value<bool>("proxying_enabled");
if (o.ContainsKey("autoproxy_mode"))
{
var (val, err) = o["autoproxy_mode"].ParseAutoproxyMode();
if (err != null)
patch.Errors.Add(err);
else
patch.AutoproxyMode = val.Value;
}
patch.AutoproxyMember = memberId;
if (o.ContainsKey("tag"))
patch.Tag = o.Value<string>("tag").NullIfEmpty();
@ -56,7 +41,7 @@ public class SystemGuildPatch: PatchObject
return patch;
}
public JObject ToJson(string memberRef, ulong guild_id)
public JObject ToJson(ulong guild_id)
{
var o = new JObject();
@ -65,12 +50,6 @@ public class SystemGuildPatch: PatchObject
if (ProxyEnabled.IsPresent)
o.Add("proxying_enabled", ProxyEnabled.Value);
if (AutoproxyMode.IsPresent)
o.Add("autoproxy_mode", AutoproxyMode.Value.ToString().ToLower());
if (AutoproxyMember.IsPresent)
o.Add("autoproxy_member", memberRef);
if (Tag.IsPresent)
o.Add("tag", Tag.Value);

View File

@ -1,68 +1,26 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using Newtonsoft.Json.Linq;
namespace PluralKit.Core;
[JsonConverter(typeof(StringEnumConverter))]
public enum AutoproxyMode
{
Off = 1,
Front = 2,
Latch = 3,
Member = 4
}
public class SystemGuildSettings
{
public ulong Guild { get; }
public SystemId System { get; }
public bool ProxyEnabled { get; } = true;
public AutoproxyMode AutoproxyMode { get; } = AutoproxyMode.Off;
public MemberId? AutoproxyMember { get; }
public string? Tag { get; }
public bool TagEnabled { get; }
}
public static class SystemGuildExt
{
public static JObject ToJson(this SystemGuildSettings settings, string? memberHid = null)
public static JObject ToJson(this SystemGuildSettings settings)
{
var o = new JObject();
o.Add("proxying_enabled", settings.ProxyEnabled);
o.Add("autoproxy_mode", settings.AutoproxyMode.ToString().ToLower());
o.Add("autoproxy_member", memberHid);
o.Add("tag", settings.Tag);
o.Add("tag_enabled", settings.TagEnabled);
return o;
}
public static (AutoproxyMode?, ValidationError?) ParseAutoproxyMode(this JToken o)
{
if (o.Type == JTokenType.Null)
return (AutoproxyMode.Off, null);
if (o.Type != JTokenType.String)
return (null, new ValidationError("autoproxy_mode"));
var value = o.Value<string>();
switch (value)
{
case "off":
return (AutoproxyMode.Off, null);
case "front":
return (AutoproxyMode.Front, null);
case "latch":
return (AutoproxyMode.Latch, null);
case "member":
return (AutoproxyMode.Member, null);
default:
return (null,
new ValidationError("autoproxy_mode", $"Value '{value}' is not a valid autoproxy mode."));
}
}
}

View File

@ -120,11 +120,10 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID.
|---|---|---|
|guild_id|snowflake|only sent if the guild ID isn't already known (in dispatch payloads)|
|proxying_enabled|boolean||
|autoproxy_mode|autoproxy mode enum||
|autoproxy_member|?string|must be set if autoproxy_mode is `member`|
|tag|?string|79-character limit|
|tag_enabled|boolean||
<!--
#### Autoproxy mode enum
|key|description|
@ -133,6 +132,7 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID.
|front|autoproxy is set to the first member in the current fronters list, or disabled if the current switch contains no members|
|latch|autoproxy is set to the last member who sent a proxied message in the server|
|member|autoproxy is set to a specific member (see `autoproxy_member` key)|
-->
### Member guild settings model