1
0
mirror of https://github.com/chylex/TweetDuck.git synced 2024-10-18 03:42:44 +02:00

Compare commits

..

No commits in common. "b1328e5b1f12cdcf7d06301bc958b2e9fda3be3f" and "b58c8f65feea50fd9b6297b005bca70372977ef0" have entirely different histories.

227 changed files with 1369 additions and 1788 deletions

View File

@ -11,8 +11,8 @@
<option name="PROJECT_EXE_PATH_TRACKING" value="1" /> <option name="PROJECT_EXE_PATH_TRACKING" value="1" />
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" /> <option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" /> <option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
<option name="PROJECT_KIND" value="DotNetCore" /> <option name="PROJECT_KIND" value="Console" />
<option name="PROJECT_TFM" value="net6.0-windows7.0" /> <option name="PROJECT_TFM" value=".NETFramework,Version=v4.7.2" />
<method v="2"> <method v="2">
<option name="Build" /> <option name="Build" />
</method> </method>

View File

@ -43,14 +43,14 @@ Download links and system requirements are on the [official website](https://twe
Building TweetDuck for Windows requires at minimum [Visual Studio 2019](https://visualstudio.microsoft.com/downloads) and Windows 7. Before opening the solution, open Visual Studio Installer and make sure you have the following Visual Studio workloads and components installed: Building TweetDuck for Windows requires at minimum [Visual Studio 2019](https://visualstudio.microsoft.com/downloads) and Windows 7. Before opening the solution, open Visual Studio Installer and make sure you have the following Visual Studio workloads and components installed:
* **.NET desktop development** * **.NET desktop development**
* .NET SDK * .NET Framework 4.7.2 targeting pack
* F# desktop language support * F# desktop language support
* **Desktop development with C++** * **Desktop development with C++**
* MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.20 / Latest) * MSVC v142 - VS 2019 C++ x64/x86 build tools (v14.20 / Latest)
In the **Installation details** panel, you can expand the workloads you selected, and uncheck any components that are not listed above to save space. You may uncheck the .NET SDK component if you installed the [.NET 6 SDK](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) directly. In the **Installation details** panel, you can expand the workloads you selected, and uncheck any components that are not listed above to save space.
Building TweetDuck for Linux requires [.NET 6 SDK](https://docs.microsoft.com/en-us/dotnet/core/install/linux). The Linux project has its own solution file in the `linux/` folder. Building TweetDuck for Linux requires [.NET 5](https://docs.microsoft.com/en-us/dotnet/core/install/linux). The Linux project has its own solution file in the `linux/` folder.
### Editors ### Editors
@ -66,15 +66,15 @@ Icons and logos were designed in [Affinity Designer](https://affinity.serif.com/
> If you don't want to build installers using the existing foundations, you can skip this section. > If you don't want to build installers using the existing foundations, you can skip this section.
Official Windows installers are built using [InnoSetup](https://jrsoftware.org/isinfo.php) and [Inno Download Plugin](https://mitrichsoftware.wordpress.com/inno-setup-tools/inno-download-plugin/), specifically: Official Windows installers are built using [InnoSetup](https://jrsoftware.org/isinfo.php) and [Inno Download Plugin](https://mitrichsoftware.wordpress.com/inno-setup-tools/inno-download-plugin/), specifically:
* [InnoSetup 6.2.1](https://files.jrsoftware.org/is/6/innosetup-6.2.1.exe) * [InnoSetup 5.6.1](https://files.jrsoftware.org/is/5/innosetup-5.6.1.exe) with Preprocessor support
* [Inno Download Plugin 1.5.1](https://drive.google.com/folderview?id=0Bzw1xBVt0mokSXZrUEFIanV4azA&usp=sharing#list) * [Inno Download Plugin 1.5.0](https://drive.google.com/folderview?id=0Bzw1xBVt0mokSXZrUEFIanV4azA&usp=sharing#list)
During installation, the download plugin will ask whether to add its include path to `ISPPBuiltins.iss`. Note that this option does not work with InnoSetup 6, so TweetDuck installers don't need it. When installing InnoSetup, you can choose to include Inno Script Studio which I recommend for editing and testing installer configuration files in the `bld` folder (`.iss` extension).
Scripts for building installers require the `PATH` environment variable to include the InnoSetup installation folder. You can either edit `PATH` manually, or use a program like [Rapid Environment Editor](https://www.rapidee.com/en/about) to simplify the process. For example, this is the installation folder I added to `PATH` under **User variables**: Scripts for building installers require the `PATH` environment variable to include the InnoSetup installation folder. You can either edit `PATH` manually, or use a program like [Rapid Environment Editor](https://www.rapidee.com/en/about) to simplify the process. For example, this is the installation folder I added to `PATH` under **User variables**:
* `C:\Program Files (x86)\Inno Setup 6` * `C:\Program Files (x86)\Inno Setup 5`
You may need to restart Visual Studio or Rider after changing `PATH` for the change to take place. You may need to restart Visual Studio after changing `PATH` for the change to take place.
## Solution Overview ## Solution Overview
@ -84,13 +84,13 @@ On Windows, TweetDuck uses the [CefSharp](https://github.com/cefsharp/CefSharp/)
On Linux, TweetDuck uses the [ChromiumGtk](https://github.com/lunixo/ChromiumGtk) library, which combines [CefGlue](https://gitlab.com/xiliumhq/chromiumembedded/cefglue) for the browser component and [GtkSharp](https://github.com/GtkSharp/GtkSharp) for the GUI. On Linux, TweetDuck uses the [ChromiumGtk](https://github.com/lunixo/ChromiumGtk) library, which combines [CefGlue](https://gitlab.com/xiliumhq/chromiumembedded/cefglue) for the browser component and [GtkSharp](https://github.com/GtkSharp/GtkSharp) for the GUI.
The solution contains several C# projects for executables and libraries, and F# projects for automated tests. All projects target `.NET 6` and either `C# 10` or `F#`. The solution contains several C# projects for executables and libraries, and F# projects for automated tests.
Projects are organized into folders: Projects are organized into folders:
* Windows projects are in the `windows/` folder * Windows projects are in the `windows/` folder, and target `.NET Framework 4.7.2` + `C# 8.0`
* Linux projects are in the `linux/` folder * Linux projects are in the `linux/` folder, and target `.NET 5` + `C#`
* Libraries (`TweetLib.*`) are in the `lib/` folder * Libraries (`TweetLib.*`) are in the `lib/` folder, and target `.NET Standard 2.0` + `C# 9.0`
* Tests (`TweetTest.*`) are also in the `lib/` folder * Tests (`TweetTest.*`) are also in the `lib/` folder, and target `.NET Framework 4.7.2` + `F#`
Here are a few things to keep in mind: Here are a few things to keep in mind:
* Executable projects have their entry points in `Program.cs` * Executable projects have their entry points in `Program.cs`
@ -130,7 +130,7 @@ Main Windows executable. It has a dependency on [CefSharp](https://github.com/ce
#### TweetDuck.Browser #### TweetDuck.Browser
Windows executable that hosts various Chromium processes. It has a dependency on [CefSharp](https://github.com/cefsharp/CefSharp/). Windows executable that hosts various Chromium processes. It depends on two specific DLLs from the [CefSharp](https://github.com/cefsharp/CefSharp/) package. After updating [CefSharp](https://github.com/cefsharp/CefSharp/), run the `windows/TweetDuck/Resources/PostCefUpdate.ps1` PowerShell script to update these dependencies to the new version.
#### TweetDuck.Video #### TweetDuck.Video
@ -142,10 +142,6 @@ By default, [CefSharp](https://github.com/cefsharp/CefSharp/) is not built with
Windows library that implements `TweetLib.Browser.CEF` using the [CefSharp](https://github.com/cefsharp/CefSharp/) library and Windows Forms. Windows library that implements `TweetLib.Browser.CEF` using the [CefSharp](https://github.com/cefsharp/CefSharp/) library and Windows Forms.
#### TweetLib.WinForms.Legacy
Windows library that re-adds some legacy Windows Forms components that were removed in .NET Core 3.1. The sources were taken from the [.NET Core 3.0 sources of Windows Forms](https://github.com/dotnet/winforms/tree/v3.0.2), and edited to remove unnecessary features.
### Linux Projects ### Linux Projects
#### TweetDuck #### TweetDuck

View File

@ -10,8 +10,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "windows\
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetImpl.CefSharp", "windows\TweetImpl.CefSharp\TweetImpl.CefSharp.csproj", "{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetImpl.CefSharp", "windows\TweetImpl.CefSharp\TweetImpl.CefSharp.csproj", "{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.WinForms.Legacy", "windows\TweetLib.WinForms.Legacy\TweetLib.WinForms.Legacy.csproj", "{B54E732A-4090-4DAA-9ABD-311368C17B68}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Communication", "lib\TweetLib.Communication\TweetLib.Communication.csproj", "{72473763-4B9D-4FB6-A923-9364B2680F06}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
@ -50,10 +48,6 @@ Global
{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Debug|x86.Build.0 = Debug|x86 {44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Debug|x86.Build.0 = Debug|x86
{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Release|x86.ActiveCfg = Release|x86 {44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Release|x86.ActiveCfg = Release|x86
{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Release|x86.Build.0 = Release|x86 {44DF3E2E-F465-4A31-8B43-F40FFFB018BA}.Release|x86.Build.0 = Release|x86
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Debug|x86.ActiveCfg = Debug|x86
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Debug|x86.Build.0 = Debug|x86
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Release|x86.ActiveCfg = Release|x86
{B54E732A-4090-4DAA-9ABD-311368C17B68}.Release|x86.Build.0 = Release|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86 {72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.ActiveCfg = Debug|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86 {72473763-4B9D-4FB6-A923-9364B2680F06}.Debug|x86.Build.0 = Debug|x86
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86 {72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86

View File

@ -6,6 +6,6 @@
namespace TweetDuck { namespace TweetDuck {
internal static class Version { internal static class Version {
public const string Tag = "1.23"; public const string Tag = "1.22.1";
} }
} }

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bld/Redist/concrt140.dll Normal file

Binary file not shown.

BIN
bld/Redist/msvcp140.dll Normal file

Binary file not shown.

BIN
bld/Redist/msvcp140_1.dll Normal file

Binary file not shown.

BIN
bld/Redist/msvcp140_2.dll Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
bld/Redist/ucrtbase.dll Normal file

Binary file not shown.

BIN
bld/Redist/vccorlib140.dll Normal file

Binary file not shown.

BIN
bld/Redist/vcruntime140.dll Normal file

Binary file not shown.

View File

@ -9,8 +9,6 @@
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe") #define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
[Setup] [Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06} AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
AppName={#MyAppName} AppName={#MyAppName}
@ -31,11 +29,13 @@ Uninstallable=TDIsUninstallable
UninstallDisplayName={#MyAppName} UninstallDisplayName={#MyAppName}
UninstallDisplayIcon={app}\{#MyAppExeName} UninstallDisplayIcon={app}\{#MyAppExeName}
Compression=lzma2/ultra Compression=lzma2/ultra
LZMADictionarySize=32768 LZMADictionarySize=15360
SolidCompression=yes SolidCompression=yes
InternalCompressLevel=normal InternalCompressLevel=normal
MinVersion=0,6.1 MinVersion=0,6.1
#include <idp.iss>
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@ -68,11 +68,20 @@ AdditionalTasks=Additional shortcuts and components:
var UpdatePath: String; var UpdatePath: String;
var VisitedTasksPage: Boolean; var VisitedTasksPage: Boolean;
{ Prepare installation variables. } function TDGetNetFrameworkVersion: Cardinal; forward;
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}') UpdatePath := ExpandConstant('{param:UPDATEPATH}')
VisitedTasksPage := False VisitedTasksPage := False
if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin
Result := False
Exit
end;
Result := True Result := True
end; end;
@ -131,3 +140,17 @@ function TDIsUninstallable: Boolean;
begin begin
Result := (UpdatePath = '') Result := (UpdatePath = '')
end; end;
{ Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal;
begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
begin
Result := FrameworkVersion
Exit
end;
Result := 0
end;

View File

@ -9,8 +9,6 @@
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe") #define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
[Setup] [Setup]
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06} AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
AppName={#MyAppName} Portable AppName={#MyAppName} Portable
@ -31,11 +29,13 @@ Uninstallable=no
UsePreviousAppDir=no UsePreviousAppDir=no
PrivilegesRequired=lowest PrivilegesRequired=lowest
Compression=lzma2/ultra Compression=lzma2/ultra
LZMADictionarySize=32768 LZMADictionarySize=15360
SolidCompression=yes SolidCompression=yes
InternalCompressLevel=normal InternalCompressLevel=normal
MinVersion=0,6.1 MinVersion=0,6.1
#include <idp.iss>
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@ -52,10 +52,19 @@ AdditionalTasks=Additional components:
[Code] [Code]
var UpdatePath: String; var UpdatePath: String;
{ Prepare installation variables. } function TDGetNetFrameworkVersion: Cardinal; forward;
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
UpdatePath := ExpandConstant('{param:UPDATEPATH}') UpdatePath := ExpandConstant('{param:UPDATEPATH}')
if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin
Result := False
Exit
end;
Result := True Result := True
end; end;
@ -93,3 +102,17 @@ begin
end; end;
end; end;
end; end;
{ Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal;
begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
begin
Result := FrameworkVersion
Exit
end;
Result := 0
end;

View File

@ -11,8 +11,6 @@
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe") #define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
#define CefVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\libcef.dll") #define CefVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\libcef.dll")
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
[Setup] [Setup]
AppId={{{#MyAppID}} AppId={{{#MyAppID}}
AppName={#MyAppName} AppName={#MyAppName}
@ -39,6 +37,8 @@ SolidCompression=True
InternalCompressLevel=normal InternalCompressLevel=normal
MinVersion=0,6.1 MinVersion=0,6.1
#include <idp.iss>
[Languages] [Languages]
Name: "english"; MessagesFile: "compiler:Default.isl" Name: "english"; MessagesFile: "compiler:Default.isl"
@ -84,6 +84,7 @@ Type: filesandordirs; Name: "{app}\scripts"
function TDIsUninstallable: Boolean; forward; function TDIsUninstallable: Boolean; forward;
function TDGetRunArgs(Param: String): String; forward; function TDGetRunArgs(Param: String): String; forward;
function TDFindUpdatePath: String; forward; function TDFindUpdatePath: String; forward;
function TDGetNetFrameworkVersion: Cardinal; forward;
function TDGetAppVersionClean: String; forward; function TDGetAppVersionClean: String; forward;
function TDGetFullDownloadFileName: String; forward; function TDGetFullDownloadFileName: String; forward;
function TDIsMatchingCEFVersion: Boolean; forward; function TDIsMatchingCEFVersion: Boolean; forward;
@ -92,7 +93,7 @@ procedure TDExecuteFullDownload; forward;
var IsPortable: Boolean; var IsPortable: Boolean;
var UpdatePath: String; var UpdatePath: String;
{ Prepare update installation, and the full download package if required. } { Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. Prepare full download package if required. }
function InitializeSetup: Boolean; function InitializeSetup: Boolean;
begin begin
IsPortable := ExpandConstant('{param:PORTABLE}') = '1' IsPortable := ExpandConstant('{param:PORTABLE}') = '1'
@ -110,6 +111,12 @@ begin
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe')) idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'))
end; end;
if (TDGetNetFrameworkVersion() < 461808) and (MsgBox('{#MyAppName} requires .NET Framework 4.7.2 or newer,'+#13+#10+'please visit {#MyAppShortURL} for a download link.'+#13+#10+#13+#10'Do you want to proceed with the setup anyway?', mbCriticalError, MB_YESNO or MB_DEFBUTTON2) = IDNO) then
begin
Result := False
Exit
end;
Result := True Result := True
end; end;
@ -204,6 +211,20 @@ begin
Result := Path Result := Path
end; end;
{ Return DWORD value containing the build version of .NET Framework. }
function TDGetNetFrameworkVersion: Cardinal;
var FrameworkVersion: Cardinal;
begin
if RegQueryDWordValue(HKEY_LOCAL_MACHINE, 'Software\Microsoft\NET Framework Setup\NDP\v4\Full', 'Release', FrameworkVersion) then
begin
Result := FrameworkVersion
Exit
end;
Result := 0
end;
{ Return the name of the full installer file to download from GitHub. } { Return the name of the full installer file to download from GitHub. }
function TDGetFullDownloadFileName: String; function TDGetFullDownloadFileName: String;
begin begin

View File

@ -1,7 +0,0 @@
{
"sdk": {
"version": "6.0.0",
"rollForward": "latestMinor",
"allowPrerelease": false
}
}

View File

@ -2,7 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
namespace TweetLib.Browser.CEF.Data { namespace TweetLib.Browser.CEF.Data {
abstract class ContextMenuActionRegistry<T> where T : notnull { abstract class ContextMenuActionRegistry<T> {
private readonly Dictionary<T, Action> actions = new (); private readonly Dictionary<T, Action> actions = new ();
protected abstract T NextId(int n); protected abstract T NextId(int n);

View File

@ -21,7 +21,7 @@ internal bool HasHandler(string url) {
} }
private void Register(string url, Func<TResourceHandler> factory) { private void Register(string url, Func<TResourceHandler> factory) {
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) { if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
throw new ArgumentException("Resource handler URL must be absolute!"); throw new ArgumentException("Resource handler URL must be absolute!");
} }

View File

@ -29,7 +29,7 @@ public bool OnFileDialog(FileDialogType type, IEnumerable<string> acceptFilters,
}; };
fileDialogOpener.OpenFile("Open Files", multiple, filters, files => { fileDialogOpener.OpenFile("Open Files", multiple, filters, files => {
string ext = Path.GetExtension(files[0]).ToLower(); string ext = Path.GetExtension(files[0])!.ToLower();
callbackAdapter.Continue(callback, Array.FindIndex(supportedExtensions, filter => ParseFileType(filter).Contains(ext)), files); callbackAdapter.Continue(callback, Array.FindIndex(supportedExtensions, filter => ParseFileType(filter).Contains(ext)), files);
callbackAdapter.Dispose(callback); callbackAdapter.Dispose(callback);
}, () => { }, () => {

View File

@ -9,7 +9,7 @@ private static (MessageDialogType, string) GetMessageDialogProperties(string tex
int pipe = text.IndexOf('|'); int pipe = text.IndexOf('|');
if (pipe != -1) { if (pipe != -1) {
type = text[..pipe] switch { type = text.Substring(0, pipe) switch {
"error" => MessageDialogType.Error, "error" => MessageDialogType.Error,
"warning" => MessageDialogType.Warning, "warning" => MessageDialogType.Warning,
"info" => MessageDialogType.Information, "info" => MessageDialogType.Information,
@ -18,7 +18,7 @@ private static (MessageDialogType, string) GetMessageDialogProperties(string tex
}; };
if (type != MessageDialogType.None) { if (type != MessageDialogType.None) {
text = text[(pipe + 1)..]; text = text.Substring(pipe + 1);
} }
} }

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms> <Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion> <LangVersion>9</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </PropertyGroup>

View File

@ -8,7 +8,7 @@ public static string GetCacheFolder(string storagePath) {
return Path.Combine(storagePath, "Cache"); return Path.Combine(storagePath, "Cache");
} }
public static CommandLineArgs ParseCommandLineArguments(string? argumentString) { public static CommandLineArgs ParseCommandLineArguments(string argumentString) {
CommandLineArgs args = new CommandLineArgs(); CommandLineArgs args = new CommandLineArgs();
if (string.IsNullOrWhiteSpace(argumentString)) { if (string.IsNullOrWhiteSpace(argumentString)) {
@ -26,8 +26,8 @@ public static CommandLineArgs ParseCommandLineArguments(string? argumentString)
value = "1"; value = "1";
} }
else { else {
key = matchValue[..indexEquals].TrimStart('-'); key = matchValue.Substring(0, indexEquals).TrimStart('-');
value = matchValue[(indexEquals + 1)..].Trim('"'); value = matchValue.Substring(indexEquals + 1).Trim('"');
} }
if (key.Length != 0) { if (key.Length != 0) {

View File

@ -1,9 +1,9 @@
namespace TweetLib.Browser.Contexts { namespace TweetLib.Browser.Contexts {
public struct Notification { public struct Notification {
public string? TweetUrl { get; } public string TweetUrl { get; }
public string? QuoteUrl { get; } public string? QuoteUrl { get; }
public Notification(string? tweetUrl, string? quoteUrl) { public Notification(string tweetUrl, string? quoteUrl) {
TweetUrl = tweetUrl; TweetUrl = tweetUrl;
QuoteUrl = quoteUrl; QuoteUrl = quoteUrl;
} }

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms> <Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion> <LangVersion>9</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </PropertyGroup>

View File

@ -13,11 +13,13 @@ public static Server CreateServer() {
public static Client CreateClient(string token) { public static Client CreateClient(string token) {
int space = token.IndexOf(' '); int space = token.IndexOf(' ');
return new Client(token[..space], token[(space + 1)..]); return new Client(token.Substring(0, space), token.Substring(space + 1));
} }
private readonly PipeStream pipeIn; private readonly PipeStream pipeIn;
private readonly PipeStream pipeOut; private readonly PipeStream pipeOut;
private readonly Thread readerThread;
private readonly StreamWriter writerStream; private readonly StreamWriter writerStream;
public event EventHandler<PipeReadEventArgs>? DataIn; public event EventHandler<PipeReadEventArgs>? DataIn;
@ -25,21 +27,22 @@ public static Client CreateClient(string token) {
private DuplexPipe(PipeStream pipeIn, PipeStream pipeOut) { private DuplexPipe(PipeStream pipeIn, PipeStream pipeOut) {
this.pipeIn = pipeIn; this.pipeIn = pipeIn;
this.pipeOut = pipeOut; this.pipeOut = pipeOut;
this.writerStream = new StreamWriter(this.pipeOut);
new Thread(ReaderThread) { IsBackground = true }.Start(); this.readerThread = new Thread(ReaderThread) {
IsBackground = true
};
this.readerThread.Start();
this.writerStream = new StreamWriter(this.pipeOut);
} }
private void ReaderThread() { private void ReaderThread() {
using StreamReader read = new StreamReader(pipeIn); using StreamReader read = new StreamReader(pipeIn);
string? data;
try { while ((data = read.ReadLine()) != null) {
while (read.ReadLine() is {} data) {
DataIn?.Invoke(this, new PipeReadEventArgs(data)); DataIn?.Invoke(this, new PipeReadEventArgs(data));
} }
} catch (ObjectDisposedException) {
// expected
}
} }
public void Write(string key) { public void Write(string key) {
@ -53,6 +56,12 @@ public void Write(string key, string data) {
} }
public void Dispose() { public void Dispose() {
try {
readerThread.Abort();
} catch {
// /shrug
}
pipeIn.Dispose(); pipeIn.Dispose();
writerStream.Dispose(); writerStream.Dispose();
} }
@ -89,8 +98,8 @@ internal PipeReadEventArgs(string line) {
Data = string.Empty; Data = string.Empty;
} }
else { else {
Key = line[..separatorIndex]; Key = line.Substring(0, separatorIndex);
Data = line[(separatorIndex + 1)..]; Data = line.Substring(separatorIndex + 1);
} }
} }
} }

View File

@ -1,9 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Platforms>x86</Platforms> <Platforms>x86</Platforms>
<LangVersion>10</LangVersion> <LangVersion>9</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </PropertyGroup>

View File

@ -110,11 +110,11 @@ private static T Validate<T>(T? obj, string name) where T : class {
} }
public sealed class AppBuilder { public sealed class AppBuilder {
public IAppSetup? Setup { get; init; } public IAppSetup? Setup { get; set; }
public IAppErrorHandler? ErrorHandler { get; init; } public IAppErrorHandler? ErrorHandler { get; set; }
public IAppSystemHandler? SystemHandler { get; init; } public IAppSystemHandler? SystemHandler { get; set; }
public IAppMessageDialogs? MessageDialogs { get; init; } public IAppMessageDialogs? MessageDialogs { get; set; }
public IAppFileDialogs? FileDialogs { get; init; } public IAppFileDialogs? FileDialogs { get; set; }
internal static AppBuilder? Instance { get; private set; } internal static AppBuilder? Instance { get; private set; }

View File

@ -34,7 +34,7 @@ public static void LoadResourceRewriteRules(string rules) {
if (resourceType is ResourceType.Script or ResourceType.Stylesheet && TweetDeckHashes.Count > 0) { if (resourceType is ResourceType.Script or ResourceType.Stylesheet && TweetDeckHashes.Count > 0) {
Match match = TweetDeckResourceUrl.Match(url); Match match = TweetDeckResourceUrl.Match(url);
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out var hash)) { if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)) {
if (match.Groups[2].Value == hash) { if (match.Groups[2].Value == hash) {
App.Logger.Debug("[RequestHandlerBase] Accepting " + url); App.Logger.Debug("[RequestHandlerBase] Accepting " + url);
} }

View File

@ -84,7 +84,7 @@ public void SaveImages(string[] urls, string? author) {
var settings = new SaveFileDialogSettings { var settings = new SaveFileDialogSettings {
DialogTitle = oneImage ? "Save Image" : "Save Images", DialogTitle = oneImage ? "Save Image" : "Save Images",
OverwritePrompt = oneImage, OverwritePrompt = oneImage,
FileName = qualityIndex == -1 ? filename : $"{author} {Path.ChangeExtension(filename, null)} {firstImageLink[(qualityIndex + 1)..]}".Trim() + ext, FileName = qualityIndex == -1 ? filename : $"{author} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex + 1)}".Trim() + ext,
Filters = new [] { new FileDialogFilter(oneImage ? "Image" : "Images", string.IsNullOrEmpty(ext) ? Array.Empty<string>() : new [] { ext }) } Filters = new [] { new FileDialogFilter(oneImage ? "Image" : "Images", string.IsNullOrEmpty(ext) ? Array.Empty<string>() : new [] { ext }) }
}; };

View File

@ -56,9 +56,7 @@ public override void Show(IContextMenuBuilder menu, Context context) {
menu.AddSeparator(); menu.AddSeparator();
if (context.Notification is {} notification) { if (context.Notification is {} notification) {
if (!string.IsNullOrEmpty(notification.TweetUrl)) {
AddCopyAction(menu, "Copy tweet address", notification.TweetUrl); AddCopyAction(menu, "Copy tweet address", notification.TweetUrl);
}
if (!string.IsNullOrEmpty(notification.QuoteUrl)) { if (!string.IsNullOrEmpty(notification.QuoteUrl)) {
AddCopyAction(menu, "Copy quoted tweet address", notification.QuoteUrl!); AddCopyAction(menu, "Copy quoted tweet address", notification.QuoteUrl!);
@ -74,7 +72,7 @@ public override void Dispose() {
this.browserComponent.PageLoadEnd -= BrowserComponentOnPageLoadEnd; this.browserComponent.PageLoadEnd -= BrowserComponentOnPageLoadEnd;
} }
private void BrowserComponentOnPageLoadEnd(object? sender, PageLoadEventArgs e) { private void BrowserComponentOnPageLoadEnd(object sender, PageLoadEventArgs e) {
string url = e.Url; string url = e.Url;
if (TwitterUrls.IsTweetDeck(url) && url != BlankURL) { if (TwitterUrls.IsTweetDeck(url) && url != BlankURL) {

View File

@ -93,7 +93,7 @@ public override int GetHashCode() {
return Identifier.GetHashCode(); return Identifier.GetHashCode();
} }
public override bool Equals(object? obj) { public override bool Equals(object obj) {
return obj is Plugin plugin && plugin.Identifier.Equals(Identifier); return obj is Plugin plugin && plugin.Identifier.Equals(Identifier);
} }

View File

@ -48,17 +48,17 @@ internal int GetTokenFromPlugin(Plugin plugin) {
} }
internal Plugin? GetPluginFromToken(int token) { internal Plugin? GetPluginFromToken(int token) {
return tokens.TryGetValue(token, out var plugin) ? plugin : null; return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
} }
// Event handlers // Event handlers
private void manager_Reloaded(object? sender, PluginErrorEventArgs e) { private void manager_Reloaded(object sender, PluginErrorEventArgs e) {
tokens.Clear(); tokens.Clear();
fileCache.Clear(); fileCache.Clear();
} }
private void Config_PluginChangedState(object? sender, PluginChangedStateEventArgs e) { private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e) {
if (!e.IsEnabled) { if (!e.IsEnabled) {
int token = GetTokenFromPlugin(e.Plugin); int token = GetTokenFromPlugin(e.Plugin);
@ -88,7 +88,7 @@ private string GetFullPathOrThrow(int token, PluginFolder folder, string path) {
private string ReadFileUnsafe(int token, PluginFolder folder, string path, bool readCached) { private string ReadFileUnsafe(int token, PluginFolder folder, string path, bool readCached) {
string fullPath = GetFullPathOrThrow(token, folder, path); string fullPath = GetFullPathOrThrow(token, folder, path);
if (readCached && fileCache.TryGetValue(token, folder, path, out var cachedContents)) { if (readCached && fileCache.TryGetValue(token, folder, path, out string cachedContents)) {
return cachedContents; return cachedContents;
} }
@ -161,7 +161,7 @@ public void Clear() {
cache.Clear(); cache.Clear();
} }
public bool TryGetValue(int token, PluginFolder folder, string path, [MaybeNullWhen(false)] out string contents) { public bool TryGetValue(int token, PluginFolder folder, string path, out string contents) {
return cache.TryGetValue(token, Key(folder, path), out contents); return cache.TryGetValue(token, Key(folder, path), out contents);
} }

View File

@ -41,7 +41,7 @@ public static IEnumerable<Result<Plugin>> AllInFolder(string pluginFolder, strin
private static Plugin FromFolder(string name, string pathRoot, string pathData, PluginGroup group) { private static Plugin FromFolder(string name, string pathRoot, string pathData, PluginGroup group) {
Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData); Plugin.Builder builder = new Plugin.Builder(group, name, pathRoot, pathData);
foreach (var environment in Directory.EnumerateFiles(pathRoot, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).Select(EnvironmentFromFileName!)) { foreach (var environment in Directory.EnumerateFiles(pathRoot, "*.js", SearchOption.TopDirectoryOnly).Select(Path.GetFileName).Select(EnvironmentFromFileName)) {
builder.AddEnvironment(environment); builder.AddEnvironment(environment);
} }
@ -55,7 +55,7 @@ private static Plugin FromFolder(string name, string pathRoot, string pathData,
string currentContents = string.Empty; string currentContents = string.Empty;
foreach (string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(static line => line.TrimEnd()).Where(static line => line.Length > 0)) { foreach (string line in File.ReadAllLines(metaFile, Encoding.UTF8).Concat(EndTag).Select(static line => line.TrimEnd()).Where(static line => line.Length > 0)) {
if (line[0] == '[' && line[^1] == ']') { if (line[0] == '[' && line[line.Length - 1] == ']') {
if (currentTag != null) { if (currentTag != null) {
SetProperty(builder, currentTag, currentContents); SetProperty(builder, currentTag, currentContents);
} }
@ -106,7 +106,7 @@ private static void SetProperty(Plugin.Builder builder, string tag, string value
builder.ConfigDefault = value; builder.ConfigDefault = value;
break; break;
case "REQUIRES": case "REQUIRES":
builder.RequiredVersion = Version.TryParse(value, out var version) ? version : throw new FormatException($"Invalid required minimum version: {value}"); builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException($"Invalid required minimum version: {value}");
break; break;
default: default:
throw new FormatException($"Invalid metadata tag: {tag}"); throw new FormatException($"Invalid metadata tag: {tag}");

View File

@ -102,7 +102,7 @@ internal void Execute(PluginEnvironment environment, IScriptExecutor executor) {
Executed?.Invoke(this, new PluginErrorEventArgs(errors)); Executed?.Invoke(this, new PluginErrorEventArgs(errors));
} }
private void Config_PluginChangedState(object? sender, PluginChangedStateEventArgs e) { private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e) {
browserExecutor?.RunFunction("TDPF_setPluginState", e.Plugin, e.IsEnabled); browserExecutor?.RunFunction("TDPF_setPluginState", e.Plugin, e.IsEnabled);
} }

View File

@ -71,12 +71,12 @@ public override void Dispose() {
App.UserConfiguration.SoundNotificationChanged -= UserConfiguration_SoundNotificationChanged; App.UserConfiguration.SoundNotificationChanged -= UserConfiguration_SoundNotificationChanged;
} }
private void browserComponent_BrowserLoaded(object? sender, BrowserLoadedEventArgs e) { private void browserComponent_BrowserLoaded(object sender, BrowserLoadedEventArgs e) {
e.AddDictionaryWords("tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD"); e.AddDictionaryWords("tweetdeck", "TweetDeck", "tweetduck", "TweetDuck", "TD");
isBrowserReady = true; isBrowserReady = true;
} }
private void browserComponent_PageLoadStart(object? sender, PageLoadEventArgs e) { private void browserComponent_PageLoadStart(object sender, PageLoadEventArgs e) {
string url = e.Url; string url = e.Url;
if (TwitterUrls.IsTweetDeck(url) || (TwitterUrls.IsTwitter(url) && !TwitterUrls.IsTwitterLogin2Factor(url))) { if (TwitterUrls.IsTweetDeck(url) || (TwitterUrls.IsTwitter(url) && !TwitterUrls.IsTwitterLogin2Factor(url))) {
@ -84,7 +84,7 @@ private void browserComponent_PageLoadStart(object? sender, PageLoadEventArgs e)
} }
} }
private void browserComponent_PageLoadEnd(object? sender, PageLoadEventArgs e) { private void browserComponent_PageLoadEnd(object sender, PageLoadEventArgs e) {
string url = e.Url; string url = e.Url;
if (TwitterUrls.IsTweetDeck(url)) { if (TwitterUrls.IsTweetDeck(url)) {
@ -105,7 +105,7 @@ private void browserComponent_PageLoadEnd(object? sender, PageLoadEventArgs e) {
browserComponent.RunBootstrap("update"); browserComponent.RunBootstrap("update");
} }
private void pluginManager_Reloaded(object? sender, PluginErrorEventArgs e) { private void pluginManager_Reloaded(object sender, PluginErrorEventArgs e) {
if (e.HasErrors) { if (e.HasErrors) {
App.MessageDialogs.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n" + string.Join("\n\n", e.Errors)); App.MessageDialogs.Error("Error Loading Plugins", "The following plugins will not be available until the issues are resolved:\n\n" + string.Join("\n\n", e.Errors));
} }
@ -115,14 +115,14 @@ private void pluginManager_Reloaded(object? sender, PluginErrorEventArgs e) {
} }
} }
private void pluginManager_Executed(object? sender, PluginErrorEventArgs e) { private void pluginManager_Executed(object sender, PluginErrorEventArgs e) {
if (e.HasErrors) { if (e.HasErrors) {
App.MessageDialogs.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors)); App.MessageDialogs.Error("Error Executing Plugins", "Failed to execute the following plugins:\n\n" + string.Join("\n\n", e.Errors));
} }
} }
private void updateChecker_CheckFinished(object? sender, UpdateCheckEventArgs e) { private void updateChecker_CheckFinished(object sender, UpdateCheckEventArgs e) {
var updateChecker = (UpdateChecker) sender!; var updateChecker = (UpdateChecker) sender;
e.Result.Handle(update => { e.Result.Handle(update => {
string tag = update.VersionTag; string tag = update.VersionTag;
@ -144,7 +144,7 @@ private void updateChecker_CheckFinished(object? sender, UpdateCheckEventArgs e)
ignoreUpdateCheckError = true; ignoreUpdateCheckError = true;
} }
private void UserConfiguration_GeneralEventHandler(object? sender, EventArgs e) { private void UserConfiguration_GeneralEventHandler(object sender, EventArgs e) {
UpdatePropertyObject(); UpdatePropertyObject();
} }
@ -236,11 +236,6 @@ private sealed class ResourceRequestHandler : BaseResourceRequestHandler {
private const string UrlVersionCheck = "/web/dist/version.json"; private const string UrlVersionCheck = "/web/dist/version.json";
public override RequestHandleResult? Handle(string url, ResourceType resourceType) { public override RequestHandleResult? Handle(string url, ResourceType resourceType) {
var result = base.Handle(url, resourceType);
if (result != null) {
return result;
}
switch (resourceType) { switch (resourceType) {
case ResourceType.MainFrame when url.EndsWithOrdinal("://twitter.com/"): case ResourceType.MainFrame when url.EndsWithOrdinal("://twitter.com/"):
return new RequestHandleResult.Redirect(TwitterUrls.TweetDeck); // redirect plain twitter.com requests, fixes bugs with login 2FA return new RequestHandleResult.Redirect(TwitterUrls.TweetDeck); // redirect plain twitter.com requests, fixes bugs with login 2FA
@ -261,7 +256,7 @@ private sealed class ResourceRequestHandler : BaseResourceRequestHandler {
return new RequestHandleResult.Redirect(url.Replace("include_entities=1", "include_entities=1&include_ext_has_nft_avatar=1")); return new RequestHandleResult.Redirect(url.Replace("include_entities=1", "include_entities=1&include_ext_has_nft_avatar=1"));
default: default:
return null; return base.Handle(url, resourceType);
} }
} }
} }

View File

@ -24,15 +24,15 @@ public void SetLink(string type, string? url) {
switch (type) { switch (type) {
case "link": case "link":
Link = new Link(url, url); Link = new Link(url!, url!);
break; break;
case "image": case "image":
Media = new Media(Type.Image, TwitterUrls.GetMediaLink(url, App.UserConfiguration.TwitterImageQuality)); Media = new Media(Type.Image, TwitterUrls.GetMediaLink(url!, App.UserConfiguration.TwitterImageQuality));
break; break;
case "video": case "video":
Media = new Media(Type.Video, url); Media = new Media(Type.Video, url!);
break; break;
} }
} }

View File

@ -57,7 +57,7 @@ public static bool TryParse(string url, out ImageUrl obj) {
return false; return false;
} }
string originalUrl = url[..question]; string originalUrl = url.Substring(0, question);
obj = new ImageUrl(Path.HasExtension(originalUrl) ? originalUrl : originalUrl + imageExtension, imageQuality); obj = new ImageUrl(Path.HasExtension(originalUrl) ? originalUrl : originalUrl + imageExtension, imageQuality);
return true; return true;

View File

@ -49,11 +49,11 @@ public enum UrlType {
} }
public static UrlType Check(string url) { public static UrlType Check(string url) {
if (url.Contains('"')) { if (url.Contains("\"")) {
return UrlType.Invalid; return UrlType.Invalid;
} }
if (Uri.TryCreate(url, UriKind.Absolute, out var uri)) { if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
string scheme = uri.Scheme; string scheme = uri.Scheme;
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto) { if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto) {

View File

@ -16,7 +16,7 @@ static ConfigManager() {
ConverterRegistry.Register(typeof(WindowState), new BasicTypeConverter<WindowState> { ConverterRegistry.Register(typeof(WindowState), new BasicTypeConverter<WindowState> {
ConvertToString = static value => $"{(value.IsMaximized ? 'M' : '_')}{value.Bounds.X} {value.Bounds.Y} {value.Bounds.Width} {value.Bounds.Height}", ConvertToString = static value => $"{(value.IsMaximized ? 'M' : '_')}{value.Bounds.X} {value.Bounds.Y} {value.Bounds.Width} {value.Bounds.Height}",
ConvertToObject = static value => { ConvertToObject = static value => {
int[] elements = StringUtils.ParseInts(value[1..], ' '); int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
return new WindowState { return new WindowState {
Bounds = new Rectangle(elements[0], elements[1], elements[2], elements[3]), Bounds = new Rectangle(elements[0], elements[1], elements[2], elements[3]),

View File

@ -43,7 +43,7 @@ private bool Log(string level, string message) {
build.Append("Please, report all issues to: ").Append(Lib.IssueTrackerUrl).Append("\r\n\r\n"); build.Append("Please, report all issues to: ").Append(Lib.IssueTrackerUrl).Append("\r\n\r\n");
} }
build.Append('[').Append(DateTime.Now.ToString("G", Lib.Culture)).Append("] ").Append(level).Append("\r\n"); build.Append("[").Append(DateTime.Now.ToString("G", Lib.Culture)).Append("] ").Append(level).Append("\r\n");
build.Append(message).Append("\r\n\r\n"); build.Append(message).Append("\r\n\r\n");
try { try {

View File

@ -37,7 +37,7 @@ public void Dispose() {
InteractionManager.Dispose(); InteractionManager.Dispose();
} }
private void timer_Elapsed(object? sender, ElapsedEventArgs e) { private void timer_Elapsed(object sender, ElapsedEventArgs e) {
Check(false); Check(false);
} }

View File

@ -26,7 +26,7 @@ public void ClearUpdate() {
nextUpdate = null; nextUpdate = null;
} }
private void updates_CheckFinished(object? sender, UpdateCheckEventArgs e) { private void updates_CheckFinished(object sender, UpdateCheckEventArgs e) {
UpdateInfo? foundUpdate = e.Result.HasValue ? e.Result.Value : null; UpdateInfo? foundUpdate = e.Result.HasValue ? e.Result.Value : null;
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)) { if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)) {

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms> <Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion> <LangVersion>9</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </PropertyGroup>

View File

@ -57,7 +57,7 @@ public void SetValue(string key, string value) {
} }
public string? GetValue(string key) { public string? GetValue(string key) {
return values.TryGetValue(key.ToLower(), out var val) ? val : null; return values.TryGetValue(key.ToLower(), out string val) ? val : null;
} }
public void RemoveValue(string key) { public void RemoveValue(string key) {

View File

@ -11,7 +11,7 @@ namespace TweetLib.Utils.Collections {
/// <typeparam name="V">The type of the values.</typeparam> /// <typeparam name="V">The type of the values.</typeparam>
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")] [SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
[SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnusedMember.Global")]
public sealed class TwoKeyDictionary<K1, K2, V> where K1 : notnull where K2 : notnull { public sealed class TwoKeyDictionary<K1, K2, V> {
private readonly Dictionary<K1, Dictionary<K2, V>> dict; private readonly Dictionary<K1, Dictionary<K2, V>> dict;
private readonly int innerCapacity; private readonly int innerCapacity;
@ -41,7 +41,7 @@ public TwoKeyDictionary(int outerCapacity, int innerCapacity) {
} }
set { set {
if (!dict.TryGetValue(outerKey, out Dictionary<K2, V>? innerDict)) { if (!dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)) {
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity)); dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
} }
@ -67,7 +67,7 @@ public IEnumerable<V> InnerValues {
/// Throws if the key pair already exists. /// Throws if the key pair already exists.
/// </summary> /// </summary>
public void Add(K1 outerKey, K2 innerKey, V value) { public void Add(K1 outerKey, K2 innerKey, V value) {
if (!dict.TryGetValue(outerKey, out Dictionary<K2, V>? innerDict)) { if (!dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)) {
dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity)); dict.Add(outerKey, innerDict = new Dictionary<K2, V>(innerCapacity));
} }
@ -100,7 +100,7 @@ public bool Contains(K1 outerKey) {
/// Determines whether the dictionary contains the key pair. /// Determines whether the dictionary contains the key pair.
/// </summary> /// </summary>
public bool Contains(K1 outerKey, K2 innerKey) { public bool Contains(K1 outerKey, K2 innerKey) {
return dict.TryGetValue(outerKey, out Dictionary<K2, V>? innerDict) && innerDict.ContainsKey(innerKey); return dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict) && innerDict.ContainsKey(innerKey);
} }
/// <summary> /// <summary>
@ -122,8 +122,8 @@ public int Count(K1 outerKey) {
/// Gets the value associated with the key pair. /// Gets the value associated with the key pair.
/// Returns true if the key pair was present. /// Returns true if the key pair was present.
/// </summary> /// </summary>
public bool TryGetValue(K1 outerKey, K2 innerKey, [MaybeNullWhen(false)] out V value) { public bool TryGetValue(K1 outerKey, K2 innerKey, out V value) {
if (dict.TryGetValue(outerKey, out Dictionary<K2, V>? innerDict)) { if (dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)) {
return innerDict.TryGetValue(innerKey, out value); return innerDict.TryGetValue(innerKey, out value);
} }
else { else {
@ -145,7 +145,7 @@ public bool Remove(K1 outerKey) {
/// Returns true if the key pair was present. /// Returns true if the key pair was present.
/// </summary> /// </summary>
public bool Remove(K1 outerKey, K2 innerKey) { public bool Remove(K1 outerKey, K2 innerKey) {
if (dict.TryGetValue(outerKey, out Dictionary<K2, V>? innerDict) && innerDict.Remove(innerKey)) { if (dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict) && innerDict.Remove(innerKey)) {
if (innerDict.Count == 0) { if (innerDict.Count == 0) {
dict.Remove(outerKey); dict.Remove(outerKey);
} }

View File

@ -2,9 +2,9 @@
namespace TweetLib.Utils.Dialogs { namespace TweetLib.Utils.Dialogs {
public sealed class SaveFileDialogSettings { public sealed class SaveFileDialogSettings {
public string DialogTitle { get; init; } = "Save File"; public string DialogTitle { get; set; } = "Save File";
public bool OverwritePrompt { get; init; } = true; public bool OverwritePrompt { get; set; } = true;
public string? FileName { get; init; } public string? FileName { get; set; }
public IReadOnlyList<FileDialogFilter>? Filters { get; init; } public IReadOnlyList<FileDialogFilter>? Filters { get; set; }
} }
} }

View File

@ -21,7 +21,7 @@ public Language(string code, string? alt = null) {
} }
} }
public override bool Equals(object? obj) { public override bool Equals(object obj) {
return obj is Language other && Code.Equals(other.Code, StringComparison.OrdinalIgnoreCase); return obj is Language other && Code.Equals(other.Code, StringComparison.OrdinalIgnoreCase);
} }
@ -38,8 +38,8 @@ public override string ToString() {
return cultureInfo.DisplayName == cultureInfo.NativeName ? capitalizedName : $"{capitalizedName}, {cultureInfo.DisplayName}"; return cultureInfo.DisplayName == cultureInfo.NativeName ? capitalizedName : $"{capitalizedName}, {cultureInfo.DisplayName}";
} }
public int CompareTo(Language? other) { public int CompareTo(Language other) {
return string.Compare(Name, other?.Name, false, CultureInfo.InvariantCulture); return string.Compare(Name, other.Name, false, CultureInfo.InvariantCulture);
} }
} }
} }

View File

@ -134,7 +134,7 @@ public sealed class Entry {
public string[] KeyValue { public string[] KeyValue {
get { get {
int index = Identifier.IndexOf(KeySeparator); int index = Identifier.IndexOf(KeySeparator);
return index == -1 ? StringUtils.EmptyArray : Identifier[(index + 1)..].Split(KeySeparator); return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index + 1).Split(KeySeparator);
} }
} }

View File

@ -2,12 +2,12 @@
namespace TweetLib.Utils.Serialization.Converters { namespace TweetLib.Utils.Serialization.Converters {
public sealed class BasicTypeConverter<T> : ITypeConverter { public sealed class BasicTypeConverter<T> : ITypeConverter {
public Func<T, string>? ConvertToString { get; init; } public Func<T, string>? ConvertToString { get; set; }
public Func<string, T>? ConvertToObject { get; init; } public Func<string, T>? ConvertToObject { get; set; }
bool ITypeConverter.TryWriteType(Type type, object? value, out string? converted) { bool ITypeConverter.TryWriteType(Type type, object value, out string? converted) {
try { try {
converted = ConvertToString!((T) value!); converted = ConvertToString!((T) value);
return true; return true;
} catch { } catch {
converted = null; converted = null;

View File

@ -6,17 +6,18 @@ sealed class ClrTypeConverter : ITypeConverter {
private ClrTypeConverter() {} private ClrTypeConverter() {}
bool ITypeConverter.TryWriteType(Type type, object? value, out string? converted) { bool ITypeConverter.TryWriteType(Type type, object value, out string? converted) {
switch (Type.GetTypeCode(type)) { switch (Type.GetTypeCode(type)) {
case TypeCode.Boolean: case TypeCode.Boolean:
converted = value!.ToString(); converted = value.ToString();
return true; return true;
case TypeCode.Int32: case TypeCode.Int32:
converted = ((int) value!).ToString(); // cast required for enums converted = ((int) value).ToString(); // cast required for enums
return true; return true;
case TypeCode.String: case TypeCode.String:
// ReSharper disable once ConstantConditionalAccessQualifier
converted = value?.ToString(); converted = value?.ToString();
return true; return true;

View File

@ -2,7 +2,7 @@
namespace TweetLib.Utils.Serialization { namespace TweetLib.Utils.Serialization {
public interface ITypeConverter { public interface ITypeConverter {
bool TryWriteType(Type type, object? value, out string? converted); bool TryWriteType(Type type, object value, out string? converted);
bool TryReadType(Type type, string value, out object? converted); bool TryReadType(Type type, string value, out object? converted);
} }
} }

View File

@ -28,7 +28,7 @@ private static string UnescapeStream(StreamReader reader) {
break; break;
} }
else { else {
build.Append(data[index..nextIndex]); build.Append(data.Substring(index, nextIndex - index));
char next = data[nextIndex + 1]; char next = data[nextIndex + 1];
@ -47,7 +47,7 @@ private static string UnescapeStream(StreamReader reader) {
} }
} }
return build.Append(data[index..]).ToString(); return build.Append(data.Substring(index)).ToString();
} }
private readonly TypeConverterRegistry converterRegistry; private readonly TypeConverterRegistry converterRegistry;
@ -114,7 +114,7 @@ public void Read(string file, T obj) {
int nextPos = contents.IndexOf(NewLineReal, currentPos); int nextPos = contents.IndexOf(NewLineReal, currentPos);
if (nextPos == -1) { if (nextPos == -1) {
line = contents[currentPos..]; line = contents.Substring(currentPos);
currentPos = -1; currentPos = -1;
if (string.IsNullOrEmpty(line)) { if (string.IsNullOrEmpty(line)) {
@ -133,10 +133,10 @@ public void Read(string file, T obj) {
continue; continue;
} }
string property = line[..space]; string property = line.Substring(0, space);
string value = UnescapeLine(line[(space + 1)..]); string value = UnescapeLine(line.Substring(space + 1));
if (props.TryGetValue(property, out var info)) { if (props.TryGetValue(property, out PropertyInfo info)) {
var type = info.PropertyType; var type = info.PropertyType;
var converter = converterRegistry.TryGet(type) ?? ClrTypeConverter.Instance; var converter = converterRegistry.TryGet(type) ?? ClrTypeConverter.Instance;

View File

@ -1,5 +1,6 @@
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO; using System.IO;
using System.Threading; using System.Threading;
using TweetLib.Utils.Static; using TweetLib.Utils.Static;
@ -90,6 +91,7 @@ public UnlockResult Unlock() {
return UnlockResult.Success; return UnlockResult.Success;
} }
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
private LockResult DetermineLockingProcessOrFail(Exception originalException) { private LockResult DetermineLockingProcessOrFail(Exception originalException) {
try { try {
int pid; int pid;
@ -108,7 +110,7 @@ private LockResult DetermineLockingProcessOrFail(Exception originalException) {
var foundProcess = Process.GetProcessById(pid); var foundProcess = Process.GetProcessById(pid);
using var currentProcess = Process.GetCurrentProcess(); using var currentProcess = Process.GetCurrentProcess();
if (currentProcess.MainModule!.FileVersionInfo.InternalName == foundProcess.MainModule!.FileVersionInfo.InternalName) { if (currentProcess.MainModule.FileVersionInfo.InternalName == foundProcess.MainModule.FileVersionInfo.InternalName) {
return new LockResult.HasProcess(foundProcess); return new LockResult.HasProcess(foundProcess);
} }
else { else {

View File

@ -40,7 +40,7 @@ public static (string before, string after)? SplitInTwo(string str, char search,
return null; return null;
} }
return (str[..index], str[(index + 1)..]); return (str.Substring(0, index), str.Substring(index + 1));
} }
/// <summary> /// <summary>
@ -49,7 +49,7 @@ public static (string before, string after)? SplitInTwo(string str, char search,
/// </summary> /// </summary>
public static string ExtractBefore(string str, char search, int startIndex = 0) { public static string ExtractBefore(string str, char search, int startIndex = 0) {
int index = str.IndexOf(search, startIndex); int index = str.IndexOf(search, startIndex);
return index == -1 ? str : str[..index]; return index == -1 ? str : str.Substring(0, index);
} }
/// <summary> /// <summary>

View File

@ -10,12 +10,10 @@ public static class WebUtils {
private static bool hasMicrosoftBeenBroughtTo2008Yet; private static bool hasMicrosoftBeenBroughtTo2008Yet;
private static bool hasSystemProxyBeenEnabled; private static bool hasSystemProxyBeenEnabled;
private static void EnsureModernTLS() { private static void EnsureTLS12() {
if (!hasMicrosoftBeenBroughtTo2008Yet) { if (!hasMicrosoftBeenBroughtTo2008Yet) {
#pragma warning disable CS0618 ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11); ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
#pragma warning restore CS0618
hasMicrosoftBeenBroughtTo2008Yet = true; hasMicrosoftBeenBroughtTo2008Yet = true;
} }
} }
@ -30,7 +28,7 @@ public static void EnableSystemProxy() {
} }
public static WebClient NewClient(string? userAgent = null) { public static WebClient NewClient(string? userAgent = null) {
EnsureModernTLS(); EnsureTLS12();
WebClient client = new WebClient(); WebClient client = new WebClient();

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<Configurations>Debug;Release</Configurations> <Configurations>Debug;Release</Configurations>
<Platforms>x86;x64</Platforms> <Platforms>x86;x64</Platforms>
<LangVersion>10</LangVersion> <LangVersion>9</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup> </PropertyGroup>

View File

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net472</TargetFramework>
<Platforms>x86</Platforms> <Platforms>x86</Platforms>
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

@ -47,7 +47,7 @@ module RegexAccount =
Assert.True(isMatch("https://twitter.com/" + name)) Assert.True(isMatch("https://twitter.com/" + name))
module Match = module Match =
let extract str = TwitterUrls.RegexAccount.Match(str).Groups[1].Value let extract str = TwitterUrls.RegexAccount.Match(str).Groups.[1].Value
[<Fact>] [<Fact>]
let ``extracts account name from simple URL`` () = let ``extracts account name from simple URL`` () =

View File

@ -1,9 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net472</TargetFramework>
<Platforms>x86</Platforms> <Platforms>x86</Platforms>
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

Some files were not shown because too many files have changed in this diff Show More