397da2e1fa
A given system can now have up to 1000 members. Within 50 members of that limit, a warning will display whenever a new member is created via the bot. Once the limit is reached, a final warning will appear indicating that no additional members can be created unless members are first deleted. Attempting to create a new member at that point by any method will result in an error message indicating that the limit has been reached. Respecting this in pk;import required some restructuring to tease apart which members already exist and which ones need to be created prior to creating any members as it seems preferable to fail early and give the user the ability to intervene rather than pushing the system to the member cap and requiring manual deletion of "lower priority" members before others can be created. One consequence of the restructure is that existing members are being read in bulk which is a performance improvement of 25-70% depending on how many switches need to be imported (the more members you have, the more noticeable this is).
149 lines
6.5 KiB
C#
149 lines
6.5 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Net.Http;
|
|
using System.Text;
|
|
using System.Threading.Tasks;
|
|
using Discord;
|
|
using Discord.Net;
|
|
using Newtonsoft.Json;
|
|
|
|
using PluralKit.Bot.CommandSystem;
|
|
|
|
namespace PluralKit.Bot.Commands
|
|
{
|
|
public class ImportExportCommands
|
|
{
|
|
private DataFileService _dataFiles;
|
|
public ImportExportCommands(DataFileService dataFiles)
|
|
{
|
|
_dataFiles = dataFiles;
|
|
}
|
|
|
|
public async Task Import(Context ctx)
|
|
{
|
|
var url = ctx.RemainderOrNull() ?? ctx.Message.Attachments.FirstOrDefault()?.Url;
|
|
if (url == null) throw Errors.NoImportFilePassed;
|
|
|
|
await ctx.BusyIndicator(async () =>
|
|
{
|
|
using (var client = new HttpClient())
|
|
{
|
|
HttpResponseMessage response;
|
|
try
|
|
{
|
|
response = await client.GetAsync(url);
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
// Invalid URL throws this, we just error back out
|
|
throw Errors.InvalidImportFile;
|
|
}
|
|
|
|
if (!response.IsSuccessStatusCode) throw Errors.InvalidImportFile;
|
|
var json = await response.Content.ReadAsStringAsync();
|
|
|
|
var settings = new JsonSerializerSettings
|
|
{
|
|
MissingMemberHandling = MissingMemberHandling.Error
|
|
};
|
|
|
|
|
|
DataFileSystem data;
|
|
|
|
// TODO: can we clean up this mess?
|
|
try
|
|
{
|
|
data = JsonConvert.DeserializeObject<DataFileSystem>(json, settings);
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
try
|
|
{
|
|
var tupperbox = JsonConvert.DeserializeObject<TupperboxProfile>(json, settings);
|
|
if (!tupperbox.Valid) throw Errors.InvalidImportFile;
|
|
|
|
var res = tupperbox.ToPluralKit();
|
|
if (res.HadGroups || res.HadMultibrackets || 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.HadMultibrackets)
|
|
issueStr += "\n- PluralKit does not support members with multiple proxy tags. Only the first pair will be imported.";
|
|
if (res.HadIndividualTags)
|
|
issueStr +=
|
|
"\n- PluralKit does not support per-member system tags. Since you had multiple members with distinct tags, tags will not be imported. You can set your system tag using the `pk;system tag <tag>` command later.";
|
|
|
|
var msg = await ctx.Reply($"{issueStr}\n\nDo you want to proceed with the import?");
|
|
if (!await ctx.PromptYesNo(msg)) throw Errors.ImportCancelled;
|
|
}
|
|
|
|
data = res.System;
|
|
}
|
|
catch (JsonException)
|
|
{
|
|
throw Errors.InvalidImportFile;
|
|
}
|
|
}
|
|
|
|
|
|
if (!data.Valid) throw Errors.InvalidImportFile;
|
|
|
|
if (data.LinkedAccounts != null && !data.LinkedAccounts.Contains(ctx.Author.Id))
|
|
{
|
|
var msg = await ctx.Reply($"{Emojis.Warn} You seem to importing a system profile belonging to another account. Are you sure you want to proceed?");
|
|
if (!await ctx.PromptYesNo(msg)) throw Errors.ImportCancelled;
|
|
}
|
|
|
|
// If passed system is null, it'll create a new one
|
|
// (and that's okay!)
|
|
var result = await _dataFiles.ImportSystem(data, ctx.System, ctx.Author.Id);
|
|
if (!result.Success)
|
|
await ctx.Reply($"{Emojis.Error} The provided system profile could not be imported. {result.Message}");
|
|
else if (ctx.System != null)
|
|
{
|
|
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.");
|
|
}
|
|
else
|
|
{
|
|
await ctx.Reply($"{Emojis.Success} Updated {result.ModifiedNames.Count} members, created {result.AddedNames.Count} members. Type `pk;system list` to check!");
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
public async Task Export(Context ctx)
|
|
{
|
|
ctx.CheckSystem();
|
|
|
|
var json = await ctx.BusyIndicator(async () =>
|
|
{
|
|
// Make the actual data file
|
|
var data = await _dataFiles.ExportSystem(ctx.System);
|
|
return JsonConvert.SerializeObject(data, Formatting.None);
|
|
});
|
|
|
|
|
|
// Send it as a Discord attachment *in DMs*
|
|
var stream = new MemoryStream(Encoding.UTF8.GetBytes(json));
|
|
|
|
try
|
|
{
|
|
await ctx.Author.SendFileAsync(stream, "system.json", $"{Emojis.Success} Here you go!");
|
|
|
|
// If the original message wasn't posted in DMs, send a public reminder
|
|
if (!(ctx.Channel is IDMChannel))
|
|
await ctx.Reply($"{Emojis.Success} Check your DMs!");
|
|
}
|
|
catch (HttpException)
|
|
{
|
|
// If user has DMs closed, tell 'em to open them
|
|
await ctx.Reply(
|
|
$"{Emojis.Error} Could not send the data file in your DMs. Do you have DMs closed?");
|
|
}
|
|
}
|
|
}
|
|
} |