mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-14 15:53:30 -07:00
Allow using bot token instead of user token (#70)
This commit is contained in:
parent
37ee0b8be3
commit
3572a21aad
|
|
@ -5,7 +5,9 @@ namespace DiscordChatExporter.Cli
|
||||||
{
|
{
|
||||||
public class CliOptions
|
public class CliOptions
|
||||||
{
|
{
|
||||||
public string Token { get; set; }
|
public string TokenValue { get; set; }
|
||||||
|
|
||||||
|
public bool IsBotToken { get; set; }
|
||||||
|
|
||||||
public string ChannelId { get; set; }
|
public string ChannelId { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ namespace DiscordChatExporter.Cli
|
||||||
Console.WriteLine($"=== Discord Chat Exporter (Command Line Interface) v{version} ===");
|
Console.WriteLine($"=== Discord Chat Exporter (Command Line Interface) v{version} ===");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("[-t] [--token] Discord authorization token.");
|
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("[-c] [--channel] Discord channel ID.");
|
||||||
Console.WriteLine("[-f] [--format] Export format. Optional.");
|
Console.WriteLine("[-f] [--format] Export format. Optional.");
|
||||||
Console.WriteLine("[-o] [--output] Output file path. Optional.");
|
Console.WriteLine("[-o] [--output] Output file path. Optional.");
|
||||||
|
|
@ -28,20 +29,27 @@ namespace DiscordChatExporter.Cli
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine($"Available export formats: {availableFormats.JoinToString(", ")}");
|
Console.WriteLine($"Available export formats: {availableFormats.JoinToString(", ")}");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("# To get authorization 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");
|
||||||
Console.WriteLine(" - Press Ctrl+Shift+I");
|
Console.WriteLine(" - Press Ctrl+Shift+I to show developer tools");
|
||||||
Console.WriteLine(" - Navigate to Application tab");
|
Console.WriteLine(" - Navigate to the Application tab");
|
||||||
Console.WriteLine(" - Expand Storage > Local Storage > https://discordapp.com");
|
Console.WriteLine(" - Expand Storage > Local Storage > https://discordapp.com");
|
||||||
Console.WriteLine(" - Find \"token\" under key and copy the value");
|
Console.WriteLine(" - Find the \"token\" key and copy its value");
|
||||||
|
Console.WriteLine();
|
||||||
|
Console.WriteLine("# To get bot token:");
|
||||||
|
Console.WriteLine(" - Go to Discord developer portal");
|
||||||
|
Console.WriteLine(" - Log in if you haven't");
|
||||||
|
Console.WriteLine(" - Open your application's settings");
|
||||||
|
Console.WriteLine(" - Navigate to the Bot section on the left");
|
||||||
|
Console.WriteLine(" - Under Token click Copy");
|
||||||
Console.WriteLine();
|
Console.WriteLine();
|
||||||
Console.WriteLine("# To get channel ID:");
|
Console.WriteLine("# To get channel ID:");
|
||||||
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");
|
||||||
Console.WriteLine(" - Go to any channel you want to export");
|
Console.WriteLine(" - Go to any channel you want to export");
|
||||||
Console.WriteLine(" - Press Ctrl+Shift+I");
|
Console.WriteLine(" - Press Ctrl+Shift+I to show developer tools");
|
||||||
Console.WriteLine(" - Navigate to Console tab");
|
Console.WriteLine(" - Navigate to the Console tab");
|
||||||
Console.WriteLine(" - Type \"document.URL\" and press Enter");
|
Console.WriteLine(" - Type \"document.URL\" and press Enter");
|
||||||
Console.WriteLine(" - Copy the long sequence of numbers after last slash");
|
Console.WriteLine(" - Copy the long sequence of numbers after last slash");
|
||||||
}
|
}
|
||||||
|
|
@ -52,7 +60,8 @@ namespace DiscordChatExporter.Cli
|
||||||
|
|
||||||
var settings = Container.SettingsService;
|
var settings = Container.SettingsService;
|
||||||
|
|
||||||
argsParser.Setup(o => o.Token).As('t', "token").Required();
|
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.ChannelId).As('c', "channel").Required();
|
||||||
argsParser.Setup(o => o.ExportFormat).As('f', "format").SetDefault(ExportFormat.HtmlDark);
|
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.FilePath).As('o', "output").SetDefault(null);
|
||||||
|
|
@ -92,10 +101,15 @@ namespace DiscordChatExporter.Cli
|
||||||
settings.DateFormat = options.DateFormat;
|
settings.DateFormat = options.DateFormat;
|
||||||
settings.MessageGroupLimit = options.MessageGroupLimit;
|
settings.MessageGroupLimit = options.MessageGroupLimit;
|
||||||
|
|
||||||
|
// Create token
|
||||||
|
var token = new AuthToken(
|
||||||
|
options.IsBotToken ? AuthTokenType.Bot : AuthTokenType.User,
|
||||||
|
options.TokenValue);
|
||||||
|
|
||||||
// Export
|
// Export
|
||||||
var vm = Container.MainViewModel;
|
var vm = Container.MainViewModel;
|
||||||
vm.ExportAsync(
|
vm.ExportAsync(
|
||||||
options.Token,
|
token,
|
||||||
options.ChannelId,
|
options.ChannelId,
|
||||||
options.FilePath,
|
options.FilePath,
|
||||||
options.ExportFormat,
|
options.ExportFormat,
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ namespace DiscordChatExporter.Cli.ViewModels
|
||||||
{
|
{
|
||||||
public interface IMainViewModel
|
public interface IMainViewModel
|
||||||
{
|
{
|
||||||
Task ExportAsync(string token, string channelId, string filePath, ExportFormat format, DateTime? from,
|
Task ExportAsync(AuthToken token, string channelId, string filePath, ExportFormat format, DateTime? from,
|
||||||
DateTime? to);
|
DateTime? to);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -21,7 +21,7 @@ namespace DiscordChatExporter.Cli.ViewModels
|
||||||
_exportService = exportService;
|
_exportService = exportService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ExportAsync(string token, string channelId, string filePath, ExportFormat format,
|
public async Task ExportAsync(AuthToken token, string channelId, string filePath, ExportFormat format,
|
||||||
DateTime? from, DateTime? to)
|
DateTime? from, DateTime? to)
|
||||||
{
|
{
|
||||||
// Get channel and guild
|
// Get channel and guild
|
||||||
|
|
|
||||||
15
DiscordChatExporter.Core/Models/AuthToken.cs
Normal file
15
DiscordChatExporter.Core/Models/AuthToken.cs
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
namespace DiscordChatExporter.Core.Models
|
||||||
|
{
|
||||||
|
public class AuthToken
|
||||||
|
{
|
||||||
|
public AuthTokenType Type { get; }
|
||||||
|
|
||||||
|
public string Value { get; }
|
||||||
|
|
||||||
|
public AuthToken(AuthTokenType type, string value)
|
||||||
|
{
|
||||||
|
Type = type;
|
||||||
|
Value = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
DiscordChatExporter.Core/Models/AuthTokenType.cs
Normal file
8
DiscordChatExporter.Core/Models/AuthTokenType.cs
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace DiscordChatExporter.Core.Models
|
||||||
|
{
|
||||||
|
public enum AuthTokenType
|
||||||
|
{
|
||||||
|
User,
|
||||||
|
Bot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,12 +2,14 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using DiscordChatExporter.Core.Exceptions;
|
using DiscordChatExporter.Core.Exceptions;
|
||||||
using DiscordChatExporter.Core.Models;
|
using DiscordChatExporter.Core.Models;
|
||||||
using Newtonsoft.Json.Linq;
|
using Newtonsoft.Json.Linq;
|
||||||
using DiscordChatExporter.Core.Internal;
|
using DiscordChatExporter.Core.Internal;
|
||||||
using Polly;
|
using Polly;
|
||||||
|
using Tyrrrz.Extensions;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Services
|
namespace DiscordChatExporter.Core.Services
|
||||||
{
|
{
|
||||||
|
|
@ -15,17 +17,9 @@ namespace DiscordChatExporter.Core.Services
|
||||||
{
|
{
|
||||||
private readonly HttpClient _httpClient = new HttpClient();
|
private readonly HttpClient _httpClient = new HttpClient();
|
||||||
|
|
||||||
private async Task<JToken> GetApiResponseAsync(string token, string resource, string endpoint,
|
private async Task<JToken> GetApiResponseAsync(AuthToken token, string resource, string endpoint,
|
||||||
params string[] parameters)
|
params string[] parameters)
|
||||||
{
|
{
|
||||||
// Format URL
|
|
||||||
const string apiRoot = "https://discordapp.com/api/v6";
|
|
||||||
var url = $"{apiRoot}/{resource}/{endpoint}?token={token}";
|
|
||||||
|
|
||||||
// Add parameters
|
|
||||||
foreach (var parameter in parameters)
|
|
||||||
url += $"&{parameter}";
|
|
||||||
|
|
||||||
// Create request policy
|
// Create request policy
|
||||||
var policy = Policy
|
var policy = Policy
|
||||||
.Handle<HttpErrorStatusCodeException>(e => (int) e.StatusCode == 429)
|
.Handle<HttpErrorStatusCodeException>(e => (int) e.StatusCode == 429)
|
||||||
|
|
@ -34,7 +28,28 @@ namespace DiscordChatExporter.Core.Services
|
||||||
// Send request
|
// Send request
|
||||||
return await policy.ExecuteAsync(async () =>
|
return await policy.ExecuteAsync(async () =>
|
||||||
{
|
{
|
||||||
using (var response = await _httpClient.GetAsync(url))
|
// Create request
|
||||||
|
const string apiRoot = "https://discordapp.com/api/v6";
|
||||||
|
using (var request = new HttpRequestMessage(HttpMethod.Get, $"{apiRoot}/{resource}/{endpoint}"))
|
||||||
|
{
|
||||||
|
// Add url parameter for the user token
|
||||||
|
if (token.Type == AuthTokenType.User)
|
||||||
|
request.RequestUri = request.RequestUri.SetQueryParameter("token", token.Value);
|
||||||
|
|
||||||
|
// Add header for the bot token
|
||||||
|
if (token.Type == AuthTokenType.Bot)
|
||||||
|
request.Headers.Authorization = new AuthenticationHeaderValue("Bot", token.Value);
|
||||||
|
|
||||||
|
// Add parameters
|
||||||
|
foreach (var parameter in parameters)
|
||||||
|
{
|
||||||
|
var key = parameter.SubstringUntil("=");
|
||||||
|
var value = parameter.SubstringAfter("=");
|
||||||
|
request.RequestUri = request.RequestUri.SetQueryParameter(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get response
|
||||||
|
using (var response = await _httpClient.SendAsync(request))
|
||||||
{
|
{
|
||||||
// Check status code
|
// Check status code
|
||||||
// We throw our own exception here because default one doesn't have status code
|
// We throw our own exception here because default one doesn't have status code
|
||||||
|
|
@ -47,10 +62,11 @@ namespace DiscordChatExporter.Core.Services
|
||||||
// Parse
|
// Parse
|
||||||
return JToken.Parse(raw);
|
return JToken.Parse(raw);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Guild> GetGuildAsync(string token, string guildId)
|
public async Task<Guild> GetGuildAsync(AuthToken token, string guildId)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, "guilds", guildId);
|
var response = await GetApiResponseAsync(token, "guilds", guildId);
|
||||||
var guild = ParseGuild(response);
|
var guild = ParseGuild(response);
|
||||||
|
|
@ -58,7 +74,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return guild;
|
return guild;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Channel> GetChannelAsync(string token, string channelId)
|
public async Task<Channel> GetChannelAsync(AuthToken token, string channelId)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, "channels", channelId);
|
var response = await GetApiResponseAsync(token, "channels", channelId);
|
||||||
var channel = ParseChannel(response);
|
var channel = ParseChannel(response);
|
||||||
|
|
@ -66,7 +82,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Guild>> GetUserGuildsAsync(string token)
|
public async Task<IReadOnlyList<Guild>> GetUserGuildsAsync(AuthToken token)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, "users", "@me/guilds", "limit=100");
|
var response = await GetApiResponseAsync(token, "users", "@me/guilds", "limit=100");
|
||||||
var guilds = response.Select(ParseGuild).ToArray();
|
var guilds = response.Select(ParseGuild).ToArray();
|
||||||
|
|
@ -74,7 +90,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return guilds;
|
return guilds;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Channel>> GetDirectMessageChannelsAsync(string token)
|
public async Task<IReadOnlyList<Channel>> GetDirectMessageChannelsAsync(AuthToken token)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, "users", "@me/channels");
|
var response = await GetApiResponseAsync(token, "users", "@me/channels");
|
||||||
var channels = response.Select(ParseChannel).ToArray();
|
var channels = response.Select(ParseChannel).ToArray();
|
||||||
|
|
@ -82,7 +98,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(string token, string guildId)
|
public async Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(AuthToken token, string guildId)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, "guilds", $"{guildId}/channels");
|
var response = await GetApiResponseAsync(token, "guilds", $"{guildId}/channels");
|
||||||
var channels = response.Select(ParseChannel).ToArray();
|
var channels = response.Select(ParseChannel).ToArray();
|
||||||
|
|
@ -90,7 +106,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return channels;
|
return channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Role>> GetGuildRolesAsync(string token, string guildId)
|
public async Task<IReadOnlyList<Role>> GetGuildRolesAsync(AuthToken token, string guildId)
|
||||||
{
|
{
|
||||||
var response = await GetApiResponseAsync(token, "guilds", $"{guildId}/roles");
|
var response = await GetApiResponseAsync(token, "guilds", $"{guildId}/roles");
|
||||||
var roles = response.Select(ParseRole).ToArray();
|
var roles = response.Select(ParseRole).ToArray();
|
||||||
|
|
@ -98,7 +114,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IReadOnlyList<Message>> GetChannelMessagesAsync(string token, string channelId,
|
public async Task<IReadOnlyList<Message>> GetChannelMessagesAsync(AuthToken token, string channelId,
|
||||||
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null)
|
||||||
{
|
{
|
||||||
var result = new List<Message>();
|
var result = new List<Message>();
|
||||||
|
|
@ -169,7 +185,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Mentionables> GetMentionablesAsync(string token, string guildId,
|
public async Task<Mentionables> GetMentionablesAsync(AuthToken token, string guildId,
|
||||||
IEnumerable<Message> messages)
|
IEnumerable<Message> messages)
|
||||||
{
|
{
|
||||||
// Get channels and roles
|
// Get channels and roles
|
||||||
|
|
|
||||||
|
|
@ -7,22 +7,22 @@ namespace DiscordChatExporter.Core.Services
|
||||||
{
|
{
|
||||||
public interface IDataService
|
public interface IDataService
|
||||||
{
|
{
|
||||||
Task<Guild> GetGuildAsync(string token, string guildId);
|
Task<Guild> GetGuildAsync(AuthToken token, string guildId);
|
||||||
|
|
||||||
Task<Channel> GetChannelAsync(string token, string channelId);
|
Task<Channel> GetChannelAsync(AuthToken token, string channelId);
|
||||||
|
|
||||||
Task<IReadOnlyList<Guild>> GetUserGuildsAsync(string token);
|
Task<IReadOnlyList<Guild>> GetUserGuildsAsync(AuthToken token);
|
||||||
|
|
||||||
Task<IReadOnlyList<Channel>> GetDirectMessageChannelsAsync(string token);
|
Task<IReadOnlyList<Channel>> GetDirectMessageChannelsAsync(AuthToken token);
|
||||||
|
|
||||||
Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(string token, string guildId);
|
Task<IReadOnlyList<Channel>> GetGuildChannelsAsync(AuthToken token, string guildId);
|
||||||
|
|
||||||
Task<IReadOnlyList<Role>> GetGuildRolesAsync(string token, string guildId);
|
Task<IReadOnlyList<Role>> GetGuildRolesAsync(AuthToken token, string guildId);
|
||||||
|
|
||||||
Task<IReadOnlyList<Message>> GetChannelMessagesAsync(string token, string channelId,
|
Task<IReadOnlyList<Message>> GetChannelMessagesAsync(AuthToken token, string channelId,
|
||||||
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null);
|
DateTime? from = null, DateTime? to = null, IProgress<double> progress = null);
|
||||||
|
|
||||||
Task<Mentionables> GetMentionablesAsync(string token, string guildId,
|
Task<Mentionables> GetMentionablesAsync(AuthToken token, string guildId,
|
||||||
IEnumerable<Message> messages);
|
IEnumerable<Message> messages);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
string DateFormat { get; set; }
|
string DateFormat { get; set; }
|
||||||
int MessageGroupLimit { get; set; }
|
int MessageGroupLimit { get; set; }
|
||||||
|
|
||||||
string LastToken { get; set; }
|
AuthToken LastToken { get; set; }
|
||||||
ExportFormat LastExportFormat { get; set; }
|
ExportFormat LastExportFormat { get; set; }
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ namespace DiscordChatExporter.Core.Services
|
||||||
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
|
public string DateFormat { get; set; } = "dd-MMM-yy hh:mm tt";
|
||||||
public int MessageGroupLimit { get; set; } = 20;
|
public int MessageGroupLimit { get; set; } = 20;
|
||||||
|
|
||||||
public string LastToken { get; set; }
|
public AuthToken LastToken { get; set; }
|
||||||
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
|
||||||
public SettingsService()
|
public SettingsService()
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,21 @@
|
||||||
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
|
<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}" />
|
||||||
</Style>
|
</Style>
|
||||||
|
|
||||||
|
<Style
|
||||||
|
x:Key="MaterialDesignFlatActionToggleButton"
|
||||||
|
BasedOn="{StaticResource MaterialDesignActionToggleButton}"
|
||||||
|
TargetType="{x:Type ToggleButton}">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource PrimaryHueMidBrush}" />
|
||||||
|
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource MaterialDesignFlatButtonClick}" />
|
||||||
|
<Setter Property="BorderBrush" Value="{DynamicResource MaterialDesignFlatButtonClick}" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
<!-- Converters -->
|
<!-- Converters -->
|
||||||
<converters:ExportFormatToStringConverter x:Key="ExportFormatToStringConverter" />
|
<converters:ExportFormatToStringConverter x:Key="ExportFormatToStringConverter" />
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,8 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IReadOnlyList<ExportFormat> AvailableFormats { get; }
|
public IReadOnlyList<ExportFormat> AvailableFormats =>
|
||||||
|
Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
|
||||||
|
|
||||||
public ExportFormat SelectedFormat
|
public ExportFormat SelectedFormat
|
||||||
{
|
{
|
||||||
|
|
@ -69,9 +70,6 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
|
|
||||||
// Defaults
|
|
||||||
AvailableFormats = Enum.GetValues(typeof(ExportFormat)).Cast<ExportFormat>().ToArray();
|
|
||||||
|
|
||||||
// Commands
|
// Commands
|
||||||
ExportCommand = new RelayCommand(Export, () => FilePath.IsNotBlank());
|
ExportCommand = new RelayCommand(Export, () => FilePath.IsNotBlank());
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
bool IsProgressIndeterminate { get; }
|
bool IsProgressIndeterminate { get; }
|
||||||
double Progress { get; }
|
double Progress { get; }
|
||||||
|
|
||||||
string Token { get; set; }
|
bool IsBotToken { get; set; }
|
||||||
|
string TokenValue { get; set; }
|
||||||
|
|
||||||
IReadOnlyList<Guild> AvailableGuilds { get; }
|
IReadOnlyList<Guild> AvailableGuilds { get; }
|
||||||
Guild SelectedGuild { get; set; }
|
Guild SelectedGuild { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,8 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
|
|
||||||
private bool _isBusy;
|
private bool _isBusy;
|
||||||
private double _progress;
|
private double _progress;
|
||||||
private string _token;
|
private bool _isBotToken;
|
||||||
|
private string _tokenValue;
|
||||||
private IReadOnlyList<Guild> _availableGuilds;
|
private IReadOnlyList<Guild> _availableGuilds;
|
||||||
private Guild _selectedGuild;
|
private Guild _selectedGuild;
|
||||||
private IReadOnlyList<Channel> _availableChannels;
|
private IReadOnlyList<Channel> _availableChannels;
|
||||||
|
|
@ -56,15 +57,21 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string Token
|
public bool IsBotToken
|
||||||
{
|
{
|
||||||
get => _token;
|
get => _isBotToken;
|
||||||
|
set => Set(ref _isBotToken, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string TokenValue
|
||||||
|
{
|
||||||
|
get => _tokenValue;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
// Remove invalid chars
|
// Remove invalid chars
|
||||||
value = value?.Trim('"');
|
value = value?.Trim('"');
|
||||||
|
|
||||||
Set(ref _token, value);
|
Set(ref _tokenValue, value);
|
||||||
PullDataCommand.RaiseCanExecuteChanged();
|
PullDataCommand.RaiseCanExecuteChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -117,7 +124,7 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
// Commands
|
// Commands
|
||||||
ViewLoadedCommand = new RelayCommand(ViewLoaded);
|
ViewLoadedCommand = new RelayCommand(ViewLoaded);
|
||||||
ViewClosedCommand = new RelayCommand(ViewClosed);
|
ViewClosedCommand = new RelayCommand(ViewClosed);
|
||||||
PullDataCommand = new RelayCommand(PullData, () => Token.IsNotBlank() && !IsBusy);
|
PullDataCommand = new RelayCommand(PullData, () => TokenValue.IsNotBlank() && !IsBusy);
|
||||||
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
ShowSettingsCommand = new RelayCommand(ShowSettings);
|
||||||
ShowAboutCommand = new RelayCommand(ShowAbout);
|
ShowAboutCommand = new RelayCommand(ShowAbout);
|
||||||
ShowExportSetupCommand = new RelayCommand<Channel>(ShowExportSetup, _ => !IsBusy);
|
ShowExportSetupCommand = new RelayCommand<Channel>(ShowExportSetup, _ => !IsBusy);
|
||||||
|
|
@ -132,8 +139,12 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
// Load settings
|
// Load settings
|
||||||
_settingsService.Load();
|
_settingsService.Load();
|
||||||
|
|
||||||
// Set last token
|
// Get last token
|
||||||
Token = _settingsService.LastToken;
|
if (_settingsService.LastToken != null)
|
||||||
|
{
|
||||||
|
IsBotToken = _settingsService.LastToken.Type == AuthTokenType.Bot;
|
||||||
|
TokenValue = _settingsService.LastToken.Value;
|
||||||
|
}
|
||||||
|
|
||||||
// Check and prepare update
|
// Check and prepare update
|
||||||
try
|
try
|
||||||
|
|
@ -169,8 +180,10 @@ namespace DiscordChatExporter.Gui.ViewModels
|
||||||
{
|
{
|
||||||
IsBusy = true;
|
IsBusy = true;
|
||||||
|
|
||||||
// Copy token so it doesn't get mutated
|
// Create token
|
||||||
var token = Token;
|
var token = new AuthToken(
|
||||||
|
IsBotToken ? AuthTokenType.Bot : AuthTokenType.User,
|
||||||
|
TokenValue);
|
||||||
|
|
||||||
// Save token
|
// Save token
|
||||||
_settingsService.LastToken = token;
|
_settingsService.LastToken = token;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@
|
||||||
Height="550"
|
Height="550"
|
||||||
Background="{DynamicResource MaterialDesignPaper}"
|
Background="{DynamicResource MaterialDesignPaper}"
|
||||||
DataContext="{Binding MainViewModel, Source={StaticResource Container}}"
|
DataContext="{Binding MainViewModel, Source={StaticResource Container}}"
|
||||||
FocusManager.FocusedElement="{Binding ElementName=TokenTextBox}"
|
FocusManager.FocusedElement="{Binding ElementName=TokenValueTextBox}"
|
||||||
FontFamily="{DynamicResource MaterialDesignFont}"
|
FontFamily="{DynamicResource MaterialDesignFont}"
|
||||||
Icon="/DiscordChatExporter;component/favicon.ico"
|
Icon="/DiscordChatExporter;component/favicon.ico"
|
||||||
SnapsToDevicePixels="True"
|
SnapsToDevicePixels="True"
|
||||||
|
|
@ -48,27 +48,47 @@
|
||||||
Margin="6,6,0,6">
|
Margin="6,6,0,6">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Token -->
|
<!-- Token type -->
|
||||||
<TextBox
|
<ToggleButton
|
||||||
x:Name="TokenTextBox"
|
|
||||||
Grid.Row="0"
|
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="6"
|
Margin="6"
|
||||||
|
IsChecked="{Binding IsBotToken}"
|
||||||
|
Style="{StaticResource MaterialDesignFlatActionToggleButton}"
|
||||||
|
ToolTip="Switch between user token and bot token">
|
||||||
|
<ToggleButton.Content>
|
||||||
|
<materialDesign:PackIcon
|
||||||
|
Width="24"
|
||||||
|
Height="24"
|
||||||
|
Kind="Account" />
|
||||||
|
</ToggleButton.Content>
|
||||||
|
<materialDesign:ToggleButtonAssist.OnContent>
|
||||||
|
<materialDesign:PackIcon
|
||||||
|
Width="24"
|
||||||
|
Height="24"
|
||||||
|
Kind="Robot" />
|
||||||
|
</materialDesign:ToggleButtonAssist.OnContent>
|
||||||
|
</ToggleButton>
|
||||||
|
|
||||||
|
<!-- Token value -->
|
||||||
|
<TextBox
|
||||||
|
x:Name="TokenValueTextBox"
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="2,6,6,7"
|
||||||
materialDesign:HintAssist.Hint="Token"
|
materialDesign:HintAssist.Hint="Token"
|
||||||
materialDesign:TextFieldAssist.DecorationVisibility="Hidden"
|
materialDesign:TextFieldAssist.DecorationVisibility="Hidden"
|
||||||
materialDesign:TextFieldAssist.TextBoxViewMargin="0,0,2,0"
|
materialDesign:TextFieldAssist.TextBoxViewMargin="0,0,2,0"
|
||||||
BorderThickness="0"
|
BorderThickness="0"
|
||||||
FontSize="16"
|
FontSize="16"
|
||||||
Text="{Binding Token, UpdateSourceTrigger=PropertyChanged}" />
|
Text="{Binding TokenValue, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
|
||||||
<!-- Pull data button -->
|
<!-- Pull data button -->
|
||||||
<Button
|
<Button
|
||||||
Grid.Row="0"
|
Grid.Column="2"
|
||||||
Grid.Column="1"
|
|
||||||
Margin="0,6,6,6"
|
Margin="0,6,6,6"
|
||||||
Padding="4"
|
Padding="4"
|
||||||
Command="{Binding PullDataCommand}"
|
Command="{Binding PullDataCommand}"
|
||||||
|
|
@ -99,8 +119,8 @@
|
||||||
<ProgressBar
|
<ProgressBar
|
||||||
Background="Transparent"
|
Background="Transparent"
|
||||||
IsIndeterminate="{Binding IsProgressIndeterminate}"
|
IsIndeterminate="{Binding IsProgressIndeterminate}"
|
||||||
Value="{Binding Progress, Mode=OneWay}"
|
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibilityConverter}}"
|
||||||
Visibility="{Binding IsBusy, Converter={StaticResource BoolToVisibilityConverter}}" />
|
Value="{Binding Progress, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Border>
|
</Border>
|
||||||
|
|
||||||
|
|
@ -196,8 +216,10 @@
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
||||||
<!-- Usage instructions -->
|
<!-- Usage instructions -->
|
||||||
<StackPanel Margin="32,32,8,8" Visibility="{Binding IsDataAvailable, Converter={StaticResource InvertBoolToVisibilityConverter}}">
|
<Grid Margin="32,32,8,8" Visibility="{Binding IsDataAvailable, Converter={StaticResource InvertBoolToVisibilityConverter}}">
|
||||||
<TextBlock FontSize="18" Text="DiscordChatExporter needs your authorization token to work." />
|
<!-- User token -->
|
||||||
|
<StackPanel Visibility="{Binding IsBotToken, Converter={StaticResource InvertBoolToVisibilityConverter}}">
|
||||||
|
<TextBlock FontSize="18" Text="DiscordChatExporter needs your user token to work." />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
Margin="0,8,0,0"
|
Margin="0,8,0,0"
|
||||||
FontSize="16"
|
FontSize="16"
|
||||||
|
|
@ -209,21 +231,50 @@
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="3. Press" />
|
<Run Text="3. Press" />
|
||||||
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+Shift+I" />
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+Shift+I" />
|
||||||
|
<Run Text="to show developer tools" />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="4. Navigate to" />
|
<Run Text="4. Navigate to the" />
|
||||||
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Application" />
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Application" />
|
||||||
<Run Text="tab" />
|
<Run Text="tab" />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="5. Expand" />
|
<Run Text="5. Expand" />
|
||||||
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Storage > Local Storage > https://discordapp.com" />
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Storage > Local Storage > https://discordapp.com" />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="6. Find" />
|
<Run Text="6. Find the" />
|
||||||
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text=""token"" />
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text=""token"" />
|
||||||
<Run Text="under key and copy the value" />
|
<Run Text="key and copy its value" />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="7. Paste the value in the textbox above" />
|
<Run Text="7. Paste the value in the textbox above" />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Bot token -->
|
||||||
|
<StackPanel Visibility="{Binding IsBotToken, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
|
<TextBlock FontSize="18" Text="DiscordChatExporter needs your bot token to work." />
|
||||||
|
<TextBlock
|
||||||
|
Margin="0,8,0,0"
|
||||||
|
FontSize="16"
|
||||||
|
Text="To obtain it, follow these steps:" />
|
||||||
|
<TextBlock Margin="8,0,0,0" FontSize="14">
|
||||||
|
<Run Text="1. Go to Discord developer portal" />
|
||||||
|
<LineBreak />
|
||||||
|
<Run Text="2. Log in if you haven't" />
|
||||||
|
<LineBreak />
|
||||||
|
<Run Text="3. Open your application's settings" />
|
||||||
|
<LineBreak />
|
||||||
|
<Run Text="4. Navigate to the" />
|
||||||
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Bot" />
|
||||||
|
<Run Text="section on the left" />
|
||||||
|
<LineBreak />
|
||||||
|
<Run Text="5. Under" />
|
||||||
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Token" />
|
||||||
|
<Run Text="click" />
|
||||||
|
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Copy" />
|
||||||
|
<LineBreak />
|
||||||
|
<Run Text="6. Paste the value in the textbox above" />
|
||||||
|
</TextBlock>
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
<materialDesign:Snackbar x:Name="Snackbar" />
|
<materialDesign:Snackbar x:Name="Snackbar" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</DockPanel>
|
</DockPanel>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue