2019-06-13 14:53:04 +00:00
using System.Collections.Generic ;
using System.Linq ;
using System.Threading.Tasks ;
2020-02-12 14:16:19 +00:00
2019-06-13 14:53:04 +00:00
using NodaTime ;
using NodaTime.TimeZones ;
2020-02-12 14:16:19 +00:00
using PluralKit.Core ;
2019-10-05 05:41:00 +00:00
2020-02-12 14:16:19 +00:00
namespace PluralKit.Bot
2019-06-13 14:53:04 +00:00
{
2020-02-01 12:03:02 +00:00
public class Switch
2019-06-13 14:53:04 +00:00
{
2020-08-29 11:46:27 +00:00
private readonly IDatabase _db ;
private readonly ModelRepository _repo ;
2019-10-05 05:41:00 +00:00
2020-08-29 11:46:27 +00:00
public Switch ( IDatabase db , ModelRepository repo )
2019-10-05 05:41:00 +00:00
{
2020-08-29 11:46:27 +00:00
_db = db ;
_repo = repo ;
2019-10-05 05:41:00 +00:00
}
2019-06-13 14:53:04 +00:00
2020-02-01 12:03:02 +00:00
public async Task SwitchDo ( Context ctx )
2019-10-05 05:41:00 +00:00
{
ctx . CheckSystem ( ) ;
2020-07-07 17:51:19 +00:00
var members = await ctx . ParseMemberList ( ctx . System . Id ) ;
2019-10-05 05:41:00 +00:00
await DoSwitchCommand ( ctx , members ) ;
}
public async Task SwitchOut ( Context ctx )
2019-06-13 14:53:04 +00:00
{
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2021-08-27 15:03:47 +00:00
2019-10-05 05:41:00 +00:00
// Switch with no members = switch-out
await DoSwitchCommand ( ctx , new PKMember [ ] { } ) ;
}
private async Task DoSwitchCommand ( Context ctx , ICollection < PKMember > members )
{
2019-06-13 14:53:04 +00:00
// Make sure there are no dupes in the list
// We do this by checking if removing duplicate member IDs results in a list of different length
if ( members . Select ( m = > m . Id ) . Distinct ( ) . Count ( ) ! = members . Count ) throw Errors . DuplicateSwitchMembers ;
2021-08-25 17:42:08 +00:00
if ( members . Count > Limits . MaxSwitchMemberCount )
throw new PKError ( $"Switch contains too many members ({members.Count} > {Limits.MaxSwitchMemberCount} members)." ) ;
2019-07-09 22:25:47 +00:00
2019-06-13 14:53:04 +00:00
// Find the last switch and its members if applicable
2020-08-29 11:46:27 +00:00
await using var conn = await _db . Obtain ( ) ;
var lastSwitch = await _repo . GetLatestSwitch ( conn , ctx . System . Id ) ;
2019-06-13 14:53:04 +00:00
if ( lastSwitch ! = null )
{
2020-08-29 11:46:27 +00:00
var lastSwitchMembers = _repo . GetSwitchMembers ( conn , lastSwitch . Id ) ;
2019-06-13 14:53:04 +00:00
// Make sure the requested switch isn't identical to the last one
2020-01-17 23:02:17 +00:00
if ( await lastSwitchMembers . Select ( m = > m . Id ) . SequenceEqualAsync ( members . Select ( m = > m . Id ) . ToAsyncEnumerable ( ) ) )
2020-06-18 15:08:36 +00:00
throw Errors . SameSwitch ( members , ctx . LookupContextFor ( ctx . System ) ) ;
2019-06-13 14:53:04 +00:00
}
2020-08-29 11:46:27 +00:00
await _repo . AddSwitch ( conn , ctx . System . Id , members . Select ( m = > m . Id ) . ToList ( ) ) ;
2019-06-13 14:53:04 +00:00
if ( members . Count = = 0 )
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch-out registered." ) ;
2019-06-13 14:53:04 +00:00
else
2020-06-20 15:36:03 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch registered. Current fronter is now {string.Join(" , ", members.Select(m => m.NameFor(ctx)))}." ) ;
2019-06-13 14:53:04 +00:00
}
2021-08-27 15:03:47 +00:00
2019-10-05 05:41:00 +00:00
public async Task SwitchMove ( Context ctx )
2019-06-13 14:53:04 +00:00
{
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2021-08-27 15:03:47 +00:00
2019-10-05 05:41:00 +00:00
var timeToMove = ctx . RemainderOrNull ( ) ? ? throw new PKSyntaxError ( "Must pass a date or time to move the switch to." ) ;
var tz = TzdbDateTimeZoneSource . Default . ForId ( ctx . System . UiTz ? ? "UTC" ) ;
2021-08-27 15:03:47 +00:00
2020-02-12 14:16:19 +00:00
var result = DateUtils . ParseDateTime ( timeToMove , true , tz ) ;
2019-10-05 05:41:00 +00:00
if ( result = = null ) throw Errors . InvalidDateTime ( timeToMove ) ;
2020-08-29 11:46:27 +00:00
await using var conn = await _db . Obtain ( ) ;
2021-08-27 15:03:47 +00:00
2019-06-13 14:53:04 +00:00
var time = result . Value ;
if ( time . ToInstant ( ) > SystemClock . Instance . GetCurrentInstant ( ) ) throw Errors . SwitchTimeInFuture ;
// Fetch the last two switches for the system to do bounds checking on
2020-08-29 11:46:27 +00:00
var lastTwoSwitches = await _repo . GetSwitches ( conn , ctx . System . Id ) . Take ( 2 ) . ToListAsync ( ) ;
2021-08-27 15:03:47 +00:00
2019-06-13 14:53:04 +00:00
// If we don't have a switch to move, don't bother
2020-01-17 23:02:17 +00:00
if ( lastTwoSwitches . Count = = 0 ) throw Errors . NoRegisteredSwitches ;
2021-08-27 15:03:47 +00:00
2021-09-27 00:08:38 +00:00
// If there's a switch *behind* the one we move, we check to make sure we're not moving the time further back than that
2020-01-17 23:02:17 +00:00
if ( lastTwoSwitches . Count = = 2 )
2019-06-13 14:53:04 +00:00
{
if ( lastTwoSwitches [ 1 ] . Timestamp > time . ToInstant ( ) )
throw Errors . SwitchMoveBeforeSecondLast ( lastTwoSwitches [ 1 ] . Timestamp . InZone ( tz ) ) ;
}
2021-08-27 15:03:47 +00:00
2019-06-13 14:53:04 +00:00
// Now we can actually do the move, yay!
// But, we do a prompt to confirm.
2020-08-29 11:46:27 +00:00
var lastSwitchMembers = _repo . GetSwitchMembers ( conn , lastTwoSwitches [ 0 ] . Id ) ;
2020-06-18 15:08:36 +00:00
var lastSwitchMemberStr = string . Join ( ", " , await lastSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
2021-07-02 10:40:40 +00:00
var lastSwitchTime = lastTwoSwitches [ 0 ] . Timestamp . ToUnixTimeSeconds ( ) ; // .FormatZoned(ctx.System)
2020-06-21 14:05:04 +00:00
var lastSwitchDeltaStr = ( SystemClock . Instance . GetCurrentInstant ( ) - lastTwoSwitches [ 0 ] . Timestamp ) . FormatDuration ( ) ;
2021-07-02 10:40:40 +00:00
var newSwitchTime = time . ToInstant ( ) . ToUnixTimeSeconds ( ) ;
2020-06-21 14:05:04 +00:00
var newSwitchDeltaStr = ( SystemClock . Instance . GetCurrentInstant ( ) - time . ToInstant ( ) ) . FormatDuration ( ) ;
2021-08-27 15:03:47 +00:00
2019-06-13 14:53:04 +00:00
// yeet
2021-07-02 10:40:40 +00:00
var msg = $"{Emojis.Warn} This will move the latest switch ({lastSwitchMemberStr}) from <t:{lastSwitchTime}> ({lastSwitchDeltaStr} ago) to <t:{newSwitchTime}> ({newSwitchDeltaStr} ago). Is this OK?" ;
if ( ! await ctx . PromptYesNo ( msg , "Move Switch" ) ) throw Errors . SwitchMoveCancelled ;
2021-08-27 15:03:47 +00:00
2019-06-13 14:53:04 +00:00
// aaaand *now* we do the move
2020-08-29 11:46:27 +00:00
await _repo . MoveSwitch ( conn , lastTwoSwitches [ 0 ] . Id , time . ToInstant ( ) ) ;
2021-07-02 10:40:40 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch moved to <t:{newSwitchTime}> ({newSwitchDeltaStr} ago)." ) ;
2019-06-13 14:53:04 +00:00
}
2021-08-27 15:03:47 +00:00
2021-09-27 00:08:38 +00:00
public async Task SwitchEdit ( Context ctx )
{
ctx . CheckSystem ( ) ;
var members = await ctx . ParseMemberList ( ctx . System . Id ) ;
await DoEditCommand ( ctx , members ) ;
}
public async Task SwitchEditOut ( Context ctx )
{
ctx . CheckSystem ( ) ;
await DoEditCommand ( ctx , new PKMember [ ] { } ) ;
}
public async Task DoEditCommand ( Context ctx , ICollection < PKMember > members )
{
// Make sure there are no dupes in the list
// We do this by checking if removing duplicate member IDs results in a list of different length
if ( members . Select ( m = > m . Id ) . Distinct ( ) . Count ( ) ! = members . Count ) throw Errors . DuplicateSwitchMembers ;
// Find the switch to edit
await using var conn = await _db . Obtain ( ) ;
var lastSwitch = await _repo . GetLatestSwitch ( conn , ctx . System . Id ) ;
// Make sure there's at least one switch
if ( lastSwitch = = null ) throw Errors . NoRegisteredSwitches ;
var lastSwitchMembers = _repo . GetSwitchMembers ( conn , lastSwitch . Id ) ;
// Make sure switch isn't being edited to have the members it already does
if ( await lastSwitchMembers . Select ( m = > m . Id ) . SequenceEqualAsync ( members . Select ( m = > m . Id ) . ToAsyncEnumerable ( ) ) )
throw Errors . SameSwitch ( members , ctx . LookupContextFor ( ctx . System ) ) ;
// Send a prompt asking the user to confirm the switch
var lastSwitchDeltaStr = ( SystemClock . Instance . GetCurrentInstant ( ) - lastSwitch . Timestamp ) . FormatDuration ( ) ;
var lastSwitchMemberStr = string . Join ( ", " , await lastSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
var newSwitchMemberStr = string . Join ( ", " , members . Select ( m = > m . NameFor ( ctx ) ) ) ;
2021-09-27 02:50:08 +00:00
2021-09-27 00:08:38 +00:00
string msg ;
if ( members . Count = = 0 )
2021-09-27 02:50:08 +00:00
msg = $"{Emojis.Warn} This will turn the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago) into a switch-out. Is this okay?" ;
2021-09-27 00:08:38 +00:00
else
2021-09-27 02:50:08 +00:00
msg = $"{Emojis.Warn} This will change the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago) to {newSwitchMemberStr}. Is this okay?" ;
2021-09-27 00:08:38 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Edit" ) ) throw Errors . SwitchEditCancelled ;
// Actually edit the switch
await _repo . EditSwitch ( conn , lastSwitch . Id , members . Select ( m = > m . Id ) . ToList ( ) ) ;
// Tell the user the edit suceeded
if ( members . Count = = 0 )
2021-09-27 02:50:08 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch edited. The latest switch is now a switch-out." ) ;
2021-09-27 00:08:38 +00:00
else
2021-09-27 02:50:08 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch edited. Current fronter is now {newSwitchMemberStr}." ) ;
2021-09-27 00:08:38 +00:00
}
2019-10-05 05:41:00 +00:00
public async Task SwitchDelete ( Context ctx )
2019-06-13 15:05:50 +00:00
{
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
2019-11-02 21:46:51 +00:00
2020-08-21 19:50:34 +00:00
if ( ctx . Match ( "all" , "clear" ) | | ctx . MatchFlag ( "all" , "clear" ) )
2019-11-02 21:46:51 +00:00
{
// Subcommand: "delete all"
2020-07-21 00:10:26 +00:00
var purgeMsg = $"{Emojis.Warn} This will delete *all registered switches* in your system. Are you sure you want to proceed?" ;
2021-07-02 10:40:40 +00:00
if ( ! await ctx . PromptYesNo ( purgeMsg , "Clear Switches" ) )
2019-11-02 21:46:51 +00:00
throw Errors . GenericCancelled ( ) ;
2020-08-29 11:46:27 +00:00
await _db . Execute ( c = > _repo . DeleteAllSwitches ( c , ctx . System . Id ) ) ;
2019-11-02 21:46:51 +00:00
await ctx . Reply ( $"{Emojis.Success} Cleared system switches!" ) ;
return ;
}
2021-08-27 15:03:47 +00:00
2020-08-29 11:46:27 +00:00
await using var conn = await _db . Obtain ( ) ;
2019-06-13 15:05:50 +00:00
// Fetch the last two switches for the system to do bounds checking on
2020-08-29 11:46:27 +00:00
var lastTwoSwitches = await _repo . GetSwitches ( conn , ctx . System . Id ) . Take ( 2 ) . ToListAsync ( ) ;
2020-01-17 23:02:17 +00:00
if ( lastTwoSwitches . Count = = 0 ) throw Errors . NoRegisteredSwitches ;
2019-06-13 15:05:50 +00:00
2020-08-29 11:46:27 +00:00
var lastSwitchMembers = _repo . GetSwitchMembers ( conn , lastTwoSwitches [ 0 ] . Id ) ;
2020-06-18 15:08:36 +00:00
var lastSwitchMemberStr = string . Join ( ", " , await lastSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
2020-06-21 14:05:04 +00:00
var lastSwitchDeltaStr = ( SystemClock . Instance . GetCurrentInstant ( ) - lastTwoSwitches [ 0 ] . Timestamp ) . FormatDuration ( ) ;
2019-06-13 15:05:50 +00:00
2020-07-21 00:10:26 +00:00
string msg ;
2020-01-17 23:02:17 +00:00
if ( lastTwoSwitches . Count = = 1 )
2019-06-13 15:05:50 +00:00
{
2020-07-21 00:10:26 +00:00
msg = $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago). You have no other switches logged. Is this okay?" ;
2019-06-13 15:05:50 +00:00
}
else
{
2020-08-29 11:46:27 +00:00
var secondSwitchMembers = _repo . GetSwitchMembers ( conn , lastTwoSwitches [ 1 ] . Id ) ;
2020-06-18 15:08:36 +00:00
var secondSwitchMemberStr = string . Join ( ", " , await secondSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
2020-06-21 14:05:04 +00:00
var secondSwitchDeltaStr = ( SystemClock . Instance . GetCurrentInstant ( ) - lastTwoSwitches [ 1 ] . Timestamp ) . FormatDuration ( ) ;
2020-07-21 00:10:26 +00:00
msg = $"{Emojis.Warn} This will delete the latest switch ({lastSwitchMemberStr}, {lastSwitchDeltaStr} ago). The next latest switch is {secondSwitchMemberStr} ({secondSwitchDeltaStr} ago). Is this okay?" ;
2019-06-13 15:05:50 +00:00
}
2021-07-02 10:40:40 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Delete Switch" ) ) throw Errors . SwitchDeleteCancelled ;
2020-08-29 11:46:27 +00:00
await _repo . DeleteSwitch ( conn , lastTwoSwitches [ 0 ] . Id ) ;
2021-08-27 15:03:47 +00:00
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch deleted." ) ;
2019-06-13 15:05:50 +00:00
}
2019-06-13 14:53:04 +00:00
}
2021-08-27 15:03:47 +00:00
}