2020-06-29 13:57:48 +02:00
using Dapper ;
2020-02-12 15:16:19 +01:00
using PluralKit.Core ;
2020-02-01 13:03:02 +01:00
2021-11-26 21:10:56 -05:00
namespace PluralKit.Bot ;
public class MemberProxy
2020-02-01 13:03:02 +01:00
{
2025-09-04 01:22:34 +03:00
public async Task ShowProxy ( Context ctx , PKMember target )
{
if ( target . ProxyTags . Count = = 0 )
await ctx . Reply ( "This member does not have any proxy tags." ) ;
else
await ctx . Reply ( $"This member's proxy tags are:\n{target.ProxyTagsString(" \ n ")}" ) ;
}
2025-10-08 03:26:40 +00:00
public async Task ClearProxy ( Context ctx , PKMember target , bool confirmYes = false )
2021-11-26 21:10:56 -05:00
{
ctx . CheckSystem ( ) . CheckOwnMember ( target ) ;
2025-09-04 01:22:34 +03:00
// If we already have multiple tags, this would clear everything, so prompt that
if ( target . ProxyTags . Count > 1 )
2020-02-01 13:03:02 +01:00
{
2025-09-04 01:22:34 +03:00
var msg = $"{Emojis.Warn} You already have multiple proxy tags set: {target.ProxyTagsString()}\nDo you want to clear them all?" ;
2025-10-08 03:26:40 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Clear" , flagValue : confirmYes ) )
2025-09-04 01:22:34 +03:00
throw Errors . GenericCancelled ( ) ;
2020-02-01 13:03:02 +01:00
}
2025-09-04 01:22:34 +03:00
var patch = new MemberPatch { ProxyTags = Partial < ProxyTag [ ] > . Present ( new ProxyTag [ 0 ] ) } ;
await ctx . Repository . UpdateMember ( target . Id , patch ) ;
2021-08-27 11:03:47 -04:00
2025-09-04 01:22:34 +03:00
await ctx . Reply ( $"{Emojis.Success} Proxy tags cleared." ) ;
}
2020-02-01 13:03:02 +01:00
2025-10-08 03:26:40 +00:00
public async Task AddProxy ( Context ctx , PKMember target , string proxyString , bool confirmYes = false )
2025-09-04 01:22:34 +03:00
{
ctx . CheckSystem ( ) . CheckOwnMember ( target ) ;
2021-08-27 11:03:47 -04:00
2025-09-04 01:22:34 +03:00
var tagToAdd = ParseProxyTag ( proxyString ) ;
if ( tagToAdd . IsEmpty ) throw Errors . EmptyProxyTags ( target , ctx ) ;
if ( target . ProxyTags . Contains ( tagToAdd ) )
throw Errors . ProxyTagAlreadyExists ( tagToAdd , target ) ;
if ( tagToAdd . ProxyString . Length > Limits . MaxProxyTagLength )
throw new PKError (
$"Proxy tag too long ({tagToAdd.ProxyString.Length} > {Limits.MaxProxyTagLength} characters)." ) ;
2021-11-26 21:10:56 -05:00
2025-10-08 03:26:40 +00:00
if ( ! await WarnOnConflict ( ctx , target , tagToAdd , confirmYes ) )
2025-09-04 01:22:34 +03:00
throw Errors . GenericCancelled ( ) ;
2021-11-26 21:10:56 -05:00
2025-09-04 01:22:34 +03:00
var newTags = target . ProxyTags . ToList ( ) ;
newTags . Add ( tagToAdd ) ;
var patch = new MemberPatch { ProxyTags = Partial < ProxyTag [ ] > . Present ( newTags . ToArray ( ) ) } ;
await ctx . Repository . UpdateMember ( target . Id , patch ) ;
await ctx . Reply ( $"{Emojis.Success} Added proxy tags {tagToAdd.ProxyString.AsCode()} (using {tagToAdd.ProxyString.Length}/{Limits.MaxProxyTagLength} characters)." ) ;
}
public async Task RemoveProxy ( Context ctx , PKMember target , string proxyString )
{
ctx . CheckSystem ( ) . CheckOwnMember ( target ) ;
var tagToRemove = ParseProxyTag ( proxyString ) ;
if ( tagToRemove . IsEmpty ) throw Errors . EmptyProxyTags ( target , ctx ) ;
if ( ! target . ProxyTags . Contains ( tagToRemove ) )
throw Errors . ProxyTagDoesNotExist ( tagToRemove , target ) ;
var newTags = target . ProxyTags . ToList ( ) ;
newTags . Remove ( tagToRemove ) ;
var patch = new MemberPatch { ProxyTags = Partial < ProxyTag [ ] > . Present ( newTags . ToArray ( ) ) } ;
await ctx . Repository . UpdateMember ( target . Id , patch ) ;
await ctx . Reply ( $"{Emojis.Success} Removed proxy tags {tagToRemove.ProxyString.AsCode()}." ) ;
}
2025-10-08 03:26:40 +00:00
public async Task SetProxy ( Context ctx , PKMember target , string proxyString , bool confirmYes = false )
2025-09-04 01:22:34 +03:00
{
ctx . CheckSystem ( ) . CheckOwnMember ( target ) ;
var requestedTag = ParseProxyTag ( proxyString ) ;
if ( requestedTag . IsEmpty ) throw Errors . EmptyProxyTags ( target , ctx ) ;
if ( target . ProxyTags . Count > 1 )
2021-11-26 21:10:56 -05:00
{
2025-09-04 01:22:34 +03:00
var msg = $"This member already has more than one proxy tag set: {target.ProxyTagsString()}\nDo you want to replace them?" ;
2025-10-08 03:26:40 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Replace" , flagValue : confirmYes ) )
2021-11-26 21:10:56 -05:00
throw Errors . GenericCancelled ( ) ;
2025-09-04 01:22:34 +03:00
}
2021-08-27 11:03:47 -04:00
2025-09-04 01:22:34 +03:00
if ( requestedTag . ProxyString . Length > Limits . MaxProxyTagLength )
throw new PKError (
$"Proxy tag too long ({requestedTag.ProxyString.Length} > {Limits.MaxProxyTagLength} characters)." ) ;
2021-11-26 21:10:56 -05:00
2025-10-08 03:26:40 +00:00
if ( ! await WarnOnConflict ( ctx , target , requestedTag , confirmYes ) )
2025-09-04 01:22:34 +03:00
throw Errors . GenericCancelled ( ) ;
var newTags = new [ ] { requestedTag } ;
var patch = new MemberPatch { ProxyTags = Partial < ProxyTag [ ] > . Present ( newTags ) } ;
await ctx . Repository . UpdateMember ( target . Id , patch ) ;
await ctx . Reply ( $"{Emojis.Success} Member proxy tags set to {requestedTag.ProxyString.AsCode()} (using {requestedTag.ProxyString.Length}/{Limits.MaxProxyTagLength} characters)." ) ;
}
private ProxyTag ParseProxyTag ( string proxyString )
{
// Make sure there's one and only one instance of "text" in the example proxy given
var prefixAndSuffix = proxyString . Split ( "text" ) ;
if ( prefixAndSuffix . Length = = 1 ) prefixAndSuffix = prefixAndSuffix [ 0 ] . Split ( "TEXT" ) ;
if ( prefixAndSuffix . Length < 2 ) throw Errors . ProxyMustHaveText ;
if ( prefixAndSuffix . Length > 2 ) throw Errors . ProxyMultipleText ;
return new ProxyTag ( prefixAndSuffix [ 0 ] , prefixAndSuffix [ 1 ] ) ;
}
2025-10-08 03:26:40 +00:00
private async Task < bool > WarnOnConflict ( Context ctx , PKMember target , ProxyTag newTag , bool confirmYes = false )
2025-09-04 01:22:34 +03:00
{
var query = "select * from (select *, (unnest(proxy_tags)).prefix as prefix, (unnest(proxy_tags)).suffix as suffix from members where system = @System) as _ where prefix is not distinct from @Prefix and suffix is not distinct from @Suffix and id != @Existing" ;
var conflicts = ( await ctx . Database . Execute ( conn = > conn . QueryAsync < PKMember > ( query ,
new { newTag . Prefix , newTag . Suffix , Existing = target . Id , system = target . System } ) ) ) . ToList ( ) ;
if ( conflicts . Count < = 0 ) return true ;
var conflictList = conflicts . Select ( m = > $"- **{m.NameFor(ctx)}**" ) ;
var msg = $"{Emojis.Warn} The following members have conflicting proxy tags:\n{string.Join('\n', conflictList)}\nDo you want to proceed anyway?" ;
2025-10-08 03:26:40 +00:00
return await ctx . PromptYesNo ( msg , "Proceed" , flagValue : confirmYes ) ;
2020-02-01 13:03:02 +01:00
}
}