Compare commits

..

5 Commits

Author SHA1 Message Date
chylex
8002236c1f
Release v33.2 (app) 2022-02-27 17:09:51 +01:00
chylex
c4fe6c4391
Move app version info out of .csproj and into a single linked .cs file 2022-02-27 16:20:39 +01:00
chylex
ebfe972a98
Update uses of Avalonia APIs & safeguard clipboard code 2022-02-27 15:29:54 +01:00
chylex
20aac4c47a
Update Avalonia to 0.10.12 2022-02-27 15:21:38 +01:00
chylex
35308e0995
Add option to re-enable Ctrl+Shift+I in the Discord app 2022-02-27 15:08:56 +01:00
12 changed files with 276 additions and 44 deletions

View File

@ -4,11 +4,11 @@ using Avalonia.Data.Converters;
namespace DHT.Desktop.Common {
sealed class NumberValueConverter : IValueConverter {
public object Convert(object value, Type targetType, object parameter, CultureInfo culture) {
public object Convert(object? value, Type targetType, object? parameter, CultureInfo culture) {
return string.Format(Program.Culture, "{0:n0}", value);
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) {
public object ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) {
throw new NotSupportedException();
}
}

View File

@ -12,22 +12,24 @@
<CheckForOverflowUnderflow>true</CheckForOverflowUnderflow>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
<AssemblyName>DiscordHistoryTracker</AssemblyName>
<Version>33.1.0.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<PackageVersion>$(Version)</PackageVersion>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>none</DebugType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.10.10" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.10" />
<PackageReference Include="Avalonia" Version="0.10.12" />
<PackageReference Include="Avalonia.Desktop" Version="0.10.12" />
<ProjectReference Include="..\Server\Server.csproj" />
</ItemGroup>
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.10" />
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.12" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Version.cs" Link="Version.cs" />
</ItemGroup>
<ItemGroup>
<Compile Update="Windows\MainWindow.axaml.cs">

View File

@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.InteropServices;
using System.Text.Json;
using System.Threading.Tasks;
using DHT.Utils.Logging;
using static System.Environment.SpecialFolder;
using static System.Environment.SpecialFolderOption;
namespace DHT.Desktop.Discord {
static class DiscordAppSettings {
private static readonly Log Log = Log.ForType(typeof(DiscordAppSettings));
private const string JsonKeyDevTools = "DANGEROUS_ENABLE_DEVTOOLS_ONLY_ENABLE_IF_YOU_KNOW_WHAT_YOURE_DOING";
public static string JsonFilePath { get; }
private static string JsonBackupFilePath { get; }
[SuppressMessage("ReSharper", "ConvertIfStatementToConditionalTernaryExpression")]
static DiscordAppSettings() {
string rootFolder;
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) {
rootFolder = Path.Combine(Environment.GetFolderPath(ApplicationData, DoNotVerify), "Discord");
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) {
rootFolder = Path.Combine(Environment.GetFolderPath(UserProfile, DoNotVerify), "Library", "Application Support", "Discord");
}
else {
rootFolder = Path.Combine(Environment.GetFolderPath(ApplicationData, DoNotVerify), "discord");
}
JsonFilePath = Path.Combine(rootFolder, "settings.json");
JsonBackupFilePath = JsonFilePath + ".bak";
}
public static async Task<bool?> AreDevToolsEnabled() {
try {
return AreDevToolsEnabled(await ReadSettingsJson());
} catch (Exception) {
return null;
}
}
private static bool AreDevToolsEnabled(Dictionary<string, object?> json) {
return json.TryGetValue(JsonKeyDevTools, out var value) && value is JsonElement { ValueKind: JsonValueKind.True };
}
public static async Task<SettingsJsonResult> ConfigureDevTools(bool enable) {
Dictionary<string, object?> json;
try {
json = await ReadSettingsJson();
} catch (FileNotFoundException) {
return SettingsJsonResult.FileNotFound;
} catch (JsonException) {
return SettingsJsonResult.InvalidJson;
} catch (Exception e) {
Log.Error(e);
return SettingsJsonResult.ReadError;
}
if (enable == AreDevToolsEnabled(json)) {
return SettingsJsonResult.AlreadySet;
}
if (enable) {
json[JsonKeyDevTools] = true;
}
else {
json.Remove(JsonKeyDevTools);
}
try {
if (!File.Exists(JsonBackupFilePath)) {
File.Copy(JsonFilePath, JsonBackupFilePath);
}
await WriteSettingsJson(json);
} catch (Exception e) {
Log.Error("An error occurred when writing settings file.");
Log.Error(e);
if (File.Exists(JsonBackupFilePath)) {
try {
File.Move(JsonBackupFilePath, JsonFilePath, true);
Log.Info("Restored settings file from backup.");
} catch (Exception e2) {
Log.Error("Cannot restore settings file from backup.");
Log.Error(e2);
}
}
return SettingsJsonResult.WriteError;
}
try {
File.Delete(JsonBackupFilePath);
} catch (Exception e) {
Log.Error("Cannot delete backup file.");
Log.Error(e);
}
return SettingsJsonResult.Success;
}
private static async Task<Dictionary<string, object?>> ReadSettingsJson() {
await using var stream = new FileStream(JsonFilePath, FileMode.Open, FileAccess.Read, FileShare.Read);
return await JsonSerializer.DeserializeAsync<Dictionary<string, object?>?>(stream) ?? throw new JsonException();
}
private static async Task WriteSettingsJson(Dictionary<string, object?> json) {
await using var stream = new FileStream(JsonFilePath, FileMode.Truncate, FileAccess.Write, FileShare.None);
await JsonSerializer.SerializeAsync(stream, json, new JsonSerializerOptions { WriteIndented = true });
}
}
}

View File

@ -0,0 +1,10 @@
namespace DHT.Desktop.Discord {
enum SettingsJsonResult {
Success,
AlreadySet,
FileNotFound,
ReadError,
InvalidJson,
WriteError
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using DHT.Desktop.Main.Controls;
using DHT.Desktop.Main.Pages;
@ -45,8 +46,8 @@ namespace DHT.Desktop.Main {
StatusBarModel.CurrentStatus = ServerLauncher.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped;
}
public void Initialize() {
TrackingPageModel.Initialize();
public async Task Initialize() {
await TrackingPageModel.Initialize();
}
private void TrackingPageModelOnServerStatusChanged(object? sender, StatusBarModel.Status e) {

View File

@ -69,7 +69,7 @@ namespace DHT.Desktop.Main {
}
}
private void WelcomeScreenModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
private async void WelcomeScreenModelOnPropertyChanged(object? sender, PropertyChangedEventArgs e) {
if (e.PropertyName == nameof(WelcomeScreenModel.Db)) {
if (MainContentScreenModel != null) {
MainContentScreenModel.DatabaseClosed -= MainContentScreenModelOnDatabaseClosed;
@ -87,7 +87,7 @@ namespace DHT.Desktop.Main {
else {
Title = Path.GetFileName(db.Path) + " - " + DefaultTitle;
MainContentScreenModel = new MainContentScreenModel(window, db);
MainContentScreenModel.Initialize();
await MainContentScreenModel.Initialize();
MainContentScreenModel.DatabaseClosed += MainContentScreenModelOnDatabaseClosed;
MainContentScreen = new MainContentScreen { DataContext = MainContentScreenModel };
OnPropertyChanged(nameof(MainContentScreen));

View File

@ -23,13 +23,13 @@
<StackPanel Spacing="10">
<TextBlock TextWrapping="Wrap">
To start tracking messages, copy the tracking script and paste it into the console of either the Discord app (Ctrl+Shift+I), or your browser with Discord open.
To start tracking messages, copy the tracking script and paste it into the console of either the Discord app, or your browser. The console is usually opened by pressing Ctrl+Shift+I.
</TextBlock>
<StackPanel Orientation="Horizontal" Spacing="10">
<StackPanel DockPanel.Dock="Left" Orientation="Horizontal" Spacing="10">
<Button x:Name="CopyTrackingScript" Click="CopyTrackingScriptButton_OnClick">Copy Tracking Script</Button>
<Button Command="{Binding OnClickToggleButton}" Content="{Binding ToggleButtonText}" IsEnabled="{Binding IsToggleButtonEnabled}" />
<Button Command="{Binding OnClickToggleTrackingButton}" Content="{Binding ToggleTrackingButtonText}" IsEnabled="{Binding IsToggleButtonEnabled}" />
</StackPanel>
<Expander Header="Advanced Settings">
<Expander Header="Advanced Tracking Settings">
<StackPanel Spacing="10">
<TextBlock TextWrapping="Wrap">
The following settings determine how the tracking script communicates with this application. If you change them, you will have to copy and apply the tracking script again.
@ -55,6 +55,10 @@
</StackPanel>
</StackPanel>
</Expander>
<TextBlock TextWrapping="Wrap" Margin="0 15 0 0">
By default, the Discord app does not allow opening the console. The button below will change a hidden setting in the Discord app that controls whether the Ctrl+Shift+I shortcut is enabled.
</TextBlock>
<Button DockPanel.Dock="Right" Command="{Binding OnClickToggleAppDevTools}" Content="{Binding ToggleAppDevToolsButtonText}" IsEnabled="{Binding IsToggleAppDevToolsButtonEnabled}" />
</StackPanel>
</UserControl>

View File

@ -24,9 +24,7 @@ namespace DHT.Desktop.Main.Pages {
var originalText = button.Content;
button.MinWidth = button.Bounds.Width;
await model.OnClickCopyTrackingScript();
if (!isCopyingScript) {
if (await model.OnClickCopyTrackingScript() && !isCopyingScript) {
isCopyingScript = true;
button.Content = "Script Copied!";

View File

@ -4,6 +4,7 @@ using System.Web;
using Avalonia;
using Avalonia.Controls;
using DHT.Desktop.Dialogs.Message;
using DHT.Desktop.Discord;
using DHT.Desktop.Main.Controls;
using DHT.Server.Database;
using DHT.Server.Service;
@ -40,14 +41,36 @@ namespace DHT.Desktop.Main.Pages {
public bool HasMadeChanges => ServerPort != InputPort || ServerToken != InputToken;
private bool isToggleButtonEnabled = true;
private bool isToggleTrackingButtonEnabled = true;
public bool IsToggleButtonEnabled {
get => isToggleButtonEnabled;
set => Change(ref isToggleButtonEnabled, value);
get => isToggleTrackingButtonEnabled;
set => Change(ref isToggleTrackingButtonEnabled, value);
}
public string ToggleButtonText => ServerLauncher.IsRunning ? "Pause Tracking" : "Resume Tracking";
public string ToggleTrackingButtonText => ServerLauncher.IsRunning ? "Pause Tracking" : "Resume Tracking";
private bool areDevToolsEnabled;
private bool AreDevToolsEnabled {
get => areDevToolsEnabled;
set {
Change(ref areDevToolsEnabled, value);
OnPropertyChanged(nameof(ToggleAppDevToolsButtonText));
}
}
public bool IsToggleAppDevToolsButtonEnabled { get; private set; } = true;
public string ToggleAppDevToolsButtonText {
get {
if (!IsToggleAppDevToolsButtonEnabled) {
return "Unavailable";
}
return AreDevToolsEnabled ? "Disable Ctrl+Shift+I" : "Enable Ctrl+Shift+I";
}
}
public event EventHandler<StatusBarModel.Status>? ServerStatusChanged;
@ -62,7 +85,7 @@ namespace DHT.Desktop.Main.Pages {
this.db = db;
}
public void Initialize() {
public async Task Initialize() {
ServerLauncher.ServerStatusChanged += ServerLauncherOnServerStatusChanged;
ServerLauncher.ServerManagementExceptionCaught += ServerLauncherOnServerManagementExceptionCaught;
@ -70,6 +93,15 @@ namespace DHT.Desktop.Main.Pages {
string token = ServerToken;
ServerLauncher.Relaunch(port, token, db);
}
bool? devToolsEnabled = await DiscordAppSettings.AreDevToolsEnabled();
if (devToolsEnabled.HasValue) {
AreDevToolsEnabled = devToolsEnabled.Value;
}
else {
IsToggleAppDevToolsButtonEnabled = false;
OnPropertyChanged(nameof(IsToggleAppDevToolsButtonEnabled));
}
}
public void Dispose() {
@ -78,6 +110,17 @@ namespace DHT.Desktop.Main.Pages {
ServerLauncher.Stop();
}
private void ServerLauncherOnServerStatusChanged(object? sender, EventArgs e) {
ServerStatusChanged?.Invoke(this, ServerLauncher.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped);
OnPropertyChanged(nameof(ToggleTrackingButtonText));
IsToggleButtonEnabled = true;
}
private async void ServerLauncherOnServerManagementExceptionCaught(object? sender, Exception ex) {
Log.Error(ex);
await Dialog.ShowOk(window, "Server Error", ex.Message);
}
private async Task<bool> StartServer() {
if (!int.TryParse(InputPort, out int port) || port is < 0 or > 65535) {
await Dialog.ShowOk(window, "Invalid Port", "Port must be a number between 0 and 65535.");
@ -96,7 +139,7 @@ namespace DHT.Desktop.Main.Pages {
ServerLauncher.Stop();
}
public async Task<bool> OnClickToggleButton() {
public async Task<bool> OnClickToggleTrackingButton() {
if (ServerLauncher.IsRunning) {
StopServer();
return true;
@ -106,7 +149,7 @@ namespace DHT.Desktop.Main.Pages {
}
}
public async Task OnClickCopyTrackingScript() {
public async Task<bool> OnClickCopyTrackingScript() {
string bootstrap = await Resources.ReadTextAsync("Tracker/bootstrap.js");
string script = bootstrap.Replace("= 0; /*[PORT]*/", "= " + ServerPort + ";")
.Replace("/*[TOKEN]*/", HttpUtility.JavaScriptStringEncode(ServerToken))
@ -114,7 +157,19 @@ namespace DHT.Desktop.Main.Pages {
.Replace("/*[CSS-CONTROLLER]*/", await Resources.ReadTextAsync("Tracker/styles/controller.css"))
.Replace("/*[CSS-SETTINGS]*/", await Resources.ReadTextAsync("Tracker/styles/settings.css"));
await Application.Current.Clipboard.SetTextAsync(script);
var clipboard = Application.Current?.Clipboard;
if (clipboard == null) {
await Dialog.ShowOk(window, "Copy Tracking Script", "Clipboard is not available on this system.");
return false;
}
try {
await clipboard.SetTextAsync(script);
return true;
} catch {
await Dialog.ShowOk(window, "Copy Tracking Script", "An error occurred while copying to clipboard.");
return false;
}
}
public void OnClickRandomizeToken() {
@ -134,15 +189,42 @@ namespace DHT.Desktop.Main.Pages {
InputToken = ServerToken;
}
private void ServerLauncherOnServerStatusChanged(object? sender, EventArgs e) {
ServerStatusChanged?.Invoke(this, ServerLauncher.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped);
OnPropertyChanged(nameof(ToggleButtonText));
IsToggleButtonEnabled = true;
}
public async void OnClickToggleAppDevTools() {
const string DialogTitle = "Discord App Settings File";
private async void ServerLauncherOnServerManagementExceptionCaught(object? sender, Exception ex) {
Log.Error(ex);
await Dialog.ShowOk(window, "Server Error", ex.Message);
bool oldState = AreDevToolsEnabled;
bool newState = !oldState;
switch (await DiscordAppSettings.ConfigureDevTools(newState)) {
case SettingsJsonResult.Success:
AreDevToolsEnabled = newState;
await Dialog.ShowOk(window, DialogTitle, "Ctrl+Shift+I was " + (newState ? "enabled." : "disabled.") + " Restart the Discord app for the change to take effect.");
break;
case SettingsJsonResult.AlreadySet:
await Dialog.ShowOk(window, DialogTitle, "Ctrl+Shift+I is already " + (newState ? "enabled." : "disabled."));
AreDevToolsEnabled = newState;
break;
case SettingsJsonResult.FileNotFound:
await Dialog.ShowOk(window, DialogTitle, "Cannot find the settings file:\n" + DiscordAppSettings.JsonFilePath);
break;
case SettingsJsonResult.ReadError:
await Dialog.ShowOk(window, DialogTitle, "Cannot read the settings file:\n" + DiscordAppSettings.JsonFilePath);
break;
case SettingsJsonResult.InvalidJson:
await Dialog.ShowOk(window, DialogTitle, "Unknown format of the settings file:\n" + DiscordAppSettings.JsonFilePath);
break;
case SettingsJsonResult.WriteError:
await Dialog.ShowOk(window, DialogTitle, "Cannot save the settings file:\n" + DiscordAppSettings.JsonFilePath);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
}
}

View File

@ -7,10 +7,9 @@
<Authors>chylex</Authors>
<Company>DiscordHistoryTracker</Company>
<Product>DiscordHistoryTrackerServer</Product>
<Version>33.1.0.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<PackageVersion>$(Version)</PackageVersion>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
@ -25,4 +24,7 @@
<ItemGroup>
<ProjectReference Include="..\Utils\Utils.csproj" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Version.cs" Link="Version.cs" />
</ItemGroup>
</Project>

View File

@ -7,10 +7,9 @@
<Authors>chylex</Authors>
<Company>DiscordHistoryTracker</Company>
<Product>DiscordHistoryTrackerUtils</Product>
<Version>33.1.0.0</Version>
<AssemblyVersion>$(Version)</AssemblyVersion>
<FileVersion>$(Version)</FileVersion>
<PackageVersion>$(Version)</PackageVersion>
<GenerateAssemblyVersionAttribute>false</GenerateAssemblyVersionAttribute>
<GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
<GenerateAssemblyInformationalVersionAttribute>false</GenerateAssemblyInformationalVersionAttribute>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DebugSymbols>true</DebugSymbols>
@ -19,4 +18,7 @@
<ItemGroup>
<PackageReference Include="JetBrains.Annotations" Version="10.3.0" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\Version.cs" Link="Version.cs" />
</ItemGroup>
</Project>

12
app/Version.cs Normal file
View File

@ -0,0 +1,12 @@
using System.Reflection;
using DHT.Utils;
[assembly: AssemblyVersion(Version.Tag)]
[assembly: AssemblyFileVersion(Version.Tag)]
[assembly: AssemblyInformationalVersion(Version.Tag)]
namespace DHT.Utils {
static class Version {
public const string Tag = "32.2.0.0";
}
}