mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-17 23:10:30 +00:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7a21c7bc45 | ||
|
|
9358aa5d1f | ||
|
|
6c9e9da740 | ||
|
|
a78a607f73 | ||
|
|
879323ed20 | ||
|
|
4e081b26a1 | ||
|
|
6cd3459dd4 | ||
|
|
482576f16d | ||
|
|
f63dbffee3 | ||
|
|
d9cdc806fe | ||
|
|
36a78b55a4 | ||
|
|
ab983099cc | ||
|
|
aab8f5f0b5 | ||
|
|
6b0bf0f9c1 | ||
|
|
3968130e15 | ||
|
|
dd488a1c9c | ||
|
|
de3c332803 | ||
|
|
684b7b6e28 | ||
|
|
d36f16e205 | ||
|
|
c3ab3d767f | ||
|
|
a790c34976 | ||
|
|
be208eab92 | ||
|
|
c9598709d6 | ||
|
|
367e7f841c | ||
|
|
8c988cfed6 | ||
|
|
5b15535661 | ||
|
|
e0c692e0ab | ||
|
|
1ddf6b34e2 | ||
|
|
8476c8c221 | ||
|
|
24ec938565 | ||
|
|
0dbfba275e | ||
|
|
55c5c70b52 | ||
|
|
17aa8297da | ||
|
|
b0aa2a9104 | ||
|
|
898c402dfc | ||
|
|
9258819c84 | ||
|
|
b5db0b511c | ||
|
|
7c6804449c | ||
|
|
ff28e2bf8f | ||
|
|
3510cd1be0 | ||
|
|
5320e8d9cc | ||
|
|
6ab9e2958d | ||
|
|
e47f286bda | ||
|
|
84b1599ca6 | ||
|
|
c5396a8ec8 | ||
|
|
9a9f18184b | ||
|
|
10d97e16e3 | ||
|
|
5ece500396 | ||
|
|
1a311513ca | ||
|
|
6e0e4835d1 | ||
|
|
ae4a784c3a | ||
|
|
287be65e7f | ||
|
|
476653a6cc | ||
|
|
407496efd6 | ||
|
|
9995b807f9 | ||
|
|
a6f4692b75 | ||
|
|
024b7ff824 | ||
|
|
68e46096ad | ||
|
|
d9f5f60854 | ||
|
|
82ccd4ddce | ||
|
|
5f69b337a6 | ||
|
|
dbbb6802d4 | ||
|
|
459cb44ff4 | ||
|
|
c1ebc813d5 | ||
|
|
948c42055b | ||
|
|
06d049d882 | ||
|
|
04f5e91e92 | ||
|
|
02a479ba13 | ||
|
|
01b99d20f6 | ||
|
|
9c6cd7f692 | ||
|
|
7899829dc7 | ||
|
|
d0693d362a | ||
|
|
227066f355 | ||
|
|
348c43b259 | ||
|
|
a23b7e1594 | ||
|
|
1c7952f8dd | ||
|
|
f0ef4c6ba6 | ||
|
|
eb71b640c5 | ||
|
|
6b7a0ff1ce | ||
|
|
d08740b6d7 | ||
|
|
70e3649e60 | ||
|
|
24d79f9b20 | ||
|
|
43c2ae6746 | ||
|
|
4bd2a3ec7f | ||
|
|
a90b375a72 | ||
|
|
9f1e562a19 | ||
|
|
4edfcff2e0 | ||
|
|
d4b48ab235 | ||
|
|
f1e5c31110 | ||
|
|
32c4522b47 | ||
|
|
fc760e1a84 | ||
|
|
2a92bf4dec | ||
|
|
716b253a04 | ||
|
|
0384365315 | ||
|
|
45dc910e01 | ||
|
|
f7e57e7d39 | ||
|
|
ace4151bbc | ||
|
|
088c3a35ed | ||
|
|
608bece8dc | ||
|
|
119caebfa8 | ||
|
|
2e0771b8d9 | ||
|
|
a08080a2ce | ||
|
|
d4bcdfde3e | ||
|
|
249ebfb590 | ||
|
|
56fc50e5ad | ||
|
|
5d8db36753 | ||
|
|
54f493467e | ||
|
|
7ed39db953 | ||
|
|
cc28d7520e | ||
|
|
109b307d0f | ||
|
|
178ecc2e4a | ||
|
|
ddfa7220d8 | ||
|
|
812523baef | ||
|
|
c5fd423a78 | ||
|
|
0b6c308d2e | ||
|
|
d6ef5e5404 | ||
|
|
9c304b8965 | ||
|
|
05c5a7fc30 | ||
|
|
2a2d4f09f1 | ||
|
|
ce5cd7bc8f | ||
|
|
a072a7b7d6 | ||
|
|
8a52f4fbbb | ||
|
|
ba07405d9a | ||
|
|
6239457f01 | ||
|
|
584fe4bd37 | ||
|
|
1f4fa2ed90 | ||
|
|
d24731999a | ||
|
|
1a3e82a1b0 | ||
|
|
f45b10f2ff | ||
|
|
2097fea6a0 | ||
|
|
c4bdb39c6d | ||
|
|
2f2b411293 | ||
|
|
860b979afb | ||
|
|
401d3f65f5 | ||
|
|
051cf043f3 | ||
|
|
4ebb4cfd9e | ||
|
|
d58a2d2717 | ||
|
|
51b764a39f | ||
|
|
0f94a05a69 | ||
|
|
16fd3c067f | ||
|
|
e13881763c | ||
|
|
90241c6076 | ||
|
|
d020a97209 | ||
|
|
d9ceea448f | ||
|
|
ec01846653 | ||
|
|
7d3c0a9d13 | ||
|
|
de88e3072b | ||
|
|
5e2ad8eb19 | ||
|
|
127107a96c | ||
|
|
1587c6facb | ||
|
|
042fadca28 | ||
|
|
4a9e6f6cc6 | ||
|
|
b7ac24eb7b | ||
|
|
c78d26a701 | ||
|
|
53614661c1 | ||
|
|
ea8c300a1a | ||
|
|
bb91bf3918 | ||
|
|
b9f72c293d | ||
|
|
7a14a394c2 | ||
|
|
dbf7148fbe | ||
|
|
59d51d1b15 | ||
|
|
acff4602cd | ||
|
|
44f135eb14 | ||
|
|
f4945c024e | ||
|
|
a329e2a3da | ||
|
|
608edf2569 | ||
|
|
f203e02a45 | ||
|
|
bdcc00af1f | ||
|
|
21d004fb26 | ||
|
|
d4ae307676 | ||
|
|
0a9993e85a | ||
|
|
1f2269dcf2 | ||
|
|
bb73916af0 | ||
|
|
12c4b7e924 | ||
|
|
c6b78b118c | ||
|
|
aaa6b6674a | ||
|
|
be2e173404 | ||
|
|
bfb189d55b | ||
|
|
3d503ed5ee | ||
|
|
ab01733860 |
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
|
||||
- name: Upload latest strings for translation on Crowdin
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' && matrix.configuration == 'Release' && startsWith(matrix.os, 'ubuntu-') }}
|
||||
uses: crowdin/github-action@v1.15.2
|
||||
uses: crowdin/github-action@v1.19.0
|
||||
with:
|
||||
crowdin_branch_name: main
|
||||
config: '.github/crowdin.yml'
|
||||
|
||||
4
.github/workflows/code-quality.yml
vendored
4
.github/workflows/code-quality.yml
vendored
@@ -33,13 +33,13 @@ jobs:
|
||||
show-progress: false
|
||||
|
||||
- name: Run Qodana scan
|
||||
uses: JetBrains/qodana-action@v2023.3.0
|
||||
uses: JetBrains/qodana-action@v2023.3.1
|
||||
with:
|
||||
args: --property=idea.headless.enable.statistics=false
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
||||
- name: Report Qodana results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3.22.12
|
||||
uses: github/codeql-action/upload-sarif@v3.24.4
|
||||
with:
|
||||
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
||||
|
||||
2
.github/workflows/docker-publish-main.yml
vendored
2
.github/workflows/docker-publish-main.yml
vendored
@@ -74,7 +74,7 @@ jobs:
|
||||
push: true
|
||||
|
||||
- name: Update DockerHub repository description
|
||||
uses: peter-evans/dockerhub-description@v3.4.2
|
||||
uses: peter-evans/dockerhub-description@v4.0.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||
|
||||
39
.github/workflows/publish.yml
vendored
39
.github/workflows/publish.yml
vendored
@@ -24,7 +24,7 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Setup Node.js with npm
|
||||
uses: actions/setup-node@v4.0.1
|
||||
uses: actions/setup-node@v4.0.2
|
||||
with:
|
||||
check-latest: true
|
||||
node-version: ${{ env.NODE_JS_VERSION }}
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
run: npm run-script deploy --no-progress --prefix ASF-ui
|
||||
|
||||
- name: Upload ASF-ui
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: ASF-ui
|
||||
path: ASF-ui/dist
|
||||
@@ -88,7 +88,7 @@ jobs:
|
||||
run: dotnet --info
|
||||
|
||||
- name: Download previously built ASF-ui
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ASF-ui
|
||||
path: ASF-ui/dist
|
||||
@@ -143,8 +143,17 @@ jobs:
|
||||
$ProgressPreference = 'SilentlyContinue'
|
||||
|
||||
dotnet restore
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
dotnet build ArchiSteamFarm -c "$env:CONFIGURATION" -p:ContinuousIntegrationBuild=true -p:TargetLatestRuntimePatch=false -p:UseAppHost=false --nologo
|
||||
|
||||
if ($LastExitCode -ne 0) {
|
||||
throw "Last command failed."
|
||||
}
|
||||
|
||||
- name: Prepare ArchiSteamFarm.OfficialPlugins.SteamTokenDumper on Unix
|
||||
if: startsWith(matrix.os, 'macos-') || startsWith(matrix.os, 'ubuntu-')
|
||||
env:
|
||||
@@ -396,7 +405,7 @@ jobs:
|
||||
}
|
||||
|
||||
- name: Upload ASF-${{ matrix.variant }}
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: ${{ matrix.os }}_ASF-${{ matrix.variant }}
|
||||
path: out/ASF-${{ matrix.variant }}.zip
|
||||
@@ -416,49 +425,49 @@ jobs:
|
||||
show-progress: false
|
||||
|
||||
- name: Download ASF-generic artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-generic
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-arm artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-linux-arm
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-arm64 artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-linux-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-linux-x64 artifact from ubuntu-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: ubuntu-latest_ASF-linux-x64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-osx-arm64 artifact from macos-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: macos-latest_ASF-osx-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-osx-x64 artifact from macos-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: macos-latest_ASF-osx-x64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-win-arm64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: windows-latest_ASF-win-arm64
|
||||
path: out
|
||||
|
||||
- name: Download ASF-win-x64 artifact from windows-latest
|
||||
uses: actions/download-artifact@v4.1.0
|
||||
uses: actions/download-artifact@v4.1.2
|
||||
with:
|
||||
name: windows-latest_ASF-win-x64
|
||||
path: out
|
||||
@@ -481,19 +490,19 @@ jobs:
|
||||
)
|
||||
|
||||
- name: Upload SHA512SUMS
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: SHA512SUMS
|
||||
path: out/SHA512SUMS
|
||||
|
||||
- name: Upload SHA512SUMS.sign
|
||||
uses: actions/upload-artifact@v4.0.0
|
||||
uses: actions/upload-artifact@v4.3.1
|
||||
with:
|
||||
name: SHA512SUMS.sign
|
||||
path: out/SHA512SUMS.sign
|
||||
|
||||
- name: Create ArchiSteamFarm GitHub release
|
||||
uses: ncipollo/release-action@v1.13.0
|
||||
uses: ncipollo/release-action@v1.14.0
|
||||
with:
|
||||
artifacts: "out/*"
|
||||
bodyFile: .github/RELEASE_TEMPLATE.md
|
||||
|
||||
2
.github/workflows/translations.yml
vendored
2
.github/workflows/translations.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
git reset --hard origin/master
|
||||
|
||||
- name: Download latest translations from Crowdin
|
||||
uses: crowdin/github-action@v1.15.2
|
||||
uses: crowdin/github-action@v1.19.0
|
||||
with:
|
||||
upload_sources: false
|
||||
download_translations: true
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: d9c38ab843...cd1173a0d6
@@ -6,7 +6,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,16 +21,17 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
@@ -45,31 +46,35 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection, IBotFriendRequest, IBotMessage, IBotModules, IBotTradeOffer {
|
||||
// This is used for identification purposes, typically you want to use a friendly name of your plugin here, such as the name of your main class
|
||||
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public string Name => nameof(ExamplePlugin);
|
||||
|
||||
// This will be displayed to the user and written in the log file, typically you should point it to the version of your library, but alternatively you can do some more advanced logic if you'd like to
|
||||
// Please note that this property can have direct dependencies only on structures that were initialized by the constructor, as it's possible to be called before OnLoaded() takes place
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public Version Version => typeof(ExamplePlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
// Plugins can expose custom properties for our GET /Api/Plugins API call, simply annotate them with [JsonProperty] (or keep public)
|
||||
[JsonProperty]
|
||||
public bool CustomIsEnabledField { get; private set; } = true;
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public bool CustomIsEnabledField { get; private init; } = true;
|
||||
|
||||
// This method, apart from being called before any bot initialization takes place, allows you to read custom global config properties that are not recognized by ASF
|
||||
// Thanks to that, you can extend default ASF config with your own stuff, then parse it here in order to customize your plugin during runtime
|
||||
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
|
||||
// In addition to that, this method also guarantees that all plugins were already OnLoaded(), which allows cross-plugins-communication to be possible
|
||||
public Task OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
||||
public Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||
if (additionalConfigProperties == null) {
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
|
||||
foreach ((string configProperty, JsonElement configValue) in additionalConfigProperties) {
|
||||
// It's a good idea to prefix your custom properties with the name of your plugin, so there will be no possible conflict of ASF or other plugins using the same name, neither now or in the future
|
||||
switch (configProperty) {
|
||||
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.Type == JTokenType.Boolean:
|
||||
bool exampleBooleanValue = configValue.Value<bool>();
|
||||
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of: {exampleBooleanValue}");
|
||||
case $"{nameof(ExamplePlugin)}TestProperty" when configValue.ValueKind == JsonValueKind.True:
|
||||
ASF.ArchiLogger.LogGenericInfo($"{nameof(ExamplePlugin)}TestProperty boolean property has been found with a value of true");
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -135,7 +140,7 @@ internal sealed class ExamplePlugin : IASF, IBot, IBotCommand2, IBotConnection,
|
||||
// Keep in mind that, as noted in the interface, additionalConfigProperties can be null if no custom, unrecognized properties are found by ASF, you should handle that case appropriately
|
||||
// Also keep in mind that this function can be called multiple times, e.g. when user edits their bot configs during runtime
|
||||
// Take a look at OnASFInit() for example parsing code
|
||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||
// For example, we'll ensure that every bot starts paused regardless of Paused property, in order to do this, we'll just call Pause here in InitModules()
|
||||
// Thanks to the fact that this method is called with each bot config reload, we'll ensure that our bot stays paused even if it'd get unpaused otherwise
|
||||
bot.ArchiLogger.LogGenericInfo("Pausing this bot as asked from the plugin");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,15 +21,17 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class MeowResponse {
|
||||
[JsonProperty("url", Required = Required.Always)]
|
||||
internal readonly Uri URL = null!;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("url")]
|
||||
[JsonRequired]
|
||||
internal Uri URL { get; private init; } = null!;
|
||||
|
||||
[JsonConstructor]
|
||||
private MeowResponse() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,8 +20,10 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Runtime;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
@@ -38,8 +40,12 @@ internal sealed class PeriodicGCPlugin : IPlugin {
|
||||
private static readonly object LockObject = new();
|
||||
private static readonly Timer PeriodicGCTimer = new(PerformGC);
|
||||
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public string Name => nameof(PeriodicGCPlugin);
|
||||
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public Version Version => typeof(PeriodicGCPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public Task OnLoaded() {
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
<PackageReference Include="AngleSharp.XPath" IncludeAssets="compile" />
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,11 +20,12 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
||||
|
||||
public sealed class SignInWithSteamRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public Uri RedirectURL { get; private set; } = null!;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
public Uri RedirectURL { get; private init; } = null!;
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,13 +20,14 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam.Data;
|
||||
|
||||
public sealed class SignInWithSteamResponse {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
public Uri ReturnURL { get; private set; }
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
public Uri ReturnURL { get; private init; }
|
||||
|
||||
internal SignInWithSteamResponse(Uri returnURL) {
|
||||
ArgumentNullException.ThrowIfNull(returnURL);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,7 +20,9 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
@@ -31,8 +33,12 @@ namespace ArchiSteamFarm.CustomPlugins.SignInWithSteam;
|
||||
[Export(typeof(IPlugin))]
|
||||
[UsedImplicitly]
|
||||
internal sealed class SignInWithSteamPlugin : IPlugin {
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public string Name => nameof(SignInWithSteamPlugin);
|
||||
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public Version Version => typeof(SignInWithSteamPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public Task OnLoaded() {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -114,6 +114,21 @@ internal static class Backend {
|
||||
return Utilities.GenerateChecksumFor(bytes);
|
||||
}
|
||||
|
||||
internal static async Task<HttpStatusCode?> GetLicenseStatus(Guid licenseID, WebBrowser webBrowser) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty);
|
||||
ArgumentNullException.ThrowIfNull(webBrowser);
|
||||
|
||||
Uri request = new(ArchiNet.URL, "/Api/Licenses/Status");
|
||||
|
||||
Dictionary<string, string> headers = new(1, StringComparer.Ordinal) {
|
||||
{ "X-License-Key", licenseID.ToString("N") }
|
||||
};
|
||||
|
||||
ObjectResponse<GenericResponse>? response = await webBrowser.UrlGetToJsonObject<GenericResponse>(request, headers, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors | WebBrowser.ERequestOptions.AllowInvalidBodyOnErrors).ConfigureAwait(false);
|
||||
|
||||
return response?.StatusCode;
|
||||
}
|
||||
|
||||
internal static async Task<(HttpStatusCode StatusCode, ImmutableHashSet<ListedUser> Users)?> GetListedUsersForMatching(Guid licenseID, Bot bot, WebBrowser webBrowser, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> acceptedMatchableTypes) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(licenseID, Guid.Empty);
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,20 +22,22 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Collections;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
|
||||
internal sealed class BotCache : SerializableFile {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
internal readonly ConcurrentList<AssetForListing> LastAnnouncedAssetsForListing = [];
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
internal ConcurrentList<AssetForListing> LastAnnouncedAssetsForListing { get; private init; } = [];
|
||||
|
||||
internal string? LastAnnouncedTradeToken {
|
||||
get => BackingLastAnnouncedTradeToken;
|
||||
@@ -63,11 +65,27 @@ internal sealed class BotCache : SerializableFile {
|
||||
}
|
||||
}
|
||||
|
||||
[JsonProperty]
|
||||
private string? BackingLastAnnouncedTradeToken;
|
||||
internal DateTime? LastRequestAt {
|
||||
get => BackingLastRequestAt;
|
||||
|
||||
[JsonProperty]
|
||||
private string? BackingLastInventoryChecksumBeforeDeduplication;
|
||||
set {
|
||||
if (BackingLastRequestAt == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
BackingLastRequestAt = value;
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
}
|
||||
|
||||
[JsonInclude]
|
||||
private string? BackingLastAnnouncedTradeToken { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
private string? BackingLastInventoryChecksumBeforeDeduplication { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
private DateTime? BackingLastRequestAt { get; set; }
|
||||
|
||||
private BotCache(string filePath) : this() {
|
||||
ArgumentException.ThrowIfNullOrEmpty(filePath);
|
||||
@@ -84,6 +102,9 @@ internal sealed class BotCache : SerializableFile {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingLastInventoryChecksumBeforeDeduplication() => !string.IsNullOrEmpty(BackingLastInventoryChecksumBeforeDeduplication);
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeBackingLastRequestAt() => BackingLastRequestAt.HasValue;
|
||||
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeLastAnnouncedAssetsForListing() => LastAnnouncedAssetsForListing.Count > 0;
|
||||
|
||||
@@ -97,6 +118,8 @@ internal sealed class BotCache : SerializableFile {
|
||||
base.Dispose(disposing);
|
||||
}
|
||||
|
||||
protected override Task Save() => Save(this);
|
||||
|
||||
internal static async Task<BotCache> CreateOrLoad(string filePath) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(filePath);
|
||||
|
||||
@@ -115,7 +138,7 @@ internal sealed class BotCache : SerializableFile {
|
||||
return new BotCache(filePath);
|
||||
}
|
||||
|
||||
botCache = JsonConvert.DeserializeObject<BotCache>(json);
|
||||
botCache = json.ToJsonObject<BotCache>();
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,19 +22,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class AnnouncementDiffRequest : AnnouncementRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableHashSet<AssetForListing> InventoryRemoved;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ImmutableHashSet<AssetForListing> InventoryRemoved { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly string PreviousInventoryChecksum;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private string PreviousInventoryChecksum { get; init; }
|
||||
|
||||
internal AnnouncementDiffRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, IReadOnlyCollection<AssetForListing> inventoryRemoved, string previousInventoryChecksum, string? nickname = null, string? avatarHash = null) : base(guid, steamID, inventory, inventoryChecksum, matchableTypes, totalInventoryCount, matchEverything, maxTradeHoldDuration, tradeToken, nickname, avatarHash) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,47 +22,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal class AnnouncementRequest {
|
||||
[JsonProperty]
|
||||
private readonly string? AvatarHash;
|
||||
[JsonInclude]
|
||||
private string? AvatarHash { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private Guid Guid { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableHashSet<AssetForListing> Inventory;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ImmutableHashSet<AssetForListing> Inventory { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly string InventoryChecksum;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private string InventoryChecksum { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ImmutableHashSet<Asset.EType> MatchableTypes { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly bool MatchEverything;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private bool MatchEverything { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly byte MaxTradeHoldDuration;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private byte MaxTradeHoldDuration { get; init; }
|
||||
|
||||
[JsonProperty]
|
||||
private readonly string? Nickname;
|
||||
[JsonInclude]
|
||||
private string? Nickname { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private ulong SteamID { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly uint TotalInventoryCount;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private uint TotalInventoryCount { get; init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
private readonly string TradeToken;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
private string TradeToken { get; init; }
|
||||
|
||||
internal AnnouncementRequest(Guid guid, ulong steamID, IReadOnlyCollection<AssetForListing> inventory, string inventoryChecksum, IReadOnlyCollection<Asset.EType> matchableTypes, uint totalInventoryCount, bool matchEverything, byte maxTradeHoldDuration, string tradeToken, string? nickname = null, string? avatarHash = null) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,19 +20,23 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class AssetForListing : AssetInInventory {
|
||||
[JsonProperty("i", Required = Required.Always)]
|
||||
internal readonly uint Index;
|
||||
internal string BackendHashCode => $"{Index}-{PreviousAssetID}-{AssetID}-{ClassID}-{Rarity}-{RealAppID}-{Tradable}-{Type}-{Amount}";
|
||||
|
||||
[JsonProperty("l", Required = Required.Always)]
|
||||
internal readonly ulong PreviousAssetID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("i")]
|
||||
[JsonRequired]
|
||||
internal uint Index { get; private init; }
|
||||
|
||||
internal string BackendHashCode => Index + "-" + PreviousAssetID + "-" + AssetID + "-" + ClassID + "-" + Rarity + "-" + RealAppID + "-" + Tradable + "-" + Type + "-" + Amount;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("l")]
|
||||
[JsonRequired]
|
||||
internal ulong PreviousAssetID { get; private init; }
|
||||
|
||||
internal AssetForListing(Asset asset, uint index, ulong previousAssetID) : base(asset) {
|
||||
ArgumentNullException.ThrowIfNull(asset);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,30 +20,42 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal class AssetForMatching {
|
||||
[JsonProperty("c", Required = Required.Always)]
|
||||
internal readonly ulong ClassID;
|
||||
|
||||
[JsonProperty("r", Required = Required.Always)]
|
||||
internal readonly Asset.ERarity Rarity;
|
||||
|
||||
[JsonProperty("e", Required = Required.Always)]
|
||||
internal readonly uint RealAppID;
|
||||
|
||||
[JsonProperty("t", Required = Required.Always)]
|
||||
internal readonly bool Tradable;
|
||||
|
||||
[JsonProperty("p", Required = Required.Always)]
|
||||
internal readonly Asset.EType Type;
|
||||
|
||||
[JsonProperty("a", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("a")]
|
||||
[JsonRequired]
|
||||
internal uint Amount { get; set; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("c")]
|
||||
[JsonRequired]
|
||||
internal ulong ClassID { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("r")]
|
||||
[JsonRequired]
|
||||
internal Asset.ERarity Rarity { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("e")]
|
||||
[JsonRequired]
|
||||
internal uint RealAppID { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("t")]
|
||||
[JsonRequired]
|
||||
internal bool Tradable { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("p")]
|
||||
[JsonRequired]
|
||||
internal Asset.EType Type { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
protected AssetForMatching() { }
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,14 +20,16 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal class AssetInInventory : AssetForMatching {
|
||||
[JsonProperty("d", Required = Required.Always)]
|
||||
internal readonly ulong AssetID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("d")]
|
||||
[JsonRequired]
|
||||
internal ulong AssetID { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
protected AssetInInventory() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,22 +21,20 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class BackgroundTaskResponse {
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly bool Finished;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal bool Finished { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid RequestID;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid RequestID { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private BackgroundTaskResponse() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,17 +20,19 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class HeartBeatRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid Guid { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal HeartBeatRequest(Guid guid, ulong steamID) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,24 +23,28 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class InventoriesRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid Guid { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<AssetForMatching> Inventory;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<AssetForMatching> Inventory { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal InventoriesRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset> inventory, IReadOnlyCollection<Asset.EType> matchableTypes) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,47 +21,48 @@
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class ListedUser {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<AssetInInventory> Assets = ImmutableHashSet<AssetInInventory>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<AssetInInventory> Assets { get; private init; } = ImmutableHashSet<AssetInInventory>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes = ImmutableHashSet<Asset.EType>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; } = ImmutableHashSet<Asset.EType>.Empty;
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly bool MatchEverything;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal bool MatchEverything { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly byte MaxTradeHoldDuration;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal byte MaxTradeHoldDuration { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.AllowNull)]
|
||||
internal readonly string? Nickname;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
internal string? Nickname { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly uint TotalInventoryCount;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal uint TotalGamesCount { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly string TradeToken = "";
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal uint TotalInventoryCount { get; private init; }
|
||||
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal string TradeToken { get; private init; } = "";
|
||||
|
||||
[JsonConstructor]
|
||||
private ListedUser() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,33 +20,33 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class SetPart {
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("c", Required = Required.Always)]
|
||||
internal readonly ulong ClassID;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("c")]
|
||||
[JsonRequired]
|
||||
internal ulong ClassID { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("r", Required = Required.Always)]
|
||||
internal readonly Asset.ERarity Rarity;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("r")]
|
||||
[JsonRequired]
|
||||
internal Asset.ERarity Rarity { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("e", Required = Required.Always)]
|
||||
internal readonly uint RealAppID;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("e")]
|
||||
[JsonRequired]
|
||||
internal uint RealAppID { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("p", Required = Required.Always)]
|
||||
internal readonly Asset.EType Type;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("p")]
|
||||
[JsonRequired]
|
||||
internal Asset.EType Type { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private SetPart() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,24 +22,28 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Steam.Data;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Data;
|
||||
|
||||
internal sealed class SetPartsRequest {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly Guid Guid;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal Guid Guid { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<Asset.EType> MatchableTypes;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<Asset.EType> MatchableTypes { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> RealAppIDs;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> RealAppIDs { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal SetPartsRequest(Guid guid, ulong steamID, IReadOnlyCollection<Asset.EType> matchableTypes, IReadOnlyCollection<uint> realAppIDs) {
|
||||
ArgumentOutOfRangeException.ThrowIfEqual(guid, Guid.Empty);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,8 +23,11 @@ using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Linq;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.OfficialPlugins.ItemsMatcher.Localization;
|
||||
@@ -33,8 +36,6 @@ using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Exchange;
|
||||
using ArchiSteamFarm.Steam.Integration.Callbacks;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
@@ -43,10 +44,12 @@ namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, IBotIdentity, IBotModules, IBotTradeOfferResults, IBotUserNotifications {
|
||||
internal static readonly ConcurrentDictionary<Bot, RemoteCommunication> RemoteCommunications = new();
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override string Name => nameof(ItemsMatcherPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override Version Version => typeof(ItemsMatcherPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||
@@ -83,7 +86,7 @@ internal sealed class ItemsMatcherPlugin : OfficialPlugin, IBot, IBotCommand2, I
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
||||
public async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (RemoteCommunications.TryRemove(bot, out RemoteCommunication? remoteCommunication)) {
|
||||
|
||||
@@ -66,4 +66,8 @@
|
||||
<value>{0} sets ont été matché pendant ce round.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||
<value>Certaines confirmations ont échoué, environ {0} sur les transactions {1} ont été envoyées avec succès.</value>
|
||||
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -66,4 +66,16 @@
|
||||
<value>Abbinati un totale di {0} set questo round.</value>
|
||||
<comment>{0} will be replaced by number of sets traded</comment>
|
||||
</data>
|
||||
<data name="MatchingFound" xml:space="preserve">
|
||||
<value>Abbinato un totale di {0} elementi tramite bot {1} ({2}), inviando un'offerta commerciale...</value>
|
||||
<comment>{0} will be replaced by number of items matched, {1} will be replaced by steam ID (number), {2} will be replaced by user's nickname</comment>
|
||||
</data>
|
||||
<data name="TradeOfferFailed" xml:space="preserve">
|
||||
<value>Impossibile inviare un'offerta di scambio al bot {0} ({1}), proseguendo...</value>
|
||||
<comment>{0} will be replaced by steam ID (number), {1} will be replaced by user's nickname'</comment>
|
||||
</data>
|
||||
<data name="ActivelyMatchingSomeConfirmationsFailed" xml:space="preserve">
|
||||
<value>Alcune conferme sono fallite, circa {0} su {1} sono state inviate con successo.</value>
|
||||
<comment>{0} will be replaced by amount of the trade offers that succeeded (number), {1} will be replaced by amount of the trade offers that were supposed to be sent in total (number)</comment>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,6 +20,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
@@ -27,6 +28,7 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
@@ -42,7 +44,6 @@ using ArchiSteamFarm.Steam.Storage;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
@@ -51,6 +52,7 @@ namespace ArchiSteamFarm.OfficialPlugins.ItemsMatcher;
|
||||
internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
private const string MatchActivelyTradeOfferIDsStorageKey = $"{nameof(ItemsMatcher)}-{nameof(MatchActively)}-TradeOfferIDs";
|
||||
private const byte MaxAnnouncementTTL = 60; // Maximum amount of minutes we can wait if the next announcement doesn't happen naturally
|
||||
private const byte MaxInactivityDays = 14; // How long the server is willing to keep information about us for
|
||||
private const uint MaxItemsCount = 500000; // Server is unwilling to accept more items than this
|
||||
private const byte MaxTradeOffersActive = 5; // The actual upper limit is 30, but we should use lower amount to allow some bots to react before we hit the maximum allowed
|
||||
private const byte MinAnnouncementTTL = 5; // Minimum amount of minutes we must wait before the next Announcement
|
||||
@@ -59,12 +61,12 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
private const byte MinimumSteamGuardEnabledDays = 15; // As imposed by Steam limits
|
||||
private const byte MinPersonaStateTTL = 5; // Minimum amount of minutes we must wait before requesting persona state update
|
||||
|
||||
private static readonly ImmutableHashSet<Asset.EType> AcceptedMatchableTypes = ImmutableHashSet.Create(
|
||||
private static readonly FrozenSet<Asset.EType> AcceptedMatchableTypes = new HashSet<Asset.EType>(4) {
|
||||
Asset.EType.Emoticon,
|
||||
Asset.EType.FoilTradingCard,
|
||||
Asset.EType.ProfileBackground,
|
||||
Asset.EType.TradingCard
|
||||
);
|
||||
}.ToFrozenSet();
|
||||
|
||||
private readonly Bot Bot;
|
||||
private readonly Timer? HeartBeatTimer;
|
||||
@@ -332,7 +334,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
|
||||
string inventoryChecksumBeforeDeduplication = Backend.GenerateChecksumFor(assetsForListing);
|
||||
|
||||
if ((tradeToken == BotCache.LastAnnouncedTradeToken) && !string.IsNullOrEmpty(BotCache.LastInventoryChecksumBeforeDeduplication)) {
|
||||
if (BotCache.LastRequestAt.HasValue && (DateTime.UtcNow.Subtract(BotCache.LastRequestAt.Value).TotalDays < MaxInactivityDays) && (tradeToken == BotCache.LastAnnouncedTradeToken) && !string.IsNullOrEmpty(BotCache.LastInventoryChecksumBeforeDeduplication)) {
|
||||
if (inventoryChecksumBeforeDeduplication == BotCache.LastInventoryChecksumBeforeDeduplication) {
|
||||
// We've determined our state to be the same, we can skip announce entirely and start sending heartbeats exclusively
|
||||
bool triggerImmediately = !ShouldSendHeartBeats;
|
||||
@@ -536,7 +538,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
string checksum = Backend.GenerateChecksumFor(assetsForListing);
|
||||
string? previousChecksum = BotCache.LastAnnouncedAssetsForListing.Count > 0 ? Backend.GenerateChecksumFor(BotCache.LastAnnouncedAssetsForListing) : null;
|
||||
|
||||
if ((tradeToken == BotCache.LastAnnouncedTradeToken) && (checksum == previousChecksum)) {
|
||||
if (BotCache.LastRequestAt.HasValue && (DateTime.UtcNow.Subtract(BotCache.LastRequestAt.Value).TotalDays < MaxInactivityDays) && (tradeToken == BotCache.LastAnnouncedTradeToken) && (checksum == previousChecksum)) {
|
||||
// We've determined our state to be the same, we can skip announce entirely and start sending heartbeats exclusively
|
||||
bool triggerImmediately = !ShouldSendHeartBeats;
|
||||
|
||||
@@ -654,9 +656,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
LastAnnouncement = LastHeartBeat = DateTime.UtcNow;
|
||||
ShouldSendAnnouncementEarlier = false;
|
||||
ShouldSendHeartBeats = true;
|
||||
|
||||
BotCache.LastAnnouncedAssetsForListing.ReplaceWith(assetsForListing);
|
||||
BotCache.LastAnnouncedTradeToken = tradeToken;
|
||||
BotCache.LastInventoryChecksumBeforeDeduplication = inventoryChecksumBeforeDeduplication;
|
||||
BotCache.LastRequestAt = LastHeartBeat;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -757,9 +761,11 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
LastAnnouncement = LastHeartBeat = DateTime.UtcNow;
|
||||
ShouldSendAnnouncementEarlier = false;
|
||||
ShouldSendHeartBeats = true;
|
||||
|
||||
BotCache.LastAnnouncedAssetsForListing.ReplaceWith(assetsForListing);
|
||||
BotCache.LastAnnouncedTradeToken = tradeToken;
|
||||
BotCache.LastInventoryChecksumBeforeDeduplication = inventoryChecksumBeforeDeduplication;
|
||||
BotCache.LastRequestAt = LastHeartBeat;
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -914,6 +920,20 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
try {
|
||||
Bot.ArchiLogger.LogGenericInfo(Strings.Starting);
|
||||
|
||||
HttpStatusCode? licenseStatus = await Backend.GetLicenseStatus(ASF.GlobalConfig.LicenseID.Value, WebBrowser).ConfigureAwait(false);
|
||||
|
||||
if (licenseStatus == null) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, nameof(licenseStatus)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!licenseStatus.Value.IsSuccessCode()) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, licenseStatus.Value));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<Asset> assetsForMatching;
|
||||
|
||||
try {
|
||||
@@ -1170,11 +1190,19 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
// Cancel previous trade offers sent and deprioritize SteamIDs that didn't answer us in this round
|
||||
HashSet<ulong>? matchActivelyTradeOfferIDs = null;
|
||||
|
||||
JToken? matchActivelyTradeOfferIDsToken = Bot.BotDatabase.LoadFromJsonStorage(MatchActivelyTradeOfferIDsStorageKey);
|
||||
JsonElement matchActivelyTradeOfferIDsToken = Bot.BotDatabase.LoadFromJsonStorage(MatchActivelyTradeOfferIDsStorageKey);
|
||||
|
||||
if (matchActivelyTradeOfferIDsToken != null) {
|
||||
if (matchActivelyTradeOfferIDsToken.ValueKind == JsonValueKind.Array) {
|
||||
try {
|
||||
matchActivelyTradeOfferIDs = matchActivelyTradeOfferIDsToken.ToObject<HashSet<ulong>>();
|
||||
matchActivelyTradeOfferIDs = new HashSet<ulong>(matchActivelyTradeOfferIDsToken.GetArrayLength());
|
||||
|
||||
foreach (JsonElement tradeIDElement in matchActivelyTradeOfferIDsToken.EnumerateArray()) {
|
||||
if (!tradeIDElement.TryGetUInt64(out ulong tradeID)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
matchActivelyTradeOfferIDs.Add(tradeID);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
}
|
||||
@@ -1203,7 +1231,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
matchActivelyTradeOfferIDs = activeTradeOfferIDs;
|
||||
|
||||
if (matchActivelyTradeOfferIDs.Count > 0) {
|
||||
Bot.BotDatabase.SaveToJsonStorage(MatchActivelyTradeOfferIDsStorageKey, JToken.FromObject(matchActivelyTradeOfferIDs));
|
||||
Bot.BotDatabase.SaveToJsonStorage(MatchActivelyTradeOfferIDsStorageKey, matchActivelyTradeOfferIDs);
|
||||
} else {
|
||||
Bot.BotDatabase.DeleteFromJsonStorage(MatchActivelyTradeOfferIDsStorageKey);
|
||||
}
|
||||
@@ -1220,7 +1248,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
byte failuresInRow = 0;
|
||||
uint matchedSets = 0;
|
||||
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => deprioritizedSteamIDs.Contains(listedUser.SteamID)).ThenByDescending(static listedUser => listedUser.MatchEverything).ThenBy(static listedUser => listedUser.TotalInventoryCount)) {
|
||||
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderByDescending(listedUser => !deprioritizedSteamIDs.Contains(listedUser.SteamID)).ThenByDescending(static listedUser => listedUser.TotalGamesCount > 1).ThenByDescending(static listedUser => listedUser.MatchEverything).ThenBy(static listedUser => listedUser.TotalInventoryCount)) {
|
||||
if (failuresInRow >= WebBrowser.MaxTries) {
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, $"{nameof(failuresInRow)} >= {WebBrowser.MaxTries}"));
|
||||
|
||||
@@ -1407,7 +1435,7 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
if (tradeOfferIDs?.Count > 0) {
|
||||
matchActivelyTradeOfferIDs.UnionWith(tradeOfferIDs);
|
||||
|
||||
Bot.BotDatabase.SaveToJsonStorage(MatchActivelyTradeOfferIDsStorageKey, JToken.FromObject(matchActivelyTradeOfferIDs));
|
||||
Bot.BotDatabase.SaveToJsonStorage(MatchActivelyTradeOfferIDsStorageKey, matchActivelyTradeOfferIDs);
|
||||
}
|
||||
|
||||
if (mobileTradeOfferIDs?.Count > 0) {
|
||||
@@ -1587,15 +1615,42 @@ internal sealed class RemoteCommunication : IAsyncDisposable, IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.StatusCode.IsClientErrorCode()) {
|
||||
BotCache ??= await BotCache.CreateOrLoad(BotCacheFilePath).ConfigureAwait(false);
|
||||
|
||||
if (!response.StatusCode.IsSuccessCode()) {
|
||||
ShouldSendHeartBeats = false;
|
||||
|
||||
Bot.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.WarningFailedWithError, response.StatusCode));
|
||||
|
||||
return;
|
||||
switch (response.StatusCode) {
|
||||
case HttpStatusCode.Conflict:
|
||||
// ArchiNet told us to that we need to announce again
|
||||
LastAnnouncement = DateTime.MinValue;
|
||||
|
||||
BotCache.LastAnnouncedAssetsForListing.Clear();
|
||||
BotCache.LastInventoryChecksumBeforeDeduplication = BotCache.LastAnnouncedTradeToken = null;
|
||||
BotCache.LastRequestAt = null;
|
||||
|
||||
return;
|
||||
case HttpStatusCode.Forbidden:
|
||||
// ArchiNet told us to stop submitting data for now
|
||||
LastAnnouncement = DateTime.UtcNow.AddYears(1);
|
||||
|
||||
return;
|
||||
case HttpStatusCode.TooManyRequests:
|
||||
// ArchiNet told us to try again later
|
||||
LastAnnouncement = DateTime.UtcNow.AddDays(1);
|
||||
|
||||
return;
|
||||
default:
|
||||
// There is something wrong with our payload or the server, we shouldn't retry for at least several hours
|
||||
LastAnnouncement = DateTime.UtcNow.AddHours(6);
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
LastHeartBeat = DateTime.UtcNow;
|
||||
BotCache.LastRequestAt = LastHeartBeat = DateTime.UtcNow;
|
||||
} finally {
|
||||
RequestsSemaphore.Release();
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -27,9 +27,9 @@ using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
@@ -128,7 +128,7 @@ internal static class Commands {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||
}
|
||||
|
||||
Steam.Security.MobileAuthenticator? mobileAuthenticator = JsonConvert.DeserializeObject<Steam.Security.MobileAuthenticator>(json);
|
||||
Steam.Security.MobileAuthenticator? mobileAuthenticator = json.ToJsonObject<Steam.Security.MobileAuthenticator>();
|
||||
|
||||
if (mobileAuthenticator == null) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||
@@ -261,7 +261,7 @@ internal static class Commands {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||
}
|
||||
|
||||
Steam.Security.MobileAuthenticator? mobileAuthenticator = JsonConvert.DeserializeObject<Steam.Security.MobileAuthenticator>(json);
|
||||
Steam.Security.MobileAuthenticator? mobileAuthenticator = json.ToJsonObject<Steam.Security.MobileAuthenticator>();
|
||||
|
||||
if (mobileAuthenticator == null) {
|
||||
return bot.Commands.FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsEmpty, nameof(json)));
|
||||
@@ -360,7 +360,7 @@ internal static class Commands {
|
||||
MaFileData maFileData = new(response, bot.SteamID, deviceID);
|
||||
|
||||
string maFilePendingPath = $"{bot.GetFilePath(Bot.EFileType.MobileAuthenticator)}.PENDING";
|
||||
string json = JsonConvert.SerializeObject(maFileData, Formatting.Indented);
|
||||
string json = maFileData.ToJsonText(true);
|
||||
|
||||
try {
|
||||
await File.WriteAllTextAsync(maFilePendingPath, json).ConfigureAwait(false);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,48 +20,71 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamKit2;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
|
||||
internal sealed class MaFileData {
|
||||
[JsonProperty("account_name", Required = Required.Always)]
|
||||
internal readonly string AccountName;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("account_name")]
|
||||
[JsonRequired]
|
||||
internal string AccountName { get; private init; }
|
||||
|
||||
[JsonProperty("device_id", Required = Required.Always)]
|
||||
internal readonly string DeviceID;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("device_id")]
|
||||
[JsonRequired]
|
||||
internal string DeviceID { get; private init; }
|
||||
|
||||
[JsonProperty("identity_secret", Required = Required.Always)]
|
||||
internal readonly string IdentitySecret;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("identity_secret")]
|
||||
[JsonRequired]
|
||||
internal string IdentitySecret { get; private init; }
|
||||
|
||||
[JsonProperty("revocation_code", Required = Required.Always)]
|
||||
internal readonly string RevocationCode;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("revocation_code")]
|
||||
[JsonRequired]
|
||||
internal string RevocationCode { get; private init; }
|
||||
|
||||
[JsonProperty("secret_1", Required = Required.Always)]
|
||||
internal readonly string Secret1;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("secret_1")]
|
||||
[JsonRequired]
|
||||
internal string Secret1 { get; private init; }
|
||||
|
||||
[JsonProperty("serial_number", Required = Required.Always)]
|
||||
internal readonly ulong SerialNumber;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("serial_number")]
|
||||
[JsonRequired]
|
||||
internal ulong SerialNumber { get; private init; }
|
||||
|
||||
[JsonProperty("server_time", Required = Required.Always)]
|
||||
internal readonly ulong ServerTime;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("server_time")]
|
||||
[JsonRequired]
|
||||
internal ulong ServerTime { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly MaFileSessionData Session;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal MaFileSessionData Session { get; private init; }
|
||||
|
||||
[JsonProperty("shared_secret", Required = Required.Always)]
|
||||
internal readonly string SharedSecret;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("shared_secret")]
|
||||
[JsonRequired]
|
||||
internal string SharedSecret { get; private init; }
|
||||
|
||||
[JsonProperty("status", Required = Required.Always)]
|
||||
internal readonly int Status;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("status")]
|
||||
[JsonRequired]
|
||||
internal int Status { get; private init; }
|
||||
|
||||
[JsonProperty("token_gid", Required = Required.Always)]
|
||||
internal readonly string TokenGid;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("token_gid")]
|
||||
[JsonRequired]
|
||||
internal string TokenGid { get; private init; }
|
||||
|
||||
[JsonProperty("uri", Required = Required.Always)]
|
||||
internal readonly string Uri;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("uri")]
|
||||
[JsonRequired]
|
||||
internal string Uri { get; private init; }
|
||||
|
||||
internal MaFileData(CTwoFactor_AddAuthenticator_Response data, ulong steamID, string deviceID) {
|
||||
ArgumentNullException.ThrowIfNull(data);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,14 +20,15 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
|
||||
internal sealed class MaFileSessionData {
|
||||
[JsonProperty(Required = Required.Always)]
|
||||
internal readonly ulong SteamID;
|
||||
[JsonInclude]
|
||||
[JsonRequired]
|
||||
internal ulong SteamID { get; private init; }
|
||||
|
||||
internal MaFileSessionData(ulong steamID) {
|
||||
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,15 +22,16 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.OfficialPlugins.MobileAuthenticator.Localization;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
using ArchiSteamFarm.Plugins.Interfaces;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
@@ -38,10 +39,12 @@ namespace ArchiSteamFarm.OfficialPlugins.MobileAuthenticator;
|
||||
[Export(typeof(IPlugin))]
|
||||
[SuppressMessage("ReSharper", "MemberCanBeFileLocal")]
|
||||
internal sealed class MobileAuthenticatorPlugin : OfficialPlugin, IBotCommand2, IBotSteamClient {
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override string Name => nameof(MobileAuthenticatorPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override Version Version => typeof(MobileAuthenticatorPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public async Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Newtonsoft.Json" IncludeAssets="compile" />
|
||||
<PackageReference Include="SteamKit2" IncludeAssets="compile" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" IncludeAssets="compile" />
|
||||
<PackageReference Include="System.Composition.AttributedModel" IncludeAssets="compile" />
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,34 +23,44 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Core;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
internal sealed class SubmitRequest {
|
||||
[JsonProperty("guid", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("guid")]
|
||||
private static string Guid => ASF.GlobalDatabase?.Identifier.ToString("N") ?? throw new InvalidOperationException(nameof(ASF.GlobalDatabase.Identifier));
|
||||
|
||||
[JsonProperty("token", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("token")]
|
||||
private static string Token => SharedInfo.Token;
|
||||
|
||||
[JsonProperty("v", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("v")]
|
||||
private static byte Version => SharedInfo.ApiVersion;
|
||||
|
||||
[JsonProperty("apps", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Apps;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("apps")]
|
||||
[JsonRequired]
|
||||
private ImmutableDictionary<string, string> Apps { get; init; }
|
||||
|
||||
[JsonProperty("depots", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Depots;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("depots")]
|
||||
[JsonRequired]
|
||||
private ImmutableDictionary<string, string> Depots { get; init; }
|
||||
|
||||
private readonly ulong SteamID;
|
||||
|
||||
[JsonProperty("subs", Required = Required.Always)]
|
||||
private readonly ImmutableDictionary<string, string> Subs;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("subs")]
|
||||
[JsonRequired]
|
||||
private ImmutableDictionary<string, string> Subs { get; init; }
|
||||
|
||||
[JsonProperty("steamid", Required = Required.Always)]
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("steamid")]
|
||||
private string SteamIDText => new SteamID(SteamID).Render();
|
||||
|
||||
internal SubmitRequest(ulong steamID, IReadOnlyCollection<KeyValuePair<uint, ulong>> apps, IReadOnlyCollection<KeyValuePair<uint, ulong>> accessTokens, IReadOnlyCollection<KeyValuePair<uint, string>> depots) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,22 +20,21 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
|
||||
internal sealed class SubmitResponse {
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("data", Required = Required.DisallowNull)]
|
||||
internal readonly SubmitResponseData? Data;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("data")]
|
||||
internal SubmitResponseData? Data { get; private init; }
|
||||
|
||||
#pragma warning disable CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonProperty("success", Required = Required.Always)]
|
||||
internal readonly bool Success;
|
||||
#pragma warning restore CS0649 // False positive, the field is used during json deserialization
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("success")]
|
||||
[JsonRequired]
|
||||
internal bool Success { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
private SubmitResponse() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,28 +20,40 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
|
||||
#pragma warning disable CA1812 // False positive, the class is used during json deserialization
|
||||
internal sealed class SubmitResponseData {
|
||||
[JsonProperty("new_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewApps = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_apps")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("new_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewDepots = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_depots")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("new_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> NewPackages = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("new_subs")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> NewPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_apps", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedApps = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_apps")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedApps { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_depots", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedDepots = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_depots")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedDepots { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty("verified_subs", Required = Required.Always)]
|
||||
internal readonly ImmutableHashSet<uint> VerifiedPackages = ImmutableHashSet<uint>.Empty;
|
||||
[JsonInclude]
|
||||
[JsonPropertyName("verified_subs")]
|
||||
[JsonRequired]
|
||||
internal ImmutableHashSet<uint> VerifiedPackages { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
}
|
||||
#pragma warning restore CA1812 // False positive, the class is used during json deserialization
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,49 +21,57 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
internal sealed class GlobalCache : SerializableFile {
|
||||
internal static readonly ArchiCacheable<ImmutableHashSet<uint>> KnownDepotIDs = new(ResolveKnownDepotIDs, TimeSpan.FromDays(7));
|
||||
internal static readonly ArchiCacheable<FrozenSet<uint>> KnownDepotIDs = new(ResolveKnownDepotIDs, TimeSpan.FromDays(7));
|
||||
|
||||
private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, $"{nameof(SteamTokenDumper)}.cache");
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<uint, uint> AppChangeNumbers = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<uint, ulong> AppTokens = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<uint, string> DepotKeys = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<uint, ulong> SubmittedApps = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<uint, string> SubmittedDepots = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<uint, ulong> SubmittedPackages = new();
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonInclude]
|
||||
internal uint LastChangeNumber { get; private set; }
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
private ConcurrentDictionary<uint, uint> AppChangeNumbers { get; init; } = new();
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
private ConcurrentDictionary<uint, ulong> AppTokens { get; init; } = new();
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
private ConcurrentDictionary<uint, string> DepotKeys { get; init; } = new();
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
private ConcurrentDictionary<uint, ulong> SubmittedApps { get; init; } = new();
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
private ConcurrentDictionary<uint, string> SubmittedDepots { get; init; } = new();
|
||||
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
private ConcurrentDictionary<uint, ulong> SubmittedPackages { get; init; } = new();
|
||||
|
||||
[JsonConstructor]
|
||||
internal GlobalCache() => FilePath = SharedFilePath;
|
||||
|
||||
[UsedImplicitly]
|
||||
@@ -87,6 +95,8 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
[UsedImplicitly]
|
||||
public bool ShouldSerializeSubmittedPackages() => !SubmittedPackages.IsEmpty;
|
||||
|
||||
protected override Task Save() => Save(this);
|
||||
|
||||
internal ulong GetAppToken(uint appID) => AppTokens[appID];
|
||||
|
||||
internal Dictionary<uint, ulong> GetAppTokensForSubmission() => AppTokens.Where(appToken => (SteamTokenDumperPlugin.Config?.SecretAppIDs.Contains(appToken.Key) != true) && (appToken.Value > 0) && (!SubmittedApps.TryGetValue(appToken.Key, out ulong token) || (appToken.Value != token))).ToDictionary(static appToken => appToken.Key, static appToken => appToken.Value);
|
||||
@@ -118,7 +128,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
return null;
|
||||
}
|
||||
|
||||
globalCache = JsonConvert.DeserializeObject<GlobalCache>(json);
|
||||
globalCache = json.ToJsonObject<GlobalCache>();
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -306,7 +316,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
return (depotKey.Length == 64) && Utilities.IsValidHexadecimalText(depotKey);
|
||||
}
|
||||
|
||||
private static async Task<(bool Success, ImmutableHashSet<uint>? Result)> ResolveKnownDepotIDs(CancellationToken cancellationToken = default) {
|
||||
private static async Task<(bool Success, FrozenSet<uint>? Result)> ResolveKnownDepotIDs(CancellationToken cancellationToken = default) {
|
||||
if (ASF.WebBrowser == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.WebBrowser));
|
||||
}
|
||||
@@ -343,7 +353,7 @@ internal sealed class GlobalCache : SerializableFile {
|
||||
result.Add(depotID);
|
||||
}
|
||||
|
||||
return (result.Count > 0, result.ToImmutableHashSet());
|
||||
return (result.Count > 0, result.ToFrozenSet());
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,16 +19,16 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
public sealed class GlobalConfigExtension {
|
||||
[JsonProperty]
|
||||
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private set; }
|
||||
[JsonInclude]
|
||||
public SteamTokenDumperConfig? SteamTokenDumperPlugin { get; private init; }
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SteamTokenDumperPluginEnabled { get; private set; }
|
||||
[JsonInclude]
|
||||
public bool SteamTokenDumperPluginEnabled { get; private init; }
|
||||
|
||||
[JsonConstructor]
|
||||
internal GlobalConfigExtension() { }
|
||||
|
||||
@@ -97,7 +97,10 @@
|
||||
<value>Pabeidza iegūt kopumā {0} aplikāciju piekļuves marķierus.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of app access tokens retrieved</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Notiek visu depot atslēgu izgūšana, kopā {0} lietotnēm...</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps being retrieved</comment>
|
||||
</data>
|
||||
<data name="BotRetrievingAppInfos" xml:space="preserve">
|
||||
<value>Iegūst {0} aplikāciju informāciju...</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos being retrieved</comment>
|
||||
@@ -106,13 +109,33 @@
|
||||
<value>Pabeidza iegūt {0} aplikāciju informāciju.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of app infos retrieved</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="BotFinishedRetrievingDepotKeys" xml:space="preserve">
|
||||
<value>Veiksmīgi izgūtas {0} no {1} depot atslēgām.</value>
|
||||
<comment>{0} will be replaced by the number (count this batch) of depot keys that were successfully retrieved, {1} will be replaced by the number (count this batch) of depot keys that were supposed to be retrieved</comment>
|
||||
</data>
|
||||
<data name="BotFinishedRetrievingTotalDepots" xml:space="preserve">
|
||||
<value>Pabeigta visu depot atslēgu izgūšana, kopā {0} lietotnēm.</value>
|
||||
<comment>{0} will be replaced by the number (total count) of apps retrieved</comment>
|
||||
</data>
|
||||
<data name="SubmissionNoNewData" xml:space="preserve">
|
||||
<value>Nav jaunu datu, ko iesniegt, viss ir atjaunināts.</value>
|
||||
</data>
|
||||
<data name="SubmissionNoContributorSet" xml:space="preserve">
|
||||
<value>Nevarēja iesniegt datus, jo nav derīgas SteamID kopas, ko mēs varētu klasificēt kā atbalstītāju. Apsveriet iespēju iestatīt {0} rekvizītu.</value>
|
||||
<comment>{0} will be replaced by the name of the config property (e.g. "SteamOwnerID") that the user is expected to set</comment>
|
||||
</data>
|
||||
<data name="SubmissionInProgress" xml:space="preserve">
|
||||
<value>Iesniedz reģistrētās lietotnes/pakotnes/depot, kopumā: {0}/{1}/{2}...</value>
|
||||
<comment>{0} will be replaced by the number of app access tokens being submitted, {1} will be replaced by the number of package access tokens being submitted, {2} will be replaced by the number of depot keys being submitted</comment>
|
||||
</data>
|
||||
<data name="SubmissionFailedTooManyRequests" xml:space="preserve">
|
||||
<value>Iesniegšana neizdevās, jo tika nosūtīti pārāk daudz pieprasījumi. Mēs mēģināsim vēlreiz pēc aptuveni {0} no šī brīža.</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "53 minutes")</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessful" xml:space="preserve">
|
||||
<value>Dati ir veiksmīgi iesniegti. Serveris ir reģistrējis pavisam jaunas lietotnes/pakotnes/depots: {0} ({1} verificēts)/{2} ({3} verificēts)/{4} ({5} verificēts).</value>
|
||||
<comment>{0} will be replaced by the number of new app access tokens that the server has registered, {1} will be replaced by the number of verified app access tokens that the server has registered, {2} will be replaced by the number of new package access tokens that the server has registered, {3} will be replaced by the number of verified package access tokens that the server has registered, {4} will be replaced by the number of new depot keys that the server has registered, {5} will be replaced by the number of verified depot keys that the server has registered</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulNewApps" xml:space="preserve">
|
||||
<value>Jaunas aplikācijas: {0}</value>
|
||||
<comment>{0} will be replaced by list of the apps (IDs, numbers), separated by a comma</comment>
|
||||
@@ -129,10 +152,25 @@
|
||||
<value>Pārbaudītas pakas: {0}</value>
|
||||
<comment>{0} will be replaced by list of the packages (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="SubmissionSuccessfulNewDepots" xml:space="preserve">
|
||||
<value>Jaunas aplikācijas: {0}</value>
|
||||
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="SubmissionSuccessfulVerifiedDepots" xml:space="preserve">
|
||||
<value>Pārbaudītas aplikācijas: {0}</value>
|
||||
<comment>{0} will be replaced by list of the depots (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="PluginSecretListInitialized" xml:space="preserve">
|
||||
<value>{0} inicializēts, spraudnis neatrisinās nevienu no šiem: {1}.</value>
|
||||
<comment>{0} will be replaced by the name of the config property (e.g. "SecretPackageIDs"), {1} will be replaced by list of the objects (IDs, numbers), separated by a comma</comment>
|
||||
</data>
|
||||
<data name="LoadingGlobalCache" xml:space="preserve">
|
||||
<value>Notiek STD globālās kešatmiņas ielāde...</value>
|
||||
</data>
|
||||
<data name="ValidatingGlobalCacheIntegrity" xml:space="preserve">
|
||||
<value>Notiek STD globālās kešatmiņas integritātes apstiprināšana...</value>
|
||||
</data>
|
||||
<data name="GlobalCacheIntegrityValidationFailed" xml:space="preserve">
|
||||
<value>Neizdevās pārbaudīt STD globālās kešatmiņas integritāti. Tas liecina par iespējamu faila/atmiņas bojājumu, tā vietā tiks inicializēta jauna instance.</value>
|
||||
</data>
|
||||
</root>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,29 +20,33 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System.Collections.Immutable;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.IPC.Integration;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
|
||||
public sealed class SteamTokenDumperConfig {
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonInclude]
|
||||
public bool Enabled { get; internal set; }
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||
public ImmutableHashSet<uint> SecretAppIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretAppIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||
public ImmutableHashSet<uint> SecretDepotIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretDepotIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
[JsonDisallowNull]
|
||||
[JsonInclude]
|
||||
[SwaggerItemsMinMax(MinimumUint = 1, MaximumUint = uint.MaxValue)]
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private set; } = ImmutableHashSet<uint>.Empty;
|
||||
public ImmutableHashSet<uint> SecretPackageIDs { get; private init; } = ImmutableHashSet<uint>.Empty;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
public bool SkipAutoGrantPackages { get; private set; } = true;
|
||||
[JsonInclude]
|
||||
public bool SkipAutoGrantPackages { get; private init; } = true;
|
||||
|
||||
[JsonConstructor]
|
||||
internal SteamTokenDumperConfig() { }
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -30,6 +30,6 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
public sealed class SteamTokenDumperController : ArchiController {
|
||||
[HttpGet(nameof(GlobalConfigExtension))]
|
||||
[ProducesResponseType<GlobalConfigExtension>((int) HttpStatusCode.OK)]
|
||||
[SwaggerOperation(Tags = new[] { nameof(GlobalConfigExtension) })]
|
||||
[SwaggerOperation(Tags = [nameof(GlobalConfigExtension)])]
|
||||
public ActionResult<GlobalConfigExtension> Get() => Ok(new GlobalConfigExtension());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,17 +21,21 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Composition;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Data;
|
||||
using ArchiSteamFarm.OfficialPlugins.SteamTokenDumper.Localization;
|
||||
using ArchiSteamFarm.Plugins;
|
||||
@@ -41,8 +45,6 @@ using ArchiSteamFarm.Steam.Interaction;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using ArchiSteamFarm.Web;
|
||||
using ArchiSteamFarm.Web.Responses;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2;
|
||||
|
||||
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
@@ -51,7 +53,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper;
|
||||
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotCommand2, IBotSteamClient, ISteamPICSChanges {
|
||||
private const ushort DepotsRateLimitingDelay = 500;
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
internal static SteamTokenDumperConfig? Config { get; private set; }
|
||||
|
||||
private static readonly ConcurrentDictionary<Bot, IDisposable> BotSubscriptions = new();
|
||||
@@ -62,15 +64,17 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
private static GlobalCache? GlobalCache;
|
||||
private static DateTimeOffset LastUploadAt = DateTimeOffset.MinValue;
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override string Name => nameof(SteamTokenDumperPlugin);
|
||||
|
||||
[JsonProperty]
|
||||
[JsonInclude]
|
||||
[Required]
|
||||
public override Version Version => typeof(SteamTokenDumperPlugin).Assembly.GetName().Version ?? throw new InvalidOperationException(nameof(Version));
|
||||
|
||||
public Task<uint> GetPreferredChangeNumberToStartFrom() => Task.FromResult(GlobalCache?.LastChangeNumber ?? 0);
|
||||
|
||||
public async Task OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
|
||||
public async Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null) {
|
||||
if (!SharedInfo.HasValidToken) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, Strings.PluginDisabledMissingBuildToken, nameof(SteamTokenDumperPlugin)));
|
||||
|
||||
@@ -81,15 +85,19 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
SteamTokenDumperConfig? config = null;
|
||||
|
||||
if (additionalConfigProperties != null) {
|
||||
foreach ((string configProperty, JToken configValue) in additionalConfigProperties) {
|
||||
foreach ((string configProperty, JsonElement configValue) in additionalConfigProperties) {
|
||||
try {
|
||||
switch (configProperty) {
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPlugin):
|
||||
config = configValue.ToObject<SteamTokenDumperConfig>();
|
||||
config = configValue.ToJsonObject<SteamTokenDumperConfig>();
|
||||
|
||||
break;
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled):
|
||||
isEnabled = configValue.Value<bool>();
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled) when configValue.ValueKind == JsonValueKind.False:
|
||||
isEnabled = false;
|
||||
|
||||
break;
|
||||
case nameof(GlobalConfigExtension.SteamTokenDumperPluginEnabled) when configValue.ValueKind == JsonValueKind.True:
|
||||
isEnabled = true;
|
||||
|
||||
break;
|
||||
}
|
||||
@@ -289,14 +297,26 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
return;
|
||||
}
|
||||
|
||||
HashSet<uint> packageIDs = callback.LicenseList.Where(static license => !Config.SecretPackageIDs.Contains(license.PackageID) && ((license.PaymentMethod != EPaymentMethod.AutoGrant) || !Config.SkipAutoGrantPackages)).Select(static license => license.PackageID).ToHashSet();
|
||||
// Schedule a refresh in a while from now
|
||||
if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Refresh(bot, packageIDs).ConfigureAwait(false);
|
||||
if (!await synchronization.RefreshSemaphore.WaitAsync(0).ConfigureAwait(false)) {
|
||||
// Another refresh is in progress, skip the refresh for now
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
synchronization.RefreshTimer.Change(TimeSpan.FromMinutes(1), TimeSpan.FromHours(SharedInfo.MaximumHoursBetweenRefresh));
|
||||
} finally {
|
||||
synchronization.RefreshSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private static async void OnSubmissionTimer(object? state = null) => await SubmitData().ConfigureAwait(false);
|
||||
|
||||
private static async Task Refresh(Bot bot, IReadOnlyCollection<uint>? packageIDs = null) {
|
||||
private static async Task Refresh(Bot bot) {
|
||||
ArgumentNullException.ThrowIfNull(bot);
|
||||
|
||||
if (GlobalCache == null) {
|
||||
@@ -322,7 +342,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
return;
|
||||
}
|
||||
|
||||
packageIDs ??= bot.OwnedPackageIDs.Where(static package => (Config?.SecretPackageIDs.Contains(package.Key) != true) && ((package.Value.PaymentMethod != EPaymentMethod.AutoGrant) || (Config?.SkipAutoGrantPackages == false))).Select(static package => package.Key).ToHashSet();
|
||||
HashSet<uint> packageIDs = bot.OwnedPackageIDs.Where(static package => (Config?.SecretPackageIDs.Contains(package.Key) != true) && ((package.Value.PaymentMethod != EPaymentMethod.AutoGrant) || (Config?.SkipAutoGrantPackages == false))).Select(static package => package.Key).ToHashSet();
|
||||
|
||||
HashSet<uint> appIDsToRefresh = [];
|
||||
|
||||
@@ -384,7 +404,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingTotalAppAccessTokens, appIDsToRefresh.Count));
|
||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotRetrievingTotalDepots, appIDsToRefresh.Count));
|
||||
|
||||
(_, ImmutableHashSet<uint>? knownDepotIDs) = await GlobalCache.KnownDepotIDs.GetValue(ECacheFallback.SuccessPreviously).ConfigureAwait(false);
|
||||
(_, FrozenSet<uint>? knownDepotIDs) = await GlobalCache.KnownDepotIDs.GetValue(ECacheFallback.SuccessPreviously).ConfigureAwait(false);
|
||||
|
||||
using (HashSet<uint>.Enumerator enumerator = appIDsToRefresh.GetEnumerator()) {
|
||||
while (true) {
|
||||
@@ -500,7 +520,9 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
}
|
||||
}
|
||||
|
||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingDepotKeys, depotKeysSuccessful, depotKeysTotal));
|
||||
if (depotKeysTotal > 0) {
|
||||
bot.ArchiLogger.LogGenericInfo(string.Format(CultureInfo.CurrentCulture, Strings.BotFinishedRetrievingDepotKeys, depotKeysSuccessful, depotKeysTotal));
|
||||
}
|
||||
|
||||
if (depotKeysSuccessful < depotKeysTotal) {
|
||||
// We're not going to record app change numbers, as we didn't fetch all the depot keys we wanted
|
||||
@@ -689,7 +711,7 @@ internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotC
|
||||
}
|
||||
|
||||
if (response.Content.Data == null) {
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid), nameof(response.Content.Data));
|
||||
ASF.ArchiLogger.LogGenericError(string.Format(CultureInfo.CurrentCulture, ArchiSteamFarm.Localization.Strings.ErrorIsInvalid, nameof(response.Content.Data)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -6,9 +6,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ConfigureAwaitChecker.Analyzer" PrivateAssets="all" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" />
|
||||
<PackageReference Include="MSTest.TestAdapter" />
|
||||
<PackageReference Include="MSTest.TestFramework" />
|
||||
<PackageReference Include="MSTest" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -55,7 +55,6 @@ public sealed class Bot {
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
public void MaxItemsTooSmall() {
|
||||
const uint appID = 42;
|
||||
|
||||
@@ -64,9 +63,7 @@ public sealed class Bot {
|
||||
CreateCard(2, appID)
|
||||
];
|
||||
|
||||
GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1);
|
||||
|
||||
Assert.Fail();
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => GetItemsForFullBadge(items, 2, appID, MinCardsPerBadge - 1));
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
@@ -460,7 +457,6 @@ public sealed class Bot {
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
[ExpectedException(typeof(InvalidOperationException))]
|
||||
public void TooManyCardsPerSet() {
|
||||
const uint appID0 = 42;
|
||||
const uint appID1 = 43;
|
||||
@@ -473,15 +469,15 @@ public sealed class Bot {
|
||||
CreateCard(4, appID0)
|
||||
];
|
||||
|
||||
GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
{ appID0, 3 },
|
||||
{ appID1, 3 },
|
||||
{ appID2, 3 }
|
||||
}
|
||||
Assert.ThrowsException<InvalidOperationException>(
|
||||
() => GetItemsForFullBadge(
|
||||
items, new Dictionary<uint, byte> {
|
||||
{ appID0, 3 },
|
||||
{ appID1, 3 },
|
||||
{ appID2, 3 }
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Assert.Fail();
|
||||
}
|
||||
|
||||
private static void AssertResultMatchesExpectation(IReadOnlyDictionary<(uint RealAppID, ulong ContextID, ulong ClassID), uint> expectedResult, IReadOnlyCollection<Asset> itemsToSend) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -311,27 +311,21 @@ public sealed class SteamChatMessage {
|
||||
Assert.AreEqual(newlinePart, output[3]);
|
||||
}
|
||||
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
[TestMethod]
|
||||
public async Task ThrowsOnTooLongNewlinesPrefix() {
|
||||
string prefix = new('\n', (MaxMessagePrefixBytes / NewlineWeight) + 1);
|
||||
|
||||
const string message = "asdf";
|
||||
|
||||
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
Assert.Fail();
|
||||
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[ExpectedException(typeof(ArgumentOutOfRangeException))]
|
||||
[TestMethod]
|
||||
public async Task ThrowsOnTooLongPrefix() {
|
||||
string prefix = new('x', MaxMessagePrefixBytes + 1);
|
||||
|
||||
const string message = "asdf";
|
||||
|
||||
await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false);
|
||||
|
||||
Assert.Fail();
|
||||
await Assert.ThrowsExceptionAsync<ArgumentOutOfRangeException>(async () => await GetMessageParts(message, prefix).ToListAsync().ConfigureAwait(false)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -16,15 +16,13 @@
|
||||
<PackageReference Include="Humanizer" />
|
||||
<PackageReference Include="JetBrains.Annotations" PrivateAssets="all" />
|
||||
<PackageReference Include="Markdig.Signed" />
|
||||
<PackageReference Include="Newtonsoft.Json" />
|
||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||
<PackageReference Include="Nito.AsyncEx.Coordination" />
|
||||
<PackageReference Include="NLog.Web.AspNetCore" />
|
||||
<PackageReference Include="SteamKit2" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Annotations" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" />
|
||||
<PackageReference Include="System.Composition" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" />
|
||||
<PackageReference Include="System.Linq.Async" />
|
||||
<PackageReference Include="System.Security.Cryptography.ProtectedData" />
|
||||
<PackageReference Include="zxcvbn-core" />
|
||||
@@ -66,7 +64,7 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="Exists($([System.IO.Path]::Combine('overlay', 'variant-base', $(ASFVariant.Split('-')[0]))))">
|
||||
<ItemGroup Condition="'$(ASFVariant)' != '' AND Exists($([System.IO.Path]::Combine('overlay', 'variant-base', $(ASFVariant.Split('-')[0]))))">
|
||||
<Content Include="overlay/variant-base/$(ASFVariant.Split('-')[0])/**/*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
@@ -74,7 +72,7 @@
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="Exists($([System.IO.Path]::Combine('overlay', 'variant-specific', $(ASFVariant))))">
|
||||
<ItemGroup Condition="'$(ASFVariant)' != '' AND Exists($([System.IO.Path]::Combine('overlay', 'variant-specific', $(ASFVariant))))">
|
||||
<Content Include="overlay/variant-specific/$(ASFVariant)/**/*.*">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -24,11 +24,12 @@ using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where T : notnull {
|
||||
public sealed class ConcurrentHashSet<T> : IReadOnlySet<T>, ISet<T> where T : notnull {
|
||||
[PublicAPI]
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
@@ -37,15 +38,31 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
|
||||
private readonly ConcurrentDictionary<T, bool> BackingCollection;
|
||||
|
||||
[JsonConstructor]
|
||||
public ConcurrentHashSet() => BackingCollection = new ConcurrentDictionary<T, bool>();
|
||||
|
||||
public ConcurrentHashSet(IEnumerable<T> collection) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
BackingCollection = new ConcurrentDictionary<T, bool>(collection.Select(static item => new KeyValuePair<T, bool>(item, true)));
|
||||
}
|
||||
|
||||
public ConcurrentHashSet(IEqualityComparer<T> comparer) {
|
||||
ArgumentNullException.ThrowIfNull(comparer);
|
||||
|
||||
BackingCollection = new ConcurrentDictionary<T, bool>(comparer);
|
||||
}
|
||||
|
||||
public ConcurrentHashSet(IEnumerable<T> collection, IEqualityComparer<T> comparer) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
ArgumentNullException.ThrowIfNull(comparer);
|
||||
|
||||
BackingCollection = new ConcurrentDictionary<T, bool>(collection.Select(static item => new KeyValuePair<T, bool>(item, true)), comparer);
|
||||
}
|
||||
|
||||
public bool Add(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
if (!BackingCollection.TryAdd(item, true)) {
|
||||
return false;
|
||||
}
|
||||
@@ -65,9 +82,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
OnModified?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
public bool Contains(T item) => BackingCollection.ContainsKey(item);
|
||||
public bool Contains(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
|
||||
return BackingCollection.ContainsKey(item);
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||
|
||||
BackingCollection.Keys.CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public void ExceptWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
@@ -80,6 +106,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
public IEnumerator<T> GetEnumerator() => BackingCollection.Keys.GetEnumerator();
|
||||
|
||||
public void IntersectWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
foreach (T item in this.Where(item => !otherSet.Contains(item))) {
|
||||
@@ -88,36 +116,48 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
}
|
||||
|
||||
public bool IsProperSubsetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return (otherSet.Count > Count) && IsSubsetOf(otherSet);
|
||||
}
|
||||
|
||||
public bool IsProperSupersetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return (otherSet.Count < Count) && IsSupersetOf(otherSet);
|
||||
}
|
||||
|
||||
public bool IsSubsetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return this.All(otherSet.Contains);
|
||||
}
|
||||
|
||||
public bool IsSupersetOf(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return otherSet.All(Contains);
|
||||
}
|
||||
|
||||
public bool Overlaps(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return otherSet.Any(Contains);
|
||||
}
|
||||
|
||||
public bool Remove(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
if (!BackingCollection.TryRemove(item, out _)) {
|
||||
return false;
|
||||
}
|
||||
@@ -128,12 +168,16 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
}
|
||||
|
||||
public bool SetEquals(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
|
||||
return (otherSet.Count == Count) && otherSet.All(Contains);
|
||||
}
|
||||
|
||||
public void SymmetricExceptWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
ISet<T> otherSet = other as ISet<T> ?? other.ToHashSet();
|
||||
HashSet<T> removed = [];
|
||||
|
||||
@@ -155,12 +199,18 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
}
|
||||
}
|
||||
|
||||
void ICollection<T>.Add(T item) => Add(item);
|
||||
void ICollection<T>.Add(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
Add(item);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
[PublicAPI]
|
||||
public bool AddRange(IEnumerable<T> items) {
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
bool result = false;
|
||||
|
||||
foreach (T _ in items.Where(Add)) {
|
||||
@@ -172,6 +222,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
|
||||
[PublicAPI]
|
||||
public bool RemoveRange(IEnumerable<T> items) {
|
||||
ArgumentNullException.ThrowIfNull(items);
|
||||
|
||||
bool result = false;
|
||||
|
||||
foreach (T _ in items.Where(Remove)) {
|
||||
@@ -181,8 +233,17 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
return result;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public int RemoveWhere(Predicate<T> match) {
|
||||
ArgumentNullException.ThrowIfNull(match);
|
||||
|
||||
return BackingCollection.Keys.Where(match.Invoke).Count(key => BackingCollection.TryRemove(key, out _));
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
if (SetEquals(other)) {
|
||||
return false;
|
||||
}
|
||||
@@ -194,6 +255,8 @@ public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where
|
||||
|
||||
[PublicAPI]
|
||||
public void ReplaceWith(IEnumerable<T> other) {
|
||||
ArgumentNullException.ThrowIfNull(other);
|
||||
|
||||
Clear();
|
||||
UnionWith(other);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -22,18 +22,18 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Nito.AsyncEx;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
public sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> where T : notnull {
|
||||
[PublicAPI]
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
internal int Count {
|
||||
[PublicAPI]
|
||||
public int Count {
|
||||
get {
|
||||
using (Lock.ReaderLock()) {
|
||||
return BackingCollection.Count;
|
||||
@@ -41,7 +41,9 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private readonly List<T> BackingCollection = [];
|
||||
public bool IsReadOnly => false;
|
||||
|
||||
private readonly List<T> BackingCollection;
|
||||
private readonly AsyncReaderWriterLock Lock = new();
|
||||
|
||||
int ICollection<T>.Count => Count;
|
||||
@@ -55,6 +57,8 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
|
||||
set {
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection[index] = value;
|
||||
}
|
||||
@@ -63,7 +67,18 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public ConcurrentList() => BackingCollection = [];
|
||||
|
||||
public ConcurrentList(IEnumerable<T> collection) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
BackingCollection = [..collection];
|
||||
}
|
||||
|
||||
public void Add(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Add(item);
|
||||
}
|
||||
@@ -80,12 +95,17 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
|
||||
public bool Contains(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.ReaderLock()) {
|
||||
return BackingCollection.Contains(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void CopyTo(T[] array, int arrayIndex) {
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||
|
||||
using (Lock.ReaderLock()) {
|
||||
BackingCollection.CopyTo(array, arrayIndex);
|
||||
}
|
||||
@@ -94,12 +114,17 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, Lock.ReaderLock());
|
||||
|
||||
public int IndexOf(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.ReaderLock()) {
|
||||
return BackingCollection.IndexOf(item);
|
||||
}
|
||||
}
|
||||
|
||||
public void Insert(int index, T item) {
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Insert(index, item);
|
||||
}
|
||||
@@ -108,6 +133,8 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
|
||||
public bool Remove(T item) {
|
||||
ArgumentNullException.ThrowIfNull(item);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
if (!BackingCollection.Remove(item)) {
|
||||
return false;
|
||||
@@ -120,6 +147,8 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
}
|
||||
|
||||
public void RemoveAt(int index) {
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(index);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.RemoveAt(index);
|
||||
}
|
||||
@@ -129,7 +158,10 @@ internal sealed class ConcurrentList<T> : IList<T>, IReadOnlyList<T> {
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal void ReplaceWith(IEnumerable<T> collection) {
|
||||
[PublicAPI]
|
||||
public void ReplaceWith(IEnumerable<T> collection) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
using (Lock.WriterLock()) {
|
||||
BackingCollection.Clear();
|
||||
BackingCollection.AddRange(collection);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,22 +23,17 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using ArchiSteamFarm.Core;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
||||
internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> where T : notnull {
|
||||
private readonly ConcurrentQueue<T> BackingQueue = new();
|
||||
|
||||
internal byte MaxCount {
|
||||
get => BackingMaxCount;
|
||||
|
||||
set {
|
||||
if (value == 0) {
|
||||
ASF.ArchiLogger.LogNullError(value);
|
||||
|
||||
return;
|
||||
}
|
||||
ArgumentOutOfRangeException.ThrowIfZero(value);
|
||||
|
||||
BackingMaxCount = value;
|
||||
|
||||
@@ -58,6 +53,8 @@ internal sealed class FixedSizeConcurrentQueue<T> : IEnumerable<T> {
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
|
||||
internal void Enqueue(T obj) {
|
||||
ArgumentNullException.ThrowIfNull(obj);
|
||||
|
||||
BackingQueue.Enqueue(obj);
|
||||
|
||||
Resize();
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -23,12 +23,13 @@ using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.Collections;
|
||||
|
||||
public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<TKey, TValue>, IReadOnlyDictionary<TKey, TValue> where TKey : notnull {
|
||||
[PublicAPI]
|
||||
public event EventHandler? OnModified;
|
||||
|
||||
[PublicAPI]
|
||||
@@ -42,8 +43,7 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
[PublicAPI]
|
||||
public ICollection<TKey> Keys => BackingDictionary.Keys;
|
||||
|
||||
[JsonProperty(Required = Required.DisallowNull)]
|
||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary = new();
|
||||
private readonly ConcurrentDictionary<TKey, TValue> BackingDictionary;
|
||||
|
||||
int ICollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||
int IReadOnlyCollection<KeyValuePair<TKey, TValue>>.Count => BackingDictionary.Count;
|
||||
@@ -54,7 +54,10 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
|
||||
public TValue this[TKey key] {
|
||||
get => BackingDictionary[key];
|
||||
|
||||
set {
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
if (BackingDictionary.TryGetValue(key, out TValue? savedValue) && EqualityComparer<TValue>.Default.Equals(savedValue, value)) {
|
||||
return;
|
||||
}
|
||||
@@ -64,13 +67,39 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
}
|
||||
|
||||
[JsonConstructor]
|
||||
public ObservableConcurrentDictionary() => BackingDictionary = new ConcurrentDictionary<TKey, TValue>();
|
||||
|
||||
public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
|
||||
BackingDictionary = new ConcurrentDictionary<TKey, TValue>(collection);
|
||||
}
|
||||
|
||||
public ObservableConcurrentDictionary(IEqualityComparer<TKey> comparer) {
|
||||
ArgumentNullException.ThrowIfNull(comparer);
|
||||
|
||||
BackingDictionary = new ConcurrentDictionary<TKey, TValue>(comparer);
|
||||
}
|
||||
|
||||
public ObservableConcurrentDictionary(IEnumerable<KeyValuePair<TKey, TValue>> collection, IEqualityComparer<TKey> comparer) {
|
||||
ArgumentNullException.ThrowIfNull(collection);
|
||||
ArgumentNullException.ThrowIfNull(comparer);
|
||||
|
||||
BackingDictionary = new ConcurrentDictionary<TKey, TValue>(collection, comparer);
|
||||
}
|
||||
|
||||
public void Add(KeyValuePair<TKey, TValue> item) {
|
||||
(TKey key, TValue value) = item;
|
||||
|
||||
Add(key, value);
|
||||
}
|
||||
|
||||
public void Add(TKey key, TValue value) => TryAdd(key, value);
|
||||
public void Add(TKey key, TValue value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
TryAdd(key, value);
|
||||
}
|
||||
|
||||
public void Clear() {
|
||||
if (BackingDictionary.IsEmpty) {
|
||||
@@ -82,7 +111,14 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
public bool Contains(KeyValuePair<TKey, TValue> item) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).Contains(item);
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) => ((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).CopyTo(array, arrayIndex);
|
||||
|
||||
public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) {
|
||||
ArgumentNullException.ThrowIfNull(array);
|
||||
ArgumentOutOfRangeException.ThrowIfNegative(arrayIndex);
|
||||
|
||||
((ICollection<KeyValuePair<TKey, TValue>>) BackingDictionary).CopyTo(array, arrayIndex);
|
||||
}
|
||||
|
||||
public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() => BackingDictionary.GetEnumerator();
|
||||
|
||||
public bool Remove(KeyValuePair<TKey, TValue> item) {
|
||||
@@ -98,6 +134,8 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
public bool Remove(TKey key) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
if (!BackingDictionary.TryRemove(key, out _)) {
|
||||
return false;
|
||||
}
|
||||
@@ -107,14 +145,36 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) => BackingDictionary.ContainsKey(key);
|
||||
bool IDictionary<TKey, TValue>.ContainsKey(TKey key) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return BackingDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
bool IReadOnlyDictionary<TKey, TValue>.ContainsKey(TKey key) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return BackingDictionary.ContainsKey(key);
|
||||
}
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||
bool IReadOnlyDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => BackingDictionary.TryGetValue(key, out value!);
|
||||
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) => BackingDictionary.TryGetValue(key, out value!);
|
||||
|
||||
bool IReadOnlyDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return BackingDictionary.TryGetValue(key, out value!);
|
||||
}
|
||||
|
||||
bool IDictionary<TKey, TValue>.TryGetValue(TKey key, out TValue value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return BackingDictionary.TryGetValue(key, out value!);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryAdd(TKey key, TValue value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
if (!BackingDictionary.TryAdd(key, value)) {
|
||||
return false;
|
||||
}
|
||||
@@ -125,5 +185,9 @@ public sealed class ObservableConcurrentDictionary<TKey, TValue> : IDictionary<T
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public bool TryGetValue(TKey key, out TValue? value) => BackingDictionary.TryGetValue(key, out value);
|
||||
public bool TryGetValue(TKey key, out TValue? value) {
|
||||
ArgumentNullException.ThrowIfNull(key);
|
||||
|
||||
return BackingDictionary.TryGetValue(key, out value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,8 +21,8 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
@@ -75,9 +75,9 @@ public static class ASF {
|
||||
internal static ICrossProcessSemaphore? LoginRateLimitingSemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore? LoginSemaphore { get; private set; }
|
||||
internal static ICrossProcessSemaphore? RateLimitingSemaphore { get; private set; }
|
||||
internal static ImmutableDictionary<Uri, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>? WebLimitingSemaphores { get; private set; }
|
||||
internal static FrozenDictionary<Uri, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>? WebLimitingSemaphores { get; private set; }
|
||||
|
||||
private static readonly ImmutableHashSet<string> AssembliesNeededBeforeUpdate = ImmutableHashSet.Create(StringComparer.Ordinal, "System.IO.Pipes");
|
||||
private static readonly FrozenSet<string> AssembliesNeededBeforeUpdate = new HashSet<string>(1, StringComparer.Ordinal) { "System.IO.Pipes" }.ToFrozenSet(StringComparer.Ordinal);
|
||||
private static readonly SemaphoreSlim UpdateSemaphore = new(1, 1);
|
||||
|
||||
private static Timer? AutoUpdatesTimer;
|
||||
@@ -101,23 +101,32 @@ public static class ASF {
|
||||
return fileType switch {
|
||||
EFileType.Config => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName),
|
||||
EFileType.Database => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName),
|
||||
EFileType.Crash => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalCrashFileName),
|
||||
_ => throw new InvalidOperationException(nameof(fileType))
|
||||
};
|
||||
}
|
||||
|
||||
internal static async Task Init() {
|
||||
internal static async Task<bool> Init() {
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
}
|
||||
|
||||
if (!PluginsCore.InitPlugins()) {
|
||||
await Task.Delay(SharedInfo.InformationDelay).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
WebBrowser = new WebBrowser(ArchiLogger, GlobalConfig.WebProxy, true);
|
||||
|
||||
await UpdateAndRestart().ConfigureAwait(false);
|
||||
|
||||
if (!Program.IgnoreUnsupportedEnvironment && !await ProtectAgainstCrashes().ConfigureAwait(false)) {
|
||||
ArchiLogger.LogFatalError(Strings.ErrorTooManyCrashes);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Program.AllowCrashFileRemoval = true;
|
||||
|
||||
if (!await PluginsCore.InitPlugins().ConfigureAwait(false)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await PluginsCore.OnASFInitModules(GlobalConfig.AdditionalProperties).ConfigureAwait(false);
|
||||
await InitRateLimiters().ConfigureAwait(false);
|
||||
|
||||
@@ -142,6 +151,8 @@ public static class ASF {
|
||||
if (Program.ConfigWatch) {
|
||||
InitConfigWatchEvents();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static bool IsValidBotName(string botName) {
|
||||
@@ -441,7 +452,7 @@ public static class ASF {
|
||||
{ ArchiWebHandler.SteamHelpURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamHelpURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ ArchiWebHandler.SteamStoreURL, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(ArchiWebHandler.SteamStoreURL)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) },
|
||||
{ WebAPI.DefaultBaseAddress, (await PluginsCore.GetCrossProcessSemaphore($"{nameof(ArchiWebHandler)}-{nameof(WebAPI)}").ConfigureAwait(false), new SemaphoreSlim(WebBrowser.MaxConnections, WebBrowser.MaxConnections)) }
|
||||
}.ToImmutableDictionary();
|
||||
}.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
private static void LoadAllAssemblies() {
|
||||
@@ -813,6 +824,37 @@ public static class ASF {
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task<bool> ProtectAgainstCrashes() {
|
||||
if (Debugging.IsDebugBuild) {
|
||||
// Allow debug builds to run unconditionally, we expect to crash a lot in those
|
||||
return true;
|
||||
}
|
||||
|
||||
string crashFilePath = GetFilePath(EFileType.Crash);
|
||||
|
||||
CrashFile crashFile = await CrashFile.CreateOrLoad(crashFilePath).ConfigureAwait(false);
|
||||
|
||||
if (crashFile.StartupCount >= WebBrowser.MaxTries) {
|
||||
// We've reached maximum allowed count of recent crashes, return failure
|
||||
return false;
|
||||
}
|
||||
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
if (now - crashFile.LastStartup > TimeSpan.FromMinutes(5)) {
|
||||
// Last crash was long ago, restart counter
|
||||
crashFile.StartupCount = 1;
|
||||
} else if (++crashFile.StartupCount >= WebBrowser.MaxTries) {
|
||||
// We've reached maximum allowed count of recent crashes, return failure
|
||||
return false;
|
||||
}
|
||||
|
||||
crashFile.LastStartup = now;
|
||||
|
||||
// We're allowing this run to proceed
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task RegisterBots() {
|
||||
if (GlobalConfig == null) {
|
||||
throw new InvalidOperationException(nameof(GlobalConfig));
|
||||
@@ -905,6 +947,9 @@ public static class ASF {
|
||||
return;
|
||||
}
|
||||
|
||||
// Allow crash file recovery, if needed
|
||||
Program.AllowCrashFileRemoval = true;
|
||||
|
||||
await RestartOrExit().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -1047,6 +1092,7 @@ public static class ASF {
|
||||
|
||||
internal enum EFileType : byte {
|
||||
Config,
|
||||
Database
|
||||
Database,
|
||||
Crash
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IdentityModel.Tokens.Jwt;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
@@ -39,6 +39,7 @@ using ArchiSteamFarm.Storage;
|
||||
using Humanizer;
|
||||
using Humanizer.Localisation;
|
||||
using JetBrains.Annotations;
|
||||
using Microsoft.IdentityModel.JsonWebTokens;
|
||||
using SteamKit2;
|
||||
using Zxcvbn;
|
||||
|
||||
@@ -48,9 +49,7 @@ public static class Utilities {
|
||||
private const byte TimeoutForLongRunningTasksInSeconds = 60;
|
||||
|
||||
// normally we'd just use words like "steam" and "farm", but the library we're currently using is a bit iffy about banned words, so we need to also add combinations such as "steamfarm"
|
||||
private static readonly ImmutableHashSet<string> ForbiddenPasswordPhrases = ImmutableHashSet.Create(StringComparer.InvariantCultureIgnoreCase, "archisteamfarm", "archi", "steam", "farm", "archisteam", "archifarm", "steamfarm", "asf", "asffarm", "password");
|
||||
|
||||
private static readonly JwtSecurityTokenHandler JwtSecurityTokenHandler = new();
|
||||
private static readonly FrozenSet<string> ForbiddenPasswordPhrases = new HashSet<string>(10, StringComparer.InvariantCultureIgnoreCase) { "archisteamfarm", "archi", "steam", "farm", "archisteam", "archifarm", "steamfarm", "asf", "asffarm", "password" }.ToFrozenSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
[PublicAPI]
|
||||
public static string GenerateChecksumFor(byte[] source) {
|
||||
@@ -179,19 +178,6 @@ public static class Utilities {
|
||||
return (text.Length % 2 == 0) && text.All(Uri.IsHexDigit);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static JwtSecurityToken? ReadJwtToken(string token) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(token);
|
||||
|
||||
try {
|
||||
return JwtSecurityTokenHandler.ReadJwtToken(token);
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static IList<INode> SelectNodes(this IDocument document, string xpath) {
|
||||
ArgumentNullException.ThrowIfNull(document);
|
||||
@@ -260,6 +246,23 @@ public static class Utilities {
|
||||
return job.ToTask();
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static bool TryReadJsonWebToken(string token, [NotNullWhen(true)] out JsonWebToken? result) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(token);
|
||||
|
||||
try {
|
||||
result = new JsonWebToken(token);
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericDebuggingException(e);
|
||||
|
||||
result = null;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(directory);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -35,7 +35,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
||||
|
||||
private bool IsInitialized => InitializedAt > DateTime.MinValue;
|
||||
private bool IsPermanentCache => CacheLifetime == Timeout.InfiniteTimeSpan;
|
||||
private bool IsRecent => IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime);
|
||||
private bool IsRecent => IsInitialized && (IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime));
|
||||
|
||||
private DateTime InitializedAt;
|
||||
private T? InitializedValue;
|
||||
@@ -55,7 +55,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
||||
throw new InvalidEnumArgumentException(nameof(cacheFallback), (int) cacheFallback, typeof(ECacheFallback));
|
||||
}
|
||||
|
||||
if (IsInitialized && IsRecent) {
|
||||
if (IsRecent) {
|
||||
return (true, InitializedValue);
|
||||
}
|
||||
|
||||
@@ -68,7 +68,7 @@ public sealed class ArchiCacheable<T> : IDisposable {
|
||||
}
|
||||
|
||||
try {
|
||||
if (IsInitialized && IsRecent) {
|
||||
if (IsRecent) {
|
||||
return (true, InitializedValue);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -20,8 +20,8 @@
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Frozen;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.ComponentModel;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
@@ -45,7 +45,7 @@ public static class ArchiCryptoHelper {
|
||||
|
||||
internal static bool HasDefaultCryptKey { get; private set; } = true;
|
||||
|
||||
private static readonly ImmutableHashSet<string> ForbiddenCryptKeyPhrases = ImmutableHashSet.Create(StringComparer.InvariantCultureIgnoreCase, "crypt", "key", "cryptkey");
|
||||
private static readonly FrozenSet<string> ForbiddenCryptKeyPhrases = new HashSet<string>(3, StringComparer.InvariantCultureIgnoreCase) { "crypt", "key", "cryptkey" }.ToFrozenSet(StringComparer.InvariantCultureIgnoreCase);
|
||||
|
||||
private static IEnumerable<byte> SteamParentalCharacters => Enumerable.Range('0', 10).Select(static character => (byte) character);
|
||||
|
||||
@@ -59,53 +59,53 @@ public static class ArchiCryptoHelper {
|
||||
|
||||
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
|
||||
|
||||
internal static async Task<string?> Decrypt(ECryptoMethod cryptoMethod, string encryptedString) {
|
||||
internal static async Task<string?> Decrypt(ECryptoMethod cryptoMethod, string text) {
|
||||
if (!Enum.IsDefined(cryptoMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(encryptedString);
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
return cryptoMethod switch {
|
||||
ECryptoMethod.AES => DecryptAES(encryptedString),
|
||||
ECryptoMethod.EnvironmentVariable => Environment.GetEnvironmentVariable(encryptedString)?.Trim(),
|
||||
ECryptoMethod.File => await ReadFromFile(encryptedString).ConfigureAwait(false),
|
||||
ECryptoMethod.PlainText => encryptedString,
|
||||
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(encryptedString),
|
||||
ECryptoMethod.AES => DecryptAES(text),
|
||||
ECryptoMethod.EnvironmentVariable => Environment.GetEnvironmentVariable(text)?.Trim(),
|
||||
ECryptoMethod.File => await ReadFromFile(text).ConfigureAwait(false),
|
||||
ECryptoMethod.PlainText => text,
|
||||
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(text),
|
||||
_ => throw new InvalidOperationException(nameof(cryptoMethod))
|
||||
};
|
||||
}
|
||||
|
||||
internal static string? Encrypt(ECryptoMethod cryptoMethod, string decryptedString) {
|
||||
internal static string? Encrypt(ECryptoMethod cryptoMethod, string text) {
|
||||
if (!Enum.IsDefined(cryptoMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(cryptoMethod), (int) cryptoMethod, typeof(ECryptoMethod));
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(decryptedString);
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
return cryptoMethod switch {
|
||||
ECryptoMethod.AES => EncryptAES(decryptedString),
|
||||
ECryptoMethod.EnvironmentVariable => decryptedString,
|
||||
ECryptoMethod.File => decryptedString,
|
||||
ECryptoMethod.PlainText => decryptedString,
|
||||
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(decryptedString),
|
||||
ECryptoMethod.AES => EncryptAES(text),
|
||||
ECryptoMethod.EnvironmentVariable => text,
|
||||
ECryptoMethod.File => text,
|
||||
ECryptoMethod.PlainText => text,
|
||||
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(text),
|
||||
_ => throw new InvalidOperationException(nameof(cryptoMethod))
|
||||
};
|
||||
}
|
||||
|
||||
internal static string Hash(EHashingMethod hashingMethod, string stringToHash) {
|
||||
internal static string Hash(EHashingMethod hashingMethod, string text) {
|
||||
if (!Enum.IsDefined(hashingMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(stringToHash);
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
if (hashingMethod == EHashingMethod.PlainText) {
|
||||
return stringToHash;
|
||||
return text;
|
||||
}
|
||||
|
||||
byte[] passwordBytes = Encoding.UTF8.GetBytes(stringToHash);
|
||||
byte[] hashBytes = Hash(passwordBytes, EncryptionKey, DefaultHashLength, hashingMethod);
|
||||
byte[] textBytes = Encoding.UTF8.GetBytes(text);
|
||||
byte[] hashBytes = Hash(textBytes, EncryptionKey, DefaultHashLength, hashingMethod);
|
||||
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
@@ -197,13 +197,31 @@ public static class ArchiCryptoHelper {
|
||||
EncryptionKey = encryptionKey;
|
||||
}
|
||||
|
||||
private static string? DecryptAES(string encryptedString) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(encryptedString);
|
||||
internal static bool VerifyHash(EHashingMethod hashingMethod, string text, string hash) {
|
||||
if (!Enum.IsDefined(hashingMethod)) {
|
||||
throw new InvalidEnumArgumentException(nameof(hashingMethod), (int) hashingMethod, typeof(EHashingMethod));
|
||||
}
|
||||
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
ArgumentException.ThrowIfNullOrEmpty(hash);
|
||||
|
||||
// Text is always provided as plain text
|
||||
byte[] textBytes = Encoding.UTF8.GetBytes(text);
|
||||
textBytes = Hash(textBytes, EncryptionKey, DefaultHashLength, hashingMethod);
|
||||
|
||||
// Hash is either plain text password (when EHashingMethod.PlainText), or base64-encoded hash
|
||||
byte[] hashBytes = hashingMethod == EHashingMethod.PlainText ? Encoding.UTF8.GetBytes(hash) : Convert.FromBase64String(hash);
|
||||
|
||||
return CryptographicOperations.FixedTimeEquals(textBytes, hashBytes);
|
||||
}
|
||||
|
||||
private static string? DecryptAES(string text) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
try {
|
||||
byte[] key = SHA256.HashData(EncryptionKey);
|
||||
|
||||
byte[] decryptedData = Convert.FromBase64String(encryptedString);
|
||||
byte[] decryptedData = Convert.FromBase64String(text);
|
||||
decryptedData = CryptoHelper.SymmetricDecrypt(decryptedData, key);
|
||||
|
||||
return Encoding.UTF8.GetString(decryptedData);
|
||||
@@ -214,8 +232,8 @@ public static class ArchiCryptoHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static string? DecryptProtectedDataForCurrentUser(string encryptedString) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(encryptedString);
|
||||
private static string? DecryptProtectedDataForCurrentUser(string text) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
if (!OperatingSystem.IsWindows()) {
|
||||
return null;
|
||||
@@ -223,7 +241,7 @@ public static class ArchiCryptoHelper {
|
||||
|
||||
try {
|
||||
byte[] decryptedData = ProtectedData.Unprotect(
|
||||
Convert.FromBase64String(encryptedString),
|
||||
Convert.FromBase64String(text),
|
||||
EncryptionKey,
|
||||
DataProtectionScope.CurrentUser
|
||||
);
|
||||
@@ -236,13 +254,13 @@ public static class ArchiCryptoHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static string? EncryptAES(string decryptedString) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(decryptedString);
|
||||
private static string? EncryptAES(string text) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
try {
|
||||
byte[] key = SHA256.HashData(EncryptionKey);
|
||||
|
||||
byte[] encryptedData = Encoding.UTF8.GetBytes(decryptedString);
|
||||
byte[] encryptedData = Encoding.UTF8.GetBytes(text);
|
||||
encryptedData = CryptoHelper.SymmetricEncrypt(encryptedData, key);
|
||||
|
||||
return Convert.ToBase64String(encryptedData);
|
||||
@@ -253,8 +271,8 @@ public static class ArchiCryptoHelper {
|
||||
}
|
||||
}
|
||||
|
||||
private static string? EncryptProtectedDataForCurrentUser(string decryptedString) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(decryptedString);
|
||||
private static string? EncryptProtectedDataForCurrentUser(string text) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(text);
|
||||
|
||||
if (!OperatingSystem.IsWindows()) {
|
||||
return null;
|
||||
@@ -262,7 +280,7 @@ public static class ArchiCryptoHelper {
|
||||
|
||||
try {
|
||||
byte[] encryptedData = ProtectedData.Protect(
|
||||
Encoding.UTF8.GetBytes(decryptedString),
|
||||
Encoding.UTF8.GetBytes(text),
|
||||
EncryptionKey,
|
||||
DataProtectionScope.CurrentUser
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
30
ArchiSteamFarm/Helpers/Json/JsonDisallowNullAttribute.cs
Normal file
30
ArchiSteamFarm/Helpers/Json/JsonDisallowNullAttribute.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property)]
|
||||
[PublicAPI]
|
||||
public sealed class JsonDisallowNullAttribute : JsonAttribute;
|
||||
186
ArchiSteamFarm/Helpers/Json/JsonUtilities.cs
Normal file
186
ArchiSteamFarm/Helpers/Json/JsonUtilities.cs
Normal file
@@ -0,0 +1,186 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
// |
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
// |
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization.Metadata;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
public static class JsonUtilities {
|
||||
[PublicAPI]
|
||||
public static readonly JsonSerializerOptions DefaultJsonSerialierOptions = CreateDefaultJsonSerializerOptions();
|
||||
|
||||
[PublicAPI]
|
||||
public static readonly JsonSerializerOptions IndentedJsonSerialierOptions = CreateDefaultJsonSerializerOptions(true);
|
||||
|
||||
[PublicAPI]
|
||||
public static JsonElement ToJsonElement<T>(this T obj, bool writeIndented = false) where T : notnull {
|
||||
ArgumentNullException.ThrowIfNull(obj);
|
||||
|
||||
return JsonSerializer.SerializeToElement(obj, writeIndented ? IndentedJsonSerialierOptions : DefaultJsonSerialierOptions);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static T? ToJsonObject<T>(this JsonElement jsonElement, CancellationToken cancellationToken = default) => jsonElement.Deserialize<T>(DefaultJsonSerialierOptions);
|
||||
|
||||
[PublicAPI]
|
||||
public static async ValueTask<T?> ToJsonObject<T>(this Stream stream, CancellationToken cancellationToken = default) {
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
|
||||
return await JsonSerializer.DeserializeAsync<T>(stream, DefaultJsonSerialierOptions, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static T? ToJsonObject<T>([StringSyntax(StringSyntaxAttribute.Json)] this string json) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(json);
|
||||
|
||||
return JsonSerializer.Deserialize<T>(json, DefaultJsonSerialierOptions);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static string ToJsonText<T>(this T obj, bool writeIndented = false) => JsonSerializer.Serialize(obj, writeIndented ? IndentedJsonSerialierOptions : DefaultJsonSerialierOptions);
|
||||
|
||||
private static void ApplyCustomModifiers(JsonTypeInfo jsonTypeInfo) {
|
||||
ArgumentNullException.ThrowIfNull(jsonTypeInfo);
|
||||
|
||||
bool potentialDisallowedNullsPossible = false;
|
||||
|
||||
foreach (JsonPropertyInfo property in jsonTypeInfo.Properties) {
|
||||
// All our modifications require a valid Get method on a property
|
||||
if (property.Get == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The object should be validated against potential nulls if at least one property has [JsonDisallowNull] declared, avoid performance penalty otherwise
|
||||
if (property.AttributeProvider?.IsDefined(typeof(JsonDisallowNullAttribute), false) == true) {
|
||||
if (property.PropertyType.IsValueType && (Nullable.GetUnderlyingType(property.PropertyType) == null)) {
|
||||
// We should have no [JsonDisallowNull] declared on non-nullable types, this requires developer correction
|
||||
throw new InvalidOperationException(string.Format(CultureInfo.CurrentCulture, Strings.WarningUnknownValuePleaseReport, nameof(JsonDisallowNullAttribute), $"{property.Name} ({jsonTypeInfo.Type})"));
|
||||
}
|
||||
|
||||
potentialDisallowedNullsPossible = true;
|
||||
}
|
||||
|
||||
// The property should be checked against ShouldSerialize if there is a valid method to invoke, avoid performance penalty otherwise
|
||||
MethodInfo? shouldSerializeMethod = GetShouldSerializeMethod(jsonTypeInfo.Type, property);
|
||||
|
||||
if (shouldSerializeMethod != null) {
|
||||
property.ShouldSerialize = (parent, _) => ShouldSerialize(shouldSerializeMethod, parent);
|
||||
}
|
||||
}
|
||||
|
||||
if (potentialDisallowedNullsPossible) {
|
||||
jsonTypeInfo.OnDeserialized = OnPotentialDisallowedNullsDeserialized;
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonSerializerOptions CreateDefaultJsonSerializerOptions(bool writeIndented = false) =>
|
||||
new() {
|
||||
AllowTrailingCommas = true,
|
||||
PropertyNamingPolicy = null,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { ApplyCustomModifiers } },
|
||||
WriteIndented = writeIndented
|
||||
};
|
||||
|
||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2070", Justification = "We don't care about trimmed methods, it's not like we can make it work differently anyway")]
|
||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2075", Justification = "We don't care about trimmed properties, it's not like we can make it work differently anyway")]
|
||||
private static MethodInfo? GetShouldSerializeMethod([SuppressMessage("ReSharper", "SuggestBaseTypeForParameter")] Type parent, JsonPropertyInfo property) {
|
||||
ArgumentNullException.ThrowIfNull(parent);
|
||||
ArgumentNullException.ThrowIfNull(property);
|
||||
|
||||
// Handle most common case where ShouldSerializeXYZ() matches property name
|
||||
MethodInfo? result = parent.GetMethod($"ShouldSerialize{property.Name}", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null);
|
||||
|
||||
if (result?.ReturnType == typeof(bool)) {
|
||||
// Method exists and returns a boolean, that's what we'd like to hear
|
||||
return result;
|
||||
}
|
||||
|
||||
// Handle less common case where ShouldSerializeXYZ() matches original member name
|
||||
PropertyInfo? memberNameProperty = property.GetType().GetProperty("MemberName", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);
|
||||
|
||||
if (memberNameProperty == null) {
|
||||
// Should never happen, investigate if it does
|
||||
throw new InvalidOperationException(nameof(memberNameProperty));
|
||||
}
|
||||
|
||||
object? memberNameResult = memberNameProperty.GetValue(property);
|
||||
|
||||
if (memberNameResult is not string memberName) {
|
||||
// Should never happen, investigate if it does
|
||||
throw new InvalidOperationException(nameof(memberName));
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(memberName) || (memberName == property.Name)) {
|
||||
// We don't have anything to work with further, there is no ShouldSerialize() method
|
||||
return null;
|
||||
}
|
||||
|
||||
result = parent.GetMethod($"ShouldSerialize{memberName}", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null);
|
||||
|
||||
// Use alternative method if it exists and returns a boolean
|
||||
return result?.ReturnType == typeof(bool) ? result : null;
|
||||
}
|
||||
|
||||
[UnconditionalSuppressMessage("AssemblyLoadTrimming", "IL2075", Justification = "We don't care about trimmed properties, it's not like we can make it work differently anyway")]
|
||||
private static void OnPotentialDisallowedNullsDeserialized(object obj) {
|
||||
ArgumentNullException.ThrowIfNull(obj);
|
||||
|
||||
Type type = obj.GetType();
|
||||
|
||||
foreach (FieldInfo field in type.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(field => field.IsDefined(typeof(JsonDisallowNullAttribute), false) && (field.GetValue(obj) == null))) {
|
||||
throw new JsonException($"Required field {field.Name} expects a non-null value.");
|
||||
}
|
||||
|
||||
foreach (PropertyInfo property in type.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).Where(property => (property.GetMethod != null) && property.IsDefined(typeof(JsonDisallowNullAttribute), false) && (property.GetValue(obj) == null))) {
|
||||
throw new JsonException($"Required property {property.Name} expects a non-null value.");
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ShouldSerialize(MethodInfo shouldSerializeMethod, object parent) {
|
||||
ArgumentNullException.ThrowIfNull(shouldSerializeMethod);
|
||||
ArgumentNullException.ThrowIfNull(parent);
|
||||
|
||||
if (shouldSerializeMethod.ReturnType != typeof(bool)) {
|
||||
throw new InvalidOperationException(nameof(shouldSerializeMethod));
|
||||
}
|
||||
|
||||
object? shouldSerialize = shouldSerializeMethod.Invoke(parent, null);
|
||||
|
||||
if (shouldSerialize is not bool result) {
|
||||
// Should not happen, we've already determined we have a method that returns a boolean
|
||||
throw new InvalidOperationException(nameof(shouldSerialize));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -24,7 +24,8 @@ using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using Newtonsoft.Json;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using JetBrains.Annotations;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers;
|
||||
|
||||
@@ -49,47 +50,60 @@ public abstract class SerializableFile : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
protected async Task Save() {
|
||||
if (string.IsNullOrEmpty(FilePath)) {
|
||||
/// <summary>
|
||||
/// Implementing this method in your target class is crucial for providing supported functionality.
|
||||
/// In order to do so, it's enough to call static <see cref="Save" /> function from the parent class, providing <code>this</code> as input parameter.
|
||||
/// Afterwards, simply call your <see cref="Save" /> function whenever you need to save changes.
|
||||
/// This approach will allow JSON serializer used in the <see cref="SerializableFile" /> to properly discover all of the properties used in your class.
|
||||
/// Unfortunately, due to STJ's limitations, called by some "security", it's not possible for base class to resolve your properties automatically otherwise.
|
||||
/// </summary>
|
||||
/// <example>protected override Task Save() => Save(this);</example>
|
||||
[UsedImplicitly]
|
||||
protected abstract Task Save();
|
||||
|
||||
protected static async Task Save<T>(T serializableFile) where T : SerializableFile {
|
||||
ArgumentNullException.ThrowIfNull(serializableFile);
|
||||
|
||||
if (string.IsNullOrEmpty(serializableFile.FilePath)) {
|
||||
throw new InvalidOperationException(nameof(FilePath));
|
||||
}
|
||||
|
||||
if (ReadOnly) {
|
||||
if (serializableFile.ReadOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
|
||||
lock (FileSemaphore) {
|
||||
if (SavingScheduled) {
|
||||
lock (serializableFile.FileSemaphore) {
|
||||
if (serializableFile.SavingScheduled) {
|
||||
return;
|
||||
}
|
||||
|
||||
SavingScheduled = true;
|
||||
serializableFile.SavingScheduled = true;
|
||||
}
|
||||
|
||||
await FileSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
await serializableFile.FileSemaphore.WaitAsync().ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
// ReSharper disable once SuspiciousLockOverSynchronizationPrimitive - this is not a mistake, we need extra synchronization, and we can re-use the semaphore object for that
|
||||
lock (FileSemaphore) {
|
||||
SavingScheduled = false;
|
||||
lock (serializableFile.FileSemaphore) {
|
||||
serializableFile.SavingScheduled = false;
|
||||
}
|
||||
|
||||
if (ReadOnly) {
|
||||
if (serializableFile.ReadOnly) {
|
||||
return;
|
||||
}
|
||||
|
||||
string json = JsonConvert.SerializeObject(this, Debugging.IsUserDebugging ? Formatting.Indented : Formatting.None);
|
||||
string json = serializableFile.ToJsonText(Debugging.IsUserDebugging);
|
||||
|
||||
if (string.IsNullOrEmpty(json)) {
|
||||
throw new InvalidOperationException(nameof(json));
|
||||
}
|
||||
|
||||
// We always want to write entire content to temporary file first, in order to never load corrupted data, also when target file doesn't exist
|
||||
string newFilePath = $"{FilePath}.new";
|
||||
string newFilePath = $"{serializableFile.FilePath}.new";
|
||||
|
||||
if (File.Exists(FilePath)) {
|
||||
string currentJson = await File.ReadAllTextAsync(FilePath).ConfigureAwait(false);
|
||||
if (File.Exists(serializableFile.FilePath)) {
|
||||
string currentJson = await File.ReadAllTextAsync(serializableFile.FilePath).ConfigureAwait(false);
|
||||
|
||||
if (json == currentJson) {
|
||||
return;
|
||||
@@ -97,16 +111,16 @@ public abstract class SerializableFile : IDisposable {
|
||||
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
File.Replace(newFilePath, FilePath, null);
|
||||
File.Replace(newFilePath, serializableFile.FilePath, null);
|
||||
} else {
|
||||
await File.WriteAllTextAsync(newFilePath, json).ConfigureAwait(false);
|
||||
|
||||
File.Move(newFilePath, FilePath);
|
||||
File.Move(newFilePath, serializableFile.FilePath);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
} finally {
|
||||
FileSemaphore.Release();
|
||||
serializableFile.FileSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,8 +21,10 @@
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Text.Json.Nodes;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.IPC.Controllers.Api;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.NLog;
|
||||
@@ -31,8 +33,6 @@ using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using NLog.Web;
|
||||
|
||||
namespace ArchiSteamFarm.IPC;
|
||||
@@ -83,9 +83,9 @@ internal static class ArchiKestrel {
|
||||
string json = await File.ReadAllTextAsync(customConfigPath).ConfigureAwait(false);
|
||||
|
||||
if (!string.IsNullOrEmpty(json)) {
|
||||
JObject jObject = JObject.Parse(json);
|
||||
JsonNode? jsonNode = JsonNode.Parse(json);
|
||||
|
||||
ASF.ArchiLogger.LogGenericDebug($"{SharedInfo.IPCConfigFile}: {jObject.ToString(Formatting.Indented)}");
|
||||
ASF.ArchiLogger.LogGenericDebug($"{SharedInfo.IPCConfigFile}: {jsonNode?.ToJsonText(true) ?? "null"}");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ASF.ArchiLogger.LogGenericException(e);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -24,6 +24,7 @@ using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Requests;
|
||||
@@ -32,7 +33,6 @@ using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam.Interaction;
|
||||
using ArchiSteamFarm.Storage;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
|
||||
@@ -124,9 +124,9 @@ public sealed class ASFController : ArchiController {
|
||||
}
|
||||
|
||||
if (ASF.GlobalConfig.AdditionalProperties is { Count: > 0 }) {
|
||||
request.GlobalConfig.AdditionalProperties ??= new Dictionary<string, JToken>(ASF.GlobalConfig.AdditionalProperties.Count, ASF.GlobalConfig.AdditionalProperties.Comparer);
|
||||
request.GlobalConfig.AdditionalProperties ??= new Dictionary<string, JsonElement>(ASF.GlobalConfig.AdditionalProperties.Count, ASF.GlobalConfig.AdditionalProperties.Comparer);
|
||||
|
||||
foreach ((string key, JToken value) in ASF.GlobalConfig.AdditionalProperties.Where(property => !request.GlobalConfig.AdditionalProperties.ContainsKey(property.Key))) {
|
||||
foreach ((string key, JsonElement value) in ASF.GlobalConfig.AdditionalProperties.Where(property => !request.GlobalConfig.AdditionalProperties.ContainsKey(property.Key))) {
|
||||
request.GlobalConfig.AdditionalProperties.Add(key, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -25,6 +25,7 @@ using System.Collections.Specialized;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Requests;
|
||||
@@ -33,7 +34,6 @@ using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.Steam;
|
||||
using ArchiSteamFarm.Steam.Storage;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using SteamKit2.Internal;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
@@ -127,9 +127,9 @@ public sealed class BotController : ArchiController {
|
||||
}
|
||||
|
||||
if (bot.BotConfig.AdditionalProperties?.Count > 0) {
|
||||
request.BotConfig.AdditionalProperties ??= new Dictionary<string, JToken>(bot.BotConfig.AdditionalProperties.Count, bot.BotConfig.AdditionalProperties.Comparer);
|
||||
request.BotConfig.AdditionalProperties ??= new Dictionary<string, JsonElement>(bot.BotConfig.AdditionalProperties.Count, bot.BotConfig.AdditionalProperties.Comparer);
|
||||
|
||||
foreach ((string key, JToken value) in bot.BotConfig.AdditionalProperties.Where(property => !request.BotConfig.AdditionalProperties.ContainsKey(property.Key))) {
|
||||
foreach ((string key, JsonElement value) in bot.BotConfig.AdditionalProperties.Where(property => !request.BotConfig.AdditionalProperties.ContainsKey(property.Key))) {
|
||||
request.BotConfig.AdditionalProperties.Add(key, value);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -30,13 +30,13 @@ using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using ArchiSteamFarm.Localization;
|
||||
using ArchiSteamFarm.NLog;
|
||||
using ArchiSteamFarm.NLog.Targets;
|
||||
using Microsoft.AspNetCore.Connections;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
|
||||
@@ -156,7 +156,7 @@ public sealed class NLogController : ArchiController {
|
||||
return;
|
||||
}
|
||||
|
||||
string json = JsonConvert.SerializeObject(new GenericResponse<string>(newHistoryEntryArgs.Message));
|
||||
string json = new GenericResponse<string>(newHistoryEntryArgs.Message).ToJsonText();
|
||||
|
||||
await Task.WhenAll(ActiveLogWebSockets.Where(static kv => kv.Key.State == WebSocketState.Open).Select(kv => PostLoggedJsonUpdate(kv.Key, json, kv.Value.Semaphore, kv.Value.CancellationToken))).ConfigureAwait(false);
|
||||
}
|
||||
@@ -206,7 +206,7 @@ public sealed class NLogController : ArchiController {
|
||||
return;
|
||||
}
|
||||
|
||||
string response = JsonConvert.SerializeObject(new GenericResponse<string>(loggedMessage));
|
||||
string response = new GenericResponse<string>(loggedMessage).ToJsonText();
|
||||
|
||||
await PostLoggedJsonUpdate(webSocket, response, sendSemaphore, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -19,7 +19,6 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
@@ -33,9 +32,5 @@ namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
public sealed class PluginsController : ArchiController {
|
||||
[HttpGet]
|
||||
[ProducesResponseType<GenericResponse<IReadOnlyCollection<IPlugin>>>((int) HttpStatusCode.OK)]
|
||||
public ActionResult<GenericResponse<IReadOnlyCollection<IPlugin>>> PluginsGet() {
|
||||
IReadOnlyCollection<IPlugin> activePlugins = PluginsCore.ActivePlugins ?? (IReadOnlyCollection<IPlugin>) Array.Empty<IPlugin>();
|
||||
|
||||
return Ok(new GenericResponse<IReadOnlyCollection<IPlugin>>(activePlugins));
|
||||
}
|
||||
public ActionResult<GenericResponse<IReadOnlyCollection<IPlugin>>> PluginsGet() => Ok(new GenericResponse<IReadOnlyCollection<IPlugin>>(PluginsCore.ActivePlugins));
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// Copyright 2015-2023 Łukasz "JustArchi" Domeradzki
|
||||
// Copyright 2015-2024 Łukasz "JustArchi" Domeradzki
|
||||
// Contact: JustArchi@JustArchi.net
|
||||
// |
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -21,10 +21,10 @@
|
||||
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Text.Json;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.IPC.Responses;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Linq;
|
||||
|
||||
namespace ArchiSteamFarm.IPC.Controllers.Api;
|
||||
|
||||
@@ -51,7 +51,7 @@ public sealed class StorageController : ArchiController {
|
||||
/// Loads entry under specified key from ASF's persistent KeyValue JSON storage.
|
||||
/// </summary>
|
||||
[HttpGet]
|
||||
[ProducesResponseType<GenericResponse<JToken>>((int) HttpStatusCode.OK)]
|
||||
[ProducesResponseType<GenericResponse<JsonElement?>>((int) HttpStatusCode.OK)]
|
||||
public ActionResult<GenericResponse> StorageGet(string key) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
|
||||
@@ -59,9 +59,9 @@ public sealed class StorageController : ArchiController {
|
||||
throw new InvalidOperationException(nameof(ASF.GlobalDatabase));
|
||||
}
|
||||
|
||||
JToken? value = ASF.GlobalDatabase.LoadFromJsonStorage(key);
|
||||
JsonElement value = ASF.GlobalDatabase.LoadFromJsonStorage(key);
|
||||
|
||||
return Ok(new GenericResponse<JToken>(true, value));
|
||||
return Ok(new GenericResponse<JsonElement?>(true, value.ValueKind != JsonValueKind.Undefined ? value : null));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -70,15 +70,18 @@ public sealed class StorageController : ArchiController {
|
||||
[Consumes("application/json")]
|
||||
[HttpPost]
|
||||
[ProducesResponseType<GenericResponse>((int) HttpStatusCode.OK)]
|
||||
public ActionResult<GenericResponse> StoragePost(string key, [FromBody] JToken value) {
|
||||
public ActionResult<GenericResponse> StoragePost(string key, [FromBody] JsonElement value) {
|
||||
ArgumentException.ThrowIfNullOrEmpty(key);
|
||||
ArgumentNullException.ThrowIfNull(value);
|
||||
|
||||
if (value.ValueKind == JsonValueKind.Undefined) {
|
||||
throw new ArgumentOutOfRangeException(nameof(value));
|
||||
}
|
||||
|
||||
if (ASF.GlobalDatabase == null) {
|
||||
throw new InvalidOperationException(nameof(ASF.GlobalDatabase));
|
||||
}
|
||||
|
||||
if (value.Type == JTokenType.Null) {
|
||||
if (value.ValueKind == JsonValueKind.Null) {
|
||||
ASF.GlobalDatabase.DeleteFromJsonStorage(key);
|
||||
} else {
|
||||
ASF.GlobalDatabase.SaveToJsonStorage(key, value);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user