Merge pull request #252 from dev-kittens/feat/ap

Autoproxy improvements
This commit is contained in:
Astrid 2020-12-08 12:25:08 +01:00 committed by GitHub
commit ab3ccac60f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 256 additions and 22 deletions

View File

@ -20,9 +20,10 @@ namespace PluralKit.Bot
_repo = repo;
}
public async Task AutoproxyRoot(Context ctx)
public async Task SetAutoproxyMode(Context ctx)
{
ctx.CheckSystem().CheckGuildContext();
// no need to check account here, it's already done at CommandTree
ctx.CheckGuildContext();
if (ctx.Match("off", "stop", "cancel", "no", "disable", "remove"))
await AutoproxyOff(ctx);
@ -122,9 +123,70 @@ namespace PluralKit.Bot
default: throw new ArgumentOutOfRangeException();
}
if (!ctx.MessageContext.AllowAutoproxy)
eb.AddField("\u200b", $"{Emojis.Note} Autoproxy is currently **disabled** for your account (<@{ctx.Author.Id}>). To enable it, use `pk;autoproxy account enable`.");
return eb.Build();
}
public async Task AutoproxyTimeout(Context ctx)
{
if (!ctx.HasNext())
{
if (ctx.System.LatchTimeout == -1)
await ctx.Reply($"You do not have a custom autoproxy timeout duration set. The default latch timeout duration is {PluralKit.Bot.ProxyMatcher.DefaultLatchExpiryTime} hour(s).");
else if (ctx.System.LatchTimeout == 0)
await ctx.Reply("Latch timeout is currently **disabled** for your system. Latch mode autoproxy will never timeout.");
else
await ctx.Reply($"The current latch timeout duration for your system is {ctx.System.LatchTimeout} hour(s).");
return;
}
// todo: somehow parse a more human-friendly date format
int newTimeout;
if (ctx.Match("off", "stop", "cancel", "no", "disable", "remove")) newTimeout = 0;
else if (ctx.Match("reset", "default")) newTimeout = -1;
else if (!int.TryParse(ctx.RemainderOrNull(), out newTimeout)) throw new PKError("Duration must be an integer.");
await _db.Execute(conn => _repo.UpdateSystem(conn, ctx.System.Id, new SystemPatch{LatchTimeout = newTimeout}));
if (newTimeout == -1)
await ctx.Reply($"{Emojis.Success} Latch timeout reset to default ({PluralKit.Bot.ProxyMatcher.DefaultLatchExpiryTime} hours).");
else if (newTimeout == 0)
await ctx.Reply($"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never timeout.");
else
await ctx.Reply($"{Emojis.Success} Latch timeout set to {newTimeout} hours.");
}
public async Task AutoproxyAccount(Context ctx)
{
// todo: this might be useful elsewhere, consider moving it to ctx.MatchToggle
if (ctx.Match("enable", "on"))
await AutoproxyEnableDisable(ctx, true);
else if (ctx.Match("disable", "off"))
await AutoproxyEnableDisable(ctx, false);
else if (ctx.HasNext())
throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
else
{
var statusString = ctx.MessageContext.AllowAutoproxy ? "enabled" : "disabled";
await ctx.Reply($"Autoproxy is currently **{statusString}** for account <@{ctx.Author.Id}>.", mentions: new IMention[]{});
}
}
private async Task AutoproxyEnableDisable(Context ctx, bool allow)
{
var statusString = allow ? "enabled" : "disabled";
if (ctx.MessageContext.AllowAutoproxy == allow)
{
await ctx.Reply($"{Emojis.Note} Autoproxy is already {statusString} for account <@{ctx.Author.Id}>.", mentions: new IMention[]{});
return;
}
var patch = new AccountPatch { AllowAutoproxy = allow };
await _db.Execute(conn => _repo.UpdateAccount(conn, ctx.Author.Id, patch));
await ctx.Reply($"{Emojis.Success} Autoproxy {statusString} for account <@{ctx.Author.Id}>.", mentions: new IMention[]{});
}
private Task UpdateAutoproxy(Context ctx, AutoproxyMode autoproxyMode, MemberId? autoproxyMember)
{
var patch = new SystemGuildPatch {AutoproxyMode = autoproxyMode, AutoproxyMember = autoproxyMember};

View File

@ -28,7 +28,9 @@ namespace PluralKit.Bot
public static Command SystemFrontPercent = new Command("system frontpercent", "system [system] frontpercent [timespan]", "Shows a system's front breakdown");
public static Command SystemPing = new Command("system ping", "system ping <enable|disable>", "Changes your system's ping preferences");
public static Command SystemPrivacy = new Command("system privacy", "system privacy <description|members|fronter|fronthistory|all> <public|private>", "Changes your system's privacy settings");
public static Command Autoproxy = new Command("autoproxy", "autoproxy [off|front|latch|member]", "Sets your system's autoproxy mode for this server");
public static Command AutoproxySet = new Command("autoproxy", "autoproxy [off|front|latch|member]", "Sets your system's autoproxy mode for the current server");
public static Command AutoproxyTimeout = new Command("autoproxy", "autoproxy timeout [<duration>|off|reset]", "Sets the latch timeout duration for your system");
public static Command AutoproxyAccount = new Command("autoproxy", "autoproxy account [on|off]", "Toggles autoproxy globally for the current account");
public static Command MemberInfo = new Command("member", "member <member>", "Looks up information about a member");
public static Command MemberNew = new Command("member new", "member new <name>", "Creates a new member");
public static Command MemberRename = new Command("member rename", "member <member> rename <new name>", "Renames a member");
@ -45,6 +47,7 @@ namespace PluralKit.Bot
public static Command MemberServerAvatar = new Command("member serveravatar", "member <member> serveravatar [url|@mention]", "Changes a member's avatar in the current server");
public static Command MemberDisplayName = new Command("member displayname", "member <member> displayname [display name]", "Changes a member's display name");
public static Command MemberServerName = new Command("member servername", "member <member> servername [server name]", "Changes a member's display name in the current server");
public static Command MemberAutoproxy = new Command("member autoproxy", "member <member> autoproxy [on|off]", "Sets whether a member will be autoproxied when autoproxy is set to latch or front mode.");
public static Command MemberKeepProxy = new Command("member keepproxy", "member <member> keepproxy [on|off]", "Sets whether to include a member's proxy tags when proxying");
public static Command MemberRandom = new Command("random", "random", "Shows the info card of a randomly selected member in your system.");
public static Command MemberPrivacy = new Command("member privacy", "member <member> privacy <name|description|birthday|pronouns|metadata|visibility|all> <public|private>", "Changes a members's privacy settings");
@ -94,7 +97,7 @@ namespace PluralKit.Bot
public static Command[] MemberCommands = {
MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns,
MemberColor, MemberBirthday, MemberProxy, MemberKeepProxy, MemberGroups, MemberGroupAdd, MemberGroupRemove,
MemberColor, MemberBirthday, MemberProxy, MemberAutoproxy, MemberKeepProxy, MemberGroups, MemberGroupAdd, MemberGroupRemove,
MemberDelete, MemberAvatar, MemberServerAvatar, MemberPrivacy, MemberRandom
};
@ -112,6 +115,8 @@ namespace PluralKit.Bot
public static Command[] SwitchCommands = {Switch, SwitchOut, SwitchMove, SwitchDelete, SwitchDeleteAll};
public static Command[] AutoproxyCommands = {AutoproxySet, AutoproxyTimeout, AutoproxyAccount};
public static Command[] LogCommands = {LogChannel, LogChannelClear, LogEnable, LogDisable};
public static Command[] BlacklistCommands = {BlacklistAdd, BlacklistRemove, BlacklistShow};
@ -137,7 +142,7 @@ namespace PluralKit.Bot
if (ctx.Match("commands", "cmd", "c"))
return CommandHelpRoot(ctx);
if (ctx.Match("ap", "autoproxy", "auto"))
return ctx.Execute<Autoproxy>(Autoproxy, m => m.AutoproxyRoot(ctx));
return HandleAutoproxyCommand(ctx);
if (ctx.Match("list", "find", "members", "search", "query", "l", "f", "fd"))
return ctx.Execute<SystemList>(SystemList, m => m.MemberList(ctx, ctx.System));
if (ctx.Match("link"))
@ -349,6 +354,8 @@ namespace PluralKit.Bot
await ctx.Execute<MemberEdit>(MemberDisplayName, m => m.DisplayName(ctx, target));
else if (ctx.Match("servername", "sn", "sname", "snick", "snickname", "servernick", "servernickname", "serverdisplayname", "guildname", "guildnick", "guildnickname", "serverdn"))
await ctx.Execute<MemberEdit>(MemberServerName, m => m.ServerName(ctx, target));
else if (ctx.Match("autoproxy", "ap"))
await ctx.Execute<MemberEdit>(MemberAutoproxy, m => m.MemberAutoproxy(ctx, target));
else if (ctx.Match("keepproxy", "keeptags", "showtags"))
await ctx.Execute<MemberEdit>(MemberKeepProxy, m => m.KeepProxy(ctx, target));
else if (ctx.Match("privacy"))
@ -463,7 +470,10 @@ namespace PluralKit.Bot
case "bl":
await PrintCommandList(ctx, "channel blacklisting", BlacklistCommands);
break;
// case "autoproxy": (add this when #232 is merged)
case "autoproxy":
case "ap":
await PrintCommandList(ctx, "autoproxy", AutoproxyCommands);
break;
// todo: are there any commands that still need to be added?
default:
await ctx.Reply("For the full list of commands, see the website: <https://pluralkit.me/commands>");
@ -471,6 +481,26 @@ namespace PluralKit.Bot
}
}
private Task HandleAutoproxyCommand(Context ctx)
{
// todo: merge this with the changes from #251
if (ctx.Match("commands"))
return PrintCommandList(ctx, "autoproxy", AutoproxyCommands);
// ctx.CheckSystem();
// oops, that breaks stuff! PKErrors before ctx.Execute don't actually do anything.
// so we just emulate checking and throwing an error.
if (ctx.System == null)
return ctx.Reply($"{Emojis.Error} {Errors.NoSystemError.Message}");
if (ctx.Match("account", "ac"))
return ctx.Execute<Autoproxy>(AutoproxyAccount, m => m.AutoproxyAccount(ctx));
else if (ctx.Match("timeout", "tm"))
return ctx.Execute<Autoproxy>(AutoproxyTimeout, m => m.AutoproxyTimeout(ctx));
else
return ctx.Execute<Autoproxy>(AutoproxySet, m => m.SetAutoproxyMode(ctx));
}
private async Task PrintCommandNotFoundError(Context ctx, params Command[] potentialCommands)
{
var commandListStr = CreatePotentialCommandList(potentialCommands);

View File

@ -367,6 +367,33 @@ namespace PluralKit.Bot
await ctx.Reply($"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
}
public async Task MemberAutoproxy(Context ctx, PKMember target)
{
if (ctx.System == null) throw Errors.NoSystemError;
if (target.System != ctx.System.Id) throw Errors.NotOwnMemberError;
bool newValue;
if (ctx.Match("on", "enabled", "true", "yes") || ctx.MatchFlag("on", "enabled", "true", "yes")) newValue = true;
else if (ctx.Match("off", "disabled", "false", "no") || ctx.MatchFlag("off", "disabled", "false", "no")) newValue = false;
else if (ctx.HasNext()) throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
else
{
if (target.AllowAutoproxy)
await ctx.Reply("Latch/front autoproxy are **enabled** for this member. This member will be automatically proxied when autoproxy is set to latch or front mode.");
else
await ctx.Reply("Latch/front autoproxy are **disabled** for this member. This member will not be automatically proxied when autoproxy is set to latch or front mode.");
return;
};
var patch = new MemberPatch {AllowAutoproxy = Partial<bool>.Present(newValue)};
await _db.Execute(conn => _repo.UpdateMember(conn, target.Id, patch));
if (newValue)
await ctx.Reply($"{Emojis.Success} Latch / front autoproxy have been **enabled** for this member.");
else
await ctx.Reply($"{Emojis.Success} Latch / front autoproxy have been **disabled** for this member.");
}
public async Task Privacy(Context ctx, PKMember target, PrivacyLevel? newValueFromCommand)
{
ctx.CheckSystem().CheckOwnMember(target);

View File

@ -137,7 +137,7 @@ namespace PluralKit.Bot
{
try
{
return await _proxy.HandleIncomingMessage(shard, evt.Message, ctx, allowAutoproxy: true);
return await _proxy.HandleIncomingMessage(shard, evt.Message, ctx, allowAutoproxy: ctx.AllowAutoproxy);
}
catch (PKError e)
{

View File

@ -10,7 +10,7 @@ namespace PluralKit.Bot
public class ProxyMatcher
{
private static readonly char AutoproxyEscapeCharacter = '\\';
private static readonly Duration LatchExpiryTime = Duration.FromHours(6);
public static readonly int DefaultLatchExpiryTime = 6;
private readonly IClock _clock;
private readonly ProxyTagParser _parser;
@ -56,13 +56,13 @@ namespace PluralKit.Bot
AutoproxyMode.Front when ctx.LastSwitchMembers.Length > 0 =>
members.FirstOrDefault(m => m.Id == ctx.LastSwitchMembers[0]),
AutoproxyMode.Latch when ctx.LastMessageMember != null && !IsLatchExpired(ctx.LastMessage) =>
AutoproxyMode.Latch when ctx.LastMessageMember != null && !IsLatchExpired(ctx) =>
members.FirstOrDefault(m => m.Id == ctx.LastMessageMember.Value),
_ => null
};
if (member == null) return false;
if (member == null || (ctx.AutoproxyMode != AutoproxyMode.Member && !member.AllowAutoproxy)) return false;
match = new ProxyMatch
{
Content = messageContent,
@ -75,11 +75,15 @@ namespace PluralKit.Bot
return true;
}
private bool IsLatchExpired(ulong? messageId)
private bool IsLatchExpired(MessageContext ctx)
{
if (messageId == null) return true;
var timestamp = DiscordUtils.SnowflakeToInstant(messageId.Value);
return _clock.GetCurrentInstant() - timestamp > LatchExpiryTime;
if (ctx.LastMessage == null) return true;
if (ctx.LatchTimeout == 0) return false;
var timeout = Duration.FromHours(ctx.LatchTimeout == -1 ? DefaultLatchExpiryTime : ctx.LatchTimeout);
var timestamp = DiscordUtils.SnowflakeToInstant(ctx.LastMessage.Value);
return _clock.GetCurrentInstant() - timestamp > timeout;
}
}
}

View File

@ -19,7 +19,7 @@ namespace PluralKit.Core
internal class Database: IDatabase
{
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
private const int TargetSchemaVersion = 11;
private const int TargetSchemaVersion = 12;
private readonly CoreConfig _config;
private readonly ILogger _logger;

View File

@ -24,5 +24,7 @@ namespace PluralKit.Core
public Instant? LastSwitchTimestamp { get; }
public string? SystemTag { get; }
public string? SystemAvatar { get; }
public bool AllowAutoproxy { get; }
public int LatchTimeout { get; }
}
}

View File

@ -19,6 +19,8 @@ namespace PluralKit.Core
public string? ServerAvatar { get; }
public string? Avatar { get; }
public bool AllowAutoproxy { get; }
public string ProxyName(MessageContext ctx) => ctx.SystemTag != null
? $"{ServerName ?? DisplayName ?? Name} {ctx.SystemTag}"
: ServerName ?? DisplayName ?? Name;

View File

@ -14,12 +14,14 @@
last_switch_members int[],
last_switch_timestamp timestamp,
system_tag text,
system_avatar text
system_avatar text,
allow_autoproxy bool,
latch_timeout integer
)
as $$
-- CTEs to query "static" (accessible only through args) data
with
system as (select systems.* from accounts inner join systems on systems.id = accounts.system where accounts.uid = account_id),
system as (select systems.*, allow_autoproxy as account_autoproxy from accounts inner join systems on systems.id = accounts.system 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)
select
@ -37,7 +39,9 @@ as $$
system_last_switch.members as last_switch_members,
system_last_switch.timestamp as last_switch_timestamp,
system.tag as system_tag,
system.avatar_url as system_avatar
system.avatar_url as system_avatar,
system.account_autoproxy as allow_autoproxy,
system.latch_timeout as latch_timeout
-- We need a "from" clause, so we just use some bogus data that's always present
-- This ensure we always have exactly one row going forward, so we can left join afterwards and still get data
from (select 1) as _placeholder
@ -62,7 +66,9 @@ create function proxy_members(account_id bigint, guild_id bigint)
name text,
server_avatar text,
avatar text
avatar text,
allow_autoproxy bool
)
as $$
select
@ -78,7 +84,9 @@ as $$
-- Avatar info
member_guild.avatar_url as server_avatar,
members.avatar_url as avatar
members.avatar_url as avatar,
members.allow_autoproxy as allow_autoproxy
from accounts
inner join systems on systems.id = accounts.system
inner join members on members.system = systems.id

View File

@ -0,0 +1,9 @@
-- SCHEMA VERSION 12: <insert date> --
-- Add disabling front/latch autoproxy per-member --
-- Add disabling autoproxy per-account --
-- Add configurable latch timeout --
alter table members add column allow_autoproxy bool not null default true;
alter table accounts add column allow_autoproxy bool not null default true;
alter table systems add column latch_timeout int not null default -1;
update info set schema_version = 12;

View File

@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Data;
using System.Threading.Tasks;
using Dapper;
namespace PluralKit.Core
{
public partial class ModelRepository
{
public async Task UpdateAccount(IPKConnection conn, ulong id, AccountPatch patch)
{
_logger.Information("Updated account {accountId}: {@AccountPatch}", id, patch);
var (query, pms) = patch.Apply(UpdateQueryBuilder.Update("accounts", "uid = @uid"))
.WithConstant("uid", id)
.Build();
await conn.ExecuteAsync(query, pms);
}
}
}

View File

@ -24,6 +24,7 @@ namespace PluralKit.Core {
public bool KeepProxy { get; private set; }
public Instant Created { get; private set; }
public int MessageCount { get; private set; }
public bool AllowAutoproxy { get; private set; }
public PrivacyLevel MemberVisibility { get; private set; }
public PrivacyLevel DescriptionPrivacy { get; private set; }

View File

@ -18,6 +18,7 @@ namespace PluralKit.Core {
public Instant Created { get; }
public string UiTz { get; set; }
public bool PingsEnabled { get; }
public int LatchTimeout { get; }
public PrivacyLevel DescriptionPrivacy { get; }
public PrivacyLevel MemberListPrivacy { get;}
public PrivacyLevel FrontPrivacy { get; }

View File

@ -0,0 +1,10 @@
namespace PluralKit.Core
{
public class AccountPatch: PatchObject
{
public Partial<bool> AllowAutoproxy { get; set; }
public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b
.With("allow_autoproxy", AllowAutoproxy);
}
}

View File

@ -16,6 +16,7 @@ namespace PluralKit.Core
public Partial<ProxyTag[]> ProxyTags { get; set; }
public Partial<bool> KeepProxy { get; set; }
public Partial<int> MessageCount { get; set; }
public Partial<bool> AllowAutoproxy { get; set; }
public Partial<PrivacyLevel> Visibility { get; set; }
public Partial<PrivacyLevel> NamePrivacy { get; set; }
public Partial<PrivacyLevel> DescriptionPrivacy { get; set; }
@ -35,6 +36,7 @@ namespace PluralKit.Core
.With("proxy_tags", ProxyTags)
.With("keep_proxy", KeepProxy)
.With("message_count", MessageCount)
.With("allow_autoproxy", AllowAutoproxy)
.With("member_visibility", Visibility)
.With("name_privacy", NamePrivacy)
.With("description_privacy", DescriptionPrivacy)

View File

@ -15,6 +15,7 @@ namespace PluralKit.Core
public Partial<PrivacyLevel> FrontPrivacy { get; set; }
public Partial<PrivacyLevel> FrontHistoryPrivacy { get; set; }
public Partial<bool> PingsEnabled { get; set; }
public Partial<int> LatchTimeout { get; set; }
public override UpdateQueryBuilder Apply(UpdateQueryBuilder b) => b
.With("name", Name)
@ -28,6 +29,7 @@ namespace PluralKit.Core
.With("group_list_privacy", GroupListPrivacy)
.With("front_privacy", FrontPrivacy)
.With("front_history_privacy", FrontHistoryPrivacy)
.With("pings_enabled", PingsEnabled);
.With("pings_enabled", PingsEnabled)
.With("latch_timeout", LatchTimeout);
}
}

View File

@ -49,6 +49,7 @@ Words in **\<angle brackets>** or **[square brackets]** mean fill-in-the-blank.
- `pk;member <name> proxy [tags]` - Changes the proxy tags of a member. use below add/remove commands for members with multiple tag pairs.
- `pk;member <name> proxy add [tags]` - Adds a proxy tag pair to a member.
- `pk;member <name> proxy remove [tags]` - Removes a proxy tag from a member.
- `pk;member <name> autoproxy [on|off]` - Sets whether a member will be autoproxied when autoproxy is set to latch or front mode.
- `pk;member <name> keepproxy [on|off]` - Sets whether to include a member's proxy tags in the proxied message.
- `pk;member <name> pronouns [pronouns]` - Changes the pronouns of a member.
- `pk;member <name> color [color]` - Changes the color of a member.
@ -78,6 +79,11 @@ Words in **\<angle brackets>** or **[square brackets]** mean fill-in-the-blank.
- `pk;switch delete all` - Deletes all logged switches.
- `pk;switch out` - Registers a 'switch-out' - a switch with no associated members.
## Autoproxy commands
- `pk;autoproxy [off|front|latch|<member>]` - Sets your system's autoproxy mode for the current server.
- `pk;autoproxy timeout [<duration>|off|reset]` - Sets the latch timeout duration for your system.
- `pk;autoproxy account [on|off]` - Toggles autoproxy globally for the current account.
## Server owner commands
*(all commands here require Manage Server permission)*
- `pk;log channel <channel>` - Sets the given channel to log all proxied messages.

View File

@ -315,17 +315,23 @@ Since the messages will be posted by PluralKit's webhook, there's no way to dele
To delete a PluralKit-proxied message, you can react to it with the :x: emoji. Note that this only works if the message has
been sent from your own account.
### Autoproxying
## Autoproxy
The bot's *autoproxy* feature allows you to have messages be proxied without directly including the proxy tags. Autoproxy can be set up in various ways. There are three autoproxy modes currently implemented:
To see your system's current autoproxy settings, simply use the command:
pk;autoproxy
To disable autoproxying for the current server, use the command:
pk;autoproxy off
*(hint: `pk;autoproxy` can be shortened to `pk;ap` in all related commands)*
::: tip
To disable autoproxy for a single message, add a backslash (`\`) to the beginning of your message.
:::
#### Front mode
This autoproxy mode will proxy messages as the current *first* fronter of the system. If you register a switch with `Alice` and `Bob`, messages without proxy tags will be autoproxied as `Alice`.
To enable front-mode autoproxying for a given server, use the following command:
@ -347,6 +353,47 @@ To enable member-mode autoproxying for a given server, use the following command
pk;autoproxy <member>
### Changing the latch timeout duration
By default, latch mode times out after 6 hours. It is possible to change this:
pk;autoproxy timeout <new duration>
To reset the duration, use the following command:
pk;autoproxy timeout reset
To disable timeout (never timeout), use the following command:
pk;autoproxy timeout disable
### Disabling front/latch autoproxy on a per-member basis
If a system uses front or latch mode autoproxy, but one member prefers to send messages through the account (and not proxy), you can disable the front and latch modes for that specific member.
pk;member <name> autoproxy off
To re-enable front / latch modes for that member, use the following command:
pk;member <name> autoproxy on
This will *not* disable member mode autoproxy. If you do not wish to autoproxy, please turn off autoproxy instead of setting autoproxy to a specific member.
### Disabling autoproxy per-account
It is possible to fully disable autoproxy for a certain account linked to your system. For example, you might want to do this if a specific member's name is shown on the account.
To disable autoproxy for the current account, use the following command:
pk;autoproxy account disable
To re-enable autoproxy for the current account, use the following command:
pk;autoproxy account enable
::: tip
This subcommand can also be run in DMs.
:::
## Managing switches
PluralKit allows you to log member switches through the bot.
Essentially, this means you can mark one or more members as *the current fronter(s)* for the duration until the next switch.