Allow using bot token instead of user token (#70)

pull/92/head
Alexey Golub 6 years ago committed by GitHub
parent 37ee0b8be3
commit 3572a21aad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -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

@ -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;
}
}
}

@ -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,23 +28,45 @@ 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}"))
{ {
// Check status code // Add url parameter for the user token
// We throw our own exception here because default one doesn't have status code if (token.Type == AuthTokenType.User)
if (!response.IsSuccessStatusCode) request.RequestUri = request.RequestUri.SetQueryParameter("token", token.Value);
throw new HttpErrorStatusCodeException(response.StatusCode, response.ReasonPhrase);
// Add header for the bot token
// Get content if (token.Type == AuthTokenType.Bot)
var raw = await response.Content.ReadAsStringAsync(); request.Headers.Authorization = new AuthenticationHeaderValue("Bot", token.Value);
// Parse // Add parameters
return JToken.Parse(raw); 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
// We throw our own exception here because default one doesn't have status code
if (!response.IsSuccessStatusCode)
throw new HttpErrorStatusCodeException(response.StatusCode, response.ReasonPhrase);
// Get content
var raw = await response.Content.ReadAsStringAsync();
// Parse
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,34 +216,65 @@
</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 -->
<TextBlock <StackPanel Visibility="{Binding IsBotToken, Converter={StaticResource InvertBoolToVisibilityConverter}}">
Margin="0,8,0,0" <TextBlock FontSize="18" Text="DiscordChatExporter needs your user token to work." />
FontSize="16" <TextBlock
Text="To obtain it, follow these steps:" /> Margin="0,8,0,0"
<TextBlock Margin="8,0,0,0" FontSize="14"> FontSize="16"
<Run Text="1. Open the Discord app" /> Text="To obtain it, follow these steps:" />
<LineBreak /> <TextBlock Margin="8,0,0,0" FontSize="14">
<Run Text="2. Log in if you haven't" /> <Run Text="1. Open the Discord app" />
<LineBreak /> <LineBreak />
<Run Text="3. Press" /> <Run Text="2. Log in if you haven't" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+Shift+I" /> <LineBreak />
<LineBreak /> <Run Text="3. Press" />
<Run Text="4. Navigate to" /> <Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Ctrl+Shift+I" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Application" /> <Run Text="to show developer tools" />
<Run Text="tab" /> <LineBreak />
<LineBreak /> <Run Text="4. Navigate to the" />
<Run Text="5. Expand" /> <Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Application" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Storage &gt; Local Storage &gt; https://discordapp.com" /> <Run Text="tab" />
<LineBreak /> <LineBreak />
<Run Text="6. Find" /> <Run Text="5. Expand" />
<Run Foreground="{DynamicResource PrimaryTextBrush}" Text="&quot;token&quot;" /> <Run Foreground="{DynamicResource PrimaryTextBrush}" Text="Storage &gt; Local Storage &gt; https://discordapp.com" />
<Run Text="under key and copy the value" /> <LineBreak />
<LineBreak /> <Run Text="6. Find the" />
<Run Text="7. Paste the value in the textbox above" /> <Run Foreground="{DynamicResource PrimaryTextBrush}" Text="&quot;token&quot;" />
</TextBlock> <Run Text="key and copy its value" />
</StackPanel> <LineBreak />
<Run Text="7. Paste the value in the textbox above" />
</TextBlock>
</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…
Cancel
Save