mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2025-05-20 09:55:08 -04:00
Refactor CLI (#81)
This commit is contained in:
parent
0faa427970
commit
bd9dc6455f
23 changed files with 338 additions and 227 deletions
|
@ -1,26 +0,0 @@
|
||||||
using System;
|
|
||||||
using DiscordChatExporter.Core.Models;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli
|
|
||||||
{
|
|
||||||
public class CliOptions
|
|
||||||
{
|
|
||||||
public string TokenValue { get; set; }
|
|
||||||
|
|
||||||
public bool IsBotToken { get; set; }
|
|
||||||
|
|
||||||
public string ChannelId { get; set; }
|
|
||||||
|
|
||||||
public ExportFormat ExportFormat { get; set; }
|
|
||||||
|
|
||||||
public string FilePath { get; set; }
|
|
||||||
|
|
||||||
public DateTime? From { get; set; }
|
|
||||||
|
|
||||||
public DateTime? To { get; set; }
|
|
||||||
|
|
||||||
public string DateFormat { get; set; }
|
|
||||||
|
|
||||||
public int MessageGroupLimit { get; set; }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,4 @@
|
||||||
using CommonServiceLocator;
|
using CommonServiceLocator;
|
||||||
using DiscordChatExporter.Cli.ViewModels;
|
|
||||||
using DiscordChatExporter.Core.Services;
|
using DiscordChatExporter.Core.Services;
|
||||||
using GalaSoft.MvvmLight.Ioc;
|
using GalaSoft.MvvmLight.Ioc;
|
||||||
|
|
||||||
|
@ -7,15 +6,7 @@ namespace DiscordChatExporter.Cli
|
||||||
{
|
{
|
||||||
public class Container
|
public class Container
|
||||||
{
|
{
|
||||||
public IMainViewModel MainViewModel => Resolve<IMainViewModel>();
|
public Container()
|
||||||
public ISettingsService SettingsService => Resolve<ISettingsService>();
|
|
||||||
|
|
||||||
private T Resolve<T>(string key = null)
|
|
||||||
{
|
|
||||||
return ServiceLocator.Current.GetInstance<T>(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init()
|
|
||||||
{
|
{
|
||||||
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
||||||
SimpleIoc.Default.Reset();
|
SimpleIoc.Default.Reset();
|
||||||
|
@ -25,13 +16,12 @@ namespace DiscordChatExporter.Cli
|
||||||
SimpleIoc.Default.Register<IExportService, ExportService>();
|
SimpleIoc.Default.Register<IExportService, ExportService>();
|
||||||
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
|
SimpleIoc.Default.Register<IMessageGroupService, MessageGroupService>();
|
||||||
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
SimpleIoc.Default.Register<ISettingsService, SettingsService>();
|
||||||
|
SimpleIoc.Default.Register<IUpdateService, UpdateService>();
|
||||||
// View models
|
|
||||||
SimpleIoc.Default.Register<IMainViewModel, MainViewModel>(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cleanup()
|
public T Resolve<T>(string key = null)
|
||||||
{
|
{
|
||||||
|
return ServiceLocator.Current.GetInstance<T>(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -10,8 +10,8 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="CommandLineParser" Version="2.2.1" />
|
||||||
<PackageReference Include="CommonServiceLocator" Version="2.0.3" />
|
<PackageReference Include="CommonServiceLocator" Version="2.0.3" />
|
||||||
<PackageReference Include="FluentCommandLineParser" Version="1.4.3" />
|
|
||||||
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
|
<PackageReference Include="MvvmLightLibs" Version="5.4.1" />
|
||||||
<PackageReference Include="Tyrrrz.Extensions" Version="1.5.1" />
|
<PackageReference Include="Tyrrrz.Extensions" Version="1.5.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
|
@ -1,34 +1,14 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Reflection;
|
using CommandLine;
|
||||||
using DiscordChatExporter.Core.Models;
|
using DiscordChatExporter.Cli.Verbs;
|
||||||
using Fclp;
|
using DiscordChatExporter.Cli.Verbs.Options;
|
||||||
using Tyrrrz.Extensions;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli
|
namespace DiscordChatExporter.Cli
|
||||||
{
|
{
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
private static readonly Container Container = new Container();
|
private static void ShowTokenHelp()
|
||||||
|
|
||||||
private static void ShowHelp()
|
|
||||||
{
|
{
|
||||||
var version = Assembly.GetExecutingAssembly().GetName().Version;
|
|
||||||
var availableFormats = Enum.GetNames(typeof(ExportFormat));
|
|
||||||
|
|
||||||
Console.WriteLine($"=== Discord Chat Exporter (Command Line Interface) v{version} ===");
|
|
||||||
Console.WriteLine();
|
|
||||||
Console.WriteLine("[-t] [--token] Discord authorization token.");
|
|
||||||
Console.WriteLine("[-b] [--bot] Whether this is a bot token.");
|
|
||||||
Console.WriteLine("[-c] [--channel] Discord channel ID.");
|
|
||||||
Console.WriteLine("[-f] [--format] Export format. Optional.");
|
|
||||||
Console.WriteLine("[-o] [--output] Output file path. Optional.");
|
|
||||||
Console.WriteLine(" [--datefrom] Limit to messages after this date. Optional.");
|
|
||||||
Console.WriteLine(" [--dateto] Limit to messages before this date. Optional.");
|
|
||||||
Console.WriteLine(" [--dateformat] Date format. Optional.");
|
|
||||||
Console.WriteLine(" [--grouplimit] Message group limit. Optional.");
|
|
||||||
Console.WriteLine();
|
|
||||||
Console.WriteLine($"Available export formats: {availableFormats.JoinToString(", ")}");
|
|
||||||
Console.WriteLine();
|
|
||||||
Console.WriteLine("# To get user token:");
|
Console.WriteLine("# To get user token:");
|
||||||
Console.WriteLine(" - Open Discord app");
|
Console.WriteLine(" - Open Discord app");
|
||||||
Console.WriteLine(" - Log in if you haven't");
|
Console.WriteLine(" - Log in if you haven't");
|
||||||
|
@ -43,83 +23,33 @@ namespace DiscordChatExporter.Cli
|
||||||
Console.WriteLine(" - Open your application's settings");
|
Console.WriteLine(" - Open your application's settings");
|
||||||
Console.WriteLine(" - Navigate to the Bot section on the left");
|
Console.WriteLine(" - Navigate to the Bot section on the left");
|
||||||
Console.WriteLine(" - Under Token click Copy");
|
Console.WriteLine(" - Under Token click Copy");
|
||||||
Console.WriteLine();
|
|
||||||
Console.WriteLine("# To get channel ID:");
|
|
||||||
Console.WriteLine(" - Open Discord app");
|
|
||||||
Console.WriteLine(" - Log in if you haven't");
|
|
||||||
Console.WriteLine(" - Go to any channel you want to export");
|
|
||||||
Console.WriteLine(" - Press Ctrl+Shift+I to show developer tools");
|
|
||||||
Console.WriteLine(" - Navigate to the Console tab");
|
|
||||||
Console.WriteLine(" - Type \"document.URL\" and press Enter");
|
|
||||||
Console.WriteLine(" - Copy the long sequence of numbers after last slash");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static CliOptions ParseOptions(string[] args)
|
|
||||||
{
|
|
||||||
var argsParser = new FluentCommandLineParser<CliOptions>();
|
|
||||||
|
|
||||||
var settings = Container.SettingsService;
|
|
||||||
|
|
||||||
argsParser.Setup(o => o.TokenValue).As('t', "token").Required();
|
|
||||||
argsParser.Setup(o => o.IsBotToken).As('b', "bot").SetDefault(false);
|
|
||||||
argsParser.Setup(o => o.ChannelId).As('c', "channel").Required();
|
|
||||||
argsParser.Setup(o => o.ExportFormat).As('f', "format").SetDefault(ExportFormat.HtmlDark);
|
|
||||||
argsParser.Setup(o => o.FilePath).As('o', "output").SetDefault(null);
|
|
||||||
argsParser.Setup(o => o.From).As("datefrom").SetDefault(null);
|
|
||||||
argsParser.Setup(o => o.To).As("dateto").SetDefault(null);
|
|
||||||
argsParser.Setup(o => o.DateFormat).As("dateformat").SetDefault(settings.DateFormat);
|
|
||||||
argsParser.Setup(o => o.MessageGroupLimit).As("grouplimit").SetDefault(settings.MessageGroupLimit);
|
|
||||||
|
|
||||||
var parsed = argsParser.Parse(args);
|
|
||||||
|
|
||||||
// Show help if no arguments
|
|
||||||
if (parsed.EmptyArgs)
|
|
||||||
{
|
|
||||||
ShowHelp();
|
|
||||||
Environment.Exit(0);
|
|
||||||
}
|
|
||||||
// Show error if there are any
|
|
||||||
else if (parsed.HasErrors)
|
|
||||||
{
|
|
||||||
Console.Error.Write(parsed.ErrorText);
|
|
||||||
Environment.Exit(-1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return argsParser.Object;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Main(string[] args)
|
public static void Main(string[] args)
|
||||||
{
|
{
|
||||||
// Init container
|
// Get all verb types
|
||||||
Container.Init();
|
var verbTypes = new[]
|
||||||
|
{
|
||||||
|
typeof(ExportChatOptions),
|
||||||
|
typeof(GetChannelsOptions),
|
||||||
|
typeof(GetDirectMessageChannelsOptions),
|
||||||
|
typeof(GetGuildsOptions),
|
||||||
|
typeof(UpdateAppOptions)
|
||||||
|
};
|
||||||
|
|
||||||
// Parse options
|
// Parse command line arguments
|
||||||
var options = ParseOptions(args);
|
var parsedArgs = Parser.Default.ParseArguments(args, verbTypes);
|
||||||
|
|
||||||
// Inject some settings
|
// Execute commands
|
||||||
var settings = Container.SettingsService;
|
parsedArgs.WithParsed<ExportChatOptions>(o => new ExportChatVerb(o).Execute());
|
||||||
settings.DateFormat = options.DateFormat;
|
parsedArgs.WithParsed<GetChannelsOptions>(o => new GetChannelsVerb(o).Execute());
|
||||||
settings.MessageGroupLimit = options.MessageGroupLimit;
|
parsedArgs.WithParsed<GetDirectMessageChannelsOptions>(o => new GetDirectMessageChannelsVerb(o).Execute());
|
||||||
|
parsedArgs.WithParsed<GetGuildsOptions>(o => new GetGuildsVerb(o).Execute());
|
||||||
|
parsedArgs.WithParsed<UpdateAppOptions>(o => new UpdateAppVerb(o).Execute());
|
||||||
|
|
||||||
// Create token
|
// Show token help if error
|
||||||
var token = new AuthToken(
|
if (parsedArgs.Tag == ParserResultType.NotParsed)
|
||||||
options.IsBotToken ? AuthTokenType.Bot : AuthTokenType.User,
|
ShowTokenHelp();
|
||||||
options.TokenValue);
|
|
||||||
|
|
||||||
// Export
|
|
||||||
var vm = Container.MainViewModel;
|
|
||||||
vm.ExportAsync(
|
|
||||||
token,
|
|
||||||
options.ChannelId,
|
|
||||||
options.FilePath,
|
|
||||||
options.ExportFormat,
|
|
||||||
options.From,
|
|
||||||
options.To).GetAwaiter().GetResult();
|
|
||||||
|
|
||||||
// Cleanup container
|
|
||||||
Container.Cleanup();
|
|
||||||
|
|
||||||
Console.WriteLine("Export complete.");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
69
DiscordChatExporter.Cli/Verbs/ExportChatVerb.cs
Normal file
69
DiscordChatExporter.Cli/Verbs/ExportChatVerb.cs
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
using System;
|
||||||
|
using System.IO;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordChatExporter.Cli.Verbs.Options;
|
||||||
|
using DiscordChatExporter.Core.Models;
|
||||||
|
using DiscordChatExporter.Core.Services;
|
||||||
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs
|
||||||
|
{
|
||||||
|
public class ExportChatVerb : Verb<ExportChatOptions>
|
||||||
|
{
|
||||||
|
public ExportChatVerb(ExportChatOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
// Get services
|
||||||
|
var container = new Container();
|
||||||
|
var settingsService = container.Resolve<ISettingsService>();
|
||||||
|
var dataService = container.Resolve<IDataService>();
|
||||||
|
var messageGroupService = container.Resolve<IMessageGroupService>();
|
||||||
|
var exportService = container.Resolve<IExportService>();
|
||||||
|
|
||||||
|
// Configure settings
|
||||||
|
if (Options.DateFormat.IsNotBlank())
|
||||||
|
settingsService.DateFormat = Options.DateFormat;
|
||||||
|
if (Options.MessageGroupLimit > 0)
|
||||||
|
settingsService.MessageGroupLimit = Options.MessageGroupLimit;
|
||||||
|
|
||||||
|
// Get channel and guild
|
||||||
|
var channel = await dataService.GetChannelAsync(Options.GetToken(), Options.ChannelId);
|
||||||
|
var guild = channel.GuildId == Guild.DirectMessages.Id
|
||||||
|
? Guild.DirectMessages
|
||||||
|
: await dataService.GetGuildAsync(Options.GetToken(), channel.GuildId);
|
||||||
|
|
||||||
|
// Generate file path if not set
|
||||||
|
var filePath = Options.FilePath;
|
||||||
|
if (filePath.IsBlank())
|
||||||
|
{
|
||||||
|
filePath = $"{guild.Name} - {channel.Name}.{Options.ExportFormat.GetFileExtension()}"
|
||||||
|
.Replace(Path.GetInvalidFileNameChars(), '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: extract this to make it reusable across implementations
|
||||||
|
// Get messages
|
||||||
|
var messages =
|
||||||
|
await dataService.GetChannelMessagesAsync(Options.GetToken(), channel.Id,
|
||||||
|
Options.After, Options.Before);
|
||||||
|
|
||||||
|
// Group messages
|
||||||
|
var messageGroups = messageGroupService.GroupMessages(messages);
|
||||||
|
|
||||||
|
// Get mentionables
|
||||||
|
var mentionables = await dataService.GetMentionablesAsync(Options.GetToken(), guild.Id, messages);
|
||||||
|
|
||||||
|
// Create log
|
||||||
|
var log = new ChatLog(guild, channel, Options.After, Options.Before, messageGroups, mentionables);
|
||||||
|
|
||||||
|
// Export
|
||||||
|
exportService.Export(Options.ExportFormat, filePath, log);
|
||||||
|
|
||||||
|
// Print result
|
||||||
|
Console.WriteLine($"Exported chat to [{filePath}]");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
33
DiscordChatExporter.Cli/Verbs/GetChannelsVerb.cs
Normal file
33
DiscordChatExporter.Cli/Verbs/GetChannelsVerb.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordChatExporter.Cli.Verbs.Options;
|
||||||
|
using DiscordChatExporter.Core.Models;
|
||||||
|
using DiscordChatExporter.Core.Services;
|
||||||
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs
|
||||||
|
{
|
||||||
|
public class GetChannelsVerb : Verb<GetChannelsOptions>
|
||||||
|
{
|
||||||
|
public GetChannelsVerb(GetChannelsOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
// Get data service
|
||||||
|
var container = new Container();
|
||||||
|
var dataService = container.Resolve<IDataService>();
|
||||||
|
|
||||||
|
// Get channels
|
||||||
|
var channels = await dataService.GetGuildChannelsAsync(Options.GetToken(), Options.GuildId);
|
||||||
|
|
||||||
|
// Print result
|
||||||
|
foreach (var channel in channels.Where(c => c.Type.IsEither(ChannelType.GuildTextChat))
|
||||||
|
.OrderBy(c => c.Name))
|
||||||
|
Console.WriteLine($"{channel.Id} | {channel.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordChatExporter.Cli.Verbs.Options;
|
||||||
|
using DiscordChatExporter.Core.Services;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs
|
||||||
|
{
|
||||||
|
public class GetDirectMessageChannelsVerb : Verb<GetDirectMessageChannelsOptions>
|
||||||
|
{
|
||||||
|
public GetDirectMessageChannelsVerb(GetDirectMessageChannelsOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
// Get data service
|
||||||
|
var container = new Container();
|
||||||
|
var dataService = container.Resolve<IDataService>();
|
||||||
|
|
||||||
|
// Get channels
|
||||||
|
var channels = await dataService.GetDirectMessageChannelsAsync(Options.GetToken());
|
||||||
|
|
||||||
|
// Print result
|
||||||
|
foreach (var channel in channels.OrderBy(c => c.Name))
|
||||||
|
Console.WriteLine($"{channel.Id} | {channel.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
30
DiscordChatExporter.Cli/Verbs/GetGuildsVerb.cs
Normal file
30
DiscordChatExporter.Cli/Verbs/GetGuildsVerb.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordChatExporter.Cli.Verbs.Options;
|
||||||
|
using DiscordChatExporter.Core.Services;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs
|
||||||
|
{
|
||||||
|
public class GetGuildsVerb : Verb<GetGuildsOptions>
|
||||||
|
{
|
||||||
|
public GetGuildsVerb(GetGuildsOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
// Get data service
|
||||||
|
var container = new Container();
|
||||||
|
var dataService = container.Resolve<IDataService>();
|
||||||
|
|
||||||
|
// Get guilds
|
||||||
|
var guilds = await dataService.GetUserGuildsAsync(Options.GetToken());
|
||||||
|
|
||||||
|
// Print result
|
||||||
|
foreach (var guild in guilds.OrderBy(g => g.Name))
|
||||||
|
Console.WriteLine($"{guild.Id} | {guild.Name}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
DiscordChatExporter.Cli/Verbs/Options/ExportChatOptions.cs
Normal file
31
DiscordChatExporter.Cli/Verbs/Options/ExportChatOptions.cs
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
using System;
|
||||||
|
using CommandLine;
|
||||||
|
using DiscordChatExporter.Core.Models;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
|
{
|
||||||
|
[Verb("export", HelpText = "Export channel chat log to a file.")]
|
||||||
|
public class ExportChatOptions : TokenOptions
|
||||||
|
{
|
||||||
|
[Option('c', "channel", Required = true, HelpText = "Channel ID.")]
|
||||||
|
public string ChannelId { get; set; }
|
||||||
|
|
||||||
|
[Option('f', "format", Default = ExportFormat.HtmlDark, HelpText = "Output file format.")]
|
||||||
|
public ExportFormat ExportFormat { get; set; }
|
||||||
|
|
||||||
|
[Option('o', "output", Default = null, HelpText = "Output file path.")]
|
||||||
|
public string FilePath { get; set; }
|
||||||
|
|
||||||
|
[Option("after", Default = null, HelpText = "Limit to messages sent after this date.")]
|
||||||
|
public DateTime? After { get; set; }
|
||||||
|
|
||||||
|
[Option("before", Default = null, HelpText = "Limit to messages sent before this date.")]
|
||||||
|
public DateTime? Before { get; set; }
|
||||||
|
|
||||||
|
[Option("dateformat", Default = null, HelpText = "Date format used in output.")]
|
||||||
|
public string DateFormat { get; set; }
|
||||||
|
|
||||||
|
[Option("grouplimit", Default = 0, HelpText = "Message group limit.")]
|
||||||
|
public int MessageGroupLimit { get; set; }
|
||||||
|
}
|
||||||
|
}
|
11
DiscordChatExporter.Cli/Verbs/Options/GetChannelsOptions.cs
Normal file
11
DiscordChatExporter.Cli/Verbs/Options/GetChannelsOptions.cs
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
|
{
|
||||||
|
[Verb("channels", HelpText = "Get the list of channels in the given guild.")]
|
||||||
|
public class GetChannelsOptions : TokenOptions
|
||||||
|
{
|
||||||
|
[Option('g', "guild", Required = true, HelpText = "Guild ID.")]
|
||||||
|
public string GuildId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
|
{
|
||||||
|
[Verb("dm", HelpText = "Get the list of direct message channels.")]
|
||||||
|
public class GetDirectMessageChannelsOptions : TokenOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
|
{
|
||||||
|
[Verb("guilds", HelpText = "Get the list of accessible guilds.")]
|
||||||
|
public class GetGuildsOptions : TokenOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
16
DiscordChatExporter.Cli/Verbs/Options/TokenOptions.cs
Normal file
16
DiscordChatExporter.Cli/Verbs/Options/TokenOptions.cs
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
using CommandLine;
|
||||||
|
using DiscordChatExporter.Core.Models;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
|
{
|
||||||
|
public class TokenOptions
|
||||||
|
{
|
||||||
|
[Option('t', "token", Required = true, HelpText = "Authorization token.")]
|
||||||
|
public string TokenValue { get; set; }
|
||||||
|
|
||||||
|
[Option('b', "bot", Default = false, HelpText = "Whether this authorization token belongs to a bot.")]
|
||||||
|
public bool IsBotToken { get; set; }
|
||||||
|
|
||||||
|
public AuthToken GetToken() => new AuthToken(IsBotToken ? AuthTokenType.Bot : AuthTokenType.User, TokenValue);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
using CommandLine;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs.Options
|
||||||
|
{
|
||||||
|
[Verb("update", HelpText = "Updates this application to the latest version.")]
|
||||||
|
public class UpdateAppOptions
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
38
DiscordChatExporter.Cli/Verbs/UpdateAppVerb.cs
Normal file
38
DiscordChatExporter.Cli/Verbs/UpdateAppVerb.cs
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using DiscordChatExporter.Cli.Verbs.Options;
|
||||||
|
using DiscordChatExporter.Core.Services;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs
|
||||||
|
{
|
||||||
|
public class UpdateAppVerb : Verb<UpdateAppOptions>
|
||||||
|
{
|
||||||
|
public UpdateAppVerb(UpdateAppOptions options)
|
||||||
|
: base(options)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override async Task ExecuteAsync()
|
||||||
|
{
|
||||||
|
// Get update service
|
||||||
|
var container = new Container();
|
||||||
|
var updateService = container.Resolve<IUpdateService>();
|
||||||
|
|
||||||
|
// TODO: this is configured only for GUI
|
||||||
|
// Get update version
|
||||||
|
var updateVersion = await updateService.CheckPrepareUpdateAsync();
|
||||||
|
|
||||||
|
if (updateVersion != null)
|
||||||
|
{
|
||||||
|
Console.WriteLine($"Updating to version {updateVersion}");
|
||||||
|
|
||||||
|
updateService.NeedRestart = false;
|
||||||
|
updateService.FinalizeUpdate();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Console.WriteLine("There are no application updates available.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
DiscordChatExporter.Cli/Verbs/Verb.cs
Normal file
18
DiscordChatExporter.Cli/Verbs/Verb.cs
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DiscordChatExporter.Cli.Verbs
|
||||||
|
{
|
||||||
|
public abstract class Verb<TOptions>
|
||||||
|
{
|
||||||
|
protected TOptions Options { get; }
|
||||||
|
|
||||||
|
protected Verb(TOptions options)
|
||||||
|
{
|
||||||
|
Options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Task ExecuteAsync();
|
||||||
|
|
||||||
|
public virtual void Execute() => ExecuteAsync().GetAwaiter().GetResult();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,12 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using DiscordChatExporter.Core.Models;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.ViewModels
|
|
||||||
{
|
|
||||||
public interface IMainViewModel
|
|
||||||
{
|
|
||||||
Task ExportAsync(AuthToken token, string channelId, string filePath, ExportFormat format, DateTime? from,
|
|
||||||
DateTime? to);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,56 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.IO;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using DiscordChatExporter.Core.Models;
|
|
||||||
using DiscordChatExporter.Core.Services;
|
|
||||||
using Tyrrrz.Extensions;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Cli.ViewModels
|
|
||||||
{
|
|
||||||
public class MainViewModel : IMainViewModel
|
|
||||||
{
|
|
||||||
private readonly IDataService _dataService;
|
|
||||||
private readonly IMessageGroupService _messageGroupService;
|
|
||||||
private readonly IExportService _exportService;
|
|
||||||
|
|
||||||
public MainViewModel(IDataService dataService, IMessageGroupService messageGroupService,
|
|
||||||
IExportService exportService)
|
|
||||||
{
|
|
||||||
_dataService = dataService;
|
|
||||||
_messageGroupService = messageGroupService;
|
|
||||||
_exportService = exportService;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async Task ExportAsync(AuthToken token, string channelId, string filePath, ExportFormat format,
|
|
||||||
DateTime? from, DateTime? to)
|
|
||||||
{
|
|
||||||
// Get channel and guild
|
|
||||||
var channel = await _dataService.GetChannelAsync(token, channelId);
|
|
||||||
var guild = channel.GuildId == Guild.DirectMessages.Id
|
|
||||||
? Guild.DirectMessages
|
|
||||||
: await _dataService.GetGuildAsync(token, channel.GuildId);
|
|
||||||
|
|
||||||
// Generate file path if not set
|
|
||||||
if (filePath.IsBlank())
|
|
||||||
{
|
|
||||||
filePath = $"{guild.Name} - {channel.Name}.{format.GetFileExtension()}"
|
|
||||||
.Replace(Path.GetInvalidFileNameChars(), '_');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get messages
|
|
||||||
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to);
|
|
||||||
|
|
||||||
// Group messages
|
|
||||||
var messageGroups = _messageGroupService.GroupMessages(messages);
|
|
||||||
|
|
||||||
// Get mentionables
|
|
||||||
var mentionables = await _dataService.GetMentionablesAsync(token, guild.Id, messages);
|
|
||||||
|
|
||||||
// Create log
|
|
||||||
var log = new ChatLog(guild, channel, from, to, messageGroups, mentionables);
|
|
||||||
|
|
||||||
// Export
|
|
||||||
_exportService.Export(format, filePath, log);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -4,8 +4,6 @@
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
|
xmlns:converters="clr-namespace:DiscordChatExporter.Gui.Converters"
|
||||||
xmlns:local="clr-namespace:DiscordChatExporter.Gui"
|
xmlns:local="clr-namespace:DiscordChatExporter.Gui"
|
||||||
Exit="App_Exit"
|
|
||||||
Startup="App_Startup"
|
|
||||||
StartupUri="Views/MainWindow.xaml">
|
StartupUri="Views/MainWindow.xaml">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
<ResourceDictionary>
|
<ResourceDictionary>
|
||||||
|
|
|
@ -1,19 +1,6 @@
|
||||||
using System.Windows;
|
namespace DiscordChatExporter.Gui
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui
|
|
||||||
{
|
{
|
||||||
public partial class App
|
public partial class App
|
||||||
{
|
{
|
||||||
private Container Container => (Container) Resources["Container"];
|
|
||||||
|
|
||||||
private void App_Startup(object sender, StartupEventArgs e)
|
|
||||||
{
|
|
||||||
Container.Init();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void App_Exit(object sender, ExitEventArgs e)
|
|
||||||
{
|
|
||||||
Container.Cleanup();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -11,12 +11,7 @@ namespace DiscordChatExporter.Gui
|
||||||
public IMainViewModel MainViewModel => Resolve<IMainViewModel>();
|
public IMainViewModel MainViewModel => Resolve<IMainViewModel>();
|
||||||
public ISettingsViewModel SettingsViewModel => Resolve<ISettingsViewModel>();
|
public ISettingsViewModel SettingsViewModel => Resolve<ISettingsViewModel>();
|
||||||
|
|
||||||
private T Resolve<T>(string key = null)
|
public Container()
|
||||||
{
|
|
||||||
return ServiceLocator.Current.GetInstance<T>(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Init()
|
|
||||||
{
|
{
|
||||||
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default);
|
||||||
SimpleIoc.Default.Reset();
|
SimpleIoc.Default.Reset();
|
||||||
|
@ -34,8 +29,9 @@ namespace DiscordChatExporter.Gui
|
||||||
SimpleIoc.Default.Register<ISettingsViewModel, SettingsViewModel>(true);
|
SimpleIoc.Default.Register<ISettingsViewModel, SettingsViewModel>(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Cleanup()
|
private T Resolve<T>(string key = null)
|
||||||
{
|
{
|
||||||
|
return ServiceLocator.Current.GetInstance<T>(key);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -256,6 +256,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
// TODO: extract this to make it reusable across implementations
|
||||||
// Get messages
|
// Get messages
|
||||||
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to, progressHandler);
|
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to, progressHandler);
|
||||||
|
|
||||||
|
|
|
@ -32,7 +32,7 @@ DiscordChatExporter can be used to export message history from a [Discord](https
|
||||||
- [Scriban](https://github.com/lunet-io/scriban)
|
- [Scriban](https://github.com/lunet-io/scriban)
|
||||||
- [Polly](https://github.com/App-vNext/Polly)
|
- [Polly](https://github.com/App-vNext/Polly)
|
||||||
- [Onova](https://github.com/Tyrrrz/Onova)
|
- [Onova](https://github.com/Tyrrrz/Onova)
|
||||||
- [FluentCommandLineParser](https://github.com/fclp/fluent-command-line-parser)
|
- [CommandLineParser](https://github.com/commandlineparser/commandline)
|
||||||
- [Tyrrrz.Extensions](https://github.com/Tyrrrz/Extensions)
|
- [Tyrrrz.Extensions](https://github.com/Tyrrrz/Extensions)
|
||||||
- [Tyrrrz.WpfExtensions](https://github.com/Tyrrrz/WpfExtensions)
|
- [Tyrrrz.WpfExtensions](https://github.com/Tyrrrz/WpfExtensions)
|
||||||
- [Tyrrrz.Settings](https://github.com/Tyrrrz/Settings)
|
- [Tyrrrz.Settings](https://github.com/Tyrrrz/Settings)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue