2020-04-24 19:50:28 +00:00
using System ;
2019-04-26 15:14:20 +00:00
using System.Linq ;
2021-08-23 20:53:58 +00:00
using System.Net.Http ;
2021-03-28 10:02:41 +00:00
using System.Text.RegularExpressions ;
2019-04-19 18:48:37 +00:00
using System.Threading.Tasks ;
2020-02-01 12:03:02 +00:00
2020-12-24 13:52:44 +00:00
using Myriad.Builders ;
2019-06-13 18:33:17 +00:00
using NodaTime ;
using NodaTime.Text ;
using NodaTime.TimeZones ;
2019-10-05 05:41:00 +00:00
2019-07-09 22:19:18 +00:00
using PluralKit.Core ;
2019-04-19 18:48:37 +00:00
2020-02-12 14:16:19 +00:00
namespace PluralKit.Bot
2019-04-19 18:48:37 +00:00
{
2020-02-01 12:03:02 +00:00
public class SystemEdit
2019-04-19 18:48:37 +00:00
{
2020-08-29 11:46:27 +00:00
private readonly IDatabase _db ;
private readonly ModelRepository _repo ;
2021-08-23 20:53:58 +00:00
private readonly HttpClient _client ;
2019-04-21 13:33:22 +00:00
2021-08-23 20:53:58 +00:00
public SystemEdit ( IDatabase db , ModelRepository repo , HttpClient client )
2019-10-05 05:41:00 +00:00
{
2020-06-13 14:03:57 +00:00
_db = db ;
2020-08-29 11:46:27 +00:00
_repo = repo ;
2021-08-23 20:53:58 +00:00
_client = client ;
2019-04-21 13:33:22 +00:00
}
2019-10-05 05:41:00 +00:00
public async Task Name ( Context ctx )
{
2021-08-27 23:20:14 +00:00
var noNameSetMessage = "Your system does not have a name set. Type `pk;system name <name>` to set one." ;
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2019-04-19 18:48:37 +00:00
2021-09-13 05:22:40 +00:00
if ( ctx . MatchRaw ( ) )
2020-03-04 17:13:36 +00:00
{
2021-08-27 23:20:14 +00:00
if ( ctx . System . Name ! = null )
2021-09-13 05:22:40 +00:00
await ctx . Reply ( $"```\n{ctx.System.Name}\n```" ) ;
2021-08-27 23:20:14 +00:00
else
await ctx . Reply ( noNameSetMessage ) ;
2020-03-04 17:13:36 +00:00
return ;
}
2021-09-13 06:33:34 +00:00
if ( ! ctx . HasNext ( false ) )
2020-03-04 17:13:36 +00:00
{
if ( ctx . System . Name ! = null )
2021-09-13 05:22:40 +00:00
await ctx . Reply ( $"Your system's name is currently **{ctx.System.Name}**. Type `pk;system name -clear` to clear it." ) ;
2020-03-04 17:13:36 +00:00
else
2021-08-27 23:20:14 +00:00
await ctx . Reply ( noNameSetMessage ) ;
2020-03-04 17:13:36 +00:00
return ;
}
2021-08-27 15:03:47 +00:00
2021-08-27 23:20:14 +00:00
if ( await ctx . MatchClear ( "your system's name" ) )
{
var clearPatch = new SystemPatch { Name = null } ;
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , clearPatch ) ) ;
2021-08-27 15:03:47 +00:00
2021-08-27 23:20:14 +00:00
await ctx . Reply ( $"{Emojis.Success} System name cleared." ) ;
}
else
{
var newSystemName = ctx . RemainderOrNull ( ) ;
if ( newSystemName . Length > Limits . MaxSystemNameLength )
2021-09-13 05:13:57 +00:00
throw Errors . StringTooLongError ( "System name" , newSystemName . Length , Limits . MaxSystemNameLength ) ;
2021-08-27 23:20:14 +00:00
var patch = new SystemPatch { Name = newSystemName } ;
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2021-08-27 15:03:47 +00:00
2021-08-27 23:20:14 +00:00
await ctx . Reply ( $"{Emojis.Success} System name changed." ) ;
}
2019-04-19 18:48:37 +00:00
}
2021-08-27 15:03:47 +00:00
public async Task Description ( Context ctx )
{
2021-08-27 23:20:14 +00:00
var noDescriptionSetMessage = "Your system does not have a description set. To set one, type `pk;s description <description>`." ;
2021-08-27 15:03:47 +00:00
2021-08-27 23:20:14 +00:00
ctx . CheckSystem ( ) ;
2021-08-27 15:03:47 +00:00
2021-09-13 05:22:40 +00:00
if ( ctx . MatchRaw ( ) )
2020-02-23 11:45:05 +00:00
{
if ( ctx . System . Description = = null )
2021-08-27 23:20:14 +00:00
await ctx . Reply ( noDescriptionSetMessage ) ;
2020-02-23 11:45:05 +00:00
else
2021-09-13 05:22:40 +00:00
await ctx . Reply ( $"```\n{ctx.System.Description}\n```" ) ;
2021-08-27 23:20:14 +00:00
return ;
}
2021-09-13 06:33:34 +00:00
if ( ! ctx . HasNext ( false ) )
2021-08-27 23:20:14 +00:00
{
if ( ctx . System . Description = = null )
await ctx . Reply ( noDescriptionSetMessage ) ;
else
2021-09-13 05:22:40 +00:00
await ctx . Reply ( embed : new EmbedBuilder ( )
. Title ( "System description" )
. Description ( ctx . System . Description )
. Footer ( new ( "To print the description with formatting, type `pk;s description -raw`. To clear it, type `pk;s description -clear`. To change it, type `pk;s description <new description>`." ) )
. Build ( ) ) ;
2021-08-27 23:20:14 +00:00
return ;
}
if ( await ctx . MatchClear ( "your system's description" ) )
{
var patch = new SystemPatch { Description = null } ;
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
await ctx . Reply ( $"{Emojis.Success} System description cleared." ) ;
return ;
2020-02-23 11:45:05 +00:00
}
else
{
2021-08-27 23:20:14 +00:00
var newDescription = ctx . RemainderOrNull ( ) ? . NormalizeLineEndSpacing ( ) ;
2021-09-13 05:13:57 +00:00
if ( newDescription . Length > Limits . MaxDescriptionLength )
throw Errors . StringTooLongError ( "Description" , newDescription . Length , Limits . MaxDescriptionLength ) ;
2021-08-27 15:03:47 +00:00
var patch = new SystemPatch { Description = newDescription } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2021-08-27 15:03:47 +00:00
2020-02-23 11:45:05 +00:00
await ctx . Reply ( $"{Emojis.Success} System description changed." ) ;
}
2019-04-19 18:48:37 +00:00
}
2021-03-28 10:02:41 +00:00
2021-08-27 15:03:47 +00:00
public async Task Color ( Context ctx )
{
2021-03-28 10:02:41 +00:00
ctx . CheckSystem ( ) ;
if ( await ctx . MatchClear ( ) )
{
2021-08-27 15:03:47 +00:00
var patch = new SystemPatch { Color = Partial < string > . Null ( ) } ;
2021-03-28 10:02:41 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
await ctx . Reply ( $"{Emojis.Success} System color cleared." ) ;
}
2021-08-27 15:03:47 +00:00
else if ( ! ctx . HasNext ( ) )
2021-03-28 10:02:41 +00:00
{
2021-08-27 15:03:47 +00:00
if ( ctx . System . Color = = null )
2021-03-28 10:02:41 +00:00
await ctx . Reply (
$"Your system does not have a color set. To set one, type `pk;system color <color>`." ) ;
else
await ctx . Reply ( embed : new EmbedBuilder ( )
. Title ( "System color" )
. Color ( ctx . System . Color . ToDiscordColor ( ) )
. Thumbnail ( new ( $"https://fakeimg.pl/256x256/{ctx.System.Color}/?text=%20" ) )
. Description ( $"Your system's color is **#{ctx.System.Color}**. To clear it, type `pk;s color -clear`." )
. Build ( ) ) ;
}
else
{
var color = ctx . RemainderOrNull ( ) ;
if ( color . StartsWith ( "#" ) ) color = color . Substring ( 1 ) ;
if ( ! Regex . IsMatch ( color , "^[0-9a-fA-F]{6}$" ) ) throw Errors . InvalidColorError ( color ) ;
2021-08-27 15:03:47 +00:00
var patch = new SystemPatch { Color = Partial < string > . Present ( color . ToLowerInvariant ( ) ) } ;
2021-03-28 10:02:41 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
await ctx . Reply ( embed : new EmbedBuilder ( )
2021-04-06 10:27:39 +00:00
. Title ( $"{Emojis.Success} System color changed." )
2021-03-28 10:02:41 +00:00
. Color ( color . ToDiscordColor ( ) )
. Thumbnail ( new ( $"https://fakeimg.pl/256x256/{color}/?text=%20" ) )
. Build ( ) ) ;
}
}
2021-08-27 15:03:47 +00:00
2019-10-05 05:41:00 +00:00
public async Task Tag ( Context ctx )
{
2021-08-27 23:20:14 +00:00
var noTagSetMessage = "You currently have no system tag. To set one, type `pk;s tag <tag>`." ;
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2019-04-19 18:48:37 +00:00
2021-09-13 05:22:40 +00:00
if ( ctx . MatchRaw ( ) )
2021-08-27 23:20:14 +00:00
{
if ( ctx . System . Tag = = null )
await ctx . Reply ( noTagSetMessage ) ;
else
2021-09-13 05:22:40 +00:00
await ctx . Reply ( $"```\n{ctx.System.Tag}\n```" ) ;
2021-09-07 00:07:46 +00:00
return ;
2021-08-27 23:20:14 +00:00
}
2021-09-13 06:33:34 +00:00
if ( ! ctx . HasNext ( false ) )
2021-08-27 23:20:14 +00:00
{
if ( ctx . System . Tag = = null )
await ctx . Reply ( noTagSetMessage ) ;
else
2021-09-13 05:22:40 +00:00
await ctx . Reply ( $"Your current system tag is {ctx.System.Tag.AsCode()}. To change it, type `pk;s tag <tag>`. To clear it, type `pk;s tag -clear`." ) ;
2021-09-07 00:07:46 +00:00
return ;
2021-08-27 23:20:14 +00:00
}
2020-10-04 08:53:07 +00:00
if ( await ctx . MatchClear ( "your system's tag" ) )
2020-02-22 23:52:28 +00:00
{
2021-08-27 15:03:47 +00:00
var patch = new SystemPatch { Tag = null } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2021-08-27 15:03:47 +00:00
2020-02-22 23:52:28 +00:00
await ctx . Reply ( $"{Emojis.Success} System tag cleared." ) ;
2021-08-27 15:03:47 +00:00
}
2020-02-22 23:52:28 +00:00
else
{
var newTag = ctx . RemainderOrNull ( skipFlags : false ) ;
if ( newTag ! = null )
if ( newTag . Length > Limits . MaxSystemTagLength )
2021-09-13 05:13:57 +00:00
throw Errors . StringTooLongError ( "System tag" , newTag . Length , Limits . MaxSystemTagLength ) ;
2021-08-27 15:03:47 +00:00
var patch = new SystemPatch { Tag = newTag } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2021-08-27 15:03:47 +00:00
2020-08-25 20:44:52 +00:00
await ctx . Reply ( $"{Emojis.Success} System tag changed. Member names will now end with {newTag.AsCode()} when proxied." ) ;
2020-02-22 23:52:28 +00:00
}
2019-04-19 18:48:37 +00:00
}
2021-08-02 21:22:06 +00:00
public async Task ServerTag ( Context ctx )
{
ctx . CheckSystem ( ) . CheckGuildContext ( ) ;
var setDisabledWarning = $"{Emojis.Warn} Your system tag is currently **disabled** in this server. No tag will be applied when proxying.\nTo re-enable the system tag in the current server, type `pk;s servertag -enable`." ;
2021-09-13 07:26:24 +00:00
async Task Show ( bool raw = false )
2021-08-02 21:22:06 +00:00
{
if ( ctx . MessageContext . SystemGuildTag ! = null )
{
2021-09-13 07:26:24 +00:00
if ( raw )
{
await ctx . Reply ( $"```{ctx.MessageContext.SystemGuildTag}```" ) ;
return ;
}
2021-08-02 21:22:06 +00:00
var msg = $"Your current system tag in '{ctx.Guild.Name}' is {ctx.MessageContext.SystemGuildTag.AsCode()}" ;
if ( ! ctx . MessageContext . TagEnabled )
msg + = ", but it is currently **disabled**. To re-enable it, type `pk;s servertag -enable`." ;
else
2021-08-25 16:33:24 +00:00
msg + = ". To change it, type `pk;s servertag <tag>`. To clear it, type `pk;s servertag -clear`." ;
2021-08-02 21:22:06 +00:00
await ctx . Reply ( msg ) ;
return ;
}
else if ( ! ctx . MessageContext . TagEnabled )
await ctx . Reply ( $"Your global system tag is {ctx.System.Tag}, but it is **disabled** in this server. To re-enable it, type `pk;s servertag -enable`" ) ;
else
await ctx . Reply ( $"You currently have no system tag specific to the server '{ctx.Guild.Name}'. To set one, type `pk;s servertag <tag>`. To disable the system tag in the current server, type `pk;s servertag -disable`." ) ;
}
async Task Set ( )
{
var newTag = ctx . RemainderOrNull ( skipFlags : false ) ;
if ( newTag ! = null & & newTag . Length > Limits . MaxSystemTagLength )
2021-09-13 05:13:57 +00:00
throw Errors . StringTooLongError ( "System server tag" , newTag . Length , Limits . MaxSystemTagLength ) ;
2021-08-02 21:22:06 +00:00
2021-08-27 15:03:47 +00:00
var patch = new SystemGuildPatch { Tag = newTag } ;
2021-08-02 21:22:06 +00:00
await _db . Execute ( conn = > _repo . UpsertSystemGuild ( conn , ctx . System . Id , ctx . Guild . Id , patch ) ) ;
await ctx . Reply ( $"{Emojis.Success} System server tag changed. Member names will now end with {newTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'." ) ;
if ( ! ctx . MessageContext . TagEnabled )
await ctx . Reply ( setDisabledWarning ) ;
}
async Task Clear ( )
{
2021-08-27 15:03:47 +00:00
var patch = new SystemGuildPatch { Tag = null } ;
2021-08-02 21:22:06 +00:00
await _db . Execute ( conn = > _repo . UpsertSystemGuild ( conn , ctx . System . Id , ctx . Guild . Id , patch ) ) ;
await ctx . Reply ( $"{Emojis.Success} System server tag cleared. Member names will now end with the global system tag, if there is one set." ) ;
if ( ! ctx . MessageContext . TagEnabled )
await ctx . Reply ( setDisabledWarning ) ;
}
async Task EnableDisable ( bool newValue )
{
2021-08-27 15:03:47 +00:00
var patch = new SystemGuildPatch { TagEnabled = newValue } ;
2021-08-02 21:22:06 +00:00
await _db . Execute ( conn = > _repo . UpsertSystemGuild ( conn , ctx . System . Id , ctx . Guild . Id , patch ) ) ;
await ctx . Reply ( PrintEnableDisableResult ( newValue , newValue ! = ctx . MessageContext . TagEnabled ) ) ;
}
string PrintEnableDisableResult ( bool newValue , bool changedValue )
{
var opStr = newValue ? "enabled" : "disabled" ;
var str = "" ;
if ( ! changedValue )
str = $"{Emojis.Note} The system tag is already {opStr} in this server." ;
else
str = $"{Emojis.Success} System tag {opStr} in this server." ;
if ( newValue = = true )
{
if ( ctx . MessageContext . TagEnabled )
if ( ctx . MessageContext . SystemGuildTag = = null )
str + = $" However, you do not have a system tag specific to this server. Messages will be proxied using your global system tag, if there is one set." ;
else
str + = $" Your current system tag in '{ctx.Guild.Name}' is {ctx.MessageContext.SystemGuildTag.AsCode()}." ;
else
{
if ( ctx . MessageContext . SystemGuildTag ! = null )
str + = $" Member names will now end with the server-specific tag {ctx.MessageContext.SystemGuildTag.AsCode()} when proxied in the current server '{ctx.Guild.Name}'." ;
else
str + = $" Member names will now end with the global system tag when proxied in the current server, if there is one set." ;
}
}
2021-08-27 15:03:47 +00:00
2021-08-02 21:22:06 +00:00
return str ;
}
if ( await ctx . MatchClear ( "your system's server tag" ) )
await Clear ( ) ;
else if ( ctx . Match ( "disable" ) | | ctx . MatchFlag ( "disable" ) )
await EnableDisable ( false ) ;
else if ( ctx . Match ( "enable" ) | | ctx . MatchFlag ( "enable" ) )
await EnableDisable ( true ) ;
2021-09-13 07:26:24 +00:00
else if ( ctx . MatchRaw ( ) )
await Show ( raw : true ) ;
2021-08-02 21:22:06 +00:00
else if ( ! ctx . HasNext ( skipFlags : false ) )
await Show ( ) ;
else
await Set ( ) ;
}
2021-08-27 15:03:47 +00:00
2020-02-01 12:03:02 +00:00
public async Task Avatar ( Context ctx )
2019-07-19 12:21:16 +00:00
{
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2020-07-07 21:41:51 +00:00
async Task ClearIcon ( )
2020-03-04 17:13:36 +00:00
{
2021-08-27 15:03:47 +00:00
await _db . Execute ( c = > _repo . UpdateSystem ( c , ctx . System . Id , new SystemPatch { AvatarUrl = null } ) ) ;
2020-07-07 21:41:51 +00:00
await ctx . Reply ( $"{Emojis.Success} System icon cleared." ) ;
2020-03-04 17:13:36 +00:00
}
2020-07-07 21:41:51 +00:00
async Task SetIcon ( ParsedImage img )
{
2021-08-23 20:53:58 +00:00
await AvatarUtils . VerifyAvatarOrThrow ( _client , img . Url ) ;
2020-07-07 21:41:51 +00:00
2021-08-27 15:03:47 +00:00
await _db . Execute ( c = > _repo . UpdateSystem ( c , ctx . System . Id , new SystemPatch { AvatarUrl = img . Url } ) ) ;
2020-07-07 21:41:51 +00:00
var msg = img . Source switch
{
AvatarSource . User = > $"{Emojis.Success} System icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the system icon will need to be re-set." ,
AvatarSource . Url = > $"{Emojis.Success} System icon changed to the image at the given URL." ,
AvatarSource . Attachment = > $"{Emojis.Success} System icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the system icon will stop working." ,
_ = > throw new ArgumentOutOfRangeException ( )
} ;
2021-08-27 15:03:47 +00:00
2020-07-07 21:41:51 +00:00
// The attachment's already right there, no need to preview it.
var hasEmbed = img . Source ! = AvatarSource . Attachment ;
2021-08-27 15:03:47 +00:00
await ( hasEmbed
? ctx . Reply ( msg , embed : new EmbedBuilder ( ) . Image ( new ( img . Url ) ) . Build ( ) )
2020-07-07 21:41:51 +00:00
: ctx . Reply ( msg ) ) ;
}
async Task ShowIcon ( )
2019-12-28 11:47:31 +00:00
{
if ( ( ctx . System . AvatarUrl ? . Trim ( ) ? ? "" ) . Length > 0 )
{
2020-12-25 11:56:46 +00:00
var eb = new EmbedBuilder ( )
. Title ( "System icon" )
2021-08-01 16:51:54 +00:00
. Image ( new ( ctx . System . AvatarUrl . TryGetCleanCdnUrl ( ) ) )
2020-12-25 11:56:46 +00:00
. Description ( "To clear, use `pk;system icon clear`." ) ;
2019-12-28 11:47:31 +00:00
await ctx . Reply ( embed : eb . Build ( ) ) ;
}
else
2020-07-07 21:41:51 +00:00
throw new PKSyntaxError ( "This system does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention." ) ;
2019-12-28 11:47:31 +00:00
}
2019-10-05 05:41:00 +00:00
2020-10-04 08:53:07 +00:00
if ( await ctx . MatchClear ( "your system's icon" ) )
2020-07-07 21:41:51 +00:00
await ClearIcon ( ) ;
2021-08-27 15:03:47 +00:00
else if ( await ctx . MatchImage ( ) is { } img )
2020-07-07 21:41:51 +00:00
await SetIcon ( img ) ;
2019-10-05 05:41:00 +00:00
else
2020-07-07 21:41:51 +00:00
await ShowIcon ( ) ;
2019-07-19 12:21:16 +00:00
}
2021-08-02 17:46:12 +00:00
public async Task BannerImage ( Context ctx )
{
ctx . CheckSystem ( ) ;
async Task ClearImage ( )
{
2021-08-27 15:03:47 +00:00
await _db . Execute ( c = > _repo . UpdateSystem ( c , ctx . System . Id , new SystemPatch { BannerImage = null } ) ) ;
2021-08-02 17:46:12 +00:00
await ctx . Reply ( $"{Emojis.Success} System banner image cleared." ) ;
}
async Task SetImage ( ParsedImage img )
{
2021-08-23 20:53:58 +00:00
await AvatarUtils . VerifyAvatarOrThrow ( _client , img . Url , isFullSizeImage : true ) ;
2021-08-02 17:46:12 +00:00
2021-08-27 15:03:47 +00:00
await _db . Execute ( c = > _repo . UpdateSystem ( c , ctx . System . Id , new SystemPatch { BannerImage = img . Url } ) ) ;
2021-08-02 17:46:12 +00:00
var msg = img . Source switch
{
AvatarSource . Url = > $"{Emojis.Success} System banner image changed to the image at the given URL." ,
AvatarSource . Attachment = > $"{Emojis.Success} System banner image changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the banner image will stop working." ,
AvatarSource . User = > throw new PKError ( "Cannot set a banner image to an user's avatar." ) ,
_ = > throw new ArgumentOutOfRangeException ( )
} ;
// The attachment's already right there, no need to preview it.
var hasEmbed = img . Source ! = AvatarSource . Attachment ;
2021-08-27 15:03:47 +00:00
await ( hasEmbed
? ctx . Reply ( msg , embed : new EmbedBuilder ( ) . Image ( new ( img . Url ) ) . Build ( ) )
2021-08-02 17:46:12 +00:00
: ctx . Reply ( msg ) ) ;
}
async Task ShowImage ( )
{
if ( ( ctx . System . BannerImage ? . Trim ( ) ? ? "" ) . Length > 0 )
{
var eb = new EmbedBuilder ( )
. Title ( "System banner image" )
. Image ( new ( ctx . System . BannerImage ) )
. Description ( "To clear, use `pk;system banner clear`." ) ;
await ctx . Reply ( embed : eb . Build ( ) ) ;
}
else
throw new PKSyntaxError ( "This system does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL or @mention." ) ;
}
if ( await ctx . MatchClear ( "your system's banner image" ) )
await ClearImage ( ) ;
2021-08-27 15:03:47 +00:00
else if ( await ctx . MatchImage ( ) is { } img )
2021-08-02 17:46:12 +00:00
await SetImage ( img ) ;
else
await ShowImage ( ) ;
}
2021-08-27 15:03:47 +00:00
public async Task Delete ( Context ctx )
{
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2019-07-15 21:50:32 +00:00
2020-05-05 14:03:46 +00:00
await ctx . Reply ( $"{Emojis.Warn} Are you sure you want to delete your system? If so, reply to this message with your system's ID (`{ctx.System.Hid}`).\n**Note: this action is permanent.**" ) ;
if ( ! await ctx . ConfirmWithReply ( ctx . System . Hid ) )
throw new PKError ( $"System deletion cancelled. Note that you must reply with your system ID (`{ctx.System.Hid}`) *verbatim*." ) ;
2019-07-15 21:50:32 +00:00
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . DeleteSystem ( conn , ctx . System . Id ) ) ;
2021-08-27 15:03:47 +00:00
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} System deleted." ) ;
2019-07-15 21:50:32 +00:00
}
2021-08-27 15:03:47 +00:00
2019-12-22 13:15:56 +00:00
public async Task SystemProxy ( Context ctx )
{
2021-04-06 04:25:13 +00:00
ctx . CheckSystem ( ) ;
var guild = ctx . MatchGuild ( ) ? ? ctx . Guild ? ?
throw new PKError ( "You must run this command in a server or pass a server ID." ) ;
var gs = await _db . Execute ( c = > _repo . GetSystemGuild ( c , guild . Id , ctx . System . Id ) ) ;
string serverText ;
if ( guild . Id = = ctx . Guild ? . Id )
serverText = $"this server ({guild.Name.EscapeMarkdown()})" ;
else
serverText = $"the server {guild.Name.EscapeMarkdown()}" ;
2019-12-22 13:15:56 +00:00
bool newValue ;
if ( ctx . Match ( "on" , "enabled" , "true" , "yes" ) ) newValue = true ;
else if ( ctx . Match ( "off" , "disabled" , "false" , "no" ) ) newValue = false ;
else if ( ctx . HasNext ( ) ) throw new PKSyntaxError ( "You must pass either \"on\" or \"off\"." ) ;
2020-02-27 17:06:13 +00:00
else
{
if ( gs . ProxyEnabled )
2021-04-06 04:25:13 +00:00
await ctx . Reply ( $"Proxying in {serverText} is currently **enabled** for your system. To disable it, type `pk;system proxy off`." ) ;
2020-02-27 17:06:13 +00:00
else
2021-04-06 04:25:13 +00:00
await ctx . Reply ( $"Proxying in {serverText} is currently **disabled** for your system. To enable it, type `pk;system proxy on`." ) ;
2020-02-27 17:06:13 +00:00
return ;
}
2019-12-22 13:15:56 +00:00
2021-08-27 15:03:47 +00:00
var patch = new SystemGuildPatch { ProxyEnabled = newValue } ;
2021-04-06 04:25:13 +00:00
await _db . Execute ( conn = > _repo . UpsertSystemGuild ( conn , ctx . System . Id , guild . Id , patch ) ) ;
2019-12-22 13:15:56 +00:00
if ( newValue )
2021-04-06 04:25:13 +00:00
await ctx . Reply ( $"Message proxying in {serverText} is now **enabled** for your system." ) ;
2019-12-22 13:15:56 +00:00
else
2021-04-06 04:25:13 +00:00
await ctx . Reply ( $"Message proxying in {serverText} is now **disabled** for your system." ) ;
2019-12-22 13:15:56 +00:00
}
2021-08-27 15:03:47 +00:00
public async Task SystemTimezone ( Context ctx )
2019-06-13 18:33:17 +00:00
{
2019-10-05 05:41:00 +00:00
if ( ctx . System = = null ) throw Errors . NoSystemError ;
2020-10-04 08:53:07 +00:00
if ( await ctx . MatchClear ( ) )
2019-06-13 18:33:17 +00:00
{
2021-08-27 15:03:47 +00:00
var clearPatch = new SystemPatch { UiTz = "UTC" } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , clearPatch ) ) ;
2020-06-29 12:39:19 +00:00
2020-03-04 17:13:36 +00:00
await ctx . Reply ( $"{Emojis.Success} System time zone cleared (set to UTC)." ) ;
2019-06-13 18:33:17 +00:00
return ;
}
2021-08-27 15:03:47 +00:00
2020-02-22 23:57:46 +00:00
var zoneStr = ctx . RemainderOrNull ( ) ;
if ( zoneStr = = null )
{
await ctx . Reply (
2020-06-21 14:05:04 +00:00
$"Your current system time zone is set to **{ctx.System.UiTz}**. It is currently **{SystemClock.Instance.GetCurrentInstant().FormatZoned(ctx.System)}** in that time zone. To change your system time zone, type `pk;s tz <zone>`." ) ;
2020-02-22 23:57:46 +00:00
return ;
}
2019-06-13 18:33:17 +00:00
2019-10-05 05:41:00 +00:00
var zone = await FindTimeZone ( ctx , zoneStr ) ;
2019-06-13 18:33:17 +00:00
if ( zone = = null ) throw Errors . InvalidTimeZone ( zoneStr ) ;
var currentTime = SystemClock . Instance . GetCurrentInstant ( ) . InZone ( zone ) ;
2020-07-21 00:10:26 +00:00
var msg = $"This will change the system time zone to **{zone.Id}**. The current time is **{currentTime.FormatZoned()}**. Is this correct?" ;
2021-07-02 10:40:40 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Change Timezone" ) ) throw Errors . TimezoneChangeCancelled ;
2021-08-27 15:03:47 +00:00
var patch = new SystemPatch { UiTz = zone . Id } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2019-06-13 18:33:17 +00:00
2020-02-22 23:57:46 +00:00
await ctx . Reply ( $"System time zone changed to **{zone.Id}**." ) ;
2019-06-13 18:33:17 +00:00
}
2020-01-11 15:49:20 +00:00
public async Task SystemPrivacy ( Context ctx )
{
ctx . CheckSystem ( ) ;
2020-07-07 22:46:58 +00:00
Task PrintEmbed ( )
2020-01-11 15:49:20 +00:00
{
2020-12-25 11:56:46 +00:00
var eb = new EmbedBuilder ( )
. Title ( "Current privacy settings for your system" )
. Field ( new ( "Description" , ctx . System . DescriptionPrivacy . Explanation ( ) ) )
. Field ( new ( "Member list" , ctx . System . MemberListPrivacy . Explanation ( ) ) )
. Field ( new ( "Group list" , ctx . System . GroupListPrivacy . Explanation ( ) ) )
. Field ( new ( "Current fronter(s)" , ctx . System . FrontPrivacy . Explanation ( ) ) )
. Field ( new ( "Front/switch history" , ctx . System . FrontHistoryPrivacy . Explanation ( ) ) )
. Description ( "To edit privacy settings, use the command:\n`pk;system privacy <subject> <level>`\n\n- `subject` is one of `description`, `list`, `front`, `fronthistory`, `groups`, or `all` \n- `level` is either `public` or `private`." ) ;
2020-07-07 22:46:58 +00:00
return ctx . Reply ( embed : eb . Build ( ) ) ;
2020-01-11 15:49:20 +00:00
}
2020-07-07 22:46:58 +00:00
async Task SetLevel ( SystemPrivacySubject subject , PrivacyLevel level )
2020-01-11 15:49:20 +00:00
{
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , new SystemPatch ( ) . WithPrivacy ( subject , level ) ) ) ;
2020-07-07 22:46:58 +00:00
var levelExplanation = level switch
2020-01-11 15:49:20 +00:00
{
2020-07-07 22:46:58 +00:00
PrivacyLevel . Public = > "be able to query" ,
PrivacyLevel . Private = > "*not* be able to query" ,
_ = > ""
} ;
2020-01-11 15:49:20 +00:00
2020-07-07 22:46:58 +00:00
var subjectStr = subject switch
2020-01-11 15:49:20 +00:00
{
2020-07-07 22:46:58 +00:00
SystemPrivacySubject . Description = > "description" ,
SystemPrivacySubject . Front = > "front" ,
SystemPrivacySubject . FrontHistory = > "front history" ,
SystemPrivacySubject . MemberList = > "member list" ,
2020-08-20 19:43:17 +00:00
SystemPrivacySubject . GroupList = > "group list" ,
2020-07-07 22:46:58 +00:00
_ = > ""
} ;
2020-01-11 15:49:20 +00:00
2020-07-07 22:46:58 +00:00
var msg = $"System {subjectStr} privacy has been set to **{level.LevelName()}**. Other accounts will now {levelExplanation} your system {subjectStr}." ;
await ctx . Reply ( $"{Emojis.Success} {msg}" ) ;
2020-01-11 15:49:20 +00:00
}
2020-07-07 22:46:58 +00:00
async Task SetAll ( PrivacyLevel level )
2020-01-11 15:49:20 +00:00
{
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , new SystemPatch ( ) . WithAllPrivacy ( level ) ) ) ;
2020-06-17 19:31:39 +00:00
2020-07-07 22:46:58 +00:00
var msg = level switch
{
2020-08-20 19:43:17 +00:00
PrivacyLevel . Private = > $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now not be able to view your member list, group list, front history, or system description." ,
2020-07-07 22:46:58 +00:00
PrivacyLevel . Public = > $"All system privacy settings have been set to **{level.LevelName()}**. Other accounts will now be able to view everything." ,
_ = > ""
} ;
await ctx . Reply ( $"{Emojis.Success} {msg}" ) ;
2020-06-17 19:31:39 +00:00
}
2020-01-11 15:49:20 +00:00
2020-07-07 22:46:58 +00:00
if ( ! ctx . HasNext ( ) )
await PrintEmbed ( ) ;
else if ( ctx . Match ( "all" ) )
await SetAll ( ctx . PopPrivacyLevel ( ) ) ;
2020-06-17 19:31:39 +00:00
else
2020-07-07 22:46:58 +00:00
await SetLevel ( ctx . PopSystemPrivacySubject ( ) , ctx . PopPrivacyLevel ( ) ) ;
2020-01-11 15:49:20 +00:00
}
2021-08-27 15:03:47 +00:00
public async Task SystemPing ( Context ctx )
{
ctx . CheckSystem ( ) ;
if ( ! ctx . HasNext ( ) )
{
if ( ctx . System . PingsEnabled ) { await ctx . Reply ( "Reaction pings are currently **enabled** for your system. To disable reaction pings, type `pk;s ping disable`." ) ; }
else { await ctx . Reply ( "Reaction pings are currently **disabled** for your system. To enable reaction pings, type `pk;s ping enable`." ) ; }
}
else
{
if ( ctx . Match ( "on" , "enable" ) )
{
var patch = new SystemPatch { PingsEnabled = true } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2021-08-27 15:03:47 +00:00
2020-06-11 19:32:04 +00:00
await ctx . Reply ( "Reaction pings have now been enabled." ) ;
}
2021-08-27 15:03:47 +00:00
if ( ctx . Match ( "off" , "disable" ) )
{
var patch = new SystemPatch { PingsEnabled = false } ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( conn = > _repo . UpdateSystem ( conn , ctx . System . Id , patch ) ) ;
2021-08-27 15:03:47 +00:00
2020-06-11 19:32:04 +00:00
await ctx . Reply ( "Reaction pings have now been disabled." ) ;
}
2020-03-21 13:04:41 +00:00
}
2021-08-27 15:03:47 +00:00
}
2020-03-21 13:04:41 +00:00
2021-08-27 15:03:47 +00:00
public async Task < DateTimeZone > FindTimeZone ( Context ctx , string zoneStr )
{
2019-06-13 21:42:39 +00:00
// First, if we're given a flag emoji, we extract the flag emoji code from it.
2020-02-12 14:16:19 +00:00
zoneStr = Core . StringUtils . ExtractCountryFlag ( zoneStr ) ? ? zoneStr ;
2021-08-27 15:03:47 +00:00
2019-06-13 21:42:39 +00:00
// Then, we find all *locations* matching either the given country code or the country name.
var locations = TzdbDateTimeZoneSource . Default . Zone1970Locations ;
var matchingLocations = locations . Where ( l = > l . Countries . Any ( c = >
string . Equals ( c . Code , zoneStr , StringComparison . InvariantCultureIgnoreCase ) | |
string . Equals ( c . Name , zoneStr , StringComparison . InvariantCultureIgnoreCase ) ) ) ;
2021-08-27 15:03:47 +00:00
2019-06-13 21:42:39 +00:00
// Then, we find all (unique) time zone IDs that match.
var matchingZones = matchingLocations . Select ( l = > DateTimeZoneProviders . Tzdb . GetZoneOrNull ( l . ZoneId ) )
. Distinct ( ) . ToList ( ) ;
2021-08-27 15:03:47 +00:00
2019-06-13 21:42:39 +00:00
// If the set of matching zones is empty (ie. we didn't find anything), we try a few other things.
if ( matchingZones . Count = = 0 )
{
// First, we try to just find the time zone given directly and return that.
var givenZone = DateTimeZoneProviders . Tzdb . GetZoneOrNull ( zoneStr ) ;
if ( givenZone ! = null ) return givenZone ;
// If we didn't find anything there either, we try parsing the string as an offset, then
// find all possible zones that match that offset. For an offset like UTC+2, this doesn't *quite*
// work, since there are 57(!) matching zones (as of 2019-06-13) - but for less populated time zones
// this could work nicely.
var inputWithoutUtc = zoneStr . Replace ( "UTC" , "" ) . Replace ( "GMT" , "" ) ;
var res = OffsetPattern . CreateWithInvariantCulture ( "+H" ) . Parse ( inputWithoutUtc ) ;
if ( ! res . Success ) res = OffsetPattern . CreateWithInvariantCulture ( "+H:mm" ) . Parse ( inputWithoutUtc ) ;
// If *this* didn't parse correctly, fuck it, bail.
if ( ! res . Success ) return null ;
var offset = res . Value ;
// To try to reduce the count, we go by locations from the 1970+ database instead of just the full database
// This elides regions that have been identical since 1970, omitting small distinctions due to Ancient History(tm).
var allZones = TzdbDateTimeZoneSource . Default . Zone1970Locations . Select ( l = > l . ZoneId ) . Distinct ( ) ;
matchingZones = allZones . Select ( z = > DateTimeZoneProviders . Tzdb . GetZoneOrNull ( z ) )
. Where ( z = > z . GetUtcOffset ( SystemClock . Instance . GetCurrentInstant ( ) ) = = offset ) . ToList ( ) ;
}
2021-08-27 15:03:47 +00:00
2019-06-13 21:42:39 +00:00
// If we have a list of viable time zones, we ask the user which is correct.
2021-08-27 15:03:47 +00:00
2019-06-13 21:42:39 +00:00
// If we only have one, return that one.
if ( matchingZones . Count = = 1 )
return matchingZones . First ( ) ;
2021-08-27 15:03:47 +00:00
2019-06-13 21:42:39 +00:00
// Otherwise, prompt and return!
2019-10-05 05:41:00 +00:00
return await ctx . Choose ( "There were multiple matches for your time zone query. Please select the region that matches you the closest:" , matchingZones ,
2019-06-13 21:42:39 +00:00
z = >
{
if ( TzdbDateTimeZoneSource . Default . Aliases . Contains ( z . Id ) )
return $"**{z.Id}**, {string.Join(" , ", TzdbDateTimeZoneSource.Default.Aliases[z.Id])}" ;
return $"**{z.Id}**" ;
} ) ;
2019-04-19 18:48:37 +00:00
}
}
2021-08-27 15:03:47 +00:00
}