diff --git a/DiscordChatExporter/Container.cs b/DiscordChatExporter/Container.cs index 755a480a..b5a90fa9 100644 --- a/DiscordChatExporter/Container.cs +++ b/DiscordChatExporter/Container.cs @@ -12,13 +12,14 @@ namespace DiscordChatExporter ServiceLocator.SetLocatorProvider(() => SimpleIoc.Default); // Services - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); + SimpleIoc.Default.Register(true); + SimpleIoc.Default.Register(true); + SimpleIoc.Default.Register(true); // View models - SimpleIoc.Default.Register(); - SimpleIoc.Default.Register(); + SimpleIoc.Default.Register(true); + SimpleIoc.Default.Register(true); + SimpleIoc.Default.Register(true); // Load settings ServiceLocator.Current.GetInstance().Load(); @@ -30,6 +31,7 @@ namespace DiscordChatExporter ServiceLocator.Current.GetInstance().Save(); } + public IErrorViewModel ErrorViewModel => ServiceLocator.Current.GetInstance(); public IMainViewModel MainViewModel => ServiceLocator.Current.GetInstance(); public ISettingsViewModel SettingsViewModel => ServiceLocator.Current.GetInstance(); } diff --git a/DiscordChatExporter/DiscordChatExporter.csproj b/DiscordChatExporter/DiscordChatExporter.csproj index 2f78e9bb..c227baa7 100644 --- a/DiscordChatExporter/DiscordChatExporter.csproj +++ b/DiscordChatExporter/DiscordChatExporter.csproj @@ -84,12 +84,19 @@ + + + + + + ErrorDialog.ammy + SettingsDialog.ammy @@ -98,6 +105,11 @@ XamlIntelliSenseFileGenerator App.ammy + + Designer + MSBuild:Compile + ErrorDialog.ammy + Designer MSBuild:Compile @@ -150,6 +162,7 @@ Designer + diff --git a/DiscordChatExporter/Exceptions/UnathorizedException.cs b/DiscordChatExporter/Exceptions/UnathorizedException.cs new file mode 100644 index 00000000..1ff9f044 --- /dev/null +++ b/DiscordChatExporter/Exceptions/UnathorizedException.cs @@ -0,0 +1,8 @@ +using System; + +namespace DiscordChatExporter.Exceptions +{ + public class UnathorizedException : Exception + { + } +} \ No newline at end of file diff --git a/DiscordChatExporter/Messages/ShowErrorMessage.cs b/DiscordChatExporter/Messages/ShowErrorMessage.cs new file mode 100644 index 00000000..3499cfc9 --- /dev/null +++ b/DiscordChatExporter/Messages/ShowErrorMessage.cs @@ -0,0 +1,12 @@ +namespace DiscordChatExporter.Messages +{ + public class ShowErrorMessage + { + public string Message { get; } + + public ShowErrorMessage(string message) + { + Message = message; + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter/Services/DataService.cs b/DiscordChatExporter/Services/DataService.cs index 48fc0cc0..16c033e0 100644 --- a/DiscordChatExporter/Services/DataService.cs +++ b/DiscordChatExporter/Services/DataService.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Net; using System.Net.Http; using System.Threading.Tasks; +using DiscordChatExporter.Exceptions; using DiscordChatExporter.Models; using Newtonsoft.Json.Linq; using Tyrrrz.Extensions; @@ -14,16 +16,30 @@ namespace DiscordChatExporter.Services private const string ApiRoot = "https://discordapp.com/api/v6"; private readonly HttpClient _httpClient = new HttpClient(); + private async Task GetStringAsync(string url) + { + using (var response = await _httpClient.GetAsync(url)) + { + // Check status code + if (response.StatusCode.IsEither(HttpStatusCode.Unauthorized, HttpStatusCode.Forbidden)) + throw new UnathorizedException(); + response.EnsureSuccessStatusCode(); + + // Get content + return await response.Content.ReadAsStringAsync(); + } + } + public async Task> GetGuildsAsync(string token) { // Form request url var url = $"{ApiRoot}/users/@me/guilds?token={token}&limit=100"; // Get response - var response = await _httpClient.GetStringAsync(url); + var content = await GetStringAsync(url); // Parse - var guilds = JArray.Parse(response).Select(ParseGuild); + var guilds = JArray.Parse(content).Select(ParseGuild); return guilds; } @@ -34,10 +50,10 @@ namespace DiscordChatExporter.Services var url = $"{ApiRoot}/users/@me/channels?token={token}"; // Get response - var response = await _httpClient.GetStringAsync(url); + var content = await GetStringAsync(url); // Parse - var channels = JArray.Parse(response).Select(ParseChannel); + var channels = JArray.Parse(content).Select(ParseChannel); return channels; } @@ -48,10 +64,10 @@ namespace DiscordChatExporter.Services var url = $"{ApiRoot}/guilds/{guildId}/channels?token={token}"; // Get response - var response = await _httpClient.GetStringAsync(url); + var content = await GetStringAsync(url); // Parse - var channels = JArray.Parse(response).Select(ParseChannel); + var channels = JArray.Parse(content).Select(ParseChannel); return channels; } @@ -71,10 +87,10 @@ namespace DiscordChatExporter.Services url += $"&before={beforeId}"; // Get response - var response = await _httpClient.GetStringAsync(url); + var content = await GetStringAsync(url); // Parse - var messages = JArray.Parse(response).Select(ParseMessage); + var messages = JArray.Parse(content).Select(ParseMessage); // Add messages to list string currentMessageId = null; diff --git a/DiscordChatExporter/ViewModels/ErrorViewModel.cs b/DiscordChatExporter/ViewModels/ErrorViewModel.cs new file mode 100644 index 00000000..58d2738a --- /dev/null +++ b/DiscordChatExporter/ViewModels/ErrorViewModel.cs @@ -0,0 +1,15 @@ +using DiscordChatExporter.Messages; +using GalaSoft.MvvmLight; + +namespace DiscordChatExporter.ViewModels +{ + public class ErrorViewModel : ViewModelBase, IErrorViewModel + { + public string Message { get; private set; } + + public ErrorViewModel() + { + MessengerInstance.Register(this, m => Message = m.Message); + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter/ViewModels/IErrorViewModel.cs b/DiscordChatExporter/ViewModels/IErrorViewModel.cs new file mode 100644 index 00000000..21a27803 --- /dev/null +++ b/DiscordChatExporter/ViewModels/IErrorViewModel.cs @@ -0,0 +1,7 @@ +namespace DiscordChatExporter.ViewModels +{ + public interface IErrorViewModel + { + string Message { get; } + } +} \ No newline at end of file diff --git a/DiscordChatExporter/ViewModels/MainViewModel.cs b/DiscordChatExporter/ViewModels/MainViewModel.cs index 7aac1aa5..181756f8 100644 --- a/DiscordChatExporter/ViewModels/MainViewModel.cs +++ b/DiscordChatExporter/ViewModels/MainViewModel.cs @@ -2,6 +2,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using DiscordChatExporter.Exceptions; using DiscordChatExporter.Messages; using DiscordChatExporter.Models; using DiscordChatExporter.Services; @@ -107,22 +108,29 @@ namespace DiscordChatExporter.ViewModels // Clear existing _guildChannelsMap.Clear(); - // Get DM channels + try { - var channels = await _dataService.GetDirectMessageChannelsAsync(_cachedToken); - var guild = new Guild("@me", "Direct Messages", null); - _guildChannelsMap[guild] = channels.ToArray(); - } - - // Get guild channels - { - var guilds = await _dataService.GetGuildsAsync(_cachedToken); - foreach (var guild in guilds) + // Get DM channels { - var channels = await _dataService.GetGuildChannelsAsync(_cachedToken, guild.Id); - channels = channels.Where(c => c.Type == ChannelType.GuildTextChat); + var channels = await _dataService.GetDirectMessageChannelsAsync(_cachedToken); + var guild = new Guild("@me", "Direct Messages", null); _guildChannelsMap[guild] = channels.ToArray(); } + + // Get guild channels + { + var guilds = await _dataService.GetGuildsAsync(_cachedToken); + foreach (var guild in guilds) + { + var channels = await _dataService.GetGuildChannelsAsync(_cachedToken, guild.Id); + channels = channels.Where(c => c.Type == ChannelType.GuildTextChat); + _guildChannelsMap[guild] = channels.ToArray(); + } + } + } + catch (UnathorizedException) + { + MessengerInstance.Send(new ShowErrorMessage("Failed to authorize. Make sure the token is valid.")); } AvailableGuilds = _guildChannelsMap.Keys.ToArray(); @@ -135,13 +143,13 @@ namespace DiscordChatExporter.ViewModels IsBusy = true; // Get safe file names - var safeGroupName = SelectedGuild.Name.Replace(Path.GetInvalidFileNameChars(), '_'); + var safeGuildName = SelectedGuild.Name.Replace(Path.GetInvalidFileNameChars(), '_'); var safeChannelName = channel.Name.Replace(Path.GetInvalidFileNameChars(), '_'); // Ask for path var sfd = new SaveFileDialog { - FileName = $"{safeGroupName} - {safeChannelName}.html", + FileName = $"{safeGuildName} - {safeChannelName}.html", Filter = "HTML files (*.html)|*.html|All files (*.*)|*.*", DefaultExt = "html", AddExtension = true @@ -152,14 +160,22 @@ namespace DiscordChatExporter.ViewModels return; } - // Get messages - var messages = await _dataService.GetChannelMessagesAsync(_cachedToken, channel.Id); - - // Create log - var chatLog = new ChannelChatLog(SelectedGuild, channel, messages); - // Export - _exportService.Export(sfd.FileName, chatLog, _settingsService.Theme); + try + { + // Get messages + var messages = await _dataService.GetChannelMessagesAsync(_cachedToken, channel.Id); + + // Create log + var chatLog = new ChannelChatLog(SelectedGuild, channel, messages); + + // Export + _exportService.Export(sfd.FileName, chatLog, _settingsService.Theme); + } + catch (UnathorizedException) + { + MessengerInstance.Send(new ShowErrorMessage("Failed to export. You don't have access to that channel.")); + } IsBusy = false; } diff --git a/DiscordChatExporter/Views/ErrorDialog.ammy b/DiscordChatExporter/Views/ErrorDialog.ammy new file mode 100644 index 00000000..add0a117 --- /dev/null +++ b/DiscordChatExporter/Views/ErrorDialog.ammy @@ -0,0 +1,25 @@ +using MaterialDesignThemes.Wpf + +UserControl "DiscordChatExporter.Views.ErrorDialog" { + DataContext: bind ErrorViewModel from $resource Container + Width: 250 + + StackPanel { + // Message + TextBlock { + Margin: 8 + FontSize: 16 + HorizontalAlignment: Center + TextWrapping: WrapWithOverflow + Text: bind Message + } + + // OK + Button { + Command: DialogHost.CloseDialogCommand + Content: "OK" + Margin: 8 + Style: resource dyn "MaterialDesignFlatButton" + } + } +} \ No newline at end of file diff --git a/DiscordChatExporter/Views/ErrorDialog.ammy.cs b/DiscordChatExporter/Views/ErrorDialog.ammy.cs new file mode 100644 index 00000000..a3538e7c --- /dev/null +++ b/DiscordChatExporter/Views/ErrorDialog.ammy.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace DiscordChatExporter.Views +{ + public partial class ErrorDialog + { + public ErrorDialog() + { + InitializeComponent(); + } + } +} diff --git a/DiscordChatExporter/Views/MainWindow.ammy.cs b/DiscordChatExporter/Views/MainWindow.ammy.cs index f26df299..e0a82686 100644 --- a/DiscordChatExporter/Views/MainWindow.ammy.cs +++ b/DiscordChatExporter/Views/MainWindow.ammy.cs @@ -17,6 +17,7 @@ namespace DiscordChatExporter.Views InitializeComponent(); Title += $" v{Assembly.GetExecutingAssembly().GetName().Version}"; + Messenger.Default.Register(this, m => DialogHost.Show(new ErrorDialog()).Forget()); Messenger.Default.Register(this, m => DialogHost.Show(new SettingsDialog()).Forget()); }