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
2020-04-24 19:50:28 +00:00
using DSharpPlus.Entities ;
2019-10-05 05:41:00 +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
{
2019-10-26 17:45:30 +00:00
private IDataStore _data ;
2019-10-05 05:41:00 +00:00
2020-02-01 12:03:02 +00:00
public Switch ( IDataStore data )
2019-10-05 05:41:00 +00:00
{
2019-10-26 17:45:30 +00:00
_data = data ;
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 ( ) ;
var members = new List < PKMember > ( ) ;
2019-06-13 14:53:04 +00:00
2019-10-05 05:41:00 +00:00
// Loop through all the given arguments
while ( ctx . HasNext ( ) )
{
// and attempt to match a member
var member = await ctx . MatchMember ( ) ;
if ( member = = null )
// if we can't, big error. Every member name must be valid.
throw new PKError ( ctx . CreateMemberNotFoundError ( ctx . PopArgument ( ) ) ) ;
ctx . CheckOwnMember ( member ) ; // Ensure they're in our own system
members . Add ( member ) ; // Then add to the final output list
}
2019-06-13 14:53:04 +00:00
2019-10-05 05:41:00 +00:00
// Finally, do the actual switch
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 ( ) ;
2019-07-09 22:25: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 ;
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-06-15 23:15:59 +00:00
var lastSwitch = await _data . GetLatestSwitch ( ctx . System . Id ) ;
2019-06-13 14:53:04 +00:00
if ( lastSwitch ! = null )
{
2020-01-17 23:02:17 +00:00
var lastSwitchMembers = _data . GetSwitchMembers ( lastSwitch ) ;
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-06-15 23:15:59 +00:00
await _data . AddSwitch ( ctx . System . Id , members ) ;
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
}
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 ( ) ;
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" ) ;
2019-06-13 14:53:04 +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 ) ;
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-06-15 23:15:59 +00:00
var lastTwoSwitches = await _data . GetSwitches ( ctx . System . Id ) . Take ( 2 ) . ToListAsync ( ) ;
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 ;
2019-06-13 14:53:04 +00:00
// If there's a switch *behind* the one we move, we check to make srue 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 ) ) ;
}
// Now we can actually do the move, yay!
// But, we do a prompt to confirm.
2020-01-17 23:02:17 +00:00
var lastSwitchMembers = _data . GetSwitchMembers ( lastTwoSwitches [ 0 ] ) ;
2020-06-18 15:08:36 +00:00
var lastSwitchMemberStr = string . Join ( ", " , await lastSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
2020-02-12 14:16:19 +00:00
var lastSwitchTimeStr = DateTimeFormats . ZonedDateTimeFormat . Format ( lastTwoSwitches [ 0 ] . Timestamp . InZone ( ctx . System . Zone ) ) ;
var lastSwitchDeltaStr = DateTimeFormats . DurationFormat . Format ( SystemClock . Instance . GetCurrentInstant ( ) - lastTwoSwitches [ 0 ] . Timestamp ) ;
var newSwitchTimeStr = DateTimeFormats . ZonedDateTimeFormat . Format ( time ) ;
var newSwitchDeltaStr = DateTimeFormats . DurationFormat . Format ( SystemClock . Instance . GetCurrentInstant ( ) - time . ToInstant ( ) ) ;
2019-06-13 14:53:04 +00:00
// yeet
2020-06-20 15:36:03 +00:00
var msg = await ctx . Reply ( $"{Emojis.Warn} This will move the latest switch ({lastSwitchMemberStr}) from {lastSwitchTimeStr} ({lastSwitchDeltaStr} ago) to {newSwitchTimeStr} ({newSwitchDeltaStr} ago). Is this OK?" ) ;
2019-10-05 05:41:00 +00:00
if ( ! await ctx . PromptYesNo ( msg ) ) throw Errors . SwitchMoveCancelled ;
2019-06-13 14:53:04 +00:00
// aaaand *now* we do the move
2019-10-26 17:45:30 +00:00
await _data . MoveSwitch ( lastTwoSwitches [ 0 ] , time . ToInstant ( ) ) ;
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} Switch moved." ) ;
2019-06-13 14:53:04 +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
if ( ctx . Match ( "all" , "clear" ) )
{
// Subcommand: "delete all"
var purgeMsg = await ctx . Reply ( $"{Emojis.Warn} This will delete *all registered switches* in your system. Are you sure you want to proceed?" ) ;
if ( ! await ctx . PromptYesNo ( purgeMsg ) )
throw Errors . GenericCancelled ( ) ;
await _data . DeleteAllSwitches ( ctx . System ) ;
await ctx . Reply ( $"{Emojis.Success} Cleared system switches!" ) ;
return ;
}
2019-10-05 05:41:00 +00:00
2019-06-13 15:05:50 +00:00
// Fetch the last two switches for the system to do bounds checking on
2020-06-15 23:15:59 +00:00
var lastTwoSwitches = await _data . GetSwitches ( 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-01-17 23:02:17 +00:00
var lastSwitchMembers = _data . GetSwitchMembers ( lastTwoSwitches [ 0 ] ) ;
2020-06-18 15:08:36 +00:00
var lastSwitchMemberStr = string . Join ( ", " , await lastSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
2020-02-12 14:16:19 +00:00
var lastSwitchDeltaStr = DateTimeFormats . DurationFormat . Format ( SystemClock . Instance . GetCurrentInstant ( ) - lastTwoSwitches [ 0 ] . Timestamp ) ;
2019-06-13 15:05:50 +00:00
2020-04-24 19:50:28 +00:00
DiscordMessage msg ;
2020-01-17 23:02:17 +00:00
if ( lastTwoSwitches . Count = = 1 )
2019-06-13 15:05:50 +00:00
{
2019-10-05 05:41:00 +00:00
msg = await ctx . Reply (
2020-06-20 15:36:03 +00:00
$"{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-01-17 23:02:17 +00:00
var secondSwitchMembers = _data . GetSwitchMembers ( lastTwoSwitches [ 1 ] ) ;
2020-06-18 15:08:36 +00:00
var secondSwitchMemberStr = string . Join ( ", " , await secondSwitchMembers . Select ( m = > m . NameFor ( ctx ) ) . ToListAsync ( ) ) ;
2020-02-12 14:16:19 +00:00
var secondSwitchDeltaStr = DateTimeFormats . DurationFormat . Format ( SystemClock . Instance . GetCurrentInstant ( ) - lastTwoSwitches [ 1 ] . Timestamp ) ;
2019-10-05 05:41:00 +00:00
msg = await ctx . Reply (
2020-06-20 15:36:03 +00:00
$"{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
}
2019-10-05 05:41:00 +00:00
if ( ! await ctx . PromptYesNo ( msg ) ) throw Errors . SwitchDeleteCancelled ;
2019-10-26 17:45:30 +00:00
await _data . DeleteSwitch ( lastTwoSwitches [ 0 ] ) ;
2019-06-13 15:05:50 +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
}
}