mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-05-01 02:02:22 +00:00
Add command line interface and change solution structure (#26)
This commit is contained in:
19
DiscordChatExporter.Gui/ViewModels/ErrorViewModel.cs
Normal file
19
DiscordChatExporter.Gui/ViewModels/ErrorViewModel.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class ErrorViewModel : ViewModelBase, IErrorViewModel
|
||||
{
|
||||
public string Message { get; private set; }
|
||||
|
||||
public ErrorViewModel()
|
||||
{
|
||||
// Messages
|
||||
MessengerInstance.Register<ShowErrorMessage>(this, m =>
|
||||
{
|
||||
Message = m.Message;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
32
DiscordChatExporter.Gui/ViewModels/ExportDoneViewModel.cs
Normal file
32
DiscordChatExporter.Gui/ViewModels/ExportDoneViewModel.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System.Diagnostics;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class ExportDoneViewModel : ViewModelBase, IExportDoneViewModel
|
||||
{
|
||||
private string _filePath;
|
||||
|
||||
// Commands
|
||||
public RelayCommand OpenCommand { get; }
|
||||
|
||||
public ExportDoneViewModel()
|
||||
{
|
||||
// Commands
|
||||
OpenCommand = new RelayCommand(Open);
|
||||
|
||||
// Messages
|
||||
MessengerInstance.Register<ShowExportDoneMessage>(this, m =>
|
||||
{
|
||||
_filePath = m.FilePath;
|
||||
});
|
||||
}
|
||||
|
||||
private void Open()
|
||||
{
|
||||
Process.Start(_filePath);
|
||||
}
|
||||
}
|
||||
}
|
||||
100
DiscordChatExporter.Gui/ViewModels/ExportSetupViewModel.cs
Normal file
100
DiscordChatExporter.Gui/ViewModels/ExportSetupViewModel.cs
Normal file
@@ -0,0 +1,100 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class ExportSetupViewModel : ViewModelBase, IExportSetupViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private string _filePath;
|
||||
private ExportFormat _format;
|
||||
private DateTime? _from;
|
||||
private DateTime? _to;
|
||||
|
||||
public Guild Guild { get; private set; }
|
||||
|
||||
public Channel Channel { get; private set; }
|
||||
|
||||
public string FilePath
|
||||
{
|
||||
get => _filePath;
|
||||
set
|
||||
{
|
||||
Set(ref _filePath, value);
|
||||
ExportCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<ExportFormat> AvailableFormats { get; }
|
||||
|
||||
public ExportFormat SelectedFormat
|
||||
{
|
||||
get => _format;
|
||||
set
|
||||
{
|
||||
Set(ref _format, value);
|
||||
|
||||
// Replace extension in path
|
||||
var newExt = value.GetFileExtension();
|
||||
if (FilePath != null && !FilePath.EndsWith(newExt))
|
||||
FilePath = FilePath.SubstringUntilLast(".") + "." + newExt;
|
||||
}
|
||||
}
|
||||
|
||||
public DateTime? From
|
||||
{
|
||||
get => _from;
|
||||
set => Set(ref _from, value);
|
||||
}
|
||||
|
||||
public DateTime? To
|
||||
{
|
||||
get => _to;
|
||||
set => Set(ref _to, value);
|
||||
}
|
||||
|
||||
// Commands
|
||||
public RelayCommand ExportCommand { get; }
|
||||
|
||||
public ExportSetupViewModel(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
|
||||
// Defaults
|
||||
AvailableFormats = Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
|
||||
|
||||
// Commands
|
||||
ExportCommand = new RelayCommand(Export, () => FilePath.IsNotBlank());
|
||||
|
||||
// Messages
|
||||
MessengerInstance.Register<ShowExportSetupMessage>(this, m =>
|
||||
{
|
||||
Guild = m.Guild;
|
||||
Channel = m.Channel;
|
||||
SelectedFormat = _settingsService.LastExportFormat;
|
||||
FilePath = $"{Guild} - {Channel}.{SelectedFormat.GetFileExtension()}"
|
||||
.Replace(Path.GetInvalidFileNameChars(), '_');
|
||||
From = null;
|
||||
To = null;
|
||||
});
|
||||
}
|
||||
|
||||
private void Export()
|
||||
{
|
||||
// Save format
|
||||
_settingsService.LastExportFormat = SelectedFormat;
|
||||
|
||||
// Start export
|
||||
MessengerInstance.Send(new StartExportMessage(Channel, FilePath, SelectedFormat, From, To));
|
||||
}
|
||||
}
|
||||
}
|
||||
7
DiscordChatExporter.Gui/ViewModels/IErrorViewModel.cs
Normal file
7
DiscordChatExporter.Gui/ViewModels/IErrorViewModel.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IErrorViewModel
|
||||
{
|
||||
string Message { get; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IExportDoneViewModel
|
||||
{
|
||||
RelayCommand OpenCommand { get; }
|
||||
}
|
||||
}
|
||||
20
DiscordChatExporter.Gui/ViewModels/IExportSetupViewModel.cs
Normal file
20
DiscordChatExporter.Gui/ViewModels/IExportSetupViewModel.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IExportSetupViewModel
|
||||
{
|
||||
Guild Guild { get; }
|
||||
Channel Channel { get; }
|
||||
string FilePath { get; set; }
|
||||
IReadOnlyList<ExportFormat> AvailableFormats { get; }
|
||||
ExportFormat SelectedFormat { get; set; }
|
||||
DateTime? From { get; set; }
|
||||
DateTime? To { get; set; }
|
||||
|
||||
RelayCommand ExportCommand { get; }
|
||||
}
|
||||
}
|
||||
23
DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
Normal file
23
DiscordChatExporter.Gui/ViewModels/IMainViewModel.cs
Normal file
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface IMainViewModel
|
||||
{
|
||||
bool IsBusy { get; }
|
||||
bool IsDataAvailable { get; }
|
||||
|
||||
string Token { get; set; }
|
||||
|
||||
IReadOnlyList<Guild> AvailableGuilds { get; }
|
||||
Guild SelectedGuild { get; set; }
|
||||
IReadOnlyList<Channel> AvailableChannels { get; }
|
||||
|
||||
RelayCommand PullDataCommand { get; }
|
||||
RelayCommand ShowSettingsCommand { get; }
|
||||
RelayCommand ShowAboutCommand { get; }
|
||||
RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||
}
|
||||
}
|
||||
8
DiscordChatExporter.Gui/ViewModels/ISettingsViewModel.cs
Normal file
8
DiscordChatExporter.Gui/ViewModels/ISettingsViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public interface ISettingsViewModel
|
||||
{
|
||||
string DateFormat { get; set; }
|
||||
int MessageGroupLimit { get; set; }
|
||||
}
|
||||
}
|
||||
211
DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
Normal file
211
DiscordChatExporter.Gui/ViewModels/MainViewModel.cs
Normal file
@@ -0,0 +1,211 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using DiscordChatExporter.Core.Exceptions;
|
||||
using DiscordChatExporter.Core.Models;
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using DiscordChatExporter.Gui.Messages;
|
||||
using GalaSoft.MvvmLight;
|
||||
using GalaSoft.MvvmLight.CommandWpf;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class MainViewModel : ViewModelBase, IMainViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IDataService _dataService;
|
||||
private readonly IMessageGroupService _messageGroupService;
|
||||
private readonly IExportService _exportService;
|
||||
|
||||
private readonly Dictionary<Guild, IReadOnlyList<Channel>> _guildChannelsMap;
|
||||
|
||||
private bool _isBusy;
|
||||
private string _token;
|
||||
private IReadOnlyList<Guild> _availableGuilds;
|
||||
private Guild _selectedGuild;
|
||||
private IReadOnlyList<Channel> _availableChannels;
|
||||
|
||||
public bool IsBusy
|
||||
{
|
||||
get => _isBusy;
|
||||
private set
|
||||
{
|
||||
Set(ref _isBusy, value);
|
||||
PullDataCommand.RaiseCanExecuteChanged();
|
||||
ShowExportSetupCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsDataAvailable => AvailableGuilds.NotNullAndAny();
|
||||
|
||||
public string Token
|
||||
{
|
||||
get => _token;
|
||||
set
|
||||
{
|
||||
// Remove invalid chars
|
||||
value = value?.Trim('"');
|
||||
|
||||
Set(ref _token, value);
|
||||
PullDataCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<Guild> AvailableGuilds
|
||||
{
|
||||
get => _availableGuilds;
|
||||
private set
|
||||
{
|
||||
Set(ref _availableGuilds, value);
|
||||
RaisePropertyChanged(() => IsDataAvailable);
|
||||
}
|
||||
}
|
||||
|
||||
public Guild SelectedGuild
|
||||
{
|
||||
get => _selectedGuild;
|
||||
set
|
||||
{
|
||||
Set(ref _selectedGuild, value);
|
||||
AvailableChannels = value != null ? _guildChannelsMap[value] : new Channel[0];
|
||||
ShowExportSetupCommand.RaiseCanExecuteChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public IReadOnlyList<Channel> AvailableChannels
|
||||
{
|
||||
get => _availableChannels;
|
||||
private set => Set(ref _availableChannels, value);
|
||||
}
|
||||
|
||||
public RelayCommand PullDataCommand { get; }
|
||||
public RelayCommand ShowSettingsCommand { get; }
|
||||
public RelayCommand ShowAboutCommand { get; }
|
||||
public RelayCommand<Channel> ShowExportSetupCommand { get; }
|
||||
|
||||
public MainViewModel(ISettingsService settingsService, IDataService dataService,
|
||||
IMessageGroupService messageGroupService, IExportService exportService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_dataService = dataService;
|
||||
_messageGroupService = messageGroupService;
|
||||
_exportService = exportService;
|
||||
|
||||
_guildChannelsMap = new Dictionary<Guild, IReadOnlyList<Channel>>();
|
||||
|
||||
// Commands
|
||||
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
|
||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||
ShowExportSetupCommand = new RelayCommand<Channel>(ShowExportSetup, _ => !IsBusy);
|
||||
|
||||
// Messages
|
||||
MessengerInstance.Register<StartExportMessage>(this, m =>
|
||||
{
|
||||
Export(m.Channel, m.FilePath, m.Format, m.From, m.To);
|
||||
});
|
||||
|
||||
// Defaults
|
||||
_token = _settingsService.LastToken;
|
||||
}
|
||||
|
||||
private async void PullData()
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
// Copy token so it doesn't get mutated
|
||||
var token = Token;
|
||||
|
||||
// Save token
|
||||
_settingsService.LastToken = token;
|
||||
|
||||
// Clear existing
|
||||
_guildChannelsMap.Clear();
|
||||
|
||||
try
|
||||
{
|
||||
// Get DM channels
|
||||
{
|
||||
var channels = await _dataService.GetDirectMessageChannelsAsync(token);
|
||||
var guild = Guild.DirectMessages;
|
||||
_guildChannelsMap[guild] = channels.ToArray();
|
||||
}
|
||||
|
||||
// Get guild channels
|
||||
{
|
||||
var guilds = await _dataService.GetUserGuildsAsync(token);
|
||||
foreach (var guild in guilds)
|
||||
{
|
||||
var channels = await _dataService.GetGuildChannelsAsync(token, guild.Id);
|
||||
_guildChannelsMap[guild] = channels.Where(c => c.Type == ChannelType.GuildTextChat).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Unauthorized)
|
||||
{
|
||||
const string message = "Unauthorized to perform request. Make sure token is valid.";
|
||||
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||
}
|
||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
const string message = "Forbidden to perform request. The account may be locked by 2FA.";
|
||||
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||
}
|
||||
|
||||
AvailableGuilds = _guildChannelsMap.Keys.ToArray();
|
||||
SelectedGuild = AvailableGuilds.FirstOrDefault();
|
||||
IsBusy = false;
|
||||
}
|
||||
|
||||
private void ShowSettings()
|
||||
{
|
||||
MessengerInstance.Send(new ShowSettingsMessage());
|
||||
}
|
||||
|
||||
private void ShowAbout()
|
||||
{
|
||||
Process.Start("https://github.com/Tyrrrz/DiscordChatExporter");
|
||||
}
|
||||
|
||||
private void ShowExportSetup(Channel channel)
|
||||
{
|
||||
MessengerInstance.Send(new ShowExportSetupMessage(SelectedGuild, channel));
|
||||
}
|
||||
|
||||
private async void Export(Channel channel, string filePath, ExportFormat format, DateTime? from, DateTime? to)
|
||||
{
|
||||
IsBusy = true;
|
||||
|
||||
// Get last used token
|
||||
var token = _settingsService.LastToken;
|
||||
|
||||
try
|
||||
{
|
||||
// Get messages
|
||||
var messages = await _dataService.GetChannelMessagesAsync(token, channel.Id, from, to);
|
||||
|
||||
// Group them
|
||||
var messageGroups = _messageGroupService.GroupMessages(messages);
|
||||
|
||||
// Create log
|
||||
var log = new ChannelChatLog(SelectedGuild, channel, messageGroups, messages.Count);
|
||||
|
||||
// Export
|
||||
await _exportService.ExportAsync(format, filePath, log);
|
||||
|
||||
// Notify completion
|
||||
MessengerInstance.Send(new ShowExportDoneMessage(filePath));
|
||||
}
|
||||
catch (HttpErrorStatusCodeException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
|
||||
{
|
||||
const string message = "Forbidden to view messages in that channel.";
|
||||
MessengerInstance.Send(new ShowErrorMessage(message));
|
||||
}
|
||||
|
||||
IsBusy = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
28
DiscordChatExporter.Gui/ViewModels/SettingsViewModel.cs
Normal file
28
DiscordChatExporter.Gui/ViewModels/SettingsViewModel.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
using DiscordChatExporter.Core.Services;
|
||||
using GalaSoft.MvvmLight;
|
||||
using Tyrrrz.Extensions;
|
||||
|
||||
namespace DiscordChatExporter.Gui.ViewModels
|
||||
{
|
||||
public class SettingsViewModel : ViewModelBase, ISettingsViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public string DateFormat
|
||||
{
|
||||
get => _settingsService.DateFormat;
|
||||
set => _settingsService.DateFormat = value;
|
||||
}
|
||||
|
||||
public int MessageGroupLimit
|
||||
{
|
||||
get => _settingsService.MessageGroupLimit;
|
||||
set => _settingsService.MessageGroupLimit = value.ClampMin(0);
|
||||
}
|
||||
|
||||
public SettingsViewModel(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user