mirror of
https://github.com/Tyrrrz/DiscordChatExporter.git
synced 2026-02-14 15:53:30 -07:00
Automatically detect token kind (#764)
This commit is contained in:
parent
e97151cd19
commit
2156c6cd0c
|
|
@ -36,8 +36,7 @@ public class ExportWrapperFixture : IDisposable
|
||||||
{
|
{
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { channelId },
|
ChannelIds = new[] { channelId },
|
||||||
ExportFormat = format,
|
ExportFormat = format,
|
||||||
OutputPath = filePath
|
OutputPath = filePath
|
||||||
|
|
|
||||||
|
|
@ -22,24 +22,5 @@ internal static class Secrets
|
||||||
throw new InvalidOperationException("Discord token not provided for tests.");
|
throw new InvalidOperationException("Discord token not provided for tests.");
|
||||||
});
|
});
|
||||||
|
|
||||||
private static readonly Lazy<bool> IsDiscordTokenBotLazy = new(() =>
|
|
||||||
{
|
|
||||||
var fromEnvironment = Environment.GetEnvironmentVariable("DISCORD_TOKEN_BOT");
|
|
||||||
if (!string.IsNullOrWhiteSpace(fromEnvironment))
|
|
||||||
return string.Equals(fromEnvironment, "true", StringComparison.OrdinalIgnoreCase);
|
|
||||||
|
|
||||||
var secretFilePath = Path.Combine(
|
|
||||||
Path.GetDirectoryName(typeof(Secrets).Assembly.Location) ?? Directory.GetCurrentDirectory(),
|
|
||||||
"DiscordTokenBot.secret"
|
|
||||||
);
|
|
||||||
|
|
||||||
if (File.Exists(secretFilePath))
|
|
||||||
return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
public static string DiscordToken => DiscordTokenLazy.Value;
|
public static string DiscordToken => DiscordTokenLazy.Value;
|
||||||
|
|
||||||
public static bool IsDiscordTokenBot => IsDiscordTokenBotLazy.Value;
|
|
||||||
}
|
}
|
||||||
|
|
@ -27,8 +27,7 @@ public record DateRangeSpecs(TempOutputFixture TempOutput) : IClassFixture<TempO
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
@ -74,8 +73,7 @@ public record DateRangeSpecs(TempOutputFixture TempOutput) : IClassFixture<TempO
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
@ -120,8 +118,7 @@ public record DateRangeSpecs(TempOutputFixture TempOutput) : IClassFixture<TempO
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutp
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
@ -54,8 +53,7 @@ public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutp
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
@ -83,8 +81,7 @@ public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutp
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
@ -112,8 +109,7 @@ public record FilterSpecs(TempOutputFixture TempOutput) : IClassFixture<TempOutp
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
ChannelIds = new[] { ChannelIds.FilterTestCases },
|
||||||
ExportFormat = ExportFormat.Json,
|
ExportFormat = ExportFormat.Json,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ public record PartitioningSpecs(TempOutputFixture TempOutput) : IClassFixture<Te
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
@ -50,8 +49,7 @@ public record PartitioningSpecs(TempOutputFixture TempOutput) : IClassFixture<Te
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
ChannelIds = new[] { ChannelIds.DateRangeTestCases },
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ public record SelfContainedSpecs(TempOutputFixture TempOutput) : IClassFixture<T
|
||||||
// Act
|
// Act
|
||||||
await new ExportChannelsCommand
|
await new ExportChannelsCommand
|
||||||
{
|
{
|
||||||
TokenValue = Secrets.DiscordToken,
|
Token = Secrets.DiscordToken,
|
||||||
IsBotToken = Secrets.IsDiscordTokenBot,
|
|
||||||
ChannelIds = new[] { ChannelIds.SelfContainedTestCases },
|
ChannelIds = new[] { ChannelIds.SelfContainedTestCases },
|
||||||
ExportFormat = ExportFormat.HtmlDark,
|
ExportFormat = ExportFormat.HtmlDark,
|
||||||
OutputPath = filePath,
|
OutputPath = filePath,
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using System.Threading.Tasks;
|
using System;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using CliFx;
|
using CliFx;
|
||||||
using CliFx.Attributes;
|
using CliFx.Attributes;
|
||||||
using CliFx.Infrastructure;
|
using CliFx.Infrastructure;
|
||||||
|
|
@ -9,21 +10,14 @@ namespace DiscordChatExporter.Cli.Commands.Base;
|
||||||
public abstract class TokenCommandBase : ICommand
|
public abstract class TokenCommandBase : ICommand
|
||||||
{
|
{
|
||||||
[CommandOption("token", 't', IsRequired = true, EnvironmentVariable = "DISCORD_TOKEN", Description = "Authentication token.")]
|
[CommandOption("token", 't', IsRequired = true, EnvironmentVariable = "DISCORD_TOKEN", Description = "Authentication token.")]
|
||||||
public string TokenValue { get; init; } = "";
|
public string Token { get; init; } = "";
|
||||||
|
|
||||||
[CommandOption("bot", 'b', EnvironmentVariable = "DISCORD_TOKEN_BOT", Description = "Authenticate as a bot.")]
|
[CommandOption("bot", 'b', EnvironmentVariable = "DISCORD_TOKEN_BOT", Description = "This option doesn't do anything. Kept for backwards compatibility.")]
|
||||||
|
[Obsolete("This option doesn't do anything. Kept for backwards compatibility.")]
|
||||||
public bool IsBotToken { get; init; }
|
public bool IsBotToken { get; init; }
|
||||||
|
|
||||||
private AuthToken? _authToken;
|
|
||||||
private AuthToken AuthToken => _authToken ??= new AuthToken(
|
|
||||||
IsBotToken
|
|
||||||
? AuthTokenKind.Bot
|
|
||||||
: AuthTokenKind.User,
|
|
||||||
TokenValue
|
|
||||||
);
|
|
||||||
|
|
||||||
private DiscordClient? _discordClient;
|
private DiscordClient? _discordClient;
|
||||||
protected DiscordClient Discord => _discordClient ??= new DiscordClient(AuthToken);
|
protected DiscordClient Discord => _discordClient ??= new DiscordClient(Token);
|
||||||
|
|
||||||
public abstract ValueTask ExecuteAsync(IConsole console);
|
public abstract ValueTask ExecuteAsync(IConsole console);
|
||||||
}
|
}
|
||||||
|
|
@ -61,7 +61,7 @@ public class GuideCommand : ICommand
|
||||||
|
|
||||||
// Wiki link
|
// Wiki link
|
||||||
using (console.WithForegroundColor(ConsoleColor.White))
|
using (console.WithForegroundColor(ConsoleColor.White))
|
||||||
console.Output.WriteLine("For more information, check out the wiki:");
|
console.Output.WriteLine("If you have questions or issues, please refer to the wiki:");
|
||||||
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
using (console.WithForegroundColor(ConsoleColor.DarkCyan))
|
||||||
console.Output.WriteLine("https://github.com/Tyrrrz/DiscordChatExporter/wiki");
|
console.Output.WriteLine("https://github.com/Tyrrrz/DiscordChatExporter/wiki");
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Net.Http.Headers;
|
|
||||||
|
|
||||||
namespace DiscordChatExporter.Core.Discord;
|
|
||||||
|
|
||||||
public record AuthToken(AuthTokenKind Kind, string Value)
|
|
||||||
{
|
|
||||||
public AuthenticationHeaderValue GetAuthenticationHeader() => Kind switch
|
|
||||||
{
|
|
||||||
AuthTokenKind.Bot => new AuthenticationHeaderValue("Bot", Value),
|
|
||||||
_ => new AuthenticationHeaderValue(Value)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
|
using System.Net.Http.Headers;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
|
@ -18,10 +19,30 @@ namespace DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
public class DiscordClient
|
public class DiscordClient
|
||||||
{
|
{
|
||||||
private readonly AuthToken _token;
|
private readonly string _token;
|
||||||
private readonly Uri _baseUri = new("https://discord.com/api/v8/", UriKind.Absolute);
|
private readonly Uri _baseUri = new("https://discord.com/api/v8/", UriKind.Absolute);
|
||||||
|
|
||||||
public DiscordClient(AuthToken token) => _token = token;
|
private TokenKind _tokenKind = TokenKind.Unknown;
|
||||||
|
|
||||||
|
public DiscordClient(string token) => _token = token;
|
||||||
|
|
||||||
|
private async ValueTask<HttpResponseMessage> GetResponseAsync(
|
||||||
|
string url,
|
||||||
|
bool isBot,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url));
|
||||||
|
|
||||||
|
request.Headers.Authorization = isBot
|
||||||
|
? new AuthenticationHeaderValue("Bot", _token)
|
||||||
|
: new AuthenticationHeaderValue(_token);
|
||||||
|
|
||||||
|
return await Http.Client.SendAsync(
|
||||||
|
request,
|
||||||
|
HttpCompletionOption.ResponseHeadersRead,
|
||||||
|
cancellationToken
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private async ValueTask<HttpResponseMessage> GetResponseAsync(
|
private async ValueTask<HttpResponseMessage> GetResponseAsync(
|
||||||
string url,
|
string url,
|
||||||
|
|
@ -29,14 +50,33 @@ public class DiscordClient
|
||||||
{
|
{
|
||||||
return await Http.ResponsePolicy.ExecuteAsync(async innerCancellationToken =>
|
return await Http.ResponsePolicy.ExecuteAsync(async innerCancellationToken =>
|
||||||
{
|
{
|
||||||
using var request = new HttpRequestMessage(HttpMethod.Get, new Uri(_baseUri, url));
|
if (_tokenKind == TokenKind.User)
|
||||||
request.Headers.Authorization = _token.GetAuthenticationHeader();
|
return await GetResponseAsync(url, false, innerCancellationToken);
|
||||||
|
|
||||||
return await Http.Client.SendAsync(
|
if (_tokenKind == TokenKind.Bot)
|
||||||
request,
|
return await GetResponseAsync(url, true, innerCancellationToken);
|
||||||
HttpCompletionOption.ResponseHeadersRead,
|
|
||||||
innerCancellationToken
|
// Try to authenticate as user
|
||||||
);
|
var userResponse = await GetResponseAsync(url, false, innerCancellationToken);
|
||||||
|
if (userResponse.StatusCode != HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_tokenKind = TokenKind.User;
|
||||||
|
return userResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
userResponse.Dispose();
|
||||||
|
|
||||||
|
// Otherwise, try to authenticate as bot
|
||||||
|
var botResponse = await GetResponseAsync(url, true, innerCancellationToken);
|
||||||
|
if (botResponse.StatusCode != HttpStatusCode.Unauthorized)
|
||||||
|
{
|
||||||
|
_tokenKind = TokenKind.Bot;
|
||||||
|
return botResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The token is probably invalid altogether.
|
||||||
|
// Return the last response anyway, upstream should handle the error.
|
||||||
|
return botResponse;
|
||||||
}, cancellationToken);
|
}, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
namespace DiscordChatExporter.Core.Discord;
|
namespace DiscordChatExporter.Core.Discord;
|
||||||
|
|
||||||
public enum AuthTokenKind
|
public enum TokenKind
|
||||||
{
|
{
|
||||||
|
Unknown,
|
||||||
User,
|
User,
|
||||||
Bot
|
Bot
|
||||||
}
|
}
|
||||||
|
|
@ -17,8 +17,6 @@ public class ChannelExporter
|
||||||
|
|
||||||
public ChannelExporter(DiscordClient discord) => _discord = discord;
|
public ChannelExporter(DiscordClient discord) => _discord = discord;
|
||||||
|
|
||||||
public ChannelExporter(AuthToken token) : this(new DiscordClient(token)) {}
|
|
||||||
|
|
||||||
public async ValueTask ExportChannelAsync(
|
public async ValueTask ExportChannelAsync(
|
||||||
ExportRequest request,
|
ExportRequest request,
|
||||||
IProgress<double>? progress = null,
|
IProgress<double>? progress = null,
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using DiscordChatExporter.Core.Discord;
|
using DiscordChatExporter.Core.Exporting;
|
||||||
using DiscordChatExporter.Core.Exporting;
|
|
||||||
using Tyrrrz.Settings;
|
using Tyrrrz.Settings;
|
||||||
|
|
||||||
namespace DiscordChatExporter.Gui.Services;
|
namespace DiscordChatExporter.Gui.Services;
|
||||||
|
|
@ -18,7 +17,7 @@ public class SettingsService : SettingsManager
|
||||||
|
|
||||||
public bool ShouldReuseMedia { get; set; }
|
public bool ShouldReuseMedia { get; set; }
|
||||||
|
|
||||||
public AuthToken? LastToken { get; set; }
|
public string? LastToken { get; set; }
|
||||||
|
|
||||||
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
public ExportFormat LastExportFormat { get; set; } = ExportFormat.HtmlDark;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ public class RootViewModel : Screen
|
||||||
private readonly SettingsService _settingsService;
|
private readonly SettingsService _settingsService;
|
||||||
private readonly UpdateService _updateService;
|
private readonly UpdateService _updateService;
|
||||||
|
|
||||||
|
private DiscordClient? _discord;
|
||||||
|
|
||||||
public ISnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
|
public ISnackbarMessageQueue Notifications { get; } = new SnackbarMessageQueue(TimeSpan.FromSeconds(5));
|
||||||
|
|
||||||
public IProgressManager ProgressManager { get; } = new ProgressManager();
|
public IProgressManager ProgressManager { get; } = new ProgressManager();
|
||||||
|
|
@ -33,9 +35,7 @@ public class RootViewModel : Screen
|
||||||
|
|
||||||
public bool IsProgressIndeterminate { get; private set; }
|
public bool IsProgressIndeterminate { get; private set; }
|
||||||
|
|
||||||
public bool IsBotToken { get; set; }
|
public string? Token { get; set; }
|
||||||
|
|
||||||
public string? TokenValue { get; set; }
|
|
||||||
|
|
||||||
private IReadOnlyDictionary<Guild, IReadOnlyList<Channel>>? GuildChannelMap { get; set; }
|
private IReadOnlyDictionary<Guild, IReadOnlyList<Channel>>? GuildChannelMap { get; set; }
|
||||||
|
|
||||||
|
|
@ -111,8 +111,7 @@ public class RootViewModel : Screen
|
||||||
|
|
||||||
if (_settingsService.LastToken is not null)
|
if (_settingsService.LastToken is not null)
|
||||||
{
|
{
|
||||||
IsBotToken = _settingsService.LastToken.Kind == AuthTokenKind.Bot;
|
Token = _settingsService.LastToken;
|
||||||
TokenValue = _settingsService.LastToken.Value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_settingsService.IsDarkModeEnabled)
|
if (_settingsService.IsDarkModeEnabled)
|
||||||
|
|
@ -144,7 +143,7 @@ public class RootViewModel : Screen
|
||||||
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
|
public void ShowHelp() => ProcessEx.StartShellExecute(App.GitHubProjectWikiUrl);
|
||||||
|
|
||||||
public bool CanPopulateGuildsAndChannels =>
|
public bool CanPopulateGuildsAndChannels =>
|
||||||
!IsBusy && !string.IsNullOrWhiteSpace(TokenValue);
|
!IsBusy && !string.IsNullOrWhiteSpace(Token);
|
||||||
|
|
||||||
public async void PopulateGuildsAndChannels()
|
public async void PopulateGuildsAndChannels()
|
||||||
{
|
{
|
||||||
|
|
@ -152,15 +151,10 @@ public class RootViewModel : Screen
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var tokenValue = TokenValue?.Trim('"', ' ');
|
var token = Token?.Trim('"', ' ');
|
||||||
if (string.IsNullOrWhiteSpace(tokenValue))
|
if (string.IsNullOrWhiteSpace(token))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var token = new AuthToken(
|
|
||||||
IsBotToken ? AuthTokenKind.Bot : AuthTokenKind.User,
|
|
||||||
tokenValue
|
|
||||||
);
|
|
||||||
|
|
||||||
_settingsService.LastToken = token;
|
_settingsService.LastToken = token;
|
||||||
|
|
||||||
var discord = new DiscordClient(token);
|
var discord = new DiscordClient(token);
|
||||||
|
|
@ -172,6 +166,7 @@ public class RootViewModel : Screen
|
||||||
guildChannelMap[guild] = channels.Where(c => c.IsTextChannel).ToArray();
|
guildChannelMap[guild] = channels.Where(c => c.IsTextChannel).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_discord = discord;
|
||||||
GuildChannelMap = guildChannelMap;
|
GuildChannelMap = guildChannelMap;
|
||||||
SelectedGuild = guildChannelMap.Keys.FirstOrDefault();
|
SelectedGuild = guildChannelMap.Keys.FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
@ -191,21 +186,24 @@ public class RootViewModel : Screen
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanExportChannels =>
|
public bool CanExportChannels =>
|
||||||
!IsBusy && SelectedGuild is not null && SelectedChannels is not null && SelectedChannels.Any();
|
!IsBusy &&
|
||||||
|
_discord is not null &&
|
||||||
|
SelectedGuild is not null &&
|
||||||
|
SelectedChannels is not null &&
|
||||||
|
SelectedChannels.Any();
|
||||||
|
|
||||||
public async void ExportChannels()
|
public async void ExportChannels()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var token = _settingsService.LastToken;
|
if (_discord is null || SelectedGuild is null || SelectedChannels is null || !SelectedChannels.Any())
|
||||||
if (token is null || SelectedGuild is null || SelectedChannels is null || !SelectedChannels.Any())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
|
var dialog = _viewModelFactory.CreateExportSetupViewModel(SelectedGuild, SelectedChannels);
|
||||||
if (await _dialogManager.ShowDialogAsync(dialog) != true)
|
if (await _dialogManager.ShowDialogAsync(dialog) != true)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var exporter = new ChannelExporter(token);
|
var exporter = new ChannelExporter(_discord);
|
||||||
|
|
||||||
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
|
var operations = ProgressManager.CreateOperations(dialog.Channels!.Count);
|
||||||
var successfulExportCount = 0;
|
var successfulExportCount = 0;
|
||||||
|
|
|
||||||
|
|
@ -64,39 +64,28 @@
|
||||||
<ColumnDefinition Width="Auto" />
|
<ColumnDefinition Width="Auto" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<!-- Token type -->
|
<!-- Token icon -->
|
||||||
<ToggleButton
|
<materialDesign:PackIcon
|
||||||
Grid.Column="0"
|
Grid.Column="0"
|
||||||
Margin="6"
|
Width="24"
|
||||||
IsChecked="{Binding IsBotToken}"
|
Height="24"
|
||||||
Style="{DynamicResource MaterialDesignFlatActionToggleButton}"
|
Margin="8"
|
||||||
ToolTip="Switch between user token and bot token">
|
VerticalAlignment="Center"
|
||||||
<ToggleButton.Content>
|
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||||
<materialDesign:PackIcon
|
Kind="Password" />
|
||||||
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 -->
|
<!-- Token value -->
|
||||||
<TextBox
|
<TextBox
|
||||||
x:Name="TokenValueTextBox"
|
x:Name="TokenValueTextBox"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
Margin="2,6,6,7"
|
Margin="0,6,6,8"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
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 TokenValue, UpdateSourceTrigger=PropertyChanged}" />
|
Text="{Binding Token, UpdateSourceTrigger=PropertyChanged}" />
|
||||||
|
|
||||||
<!-- Pull data button -->
|
<!-- Pull data button -->
|
||||||
<Button
|
<Button
|
||||||
|
|
@ -152,11 +141,22 @@
|
||||||
</Style>
|
</Style>
|
||||||
</Grid.Resources>
|
</Grid.Resources>
|
||||||
<!-- Placeholder / usage instructions -->
|
<!-- Placeholder / usage instructions -->
|
||||||
<Grid Margin="32,32,8,8" Visibility="{Binding AvailableGuilds, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}">
|
<Grid Visibility="{Binding AvailableGuilds, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}">
|
||||||
<!-- For user token -->
|
<ScrollViewer HorizontalScrollBarVisibility="Disabled" VerticalScrollBarVisibility="Auto">
|
||||||
<StackPanel Visibility="{Binding IsBotToken, Converter={x:Static s:BoolToVisibilityConverter.InverseInstance}}">
|
<TextBlock Margin="32,16" FontSize="14">
|
||||||
<TextBlock FontSize="18" Text="Please provide your user token to authorize" />
|
<Run FontSize="18" Text="Please provide authentication token to continue" />
|
||||||
<TextBlock Margin="8,8,0,0" FontSize="14">
|
<LineBreak />
|
||||||
|
<LineBreak />
|
||||||
|
|
||||||
|
<!-- User token -->
|
||||||
|
<InlineUIContainer>
|
||||||
|
<materialDesign:PackIcon
|
||||||
|
Margin="1,0,0,-2"
|
||||||
|
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||||
|
Kind="Account" />
|
||||||
|
</InlineUIContainer>
|
||||||
|
<Run FontSize="16" Text="Authenticate using your personal account" />
|
||||||
|
<LineBreak />
|
||||||
<Run Text="1. Open Discord" />
|
<Run Text="1. Open Discord" />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="2. Press" />
|
<Run Text="2. Press" />
|
||||||
|
|
@ -189,35 +189,20 @@
|
||||||
<Run Text="8. Copy the value of the" />
|
<Run Text="8. Copy the value of the" />
|
||||||
<Run FontWeight="SemiBold" Text="token" />
|
<Run FontWeight="SemiBold" Text="token" />
|
||||||
<Run Text="key" />
|
<Run Text="key" />
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Margin="0,24,0,0" FontSize="14">
|
|
||||||
<Run Text="Automating user accounts is technically against TOS, use at your own risk." />
|
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="To authorize using bot token instead, click" />
|
<Run Text="* Automating user accounts is technically against TOS, use at your own risk!" />
|
||||||
|
<LineBreak />
|
||||||
|
<LineBreak />
|
||||||
|
|
||||||
|
<!-- Bot token -->
|
||||||
<InlineUIContainer>
|
<InlineUIContainer>
|
||||||
<materialDesign:PackIcon
|
<materialDesign:PackIcon
|
||||||
Margin="1,0,0,-3"
|
Margin="1,0,0,-2"
|
||||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
||||||
Kind="Account" />
|
Kind="Robot" />
|
||||||
</InlineUIContainer>
|
</InlineUIContainer>
|
||||||
<Run Text="in the text box above." />
|
<Run FontSize="16" Text="Authenticate as a bot" />
|
||||||
</TextBlock>
|
<LineBreak />
|
||||||
<TextBlock Margin="0,24,0,0" FontSize="14">
|
|
||||||
<Run Text="For more information, check out the" />
|
|
||||||
<Hyperlink Command="{s:Action ShowHelp}">wiki</Hyperlink><Run Text="." />
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- For bot token -->
|
|
||||||
<StackPanel Visibility="{Binding IsBotToken, Converter={x:Static s:BoolToVisibilityConverter.Instance}}">
|
|
||||||
<TextBlock
|
|
||||||
FontSize="18"
|
|
||||||
FontWeight="Light"
|
|
||||||
Text="Please provide your bot token to authorize" />
|
|
||||||
<TextBlock
|
|
||||||
Margin="8,8,0,0"
|
|
||||||
FontSize="14"
|
|
||||||
FontWeight="Light">
|
|
||||||
<Run Text="1. Open Discord developer portal" />
|
<Run Text="1. Open Discord developer portal" />
|
||||||
<LineBreak />
|
<LineBreak />
|
||||||
<Run Text="2. Open your application's settings" />
|
<Run Text="2. Open your application's settings" />
|
||||||
|
|
@ -230,22 +215,13 @@
|
||||||
<Run FontWeight="SemiBold" Text="Token" />
|
<Run FontWeight="SemiBold" Text="Token" />
|
||||||
<Run Text="click" />
|
<Run Text="click" />
|
||||||
<Run FontWeight="SemiBold" Text="Copy" />
|
<Run FontWeight="SemiBold" Text="Copy" />
|
||||||
|
<LineBreak />
|
||||||
|
<LineBreak />
|
||||||
|
|
||||||
|
<Run FontSize="16" Text="If you have questions or issues, please refer to the" />
|
||||||
|
<Hyperlink Command="{s:Action ShowHelp}" FontSize="16">wiki</Hyperlink><Run FontSize="16" Text="." />
|
||||||
</TextBlock>
|
</TextBlock>
|
||||||
<TextBlock Margin="0,24,0,0" FontSize="14">
|
</ScrollViewer>
|
||||||
<Run Text="To authorize using user token instead, click" />
|
|
||||||
<InlineUIContainer>
|
|
||||||
<materialDesign:PackIcon
|
|
||||||
Margin="1,0,0,-1"
|
|
||||||
Foreground="{DynamicResource PrimaryHueMidBrush}"
|
|
||||||
Kind="Robot" />
|
|
||||||
</InlineUIContainer>
|
|
||||||
<Run Text="in the text box above." />
|
|
||||||
</TextBlock>
|
|
||||||
<TextBlock Margin="0,24,0,0" FontSize="14">
|
|
||||||
<Run Text="For more information, check out the" />
|
|
||||||
<Hyperlink Command="{s:Action ShowHelp}">wiki</Hyperlink><Run Text="." />
|
|
||||||
</TextBlock>
|
|
||||||
</StackPanel>
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<!-- Guilds and channels -->
|
<!-- Guilds and channels -->
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue