mirror of
https://github.com/chylex/TweetDuck.git
synced 2024-11-25 14:42:47 +01:00
Compare commits
11 Commits
b58c8f65fe
...
b1328e5b1f
Author | SHA1 | Date | |
---|---|---|---|
b1328e5b1f | |||
cb94f0c81e | |||
8de2989f12 | |||
1cf7d13873 | |||
35c2ee3673 | |||
a1b4c31450 | |||
ea95e5cbac | |||
2927097e8e | |||
b5bffdb95b | |||
bee894bfbb | |||
96d2e7cc7c |
@ -11,8 +11,8 @@
|
||||
<option name="PROJECT_EXE_PATH_TRACKING" value="1" />
|
||||
<option name="PROJECT_ARGUMENTS_TRACKING" value="1" />
|
||||
<option name="PROJECT_WORKING_DIRECTORY_TRACKING" value="1" />
|
||||
<option name="PROJECT_KIND" value="Console" />
|
||||
<option name="PROJECT_TFM" value=".NETFramework,Version=v4.7.2" />
|
||||
<option name="PROJECT_KIND" value="DotNetCore" />
|
||||
<option name="PROJECT_TFM" value="net6.0-windows7.0" />
|
||||
<method v="2">
|
||||
<option name="Build" />
|
||||
</method>
|
||||
|
32
README.md
32
README.md
@ -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:
|
||||
* **.NET desktop development**
|
||||
* .NET Framework 4.7.2 targeting pack
|
||||
* .NET SDK
|
||||
* F# desktop language support
|
||||
* **Desktop development with C++**
|
||||
* 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.
|
||||
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.
|
||||
|
||||
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.
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
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 5.6.1](https://files.jrsoftware.org/is/5/innosetup-5.6.1.exe) with Preprocessor support
|
||||
* [Inno Download Plugin 1.5.0](https://drive.google.com/folderview?id=0Bzw1xBVt0mokSXZrUEFIanV4azA&usp=sharing#list)
|
||||
* [InnoSetup 6.2.1](https://files.jrsoftware.org/is/6/innosetup-6.2.1.exe)
|
||||
* [Inno Download Plugin 1.5.1](https://drive.google.com/folderview?id=0Bzw1xBVt0mokSXZrUEFIanV4azA&usp=sharing#list)
|
||||
|
||||
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).
|
||||
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.
|
||||
|
||||
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 5`
|
||||
* `C:\Program Files (x86)\Inno Setup 6`
|
||||
|
||||
You may need to restart Visual Studio after changing `PATH` for the change to take place.
|
||||
You may need to restart Visual Studio or Rider after changing `PATH` for the change to take place.
|
||||
|
||||
## 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.
|
||||
|
||||
The solution contains several C# projects for executables and libraries, and F# projects for automated tests.
|
||||
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#`.
|
||||
|
||||
Projects are organized into folders:
|
||||
* Windows projects are in the `windows/` folder, and target `.NET Framework 4.7.2` + `C# 8.0`
|
||||
* Linux projects are in the `linux/` folder, and target `.NET 5` + `C#`
|
||||
* Libraries (`TweetLib.*`) are in the `lib/` folder, and target `.NET Standard 2.0` + `C# 9.0`
|
||||
* Tests (`TweetTest.*`) are also in the `lib/` folder, and target `.NET Framework 4.7.2` + `F#`
|
||||
* Windows projects are in the `windows/` folder
|
||||
* Linux projects are in the `linux/` folder
|
||||
* Libraries (`TweetLib.*`) are in the `lib/` folder
|
||||
* Tests (`TweetTest.*`) are also in the `lib/` folder
|
||||
|
||||
Here are a few things to keep in mind:
|
||||
* 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
|
||||
|
||||
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.
|
||||
Windows executable that hosts various Chromium processes. It has a dependency on [CefSharp](https://github.com/cefsharp/CefSharp/).
|
||||
|
||||
#### TweetDuck.Video
|
||||
|
||||
@ -142,6 +142,10 @@ 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.
|
||||
|
||||
#### 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
|
||||
|
||||
#### TweetDuck
|
||||
|
@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetDuck.Video", "windows\
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetImpl.CefSharp", "windows\TweetImpl.CefSharp\TweetImpl.CefSharp.csproj", "{44DF3E2E-F465-4A31-8B43-F40FFFB018BA}"
|
||||
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}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TweetLib.Core", "lib\TweetLib.Core\TweetLib.Core.csproj", "{93BA3CB4-A812-4949-B07D-8D393FB38937}"
|
||||
@ -48,6 +50,10 @@ Global
|
||||
{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.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.Build.0 = Debug|x86
|
||||
{72473763-4B9D-4FB6-A923-9364B2680F06}.Release|x86.ActiveCfg = Release|x86
|
||||
|
@ -6,6 +6,6 @@
|
||||
|
||||
namespace TweetDuck {
|
||||
internal static class Version {
|
||||
public const string Tag = "1.22.1";
|
||||
public const string Tag = "1.23";
|
||||
}
|
||||
}
|
||||
|
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.
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.
@ -9,6 +9,8 @@
|
||||
|
||||
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
|
||||
|
||||
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
|
||||
|
||||
[Setup]
|
||||
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
||||
AppName={#MyAppName}
|
||||
@ -29,13 +31,11 @@ Uninstallable=TDIsUninstallable
|
||||
UninstallDisplayName={#MyAppName}
|
||||
UninstallDisplayIcon={app}\{#MyAppExeName}
|
||||
Compression=lzma2/ultra
|
||||
LZMADictionarySize=15360
|
||||
LZMADictionarySize=32768
|
||||
SolidCompression=yes
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
|
||||
#include <idp.iss>
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
@ -68,20 +68,11 @@ AdditionalTasks=Additional shortcuts and components:
|
||||
var UpdatePath: String;
|
||||
var VisitedTasksPage: Boolean;
|
||||
|
||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. }
|
||||
{ Prepare installation variables. }
|
||||
function InitializeSetup: Boolean;
|
||||
begin
|
||||
UpdatePath := ExpandConstant('{param:UPDATEPATH}')
|
||||
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
|
||||
end;
|
||||
|
||||
@ -140,17 +131,3 @@ function TDIsUninstallable: Boolean;
|
||||
begin
|
||||
Result := (UpdatePath = '')
|
||||
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;
|
||||
|
@ -9,6 +9,8 @@
|
||||
|
||||
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
|
||||
|
||||
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
|
||||
|
||||
[Setup]
|
||||
AppId={{8C25A716-7E11-4AAD-9992-8B5D0C78AE06}
|
||||
AppName={#MyAppName} Portable
|
||||
@ -29,13 +31,11 @@ Uninstallable=no
|
||||
UsePreviousAppDir=no
|
||||
PrivilegesRequired=lowest
|
||||
Compression=lzma2/ultra
|
||||
LZMADictionarySize=15360
|
||||
LZMADictionarySize=32768
|
||||
SolidCompression=yes
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
|
||||
#include <idp.iss>
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
@ -52,19 +52,10 @@ AdditionalTasks=Additional components:
|
||||
[Code]
|
||||
var UpdatePath: String;
|
||||
|
||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
|
||||
{ Check .NET Framework version on startup, ask user if they want to proceed if older than 4.7.2. }
|
||||
{ Prepare installation variables. }
|
||||
function InitializeSetup: Boolean;
|
||||
begin
|
||||
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
|
||||
end;
|
||||
|
||||
@ -102,17 +93,3 @@ begin
|
||||
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;
|
||||
|
@ -11,6 +11,8 @@
|
||||
#define MyAppVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\TweetDuck.exe")
|
||||
#define CefVersion GetFileVersion("..\windows\TweetDuck\bin\x86\Release\libcef.dll")
|
||||
|
||||
#include ReadReg(HKLM, "Software\Mitrich Software\Inno Download Plugin", "InstallDir") + "\idp.iss"
|
||||
|
||||
[Setup]
|
||||
AppId={{{#MyAppID}}
|
||||
AppName={#MyAppName}
|
||||
@ -37,8 +39,6 @@ SolidCompression=True
|
||||
InternalCompressLevel=normal
|
||||
MinVersion=0,6.1
|
||||
|
||||
#include <idp.iss>
|
||||
|
||||
[Languages]
|
||||
Name: "english"; MessagesFile: "compiler:Default.isl"
|
||||
|
||||
@ -84,7 +84,6 @@ Type: filesandordirs; Name: "{app}\scripts"
|
||||
function TDIsUninstallable: Boolean; forward;
|
||||
function TDGetRunArgs(Param: String): String; forward;
|
||||
function TDFindUpdatePath: String; forward;
|
||||
function TDGetNetFrameworkVersion: Cardinal; forward;
|
||||
function TDGetAppVersionClean: String; forward;
|
||||
function TDGetFullDownloadFileName: String; forward;
|
||||
function TDIsMatchingCEFVersion: Boolean; forward;
|
||||
@ -93,7 +92,7 @@ procedure TDExecuteFullDownload; forward;
|
||||
var IsPortable: Boolean;
|
||||
var UpdatePath: String;
|
||||
|
||||
{ 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. }
|
||||
{ Prepare update installation, and the full download package if required. }
|
||||
function InitializeSetup: Boolean;
|
||||
begin
|
||||
IsPortable := ExpandConstant('{param:PORTABLE}') = '1'
|
||||
@ -111,12 +110,6 @@ begin
|
||||
idpAddFile('https://github.com/{#MyAppPublisher}/{#MyAppName}/releases/download/'+TDGetAppVersionClean()+'/'+TDGetFullDownloadFileName(), ExpandConstant('{tmp}\{#MyAppName}.Full.exe'))
|
||||
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
|
||||
end;
|
||||
|
||||
@ -211,20 +204,6 @@ begin
|
||||
Result := Path
|
||||
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. }
|
||||
function TDGetFullDownloadFileName: String;
|
||||
begin
|
||||
|
7
global.json
Normal file
7
global.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "6.0.0",
|
||||
"rollForward": "latestMinor",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
@ -2,7 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace TweetLib.Browser.CEF.Data {
|
||||
abstract class ContextMenuActionRegistry<T> {
|
||||
abstract class ContextMenuActionRegistry<T> where T : notnull {
|
||||
private readonly Dictionary<T, Action> actions = new ();
|
||||
|
||||
protected abstract T NextId(int n);
|
||||
|
@ -21,7 +21,7 @@ internal bool HasHandler(string url) {
|
||||
}
|
||||
|
||||
private void Register(string url, Func<TResourceHandler> factory) {
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
||||
if (!Uri.TryCreate(url, UriKind.Absolute, out var uri)) {
|
||||
throw new ArgumentException("Resource handler URL must be absolute!");
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ public bool OnFileDialog(FileDialogType type, IEnumerable<string> acceptFilters,
|
||||
};
|
||||
|
||||
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.Dispose(callback);
|
||||
}, () => {
|
||||
|
@ -9,7 +9,7 @@ private static (MessageDialogType, string) GetMessageDialogProperties(string tex
|
||||
|
||||
int pipe = text.IndexOf('|');
|
||||
if (pipe != -1) {
|
||||
type = text.Substring(0, pipe) switch {
|
||||
type = text[..pipe] switch {
|
||||
"error" => MessageDialogType.Error,
|
||||
"warning" => MessageDialogType.Warning,
|
||||
"info" => MessageDialogType.Information,
|
||||
@ -18,7 +18,7 @@ private static (MessageDialogType, string) GetMessageDialogProperties(string tex
|
||||
};
|
||||
|
||||
if (type != MessageDialogType.None) {
|
||||
text = text.Substring(pipe + 1);
|
||||
text = text[(pipe + 1)..];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<LangVersion>9</LangVersion>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
@ -8,7 +8,7 @@ public static string GetCacheFolder(string storagePath) {
|
||||
return Path.Combine(storagePath, "Cache");
|
||||
}
|
||||
|
||||
public static CommandLineArgs ParseCommandLineArguments(string argumentString) {
|
||||
public static CommandLineArgs ParseCommandLineArguments(string? argumentString) {
|
||||
CommandLineArgs args = new CommandLineArgs();
|
||||
|
||||
if (string.IsNullOrWhiteSpace(argumentString)) {
|
||||
@ -26,8 +26,8 @@ public static CommandLineArgs ParseCommandLineArguments(string argumentString) {
|
||||
value = "1";
|
||||
}
|
||||
else {
|
||||
key = matchValue.Substring(0, indexEquals).TrimStart('-');
|
||||
value = matchValue.Substring(indexEquals + 1).Trim('"');
|
||||
key = matchValue[..indexEquals].TrimStart('-');
|
||||
value = matchValue[(indexEquals + 1)..].Trim('"');
|
||||
}
|
||||
|
||||
if (key.Length != 0) {
|
||||
|
@ -1,9 +1,9 @@
|
||||
namespace TweetLib.Browser.Contexts {
|
||||
public struct Notification {
|
||||
public string TweetUrl { get; }
|
||||
public string? TweetUrl { get; }
|
||||
public string? QuoteUrl { get; }
|
||||
|
||||
public Notification(string tweetUrl, string? quoteUrl) {
|
||||
public Notification(string? tweetUrl, string? quoteUrl) {
|
||||
TweetUrl = tweetUrl;
|
||||
QuoteUrl = quoteUrl;
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<LangVersion>9</LangVersion>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
@ -13,13 +13,11 @@ public static Server CreateServer() {
|
||||
|
||||
public static Client CreateClient(string token) {
|
||||
int space = token.IndexOf(' ');
|
||||
return new Client(token.Substring(0, space), token.Substring(space + 1));
|
||||
return new Client(token[..space], token[(space + 1)..]);
|
||||
}
|
||||
|
||||
private readonly PipeStream pipeIn;
|
||||
private readonly PipeStream pipeOut;
|
||||
|
||||
private readonly Thread readerThread;
|
||||
private readonly StreamWriter writerStream;
|
||||
|
||||
public event EventHandler<PipeReadEventArgs>? DataIn;
|
||||
@ -27,21 +25,20 @@ public static Client CreateClient(string token) {
|
||||
private DuplexPipe(PipeStream pipeIn, PipeStream pipeOut) {
|
||||
this.pipeIn = pipeIn;
|
||||
this.pipeOut = pipeOut;
|
||||
|
||||
this.readerThread = new Thread(ReaderThread) {
|
||||
IsBackground = true
|
||||
};
|
||||
|
||||
this.readerThread.Start();
|
||||
this.writerStream = new StreamWriter(this.pipeOut);
|
||||
|
||||
new Thread(ReaderThread) { IsBackground = true }.Start();
|
||||
}
|
||||
|
||||
private void ReaderThread() {
|
||||
using StreamReader read = new StreamReader(pipeIn);
|
||||
string? data;
|
||||
|
||||
while ((data = read.ReadLine()) != null) {
|
||||
DataIn?.Invoke(this, new PipeReadEventArgs(data));
|
||||
try {
|
||||
while (read.ReadLine() is {} data) {
|
||||
DataIn?.Invoke(this, new PipeReadEventArgs(data));
|
||||
}
|
||||
} catch (ObjectDisposedException) {
|
||||
// expected
|
||||
}
|
||||
}
|
||||
|
||||
@ -56,12 +53,6 @@ public void Write(string key, string data) {
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
try {
|
||||
readerThread.Abort();
|
||||
} catch {
|
||||
// /shrug
|
||||
}
|
||||
|
||||
pipeIn.Dispose();
|
||||
writerStream.Dispose();
|
||||
}
|
||||
@ -98,8 +89,8 @@ internal PipeReadEventArgs(string line) {
|
||||
Data = string.Empty;
|
||||
}
|
||||
else {
|
||||
Key = line.Substring(0, separatorIndex);
|
||||
Data = line.Substring(separatorIndex + 1);
|
||||
Key = line[..separatorIndex];
|
||||
Data = line[(separatorIndex + 1)..];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Platforms>x86</Platforms>
|
||||
<LangVersion>9</LangVersion>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
@ -110,11 +110,11 @@ private static T Validate<T>(T? obj, string name) where T : class {
|
||||
}
|
||||
|
||||
public sealed class AppBuilder {
|
||||
public IAppSetup? Setup { get; set; }
|
||||
public IAppErrorHandler? ErrorHandler { get; set; }
|
||||
public IAppSystemHandler? SystemHandler { get; set; }
|
||||
public IAppMessageDialogs? MessageDialogs { get; set; }
|
||||
public IAppFileDialogs? FileDialogs { get; set; }
|
||||
public IAppSetup? Setup { get; init; }
|
||||
public IAppErrorHandler? ErrorHandler { get; init; }
|
||||
public IAppSystemHandler? SystemHandler { get; init; }
|
||||
public IAppMessageDialogs? MessageDialogs { get; init; }
|
||||
public IAppFileDialogs? FileDialogs { get; init; }
|
||||
|
||||
internal static AppBuilder? Instance { get; private set; }
|
||||
|
||||
|
@ -34,7 +34,7 @@ public static void LoadResourceRewriteRules(string rules) {
|
||||
if (resourceType is ResourceType.Script or ResourceType.Stylesheet && TweetDeckHashes.Count > 0) {
|
||||
Match match = TweetDeckResourceUrl.Match(url);
|
||||
|
||||
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out string hash)) {
|
||||
if (match.Success && TweetDeckHashes.TryGetValue($"{match.Groups[1]}.{match.Groups[3]}", out var hash)) {
|
||||
if (match.Groups[2].Value == hash) {
|
||||
App.Logger.Debug("[RequestHandlerBase] Accepting " + url);
|
||||
}
|
||||
|
@ -84,7 +84,7 @@ public void SaveImages(string[] urls, string? author) {
|
||||
var settings = new SaveFileDialogSettings {
|
||||
DialogTitle = oneImage ? "Save Image" : "Save Images",
|
||||
OverwritePrompt = oneImage,
|
||||
FileName = qualityIndex == -1 ? filename : $"{author} {Path.ChangeExtension(filename, null)} {firstImageLink.Substring(qualityIndex + 1)}".Trim() + ext,
|
||||
FileName = qualityIndex == -1 ? filename : $"{author} {Path.ChangeExtension(filename, null)} {firstImageLink[(qualityIndex + 1)..]}".Trim() + ext,
|
||||
Filters = new [] { new FileDialogFilter(oneImage ? "Image" : "Images", string.IsNullOrEmpty(ext) ? Array.Empty<string>() : new [] { ext }) }
|
||||
};
|
||||
|
||||
|
@ -56,7 +56,9 @@ public override void Show(IContextMenuBuilder menu, Context context) {
|
||||
menu.AddSeparator();
|
||||
|
||||
if (context.Notification is {} notification) {
|
||||
AddCopyAction(menu, "Copy tweet address", notification.TweetUrl);
|
||||
if (!string.IsNullOrEmpty(notification.TweetUrl)) {
|
||||
AddCopyAction(menu, "Copy tweet address", notification.TweetUrl);
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(notification.QuoteUrl)) {
|
||||
AddCopyAction(menu, "Copy quoted tweet address", notification.QuoteUrl!);
|
||||
@ -72,7 +74,7 @@ public override void Dispose() {
|
||||
this.browserComponent.PageLoadEnd -= BrowserComponentOnPageLoadEnd;
|
||||
}
|
||||
|
||||
private void BrowserComponentOnPageLoadEnd(object sender, PageLoadEventArgs e) {
|
||||
private void BrowserComponentOnPageLoadEnd(object? sender, PageLoadEventArgs e) {
|
||||
string url = e.Url;
|
||||
|
||||
if (TwitterUrls.IsTweetDeck(url) && url != BlankURL) {
|
||||
|
@ -93,7 +93,7 @@ public override int 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);
|
||||
}
|
||||
|
||||
|
@ -48,17 +48,17 @@ internal int GetTokenFromPlugin(Plugin plugin) {
|
||||
}
|
||||
|
||||
internal Plugin? GetPluginFromToken(int token) {
|
||||
return tokens.TryGetValue(token, out Plugin plugin) ? plugin : null;
|
||||
return tokens.TryGetValue(token, out var plugin) ? plugin : null;
|
||||
}
|
||||
|
||||
// Event handlers
|
||||
|
||||
private void manager_Reloaded(object sender, PluginErrorEventArgs e) {
|
||||
private void manager_Reloaded(object? sender, PluginErrorEventArgs e) {
|
||||
tokens.Clear();
|
||||
fileCache.Clear();
|
||||
}
|
||||
|
||||
private void Config_PluginChangedState(object sender, PluginChangedStateEventArgs e) {
|
||||
private void Config_PluginChangedState(object? sender, PluginChangedStateEventArgs e) {
|
||||
if (!e.IsEnabled) {
|
||||
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) {
|
||||
string fullPath = GetFullPathOrThrow(token, folder, path);
|
||||
|
||||
if (readCached && fileCache.TryGetValue(token, folder, path, out string cachedContents)) {
|
||||
if (readCached && fileCache.TryGetValue(token, folder, path, out var cachedContents)) {
|
||||
return cachedContents;
|
||||
}
|
||||
|
||||
@ -161,7 +161,7 @@ public void Clear() {
|
||||
cache.Clear();
|
||||
}
|
||||
|
||||
public bool TryGetValue(int token, PluginFolder folder, string path, out string contents) {
|
||||
public bool TryGetValue(int token, PluginFolder folder, string path, [MaybeNullWhen(false)] out string contents) {
|
||||
return cache.TryGetValue(token, Key(folder, path), out contents);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -55,7 +55,7 @@ private static Plugin FromFolder(string name, string pathRoot, string pathData,
|
||||
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)) {
|
||||
if (line[0] == '[' && line[line.Length - 1] == ']') {
|
||||
if (line[0] == '[' && line[^1] == ']') {
|
||||
if (currentTag != null) {
|
||||
SetProperty(builder, currentTag, currentContents);
|
||||
}
|
||||
@ -106,7 +106,7 @@ private static void SetProperty(Plugin.Builder builder, string tag, string value
|
||||
builder.ConfigDefault = value;
|
||||
break;
|
||||
case "REQUIRES":
|
||||
builder.RequiredVersion = Version.TryParse(value, out Version version) ? version : throw new FormatException($"Invalid required minimum version: {value}");
|
||||
builder.RequiredVersion = Version.TryParse(value, out var version) ? version : throw new FormatException($"Invalid required minimum version: {value}");
|
||||
break;
|
||||
default:
|
||||
throw new FormatException($"Invalid metadata tag: {tag}");
|
||||
|
@ -102,7 +102,7 @@ internal void Execute(PluginEnvironment environment, IScriptExecutor executor) {
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -71,12 +71,12 @@ public override void Dispose() {
|
||||
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");
|
||||
isBrowserReady = true;
|
||||
}
|
||||
|
||||
private void browserComponent_PageLoadStart(object sender, PageLoadEventArgs e) {
|
||||
private void browserComponent_PageLoadStart(object? sender, PageLoadEventArgs e) {
|
||||
string url = e.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;
|
||||
|
||||
if (TwitterUrls.IsTweetDeck(url)) {
|
||||
@ -105,7 +105,7 @@ private void browserComponent_PageLoadEnd(object sender, PageLoadEventArgs e) {
|
||||
browserComponent.RunBootstrap("update");
|
||||
}
|
||||
|
||||
private void pluginManager_Reloaded(object sender, PluginErrorEventArgs e) {
|
||||
private void pluginManager_Reloaded(object? sender, PluginErrorEventArgs e) {
|
||||
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));
|
||||
}
|
||||
@ -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) {
|
||||
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) {
|
||||
var updateChecker = (UpdateChecker) sender;
|
||||
private void updateChecker_CheckFinished(object? sender, UpdateCheckEventArgs e) {
|
||||
var updateChecker = (UpdateChecker) sender!;
|
||||
|
||||
e.Result.Handle(update => {
|
||||
string tag = update.VersionTag;
|
||||
@ -144,7 +144,7 @@ private void updateChecker_CheckFinished(object sender, UpdateCheckEventArgs e)
|
||||
ignoreUpdateCheckError = true;
|
||||
}
|
||||
|
||||
private void UserConfiguration_GeneralEventHandler(object sender, EventArgs e) {
|
||||
private void UserConfiguration_GeneralEventHandler(object? sender, EventArgs e) {
|
||||
UpdatePropertyObject();
|
||||
}
|
||||
|
||||
@ -236,6 +236,11 @@ private sealed class ResourceRequestHandler : BaseResourceRequestHandler {
|
||||
private const string UrlVersionCheck = "/web/dist/version.json";
|
||||
|
||||
public override RequestHandleResult? Handle(string url, ResourceType resourceType) {
|
||||
var result = base.Handle(url, resourceType);
|
||||
if (result != null) {
|
||||
return result;
|
||||
}
|
||||
|
||||
switch (resourceType) {
|
||||
case ResourceType.MainFrame when url.EndsWithOrdinal("://twitter.com/"):
|
||||
return new RequestHandleResult.Redirect(TwitterUrls.TweetDeck); // redirect plain twitter.com requests, fixes bugs with login 2FA
|
||||
@ -256,7 +261,7 @@ private sealed class ResourceRequestHandler : BaseResourceRequestHandler {
|
||||
return new RequestHandleResult.Redirect(url.Replace("include_entities=1", "include_entities=1&include_ext_has_nft_avatar=1"));
|
||||
|
||||
default:
|
||||
return base.Handle(url, resourceType);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,15 +24,15 @@ public void SetLink(string type, string? url) {
|
||||
|
||||
switch (type) {
|
||||
case "link":
|
||||
Link = new Link(url!, url!);
|
||||
Link = new Link(url, url);
|
||||
break;
|
||||
|
||||
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;
|
||||
|
||||
case "video":
|
||||
Media = new Media(Type.Video, url!);
|
||||
Media = new Media(Type.Video, url);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ public static bool TryParse(string url, out ImageUrl obj) {
|
||||
return false;
|
||||
}
|
||||
|
||||
string originalUrl = url.Substring(0, question);
|
||||
string originalUrl = url[..question];
|
||||
|
||||
obj = new ImageUrl(Path.HasExtension(originalUrl) ? originalUrl : originalUrl + imageExtension, imageQuality);
|
||||
return true;
|
||||
|
@ -49,11 +49,11 @@ public enum UrlType {
|
||||
}
|
||||
|
||||
public static UrlType Check(string url) {
|
||||
if (url.Contains("\"")) {
|
||||
if (url.Contains('"')) {
|
||||
return UrlType.Invalid;
|
||||
}
|
||||
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out Uri uri)) {
|
||||
if (Uri.TryCreate(url, UriKind.Absolute, out var uri)) {
|
||||
string scheme = uri.Scheme;
|
||||
|
||||
if (scheme == Uri.UriSchemeHttps || scheme == Uri.UriSchemeHttp || scheme == Uri.UriSchemeFtp || scheme == Uri.UriSchemeMailto) {
|
||||
|
@ -16,7 +16,7 @@ static ConfigManager() {
|
||||
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}",
|
||||
ConvertToObject = static value => {
|
||||
int[] elements = StringUtils.ParseInts(value.Substring(1), ' ');
|
||||
int[] elements = StringUtils.ParseInts(value[1..], ' ');
|
||||
|
||||
return new WindowState {
|
||||
Bounds = new Rectangle(elements[0], elements[1], elements[2], elements[3]),
|
||||
|
@ -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("[").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");
|
||||
|
||||
try {
|
||||
|
@ -37,7 +37,7 @@ public void Dispose() {
|
||||
InteractionManager.Dispose();
|
||||
}
|
||||
|
||||
private void timer_Elapsed(object sender, ElapsedEventArgs e) {
|
||||
private void timer_Elapsed(object? sender, ElapsedEventArgs e) {
|
||||
Check(false);
|
||||
}
|
||||
|
||||
|
@ -26,7 +26,7 @@ public void ClearUpdate() {
|
||||
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;
|
||||
|
||||
if (nextUpdate != null && !nextUpdate.Equals(foundUpdate)) {
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<LangVersion>9</LangVersion>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
@ -57,7 +57,7 @@ public void SetValue(string key, string value) {
|
||||
}
|
||||
|
||||
public string? GetValue(string key) {
|
||||
return values.TryGetValue(key.ToLower(), out string val) ? val : null;
|
||||
return values.TryGetValue(key.ToLower(), out var val) ? val : null;
|
||||
}
|
||||
|
||||
public void RemoveValue(string key) {
|
||||
|
@ -11,7 +11,7 @@ namespace TweetLib.Utils.Collections {
|
||||
/// <typeparam name="V">The type of the values.</typeparam>
|
||||
[SuppressMessage("ReSharper", "UnusedMethodReturnValue.Global")]
|
||||
[SuppressMessage("ReSharper", "UnusedMember.Global")]
|
||||
public sealed class TwoKeyDictionary<K1, K2, V> {
|
||||
public sealed class TwoKeyDictionary<K1, K2, V> where K1 : notnull where K2 : notnull {
|
||||
private readonly Dictionary<K1, Dictionary<K2, V>> dict;
|
||||
private readonly int innerCapacity;
|
||||
|
||||
@ -41,7 +41,7 @@ public TwoKeyDictionary(int outerCapacity, int innerCapacity) {
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
@ -67,7 +67,7 @@ public IEnumerable<V> InnerValues {
|
||||
/// Throws if the key pair already exists.
|
||||
/// </summary>
|
||||
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));
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ public bool Contains(K1 outerKey) {
|
||||
/// Determines whether the dictionary contains the key pair.
|
||||
/// </summary>
|
||||
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>
|
||||
@ -122,8 +122,8 @@ public int Count(K1 outerKey) {
|
||||
/// Gets the value associated with the key pair.
|
||||
/// Returns true if the key pair was present.
|
||||
/// </summary>
|
||||
public bool TryGetValue(K1 outerKey, K2 innerKey, out V value) {
|
||||
if (dict.TryGetValue(outerKey, out Dictionary<K2, V> innerDict)) {
|
||||
public bool TryGetValue(K1 outerKey, K2 innerKey, [MaybeNullWhen(false)] out V value) {
|
||||
if (dict.TryGetValue(outerKey, out Dictionary<K2, V>? innerDict)) {
|
||||
return innerDict.TryGetValue(innerKey, out value);
|
||||
}
|
||||
else {
|
||||
@ -145,7 +145,7 @@ public bool Remove(K1 outerKey) {
|
||||
/// Returns true if the key pair was present.
|
||||
/// </summary>
|
||||
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) {
|
||||
dict.Remove(outerKey);
|
||||
}
|
||||
|
@ -2,9 +2,9 @@
|
||||
|
||||
namespace TweetLib.Utils.Dialogs {
|
||||
public sealed class SaveFileDialogSettings {
|
||||
public string DialogTitle { get; set; } = "Save File";
|
||||
public bool OverwritePrompt { get; set; } = true;
|
||||
public string? FileName { get; set; }
|
||||
public IReadOnlyList<FileDialogFilter>? Filters { get; set; }
|
||||
public string DialogTitle { get; init; } = "Save File";
|
||||
public bool OverwritePrompt { get; init; } = true;
|
||||
public string? FileName { get; init; }
|
||||
public IReadOnlyList<FileDialogFilter>? Filters { get; init; }
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
@ -38,8 +38,8 @@ public override string ToString() {
|
||||
return cultureInfo.DisplayName == cultureInfo.NativeName ? capitalizedName : $"{capitalizedName}, {cultureInfo.DisplayName}";
|
||||
}
|
||||
|
||||
public int CompareTo(Language other) {
|
||||
return string.Compare(Name, other.Name, false, CultureInfo.InvariantCulture);
|
||||
public int CompareTo(Language? other) {
|
||||
return string.Compare(Name, other?.Name, false, CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -134,7 +134,7 @@ public sealed class Entry {
|
||||
public string[] KeyValue {
|
||||
get {
|
||||
int index = Identifier.IndexOf(KeySeparator);
|
||||
return index == -1 ? StringUtils.EmptyArray : Identifier.Substring(index + 1).Split(KeySeparator);
|
||||
return index == -1 ? StringUtils.EmptyArray : Identifier[(index + 1)..].Split(KeySeparator);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,12 +2,12 @@
|
||||
|
||||
namespace TweetLib.Utils.Serialization.Converters {
|
||||
public sealed class BasicTypeConverter<T> : ITypeConverter {
|
||||
public Func<T, string>? ConvertToString { get; set; }
|
||||
public Func<string, T>? ConvertToObject { get; set; }
|
||||
public Func<T, string>? ConvertToString { get; init; }
|
||||
public Func<string, T>? ConvertToObject { get; init; }
|
||||
|
||||
bool ITypeConverter.TryWriteType(Type type, object value, out string? converted) {
|
||||
bool ITypeConverter.TryWriteType(Type type, object? value, out string? converted) {
|
||||
try {
|
||||
converted = ConvertToString!((T) value);
|
||||
converted = ConvertToString!((T) value!);
|
||||
return true;
|
||||
} catch {
|
||||
converted = null;
|
||||
|
@ -6,18 +6,17 @@ sealed class ClrTypeConverter : ITypeConverter {
|
||||
|
||||
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)) {
|
||||
case TypeCode.Boolean:
|
||||
converted = value.ToString();
|
||||
converted = value!.ToString();
|
||||
return true;
|
||||
|
||||
case TypeCode.Int32:
|
||||
converted = ((int) value).ToString(); // cast required for enums
|
||||
converted = ((int) value!).ToString(); // cast required for enums
|
||||
return true;
|
||||
|
||||
case TypeCode.String:
|
||||
// ReSharper disable once ConstantConditionalAccessQualifier
|
||||
converted = value?.ToString();
|
||||
return true;
|
||||
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
namespace TweetLib.Utils.Serialization {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ private static string UnescapeStream(StreamReader reader) {
|
||||
break;
|
||||
}
|
||||
else {
|
||||
build.Append(data.Substring(index, nextIndex - index));
|
||||
build.Append(data[index..nextIndex]);
|
||||
|
||||
char next = data[nextIndex + 1];
|
||||
|
||||
@ -47,7 +47,7 @@ private static string UnescapeStream(StreamReader reader) {
|
||||
}
|
||||
}
|
||||
|
||||
return build.Append(data.Substring(index)).ToString();
|
||||
return build.Append(data[index..]).ToString();
|
||||
}
|
||||
|
||||
private readonly TypeConverterRegistry converterRegistry;
|
||||
@ -114,7 +114,7 @@ public void Read(string file, T obj) {
|
||||
int nextPos = contents.IndexOf(NewLineReal, currentPos);
|
||||
|
||||
if (nextPos == -1) {
|
||||
line = contents.Substring(currentPos);
|
||||
line = contents[currentPos..];
|
||||
currentPos = -1;
|
||||
|
||||
if (string.IsNullOrEmpty(line)) {
|
||||
@ -133,10 +133,10 @@ public void Read(string file, T obj) {
|
||||
continue;
|
||||
}
|
||||
|
||||
string property = line.Substring(0, space);
|
||||
string value = UnescapeLine(line.Substring(space + 1));
|
||||
string property = line[..space];
|
||||
string value = UnescapeLine(line[(space + 1)..]);
|
||||
|
||||
if (props.TryGetValue(property, out PropertyInfo info)) {
|
||||
if (props.TryGetValue(property, out var info)) {
|
||||
var type = info.PropertyType;
|
||||
var converter = converterRegistry.TryGet(type) ?? ClrTypeConverter.Instance;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using TweetLib.Utils.Static;
|
||||
@ -91,7 +90,6 @@ public UnlockResult Unlock() {
|
||||
return UnlockResult.Success;
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "PossibleNullReferenceException")]
|
||||
private LockResult DetermineLockingProcessOrFail(Exception originalException) {
|
||||
try {
|
||||
int pid;
|
||||
@ -110,7 +108,7 @@ private LockResult DetermineLockingProcessOrFail(Exception originalException) {
|
||||
var foundProcess = Process.GetProcessById(pid);
|
||||
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);
|
||||
}
|
||||
else {
|
||||
|
@ -40,7 +40,7 @@ public static (string before, string after)? SplitInTwo(string str, char search,
|
||||
return null;
|
||||
}
|
||||
|
||||
return (str.Substring(0, index), str.Substring(index + 1));
|
||||
return (str[..index], str[(index + 1)..]);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -49,7 +49,7 @@ public static (string before, string after)? SplitInTwo(string str, char search,
|
||||
/// </summary>
|
||||
public static string ExtractBefore(string str, char search, int startIndex = 0) {
|
||||
int index = str.IndexOf(search, startIndex);
|
||||
return index == -1 ? str : str.Substring(0, index);
|
||||
return index == -1 ? str : str[..index];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -10,10 +10,12 @@ public static class WebUtils {
|
||||
private static bool hasMicrosoftBeenBroughtTo2008Yet;
|
||||
private static bool hasSystemProxyBeenEnabled;
|
||||
|
||||
private static void EnsureTLS12() {
|
||||
private static void EnsureModernTLS() {
|
||||
if (!hasMicrosoftBeenBroughtTo2008Yet) {
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12;
|
||||
#pragma warning disable CS0618
|
||||
ServicePointManager.SecurityProtocol |= SecurityProtocolType.Tls12 | SecurityProtocolType.Tls13;
|
||||
ServicePointManager.SecurityProtocol &= ~(SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls | SecurityProtocolType.Tls11);
|
||||
#pragma warning restore CS0618
|
||||
hasMicrosoftBeenBroughtTo2008Yet = true;
|
||||
}
|
||||
}
|
||||
@ -28,7 +30,7 @@ public static void EnableSystemProxy() {
|
||||
}
|
||||
|
||||
public static WebClient NewClient(string? userAgent = null) {
|
||||
EnsureTLS12();
|
||||
EnsureModernTLS();
|
||||
|
||||
WebClient client = new WebClient();
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>netstandard2.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Configurations>Debug;Release</Configurations>
|
||||
<Platforms>x86;x64</Platforms>
|
||||
<LangVersion>9</LangVersion>
|
||||
<LangVersion>10</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
|
||||
</PropertyGroup>
|
||||
|
@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Platforms>x86</Platforms>
|
||||
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
@ -47,7 +47,7 @@ module RegexAccount =
|
||||
Assert.True(isMatch("https://twitter.com/" + name))
|
||||
|
||||
module Match =
|
||||
let extract str = TwitterUrls.RegexAccount.Match(str).Groups.[1].Value
|
||||
let extract str = TwitterUrls.RegexAccount.Match(str).Groups[1].Value
|
||||
|
||||
[<Fact>]
|
||||
let ``extracts account name from simple URL`` () =
|
||||
|
@ -1,8 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net472</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Platforms>x86</Platforms>
|
||||
<RuntimeIdentifiers>win7-x86;linux-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user