2019-08-12 15:47:35 +00:00
using System ;
2019-06-14 20:48:19 +00:00
using System.IO ;
using System.Linq ;
using System.Net.Http ;
using System.Text ;
using System.Threading.Tasks ;
2020-02-12 14:16:19 +00:00
2021-03-18 10:38:28 +00:00
using Myriad.Extensions ;
2020-12-25 11:56:46 +00:00
using Myriad.Rest.Exceptions ;
2021-01-15 10:29:43 +00:00
using Myriad.Rest.Types ;
using Myriad.Rest.Types.Requests ;
2020-12-25 12:58:45 +00:00
using Myriad.Types ;
2020-12-25 11:56:46 +00:00
2019-06-14 20:48:19 +00:00
using Newtonsoft.Json ;
2020-09-20 21:32:57 +00:00
using Newtonsoft.Json.Linq ;
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-14 20:48:19 +00:00
{
2020-02-01 12:03:02 +00:00
public class ImportExport
2019-06-14 20:48:19 +00:00
{
2020-08-29 11:46:27 +00:00
private readonly DataFileService _dataFiles ;
2020-12-25 11:56:46 +00:00
private readonly JsonSerializerSettings _settings = new ( )
2020-09-20 21:32:57 +00:00
{
// Otherwise it'll mess up/reformat the ISO strings for ???some??? reason >.>
DateParseHandling = DateParseHandling . None
} ;
2020-02-01 12:03:02 +00:00
public ImportExport ( DataFileService dataFiles )
2019-10-05 05:41:00 +00:00
{
_dataFiles = dataFiles ;
}
2019-06-14 20:48:19 +00:00
2019-10-05 05:41:00 +00:00
public async Task Import ( Context ctx )
2019-06-14 20:48:19 +00:00
{
2021-01-31 15:16:52 +00:00
var url = ctx . RemainderOrNull ( ) ? ? ctx . Message . Attachments . FirstOrDefault ( ) ? . Url ;
2019-06-14 20:48:19 +00:00
if ( url = = null ) throw Errors . NoImportFilePassed ;
2019-10-05 05:41:00 +00:00
await ctx . BusyIndicator ( async ( ) = >
2019-06-14 20:48:19 +00:00
{
using ( var client = new HttpClient ( ) )
{
2019-08-12 15:47:35 +00:00
HttpResponseMessage response ;
try
{
response = await client . GetAsync ( url ) ;
}
catch ( InvalidOperationException )
{
// Invalid URL throws this, we just error back out
throw Errors . InvalidImportFile ;
}
2020-09-20 21:32:57 +00:00
if ( ! response . IsSuccessStatusCode )
throw Errors . InvalidImportFile ;
2019-06-15 09:55:11 +00:00
DataFileSystem data ;
try
{
2020-09-20 21:32:57 +00:00
var json = JsonConvert . DeserializeObject < JObject > ( await response . Content . ReadAsStringAsync ( ) , _settings ) ;
data = await LoadSystem ( ctx , json ) ;
2019-06-15 09:55:11 +00:00
}
catch ( JsonException )
{
2020-09-20 21:32:57 +00:00
throw Errors . InvalidImportFile ;
2019-06-15 09:55:11 +00:00
}
2020-09-20 21:32:57 +00:00
if ( ! data . Valid )
throw Errors . InvalidImportFile ;
2019-06-14 20:48:19 +00:00
2021-01-31 15:16:52 +00:00
if ( data . LinkedAccounts ! = null & & ! data . LinkedAccounts . Contains ( ctx . Author . Id ) )
2019-06-14 20:48:19 +00:00
{
2020-07-21 00:10:26 +00:00
var msg = $"{Emojis.Warn} You seem to importing a system profile belonging to another account. Are you sure you want to proceed?" ;
2021-07-02 10:40:40 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Import" ) ) throw Errors . ImportCancelled ;
2019-06-14 20:48:19 +00:00
}
// If passed system is null, it'll create a new one
// (and that's okay!)
2021-01-31 15:16:52 +00:00
var result = await _dataFiles . ImportSystem ( data , ctx . System , ctx . Author . Id ) ;
2019-10-20 07:16:57 +00:00
if ( ! result . Success )
await ctx . Reply ( $"{Emojis.Error} The provided system profile could not be imported. {result.Message}" ) ;
2019-10-20 07:22:22 +00:00
else if ( ctx . System = = null )
2019-06-14 20:48:19 +00:00
{
2019-10-20 07:22:22 +00:00
// We didn't have a system prior to importing, so give them the new system's ID
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} PluralKit has created a system for you based on the given file. Your system ID is `{result.System.Hid}`. Type `pk;system` for more information." ) ;
2019-06-14 20:48:19 +00:00
}
else
{
2019-10-20 07:22:22 +00:00
// We already had a system, so show them what changed
2019-10-05 05:41:00 +00:00
await ctx . Reply ( $"{Emojis.Success} Updated {result.ModifiedNames.Count} members, created {result.AddedNames.Count} members. Type `pk;system list` to check!" ) ;
2019-06-14 20:48:19 +00:00
}
}
} ) ;
}
2020-09-20 21:32:57 +00:00
private async Task < DataFileSystem > LoadSystem ( Context ctx , JObject json )
{
if ( json . ContainsKey ( "tuppers" ) )
return await ImportFromTupperbox ( ctx , json ) ;
return json . ToObject < DataFileSystem > ( ) ;
}
private async Task < DataFileSystem > ImportFromTupperbox ( Context ctx , JObject json )
{
var tupperbox = json . ToObject < TupperboxProfile > ( ) ;
if ( ! tupperbox . Valid )
throw Errors . InvalidImportFile ;
var res = tupperbox . ToPluralKit ( ) ;
if ( res . HadGroups | | res . HadIndividualTags )
{
var issueStr =
$"{Emojis.Warn} The following potential issues were detected converting your Tupperbox input file:" ;
if ( res . HadGroups )
issueStr + = "\n- PluralKit does not support member groups. Members will be imported without groups." ;
if ( res . HadIndividualTags )
issueStr + = "\n- PluralKit does not support per-member system tags. Since you had multiple members with distinct tags, those tags will be applied to the members' *display names*/nicknames instead." ;
var msg = $"{issueStr}\n\nDo you want to proceed with the import?" ;
2021-07-02 21:59:27 +00:00
if ( ! await ctx . PromptYesNo ( msg , "Proceed" ) )
2020-09-20 21:32:57 +00:00
throw Errors . ImportCancelled ;
}
return res . System ;
}
2019-10-05 05:41:00 +00:00
public async Task Export ( Context ctx )
2019-06-14 20:48:19 +00:00
{
2019-10-05 05:41:00 +00:00
ctx . CheckSystem ( ) ;
var json = await ctx . BusyIndicator ( async ( ) = >
2019-06-14 20:48:19 +00:00
{
2019-07-14 19:14:16 +00:00
// Make the actual data file
2019-10-05 05:41:00 +00:00
var data = await _dataFiles . ExportSystem ( ctx . System ) ;
2019-07-14 19:14:16 +00:00
return JsonConvert . SerializeObject ( data , Formatting . None ) ;
2019-06-14 20:48:19 +00:00
} ) ;
2019-07-14 19:14:16 +00:00
// Send it as a Discord attachment *in DMs*
var stream = new MemoryStream ( Encoding . UTF8 . GetBytes ( json ) ) ;
try
{
2021-03-18 10:38:28 +00:00
var dm = await ctx . Cache . GetOrCreateDmChannel ( ctx . Rest , ctx . Author . Id ) ;
2021-01-15 10:29:43 +00:00
2021-01-31 15:16:52 +00:00
var msg = await ctx . Rest . CreateMessage ( dm . Id ,
2021-01-15 10:29:43 +00:00
new MessageRequest { Content = $"{Emojis.Success} Here you go!" } ,
new [ ] { new MultipartFile ( "system.json" , stream ) } ) ;
2021-01-31 15:16:52 +00:00
await ctx . Rest . CreateMessage ( dm . Id , new MessageRequest { Content = $"<{msg.Attachments[0].Url}>" } ) ;
2020-10-17 21:56:48 +00:00
2019-07-14 19:14:16 +00:00
// If the original message wasn't posted in DMs, send a public reminder
2021-01-31 15:16:52 +00:00
if ( ctx . Channel . Type ! = Channel . ChannelType . Dm )
2020-12-25 12:58:45 +00:00
await ctx . Reply ( $"{Emojis.Success} Check your DMs!" ) ;
2019-07-14 19:14:16 +00:00
}
2021-03-18 10:38:28 +00:00
catch ( ForbiddenException )
2019-07-14 19:14:16 +00:00
{
// If user has DMs closed, tell 'em to open them
2019-10-05 05:41:00 +00:00
await ctx . Reply (
2019-07-14 19:14:16 +00:00
$"{Emojis.Error} Could not send the data file in your DMs. Do you have DMs closed?" ) ;
}
2019-06-14 20:48:19 +00:00
}
}
2021-07-02 21:59:27 +00:00
}