2020-04-24 19:50:28 +00:00
using System ;
2020-01-23 20:20:22 +00:00
using System.Threading.Tasks ;
2020-04-24 19:50:28 +00:00
using DSharpPlus.Entities ;
2020-01-23 20:20:22 +00:00
2020-12-08 11:57:17 +00:00
using Humanizer ;
using NodaTime ;
2020-02-12 14:16:19 +00:00
using PluralKit.Core ;
2020-01-23 20:20:22 +00:00
2020-02-12 14:16:19 +00:00
namespace PluralKit.Bot
2020-01-23 20:20:22 +00:00
{
2020-02-01 12:03:02 +00:00
public class Autoproxy
2020-01-23 20:20:22 +00:00
{
2020-06-13 17:36:43 +00:00
private readonly IDatabase _db ;
2020-08-29 11:46:27 +00:00
private readonly ModelRepository _repo ;
2020-01-23 20:20:22 +00:00
2020-08-29 11:46:27 +00:00
public Autoproxy ( IDatabase db , ModelRepository repo )
2020-01-23 20:20:22 +00:00
{
2020-06-13 14:03:57 +00:00
_db = db ;
2020-08-29 11:46:27 +00:00
_repo = repo ;
2020-01-23 20:20:22 +00:00
}
2020-11-21 01:48:33 +00:00
public async Task SetAutoproxyMode ( Context ctx )
2020-01-23 20:20:22 +00:00
{
2020-11-21 01:48:33 +00:00
// no need to check account here, it's already done at CommandTree
2020-11-20 23:34:08 +00:00
ctx . CheckGuildContext ( ) ;
2020-01-23 20:20:22 +00:00
2020-07-09 13:11:04 +00:00
if ( ctx . Match ( "off" , "stop" , "cancel" , "no" , "disable" , "remove" ) )
2020-01-23 20:20:22 +00:00
await AutoproxyOff ( ctx ) ;
else if ( ctx . Match ( "latch" , "last" , "proxy" , "stick" , "sticky" ) )
await AutoproxyLatch ( ctx ) ;
else if ( ctx . Match ( "front" , "fronter" , "switch" ) )
await AutoproxyFront ( ctx ) ;
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 ) ) ;
else
2020-08-25 20:44:52 +00:00
throw new PKSyntaxError ( $"Invalid autoproxy mode {ctx.PopArgument().AsCode()}." ) ;
2020-01-23 20:20:22 +00:00
}
private async Task AutoproxyOff ( Context ctx )
{
2020-06-13 14:03:57 +00:00
if ( ctx . MessageContext . AutoproxyMode = = AutoproxyMode . Off )
2020-01-23 20:20:22 +00:00
await ctx . Reply ( $"{Emojis.Note} Autoproxy is already off in this server." ) ;
else
{
2020-06-13 14:03:57 +00:00
await UpdateAutoproxy ( ctx , AutoproxyMode . Off , null ) ;
2020-01-23 20:20:22 +00:00
await ctx . Reply ( $"{Emojis.Success} Autoproxy turned off in this server." ) ;
}
}
private async Task AutoproxyLatch ( Context ctx )
{
2020-06-13 14:03:57 +00:00
if ( ctx . MessageContext . AutoproxyMode = = AutoproxyMode . Latch )
2020-01-23 20:20:22 +00:00
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`." ) ;
else
{
2020-06-13 14:03:57 +00:00
await UpdateAutoproxy ( ctx , AutoproxyMode . Latch , null ) ;
2020-01-23 20:20:22 +00:00
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." ) ;
}
}
private async Task AutoproxyFront ( Context ctx )
{
2020-06-13 14:03:57 +00:00
if ( ctx . MessageContext . AutoproxyMode = = AutoproxyMode . Front )
2020-01-23 20:20:22 +00:00
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`." ) ;
else
{
2020-06-13 14:03:57 +00:00
await UpdateAutoproxy ( ctx , AutoproxyMode . Front , null ) ;
2020-01-23 20:20:22 +00:00
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." ) ;
}
}
private async Task AutoproxyMember ( Context ctx , PKMember member )
{
ctx . CheckOwnMember ( member ) ;
2020-06-13 14:03:57 +00:00
await UpdateAutoproxy ( ctx , AutoproxyMode . Member , member . Id ) ;
2020-06-18 15:08:36 +00:00
await ctx . Reply ( $"{Emojis.Success} Autoproxy set to **{member.NameFor(ctx)}** in this server." ) ;
2020-01-23 20:20:22 +00:00
}
2020-04-24 19:50:28 +00:00
private async Task < DiscordEmbed > CreateAutoproxyStatusEmbed ( Context ctx )
2020-01-23 20:20:22 +00:00
{
var commandList = "**pk;autoproxy latch** - Autoproxies as last-proxied member\n**pk;autoproxy front** - Autoproxies as current (first) fronter\n**pk;autoproxy <member>** - Autoproxies as a specific member" ;
2020-04-24 19:50:28 +00:00
var eb = new DiscordEmbedBuilder ( ) . WithTitle ( $"Current autoproxy status (for {ctx.Guild.Name.EscapeMarkdown()})" ) ;
2020-06-13 14:03:57 +00:00
var fronters = ctx . MessageContext . LastSwitchMembers ;
var relevantMember = ctx . MessageContext . AutoproxyMode switch
{
2020-08-29 11:46:27 +00:00
AutoproxyMode . Front = > fronters . Length > 0 ? await _db . Execute ( c = > _repo . GetMember ( c , fronters [ 0 ] ) ) : null ,
AutoproxyMode . Member = > await _db . Execute ( c = > _repo . GetMember ( c , ctx . MessageContext . AutoproxyMember . Value ) ) ,
2020-06-13 14:03:57 +00:00
_ = > null
} ;
2020-01-23 20:20:22 +00:00
2020-06-13 14:03:57 +00:00
switch ( ctx . MessageContext . AutoproxyMode ) {
2020-01-23 20:20:22 +00:00
case AutoproxyMode . Off : eb . WithDescription ( $"Autoproxy is currently **off** in this server. To enable it, use one of the following commands:\n{commandList}" ) ;
break ;
2020-06-13 14:03:57 +00:00
case AutoproxyMode . Front :
{
2020-06-14 19:37:04 +00:00
if ( fronters . Length = = 0 )
2020-06-13 14:03:57 +00:00
eb . WithDescription ( "Autoproxy is currently set to **front mode** in this server, but there are currently no fronters registered. Use the `pk;switch` command to log a switch." ) ;
2020-01-23 20:20:22 +00:00
else
{
2020-06-13 14:03:57 +00:00
if ( relevantMember = = null )
throw new ArgumentException ( "Attempted to print member autoproxy status, but the linked member ID wasn't found in the database. Should be handled appropriately." ) ;
2020-06-18 15:08:36 +00:00
eb . WithDescription ( $"Autoproxy is currently set to **front mode** in this server. The current (first) fronter is **{relevantMember.NameFor(ctx).EscapeMarkdown()}** (`{relevantMember.Hid}`). To disable, type `pk;autoproxy off`." ) ;
2020-01-23 20:20:22 +00:00
}
break ;
}
// AutoproxyMember is never null if Mode is Member, this is just to make the compiler shut up
2020-06-13 14:03:57 +00:00
case AutoproxyMode . Member when relevantMember ! = null : {
2020-06-18 15:08:36 +00:00
eb . WithDescription ( $"Autoproxy is active for member **{relevantMember.NameFor(ctx)}** (`{relevantMember.Hid}`) in this server. To disable, type `pk;autoproxy off`." ) ;
2020-01-23 20:20:22 +00:00
break ;
}
case AutoproxyMode . Latch :
2020-06-13 14:03:57 +00:00
eb . WithDescription ( "Autoproxy is currently set to **latch mode**, meaning the *last-proxied member* will be autoproxied. To disable, type `pk;autoproxy off`." ) ;
2020-01-23 20:20:22 +00:00
break ;
2020-06-13 14:03:57 +00:00
2020-01-23 20:20:22 +00:00
default : throw new ArgumentOutOfRangeException ( ) ;
}
2020-11-20 23:34:08 +00:00
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`." ) ;
2020-01-23 20:20:22 +00:00
return eb . Build ( ) ;
}
2020-06-29 13:20:28 +00:00
2020-11-21 01:48:33 +00:00
public async Task AutoproxyTimeout ( Context ctx )
2020-11-21 00:44:15 +00:00
{
if ( ! ctx . HasNext ( ) )
{
2020-12-08 11:57:17 +00:00
var timeout = ctx . System . LatchTimeout . HasValue
? Duration . FromSeconds ( ctx . System . LatchTimeout . Value )
: ( Duration ? ) null ;
if ( timeout = = null )
await ctx . Reply ( $"You do not have a custom autoproxy timeout duration set. The default latch timeout duration is {ProxyMatcher.DefaultLatchExpiryTime.ToTimeSpan().Humanize()}." ) ;
else if ( timeout = = Duration . Zero )
await ctx . Reply ( "Latch timeout is currently **disabled** for your system. Latch mode autoproxy will never time out." ) ;
2020-11-21 00:44:15 +00:00
else
2020-12-08 11:57:17 +00:00
await ctx . Reply ( $"The current latch timeout duration for your system is {timeout.Value.ToTimeSpan().Humanize()}." ) ;
2020-11-21 00:44:15 +00:00
return ;
}
// todo: somehow parse a more human-friendly date format
2020-12-08 11:57:17 +00:00
int newTimeoutHours ;
if ( ctx . Match ( "off" , "stop" , "cancel" , "no" , "disable" , "remove" ) ) newTimeoutHours = 0 ;
else if ( ctx . Match ( "reset" , "default" ) ) newTimeoutHours = - 1 ;
else if ( ! int . TryParse ( ctx . RemainderOrNull ( ) , out newTimeoutHours ) ) throw new PKError ( "Duration must be a number of hours." ) ;
var newTimeout = newTimeoutHours > - 1 ? Duration . FromHours ( newTimeoutHours ) : ( Duration ? ) null ;
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id ,
new SystemPatch { LatchTimeout = ( int? ) newTimeout ? . TotalSeconds } ) ) ;
2020-11-21 00:44:15 +00:00
2020-12-08 11:57:17 +00:00
if ( newTimeoutHours = = - 1 )
await ctx . Reply ( $"{Emojis.Success} Latch timeout reset to default ({ProxyMatcher.DefaultLatchExpiryTime.ToTimeSpan().Humanize()})." ) ;
else if ( newTimeoutHours = = 0 )
await ctx . Reply ( $"{Emojis.Success} Latch timeout disabled. Latch mode autoproxy will never time out." ) ;
2020-11-21 00:44:15 +00:00
else
2020-12-08 11:57:17 +00:00
await ctx . Reply ( $"{Emojis.Success} Latch timeout set to {newTimeout.Value!.ToTimeSpan().Humanize()} hours." ) ;
2020-11-21 00:44:15 +00:00
}
2020-11-21 01:48:33 +00:00
public async Task AutoproxyAccount ( Context ctx )
2020-11-20 23:34:08 +00:00
{
2020-11-21 01:26:34 +00:00
// todo: this might be useful elsewhere, consider moving it to ctx.MatchToggle
2020-11-20 23:34:08 +00:00
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 [ ] { } ) ;
}
2020-06-29 13:20:28 +00:00
private Task UpdateAutoproxy ( Context ctx , AutoproxyMode autoproxyMode , MemberId ? autoproxyMember )
{
var patch = new SystemGuildPatch { AutoproxyMode = autoproxyMode , AutoproxyMember = autoproxyMember } ;
2020-08-29 11:46:27 +00:00
return _db . Execute ( conn = > _repo . UpsertSystemGuild ( conn , ctx . System . Id , ctx . Guild . Id , patch ) ) ;
2020-06-29 13:20:28 +00:00
}
2020-01-23 20:20:22 +00:00
}
}