2020-07-06 17:50:39 +00:00
using System.Text ;
2021-03-28 10:02:41 +00:00
using System.Text.RegularExpressions ;
2020-07-18 11:30:54 +00:00
2020-08-16 10:10:54 +00:00
using Humanizer ;
2021-11-27 02:10:56 +00:00
using Myriad.Builders ;
using Myriad.Types ;
2021-11-25 20:33:02 +00:00
using Newtonsoft.Json.Linq ;
2021-02-09 22:36:43 +00:00
using NodaTime ;
2020-06-29 21:51:12 +00:00
using PluralKit.Core ;
2021-11-27 02:10:56 +00:00
namespace PluralKit.Bot ;
public class Groups
2020-06-29 21:51:12 +00:00
{
2021-11-27 02:10:56 +00:00
public enum AddRemoveOperation
2020-06-29 21:51:12 +00:00
{
2021-11-27 02:10:56 +00:00
Add ,
Remove
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
private readonly HttpClient _client ;
private readonly IDatabase _db ;
private readonly DispatchService _dispatch ;
private readonly EmbedService _embeds ;
private readonly ModelRepository _repo ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
public Groups ( IDatabase db , ModelRepository repo , EmbedService embeds , HttpClient client ,
DispatchService dispatch )
{
_db = db ;
_repo = repo ;
_embeds = embeds ;
_client = client ;
_dispatch = dispatch ;
}
2020-08-16 10:10:54 +00:00
2021-11-27 02:10:56 +00:00
public async Task CreateGroup ( Context ctx )
{
ctx . CheckSystem ( ) ;
// Check group name length
var groupName = ctx . RemainderOrNull ( ) ? ? throw new PKSyntaxError ( "You must pass a group name." ) ;
if ( groupName . Length > Limits . MaxGroupNameLength )
throw new PKError ( $"Group name too long ({groupName.Length}/{Limits.MaxGroupNameLength} characters)." ) ;
// Check group cap
var existingGroupCount = await _repo . GetSystemGroupCount ( ctx . System . Id ) ;
2021-11-30 02:35:21 +00:00
var groupLimit = ctx . Config . GroupLimitOverride ? ? Limits . MaxGroupCount ;
2021-11-27 02:10:56 +00:00
if ( existingGroupCount > = groupLimit )
throw new PKError (
$"System has reached the maximum number of groups ({groupLimit}). Please delete unused groups first in order to create new ones." ) ;
// Warn if there's already a group by this name
var existingGroup = await _repo . GetGroupByName ( ctx . System . Id , groupName ) ;
if ( existingGroup ! = null )
{
var msg =
$"{Emojis.Warn} You already have a group in your system with the name \" { existingGroup . Name } \ " (with ID `{existingGroup.Hid}`). Do you want to create another group with the same name?" ;
if ( ! await ctx . PromptYesNo ( msg , "Create" ) )
throw new PKError ( "Group creation cancelled." ) ;
}
2021-08-27 15:03:47 +00:00
2021-12-01 16:48:49 +00:00
using var conn = await _db . Obtain ( ) ;
2021-11-27 02:10:56 +00:00
var newGroup = await _repo . CreateGroup ( ctx . System . Id , groupName ) ;
2021-08-27 15:03:47 +00:00
2021-12-01 16:48:49 +00:00
var dispatchData = new JObject ( ) ;
dispatchData . Add ( "name" , groupName ) ;
if ( ctx . Config . GroupDefaultPrivate )
{
var patch = new GroupPatch ( ) . WithAllPrivacy ( PrivacyLevel . Private ) ;
await _repo . UpdateGroup ( newGroup . Id , patch , conn ) ;
dispatchData . Merge ( patch . ToJson ( ) ) ;
}
_ = _dispatch . Dispatch ( newGroup . Id , new UpdateDispatchData
{
Event = DispatchEvent . CREATE_GROUP ,
EventData = dispatchData
} ) ;
2021-11-25 20:33:02 +00:00
2021-11-27 02:10:56 +00:00
var eb = new EmbedBuilder ( )
. Description (
$"Your new group, **{groupName}**, has been created, with the group ID **`{newGroup.Hid}`**.\nBelow are a couple of useful commands:" )
. Field ( new Embed . Field ( "View the group card" , $"> pk;group **{newGroup.Reference()}**" ) )
. Field ( new Embed . Field ( "Add members to the group" ,
$"> pk;group **{newGroup.Reference()}** add **MemberName**\n> pk;group **{newGroup.Reference()}** add **Member1** **Member2** **Member3** (and so on...)" ) )
. Field ( new Embed . Field ( "Set the description" ,
$"> pk;group **{newGroup.Reference()}** description **This is my new group, and here is the description!**" ) )
. Field ( new Embed . Field ( "Set the group icon" ,
$"> pk;group **{newGroup.Reference()}** icon\n*(with an image attached)*" ) ) ;
await ctx . Reply ( $"{Emojis.Success} Group created!" , eb . Build ( ) ) ;
if ( existingGroupCount > = Limits . WarnThreshold ( groupLimit ) )
await ctx . Reply (
$"{Emojis.Warn} You are approaching the per-system group limit ({existingGroupCount} / {groupLimit} groups). Please review your group list for unused or duplicate groups." ) ;
}
2021-09-13 06:46:40 +00:00
2021-11-27 02:10:56 +00:00
public async Task RenameGroup ( Context ctx , PKGroup target )
{
ctx . CheckOwnGroup ( target ) ;
// Check group name length
var newName = ctx . RemainderOrNull ( ) ? ? throw new PKSyntaxError ( "You must pass a new group name." ) ;
if ( newName . Length > Limits . MaxGroupNameLength )
throw new PKError (
$"New group name too long ({newName.Length}/{Limits.MaxMemberNameLength} characters)." ) ;
2020-06-29 21:51:12 +00:00
2021-11-27 02:10:56 +00:00
// Warn if there's already a group by this name
var existingGroup = await _repo . GetGroupByName ( ctx . System . Id , newName ) ;
if ( existingGroup ! = null & & existingGroup . Id ! = target . Id )
2020-07-06 17:50:39 +00:00
{
2021-11-27 02:10:56 +00:00
var msg =
$"{Emojis.Warn} You already have a group in your system with the name \" { existingGroup . Name } \ " (with ID `{existingGroup.Hid}`). Do you want to rename this group to that name too?" ;
if ( ! await ctx . PromptYesNo ( msg , "Rename" ) )
throw new PKError ( "Group rename cancelled." ) ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
await _repo . UpdateGroup ( target . Id , new GroupPatch { Name = newName } ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Group name changed from **{target.Name}** to **{newName}**." ) ;
}
2020-08-16 10:10:54 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupDisplayName ( Context ctx , PKGroup target )
{
var noDisplayNameSetMessage = "This group does not have a display name set." ;
if ( ctx . System ? . Id = = target . System )
noDisplayNameSetMessage + =
$" To set one, type `pk;group {target.Reference()} displayname <display name>`." ;
2020-07-06 17:50:39 +00:00
2021-11-27 02:10:56 +00:00
// No perms check, display name isn't covered by member privacy
2020-08-20 19:43:17 +00:00
2021-11-27 02:10:56 +00:00
if ( ctx . MatchRaw ( ) )
2020-08-20 19:43:17 +00:00
{
2021-11-27 02:10:56 +00:00
if ( target . DisplayName = = null )
await ctx . Reply ( noDisplayNameSetMessage ) ;
else
await ctx . Reply ( $"```\n{target.DisplayName}\n```" ) ;
return ;
}
2021-08-27 23:20:14 +00:00
2021-11-27 02:10:56 +00:00
if ( ! ctx . HasNext ( false ) )
{
if ( target . DisplayName = = null )
2021-08-27 23:20:14 +00:00
{
2021-11-27 02:10:56 +00:00
await ctx . Reply ( noDisplayNameSetMessage ) ;
2021-08-27 23:20:14 +00:00
}
else
{
2021-11-27 02:10:56 +00:00
var eb = new EmbedBuilder ( )
. Field ( new Embed . Field ( "Name" , target . Name ) )
. Field ( new Embed . Field ( "Display Name" , target . DisplayName ) ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
if ( ctx . System ? . Id = = target . System )
eb . Description (
$"To change display name, type `pk;group {target.Reference()} displayname <display name>`."
+ $"To clear it, type `pk;group {target.Reference()} displayname -clear`."
+ $"To print the raw display name, type `pk;group {target.Reference()} displayname -raw`." ) ;
2020-08-20 19:43:17 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( embed : eb . Build ( ) ) ;
2020-08-20 19:43:17 +00:00
}
2021-11-27 02:10:56 +00:00
return ;
2020-07-06 17:50:39 +00:00
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
ctx . CheckOwnGroup ( target ) ;
if ( await ctx . MatchClear ( "this group's display name" ) )
2020-07-06 17:50:39 +00:00
{
2021-11-27 02:10:56 +00:00
var patch = new GroupPatch { DisplayName = Partial < string > . Null ( ) } ;
await _repo . UpdateGroup ( target . Id , patch ) ;
2020-07-06 17:50:39 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Group display name cleared." ) ;
}
else
{
var newDisplayName = ctx . RemainderOrNull ( false ) . NormalizeLineEndSpacing ( ) ;
2021-05-24 19:18:57 +00:00
2021-11-27 02:10:56 +00:00
var patch = new GroupPatch { DisplayName = Partial < string > . Present ( newDisplayName ) } ;
await _repo . UpdateGroup ( target . Id , patch ) ;
2021-08-27 23:20:14 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Group display name changed." ) ;
}
}
2020-07-06 17:50:39 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupDescription ( Context ctx , PKGroup target )
{
if ( ! target . DescriptionPrivacy . CanAccess ( ctx . LookupContextFor ( target . System ) ) )
2021-12-06 09:01:42 +00:00
throw target . System = = ctx . System ? . Id ? Errors . LookupHidden : Errors . LookupNotAllowed ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
var noDescriptionSetMessage = "This group does not have a description set." ;
if ( ctx . System ? . Id = = target . System )
noDescriptionSetMessage + =
$" To set one, type `pk;group {target.Reference()} description <description>`." ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
if ( ctx . MatchRaw ( ) )
{
if ( target . Description = = null )
await ctx . Reply ( noDescriptionSetMessage ) ;
else
await ctx . Reply ( $"```\n{target.Description}\n```" ) ;
return ;
2020-07-06 17:50:39 +00:00
}
2021-11-27 02:10:56 +00:00
if ( ! ctx . HasNext ( false ) )
2020-08-08 13:09:42 +00:00
{
2021-11-27 02:10:56 +00:00
if ( target . Description = = null )
await ctx . Reply ( noDescriptionSetMessage ) ;
else
await ctx . Reply ( embed : new EmbedBuilder ( )
. Title ( "Group description" )
. Description ( target . Description )
. Field ( new Embed . Field ( "\u200B" ,
$"To print the description with formatting, type `pk;group {target.Reference()} description -raw`."
+ ( ctx . System ? . Id = = target . System
? $" To clear it, type `pk;group {target.Reference()} description -clear`."
: "" ) ) )
. Build ( ) ) ;
return ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
ctx . CheckOwnGroup ( target ) ;
2020-08-08 13:09:42 +00:00
2021-11-27 02:10:56 +00:00
if ( await ctx . MatchClear ( "this group's description" ) )
{
var patch = new GroupPatch { Description = Partial < string > . Null ( ) } ;
await _repo . UpdateGroup ( target . Id , patch ) ;
await ctx . Reply ( $"{Emojis.Success} Group description cleared." ) ;
}
else
{
var description = ctx . RemainderOrNull ( false ) . NormalizeLineEndSpacing ( ) ;
if ( description . IsLongerThan ( Limits . MaxDescriptionLength ) )
throw Errors . StringTooLongError ( "Description" , description . Length , Limits . MaxDescriptionLength ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
var patch = new GroupPatch { Description = Partial < string > . Present ( description ) } ;
await _repo . UpdateGroup ( target . Id , patch ) ;
2020-08-08 13:09:42 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Group description changed." ) ;
}
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupIcon ( Context ctx , PKGroup target )
{
async Task ClearIcon ( )
{
ctx . CheckOwnGroup ( target ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
await _repo . UpdateGroup ( target . Id , new GroupPatch { Icon = null } ) ;
await ctx . Reply ( $"{Emojis.Success} Group icon cleared." ) ;
}
2020-08-08 13:09:42 +00:00
2021-11-27 02:10:56 +00:00
async Task SetIcon ( ParsedImage img )
{
ctx . CheckOwnGroup ( target ) ;
2020-08-08 13:09:42 +00:00
2021-11-27 02:10:56 +00:00
await AvatarUtils . VerifyAvatarOrThrow ( _client , img . Url ) ;
await _repo . UpdateGroup ( target . Id , new GroupPatch { Icon = img . Url } ) ;
var msg = img . Source switch
{
AvatarSource . User = >
$"{Emojis.Success} Group icon changed to {img.SourceUser?.Username}'s avatar!\n{Emojis.Warn} If {img.SourceUser?.Username} changes their avatar, the group icon will need to be re-set." ,
AvatarSource . Url = > $"{Emojis.Success} Group icon changed to the image at the given URL." ,
AvatarSource . Attachment = >
$"{Emojis.Success} Group icon changed to attached image.\n{Emojis.Warn} If you delete the message containing the attachment, the group icon will stop working." ,
_ = > throw new ArgumentOutOfRangeException ( )
} ;
// The attachment's already right there, no need to preview it.
var hasEmbed = img . Source ! = AvatarSource . Attachment ;
await ( hasEmbed
? ctx . Reply ( msg , new EmbedBuilder ( ) . Image ( new Embed . EmbedImage ( img . Url ) ) . Build ( ) )
: ctx . Reply ( msg ) ) ;
2020-08-08 13:09:42 +00:00
}
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
async Task ShowIcon ( )
2021-08-02 17:46:12 +00:00
{
2021-11-27 02:10:56 +00:00
if ( ! target . IconPrivacy . CanAccess ( ctx . LookupContextFor ( target . System ) ) )
2021-12-06 09:01:42 +00:00
throw target . System = = ctx . System ? . Id ? Errors . LookupHidden : Errors . LookupNotAllowed ;
2021-11-27 02:10:56 +00:00
if ( ( target . Icon ? . Trim ( ) ? ? "" ) . Length > 0 )
2021-08-02 17:46:12 +00:00
{
2021-11-27 02:10:56 +00:00
var eb = new EmbedBuilder ( )
. Title ( "Group icon" )
. Image ( new Embed . EmbedImage ( target . Icon . TryGetCleanCdnUrl ( ) ) ) ;
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
if ( target . System = = ctx . System ? . Id )
eb . Description ( $"To clear, use `pk;group {target.Reference()} icon -clear`." ) ;
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( embed : eb . Build ( ) ) ;
}
else
2021-08-02 17:46:12 +00:00
{
2021-11-27 02:10:56 +00:00
throw new PKSyntaxError (
"This group does not have an icon set. Set one by attaching an image to this command, or by passing an image URL or @mention." ) ;
}
}
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
if ( await ctx . MatchClear ( "this group's icon" ) )
await ClearIcon ( ) ;
else if ( await ctx . MatchImage ( ) is { } img )
await SetIcon ( img ) ;
else
await ShowIcon ( ) ;
}
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupBannerImage ( Context ctx , PKGroup target )
{
async Task ClearBannerImage ( )
{
ctx . CheckOwnGroup ( target ) ;
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
await _repo . UpdateGroup ( target . Id , new GroupPatch { BannerImage = null } ) ;
await ctx . Reply ( $"{Emojis.Success} Group banner image cleared." ) ;
}
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
async Task SetBannerImage ( ParsedImage img )
{
ctx . CheckOwnGroup ( target ) ;
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
await AvatarUtils . VerifyAvatarOrThrow ( _client , img . Url , true ) ;
2021-08-02 17:46:12 +00:00
2021-11-27 02:10:56 +00:00
await _repo . UpdateGroup ( target . Id , new GroupPatch { BannerImage = img . Url } ) ;
var msg = img . Source switch
{
AvatarSource . Url = > $"{Emojis.Success} Group banner image changed to the image at the given URL." ,
AvatarSource . Attachment = >
$"{Emojis.Success} Group 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 ;
await ( hasEmbed
? ctx . Reply ( msg , new EmbedBuilder ( ) . Image ( new Embed . EmbedImage ( img . Url ) ) . Build ( ) )
: ctx . Reply ( msg ) ) ;
2021-08-02 17:46:12 +00:00
}
2021-11-27 02:10:56 +00:00
async Task ShowBannerImage ( )
2021-03-28 10:02:41 +00:00
{
2021-11-27 02:10:56 +00:00
if ( ! target . DescriptionPrivacy . CanAccess ( ctx . LookupContextFor ( target . System ) ) )
2021-12-06 09:01:42 +00:00
throw target . System = = ctx . System ? . Id ? Errors . LookupHidden : Errors . LookupNotAllowed ;
2021-11-27 02:10:56 +00:00
if ( ( target . BannerImage ? . Trim ( ) ? ? "" ) . Length > 0 )
2021-03-28 10:02:41 +00:00
{
2021-11-27 02:10:56 +00:00
var eb = new EmbedBuilder ( )
. Title ( "Group banner image" )
. Image ( new Embed . EmbedImage ( target . BannerImage ) ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
if ( target . System = = ctx . System ? . Id )
eb . Description ( $"To clear, use `pk;group {target.Reference()} banner clear`." ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( embed : eb . Build ( ) ) ;
2021-03-28 10:02:41 +00:00
}
else
{
2021-11-27 02:10:56 +00:00
throw new PKSyntaxError (
"This group does not have a banner image set. Set one by attaching an image to this command, or by passing an image URL or @mention." ) ;
}
}
2021-03-28 10:02:41 +00:00
2021-11-27 02:10:56 +00:00
if ( await ctx . MatchClear ( "this group's banner image" ) )
await ClearBannerImage ( ) ;
else if ( await ctx . MatchImage ( ) is { } img )
await SetBannerImage ( img ) ;
else
await ShowBannerImage ( ) ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupColor ( Context ctx , PKGroup target )
{
var color = ctx . RemainderOrNull ( ) ;
if ( await ctx . MatchClear ( ) )
{
ctx . CheckOwnGroup ( target ) ;
var patch = new GroupPatch { Color = Partial < string > . Null ( ) } ;
await _repo . UpdateGroup ( target . Id , patch ) ;
2021-03-28 10:02:41 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Group color cleared." ) ;
}
else if ( ! ctx . HasNext ( ) )
{
if ( target . Color = = null )
if ( ctx . System ? . Id = = target . System )
await ctx . Reply (
$"This group does not have a color set. To set one, type `pk;group {target.Reference()} color <color>`." ) ;
else
await ctx . Reply ( "This group does not have a color set." ) ;
else
2021-03-28 10:02:41 +00:00
await ctx . Reply ( embed : new EmbedBuilder ( )
2021-11-27 02:10:56 +00:00
. Title ( "Group color" )
. Color ( target . Color . ToDiscordColor ( ) )
. Thumbnail ( new Embed . EmbedThumbnail ( $"https://fakeimg.pl/256x256/{target.Color}/?text=%20" ) )
. Description ( $"This group's color is **#{target.Color}**."
+ ( ctx . System ? . Id = = target . System
? $" To clear it, type `pk;group {target.Reference()} color -clear`."
: "" ) )
2021-03-28 10:02:41 +00:00
. Build ( ) ) ;
}
2021-11-27 02:10:56 +00:00
else
2020-07-06 17:50:39 +00:00
{
2021-11-27 02:10:56 +00:00
ctx . CheckOwnGroup ( target ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
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
2021-11-27 02:10:56 +00:00
var patch = new GroupPatch { Color = Partial < string > . Present ( color . ToLowerInvariant ( ) ) } ;
await _repo . UpdateGroup ( target . Id , patch ) ;
2020-07-06 17:50:39 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( embed : new EmbedBuilder ( )
. Title ( $"{Emojis.Success} Group color changed." )
. Color ( color . ToDiscordColor ( ) )
. Thumbnail ( new Embed . EmbedThumbnail ( $"https://fakeimg.pl/256x256/{color}/?text=%20" ) )
. Build ( ) ) ;
}
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
public async Task ListSystemGroups ( Context ctx , PKSystem system )
{
if ( system = = null )
{
ctx . CheckSystem ( ) ;
system = ctx . System ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
ctx . CheckSystemPrivacy ( system , system . GroupListPrivacy ) ;
2020-07-06 17:50:39 +00:00
2021-11-27 02:10:56 +00:00
// TODO: integrate with the normal "search" system
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
var pctx = LookupContext . ByNonOwner ;
if ( ctx . MatchFlag ( "a" , "all" ) )
{
if ( system . Id = = ctx . System . Id )
pctx = LookupContext . ByOwner ;
else
2021-12-06 09:01:42 +00:00
throw system . Id = = ctx . System ? . Id ? Errors . LookupHidden : Errors . LookupNotAllowed ;
2020-07-06 17:50:39 +00:00
}
2021-11-27 02:10:56 +00:00
var groups = ( await _db . Execute ( conn = > conn . QueryGroupList ( system . Id ) ) )
. Where ( g = > g . Visibility . CanAccess ( pctx ) )
. OrderBy ( g = > g . Name , StringComparer . InvariantCultureIgnoreCase )
. ToList ( ) ;
if ( groups . Count = = 0 )
2020-06-29 21:51:12 +00:00
{
2021-11-27 02:10:56 +00:00
if ( system . Id = = ctx . System ? . Id )
await ctx . Reply ( "This system has no groups. To create one, use the command `pk;group new <name>`." ) ;
else
await ctx . Reply ( "This system has no groups." ) ;
return ;
2020-06-29 21:51:12 +00:00
}
2021-11-27 02:10:56 +00:00
var title = system . Name ! = null ? $"Groups of {system.Name} (`{system.Hid}`)" : $"Groups of `{system.Hid}`" ;
await ctx . Paginate ( groups . ToAsyncEnumerable ( ) , groups . Count , 25 , title , ctx . System . Color , Renderer ) ;
Task Renderer ( EmbedBuilder eb , IEnumerable < ListedGroup > page )
2020-07-07 13:28:53 +00:00
{
2021-11-27 02:10:56 +00:00
eb . WithSimpleLineContent ( page . Select ( g = >
{
if ( g . DisplayName ! = null )
return
$"[`{g.Hid}`] **{g.Name.EscapeMarkdown()}** ({g.DisplayName.EscapeMarkdown()}) ({" member ".ToQuantity(g.MemberCount)})" ;
return $"[`{g.Hid}`] **{g.Name.EscapeMarkdown()}** ({" member ".ToQuantity(g.MemberCount)})" ;
} ) ) ;
eb . Footer ( new Embed . EmbedFooter ( $"{groups.Count} total." ) ) ;
return Task . CompletedTask ;
}
}
2020-07-07 13:28:53 +00:00
2021-11-27 02:10:56 +00:00
public async Task ShowGroupCard ( Context ctx , PKGroup target )
{
var system = await GetGroupSystem ( ctx , target ) ;
await ctx . Reply ( embed : await _embeds . CreateGroupEmbed ( ctx , system , target ) ) ;
}
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupPrivacy ( Context ctx , PKGroup target , PrivacyLevel ? newValueFromCommand )
{
ctx . CheckSystem ( ) . CheckOwnGroup ( target ) ;
// Display privacy settings
if ( ! ctx . HasNext ( ) & & newValueFromCommand = = null )
{
await ctx . Reply ( embed : new EmbedBuilder ( )
. Title ( $"Current privacy settings for {target.Name}" )
. Field ( new Embed . Field ( "Description" , target . DescriptionPrivacy . Explanation ( ) ) )
. Field ( new Embed . Field ( "Icon" , target . IconPrivacy . Explanation ( ) ) )
. Field ( new Embed . Field ( "Member list" , target . ListPrivacy . Explanation ( ) ) )
. Field ( new Embed . Field ( "Visibility" , target . Visibility . Explanation ( ) ) )
. Description (
$"To edit privacy settings, use the command:\n> pk;group **{target.Reference()}** privacy **<subject>** **<level>**\n\n- `subject` is one of `description`, `icon`, `members`, `visibility`, or `all`\n- `level` is either `public` or `private`." )
. Build ( ) ) ;
return ;
2020-07-07 17:34:23 +00:00
}
2021-11-27 02:10:56 +00:00
async Task SetAll ( PrivacyLevel level )
2020-07-07 13:28:53 +00:00
{
2021-11-27 02:10:56 +00:00
await _repo . UpdateGroup ( target . Id , new GroupPatch ( ) . WithAllPrivacy ( level ) ) ;
if ( level = = PrivacyLevel . Private )
await ctx . Reply (
$"{Emojis.Success} All {target.Name}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see nothing on the group card." ) ;
else
await ctx . Reply (
$"{Emojis.Success} All {target.Name}'s privacy settings have been set to **{level.LevelName()}**. Other accounts will now see everything on the group card." ) ;
2020-07-07 13:28:53 +00:00
}
2020-11-14 17:05:30 +00:00
2021-11-27 02:10:56 +00:00
async Task SetLevel ( GroupPrivacySubject subject , PrivacyLevel level )
2020-07-18 11:53:02 +00:00
{
2021-11-27 02:10:56 +00:00
await _repo . UpdateGroup ( target . Id , new GroupPatch ( ) . WithPrivacy ( subject , level ) ) ;
2020-07-18 11:53:02 +00:00
2021-11-27 02:10:56 +00:00
var subjectName = subject switch
2020-07-18 11:53:02 +00:00
{
2021-11-27 02:10:56 +00:00
GroupPrivacySubject . Description = > "description privacy" ,
GroupPrivacySubject . Icon = > "icon privacy" ,
GroupPrivacySubject . List = > "member list" ,
GroupPrivacySubject . Visibility = > "visibility" ,
_ = > throw new ArgumentOutOfRangeException ( $"Unknown privacy subject {subject}" )
} ;
var explanation = ( subject , level ) switch
2020-07-18 11:53:02 +00:00
{
2021-11-27 02:10:56 +00:00
( GroupPrivacySubject . Description , PrivacyLevel . Private ) = >
"This group's description is now hidden from other systems." ,
( GroupPrivacySubject . Icon , PrivacyLevel . Private ) = >
"This group's icon is now hidden from other systems." ,
( GroupPrivacySubject . Visibility , PrivacyLevel . Private ) = >
"This group is now hidden from group lists and member cards." ,
( GroupPrivacySubject . List , PrivacyLevel . Private ) = >
"This group's member list is now hidden from other systems." ,
( GroupPrivacySubject . Description , PrivacyLevel . Public ) = >
"This group's description is no longer hidden from other systems." ,
( GroupPrivacySubject . Icon , PrivacyLevel . Public ) = >
"This group's icon is no longer hidden from other systems." ,
( GroupPrivacySubject . Visibility , PrivacyLevel . Public ) = >
"This group is no longer hidden from group lists and member cards." ,
( GroupPrivacySubject . List , PrivacyLevel . Public ) = >
"This group's member list is no longer hidden from other systems." ,
_ = > throw new InvalidOperationException ( $"Invalid subject/level tuple ({subject}, {level})" )
} ;
await ctx . Reply (
$"{Emojis.Success} {target.Name}'s **{subjectName}** has been set to **{level.LevelName()}**. {explanation}" ) ;
2020-07-18 11:53:02 +00:00
}
2020-07-07 17:34:44 +00:00
2021-11-27 02:10:56 +00:00
if ( ctx . Match ( "all" ) | | newValueFromCommand ! = null )
await SetAll ( newValueFromCommand ? ? ctx . PopPrivacyLevel ( ) ) ;
else
await SetLevel ( ctx . PopGroupPrivacySubject ( ) , ctx . PopPrivacyLevel ( ) ) ;
}
2020-08-08 12:56:34 +00:00
2021-11-27 02:10:56 +00:00
public async Task DeleteGroup ( Context ctx , PKGroup target )
{
ctx . CheckOwnGroup ( target ) ;
2020-08-08 12:56:34 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply (
$"{Emojis.Warn} Are you sure you want to delete this group? If so, reply to this message with the group's ID (`{target.Hid}`).\n**Note: this action is permanent.**" ) ;
if ( ! await ctx . ConfirmWithReply ( target . Hid ) )
throw new PKError (
$"Group deletion cancelled. Note that you must reply with your group ID (`{target.Hid}`) *verbatim*." ) ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
await _repo . DeleteGroup ( target . Id ) ;
2020-08-08 12:56:34 +00:00
2021-11-27 02:10:56 +00:00
await ctx . Reply ( $"{Emojis.Success} Group deleted." ) ;
}
2021-02-09 22:36:43 +00:00
2021-11-27 02:10:56 +00:00
public async Task GroupFrontPercent ( Context ctx , PKGroup target )
{
var targetSystem = await GetGroupSystem ( ctx , target ) ;
ctx . CheckSystemPrivacy ( targetSystem , targetSystem . FrontHistoryPrivacy ) ;
2021-06-16 12:56:52 +00:00
2021-11-27 02:10:56 +00:00
var totalSwitches = await _repo . GetSwitchCount ( targetSystem . Id ) ;
if ( totalSwitches = = 0 ) throw Errors . NoRegisteredSwitches ;
2021-08-27 15:03:47 +00:00
2021-11-27 02:10:56 +00:00
var durationStr = ctx . RemainderOrNull ( ) ? ? "30d" ;
2021-02-09 22:36:43 +00:00
2021-11-27 02:10:56 +00:00
var now = SystemClock . Instance . GetCurrentInstant ( ) ;
2021-02-09 22:36:43 +00:00
2021-11-30 02:35:21 +00:00
var rangeStart = DateUtils . ParseDateTime ( durationStr , true , ctx . Zone ) ;
2021-11-27 02:10:56 +00:00
if ( rangeStart = = null ) throw Errors . InvalidDateTime ( durationStr ) ;
if ( rangeStart . Value . ToInstant ( ) > now ) throw Errors . FrontPercentTimeInFuture ;
2021-02-09 22:36:43 +00:00
2021-11-27 02:10:56 +00:00
var title = new StringBuilder ( $"Frontpercent of {target.DisplayName ?? target.Name} (`{target.Hid}`) in " ) ;
if ( targetSystem . Name ! = null )
title . Append ( $"{targetSystem.Name} (`{targetSystem.Hid}`)" ) ;
else
title . Append ( $"`{targetSystem.Hid}`" ) ;
2021-02-09 22:36:43 +00:00
2021-11-27 02:10:56 +00:00
var ignoreNoFronters = ctx . MatchFlag ( "fo" , "fronters-only" ) ;
var showFlat = ctx . MatchFlag ( "flat" ) ;
var frontpercent = await _db . Execute ( c = >
_repo . GetFrontBreakdown ( c , targetSystem . Id , target . Id , rangeStart . Value . ToInstant ( ) , now ) ) ;
await ctx . Reply ( embed : await _embeds . CreateFrontPercentEmbed ( frontpercent , targetSystem , target ,
2021-12-06 05:32:54 +00:00
ctx . Zone , ctx . LookupContextFor ( targetSystem . Id ) , title . ToString ( ) , ignoreNoFronters , showFlat ) ) ;
2021-11-27 02:10:56 +00:00
}
private async Task < PKSystem > GetGroupSystem ( Context ctx , PKGroup target )
{
var system = ctx . System ;
if ( system ? . Id = = target . System )
return system ;
return await _repo . GetSystem ( target . System ) ! ;
2020-06-29 21:51:12 +00:00
}
}