mirror of
https://github.com/JustArchiNET/ArchiSteamFarm.git
synced 2025-12-18 15:30:30 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e4c20df4a8 | ||
|
|
5135677360 | ||
|
|
f6004f558b | ||
|
|
ddf08c4dc0 | ||
|
|
388eaf614d | ||
|
|
3a0768f9ef | ||
|
|
cb0e04c022 | ||
|
|
55cdac205d | ||
|
|
1bc0d6f16c | ||
|
|
f7377a7043 | ||
|
|
76f1ad45dd | ||
|
|
f21ffde803 | ||
|
|
e9c96f175f | ||
|
|
0f9a4c7c31 | ||
|
|
87451615e8 | ||
|
|
fa19aaae2e | ||
|
|
6b8280fceb |
2
.github/workflows/code-quality.yml
vendored
2
.github/workflows/code-quality.yml
vendored
@@ -40,6 +40,6 @@ jobs:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
|
||||
- name: Report Qodana results to GitHub
|
||||
uses: github/codeql-action/upload-sarif@v3.24.4
|
||||
uses: github/codeql-action/upload-sarif@v3.24.5
|
||||
with:
|
||||
sarif_file: ${{ runner.temp }}/qodana/results/qodana.sarif.json
|
||||
|
||||
2
ASF-ui
2
ASF-ui
Submodule ASF-ui updated: cd1173a0d6...f916fb3405
56
ArchiSteamFarm/Helpers/Json/GuidJsonConverter.cs
Normal file
56
ArchiSteamFarm/Helpers/Json/GuidJsonConverter.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
// _ _ _ ____ _ _____
|
||||
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
|
||||
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
|
||||
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
|
||||
// /_/ \_\|_| \___||_| |_||_||____/ \__|\___| \__,_||_| |_| |_||_| \__,_||_| |_| |_| |_|
|
||||
// |
|
||||
// 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;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ArchiSteamFarm.Helpers.Json;
|
||||
|
||||
/// <inheritdoc />
|
||||
/// <summary>
|
||||
/// TODO: This class exists purely because STJ can't deserialize Guid in other formats than default, at least for now
|
||||
/// https://github.com/dotnet/runtime/issues/30692
|
||||
/// </summary>
|
||||
internal sealed class GuidJsonConverter : JsonConverter<Guid> {
|
||||
internal static readonly GuidJsonConverter Shared = new();
|
||||
|
||||
public override Guid Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) {
|
||||
if (reader.TryGetGuid(out Guid result)) {
|
||||
// Great, we can work with it
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
// Try again using more flexible implementation, sigh
|
||||
return Guid.Parse(reader.GetString()!);
|
||||
} catch {
|
||||
// Throw JsonException instead, which will be converted into standard message by STJ
|
||||
throw new JsonException();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Write(Utf8JsonWriter writer, Guid value, JsonSerializerOptions options) {
|
||||
ArgumentNullException.ThrowIfNull(writer);
|
||||
|
||||
writer.WriteStringValue(value.ToString());
|
||||
}
|
||||
}
|
||||
@@ -49,7 +49,7 @@ public static class JsonUtilities {
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static T? ToJsonObject<T>(this JsonElement jsonElement, CancellationToken cancellationToken = default) => jsonElement.Deserialize<T>(DefaultJsonSerialierOptions);
|
||||
public static T? ToJsonObject<T>(this JsonElement jsonElement) => jsonElement.Deserialize<T>(DefaultJsonSerialierOptions);
|
||||
|
||||
[PublicAPI]
|
||||
public static async ValueTask<T?> ToJsonObject<T>(this Stream stream, CancellationToken cancellationToken = default) {
|
||||
@@ -105,6 +105,7 @@ public static class JsonUtilities {
|
||||
private static JsonSerializerOptions CreateDefaultJsonSerializerOptions(bool writeIndented = false) =>
|
||||
new() {
|
||||
AllowTrailingCommas = true,
|
||||
Converters = { GuidJsonConverter.Shared },
|
||||
PropertyNamingPolicy = null,
|
||||
ReadCommentHandling = JsonCommentHandling.Skip,
|
||||
TypeInfoResolver = new DefaultJsonTypeInfoResolver { Modifiers = { ApplyCustomModifiers } },
|
||||
|
||||
@@ -28,6 +28,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ArchiSteamFarm.Core;
|
||||
using ArchiSteamFarm.Helpers.Json;
|
||||
using ArchiSteamFarm.IPC.Integration;
|
||||
@@ -331,6 +332,10 @@ internal sealed class Startup {
|
||||
static options => {
|
||||
JsonSerializerOptions jsonSerializerOptions = Debugging.IsUserDebugging ? JsonUtilities.IndentedJsonSerialierOptions : JsonUtilities.DefaultJsonSerialierOptions;
|
||||
|
||||
foreach (JsonConverter converter in jsonSerializerOptions.Converters) {
|
||||
options.JsonSerializerOptions.Converters.Add(converter);
|
||||
}
|
||||
|
||||
options.JsonSerializerOptions.PropertyNamingPolicy = jsonSerializerOptions.PropertyNamingPolicy;
|
||||
options.JsonSerializerOptions.TypeInfoResolver = jsonSerializerOptions.TypeInfoResolver;
|
||||
options.JsonSerializerOptions.WriteIndented = jsonSerializerOptions.WriteIndented;
|
||||
|
||||
@@ -282,15 +282,22 @@
|
||||
<value>Ponuda za razmjenu nije uspjela!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotLootingSuccess" xml:space="preserve">
|
||||
<value>Ponuda za razmenu je uspešno poslata!</value>
|
||||
</data>
|
||||
<data name="BotSendingTradeToYourself" xml:space="preserve">
|
||||
<value>Ne možete poslati zahtjev za razmjenu sebi!</value>
|
||||
</data>
|
||||
|
||||
<data name="BotNotConnected" xml:space="preserve">
|
||||
<value>Ova instanca bot-a nije povezana!</value>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
<data name="BotPointsBalance" xml:space="preserve">
|
||||
<value>Stanje bodova: {0}</value>
|
||||
<comment>{0} will be replaced by the points balance value (integer)</comment>
|
||||
</data>
|
||||
|
||||
<data name="BotReconnecting" xml:space="preserve">
|
||||
<value>Ponovno povezivanje...</value>
|
||||
|
||||
@@ -178,7 +178,7 @@ StackTrace:
|
||||
<value>Prüfe auf neue Version...</value>
|
||||
</data>
|
||||
<data name="UpdateDownloadingNewVersion" xml:space="preserve">
|
||||
<value>Lade neue Version herunter: {0} ({1} MB)... Bitte überlegen Sie es sich, uns für unsere geleistete Arbeit eine Spende zu hinterlassen, während Sie warten. :)</value>
|
||||
<value>Lade neue Version herunter: {0} ({1} MB)... Während Sie warten, würden wir uns in der Zwischenzeit über Ihre Spende für unsere geleistete Arbeit eine Spende freuen. :-)</value>
|
||||
<comment>{0} will be replaced by version string, {1} will be replaced by update size (in megabytes)</comment>
|
||||
</data>
|
||||
<data name="UpdateFinished" xml:space="preserve">
|
||||
|
||||
@@ -532,7 +532,9 @@
|
||||
<data name="BotAccountLocked" xml:space="preserve">
|
||||
<value>Akun ini terkunci, proses farming tidak tersedia secara permanen!</value>
|
||||
</data>
|
||||
|
||||
<data name="BotStatusLocked" xml:space="preserve">
|
||||
<value>Bot sedang terkundi dan tidak bisa mendapatkan kartu saat farming.</value>
|
||||
</data>
|
||||
<data name="ErrorFunctionOnlyInHeadlessMode" xml:space="preserve">
|
||||
<value>Fungsi ini tersedia hanya dalam mode headless!</value>
|
||||
</data>
|
||||
@@ -543,8 +545,14 @@
|
||||
<data name="ErrorAccessDenied" xml:space="preserve">
|
||||
<value>Akses ditolak!</value>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="WarningPreReleaseVersion" xml:space="preserve">
|
||||
<value>Anda menggunakan versi yang lebih baru dari versi terbaru yang dirilis untuk saluran pembaruan Anda. Harap dicatat bahwa versi pra-rilis ditujukan untuk pengguna yang mengetahui cara melaporkan bug, menangani masalah, dan memberikan umpan balik - tidak ada dukungan teknis yang akan diberikan.</value>
|
||||
</data>
|
||||
<data name="BotStats" xml:space="preserve">
|
||||
<value>Penggunaan memori saat ini: {0} MB.
|
||||
Waktu aktif proses: {1}</value>
|
||||
<comment>{0} will be replaced by number (in megabytes) of memory being used, {1} will be replaced by translated TimeSpan string (such as "25 minutes"). Please note that this string should include newlines for formatting.</comment>
|
||||
</data>
|
||||
<data name="ClearingDiscoveryQueue" xml:space="preserve">
|
||||
<value>Membersihkan antrian penemuan Steam #{0}...</value>
|
||||
<comment>{0} will be replaced by queue number</comment>
|
||||
@@ -560,13 +568,21 @@
|
||||
<data name="BotRefreshingPackagesData" xml:space="preserve">
|
||||
<value>Menyegarkan data paket...</value>
|
||||
</data>
|
||||
|
||||
<data name="WarningDeprecated" xml:space="preserve">
|
||||
<value>Penggunaan {0} tidak digunakan lagi dan akan dihapus pada versi program mendatang. Silakan gunakan {1} sebagai gantinya.</value>
|
||||
<comment>{0} will be replaced by the name of deprecated property (such as argument, config property or likewise), {1} will be replaced by the name of valid replacement (such as another argument or config property)</comment>
|
||||
</data>
|
||||
<data name="BotAcceptedDonationTrade" xml:space="preserve">
|
||||
<value>Menerima trade donasi: {0}</value>
|
||||
<comment>{0} will be replaced by trade's ID (number)</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="WarningWorkaroundTriggered" xml:space="preserve">
|
||||
<value>Solusi untuk bug {0} telah dipicu.</value>
|
||||
<comment>{0} will be replaced by the bug's name provided by ASF</comment>
|
||||
</data>
|
||||
<data name="TargetBotNotConnected" xml:space="preserve">
|
||||
<value>Bot target tidak terhubung!</value>
|
||||
</data>
|
||||
<data name="BotWalletBalance" xml:space="preserve">
|
||||
<value>Saldo wallet: {0} {1}</value>
|
||||
<comment>{0} will be replaced by wallet balance value, {1} will be replaced by currency name</comment>
|
||||
@@ -578,12 +594,21 @@
|
||||
<value>Bot mempunyai level {0}.</value>
|
||||
<comment>{0} will be replaced by bot's level</comment>
|
||||
</data>
|
||||
|
||||
|
||||
<data name="ActivelyMatchingItems" xml:space="preserve">
|
||||
<value>Mencocokkan item dari Steam, fase #{0}...</value>
|
||||
<comment>{0} will be replaced by round number</comment>
|
||||
</data>
|
||||
<data name="DoneActivelyMatchingItems" xml:space="preserve">
|
||||
<value>Selesai mencocokkan item dari Steam, fase #{0}.</value>
|
||||
<comment>{0} will be replaced by round number</comment>
|
||||
</data>
|
||||
<data name="ErrorAborted" xml:space="preserve">
|
||||
<value>Dibatalkan!</value>
|
||||
</data>
|
||||
|
||||
<data name="WarningExcessiveBotsCount" xml:space="preserve">
|
||||
<value>Anda menjalankan lebih banyak akun bot pribadi dari batas maksimum yang kami rekomendasikan ({0}). Perlu diketahui bahwa pengaturan ini tidak didukung dan mungkin menyebabkan berbagai masalah terkait Steam, termasuk penangguhan akun. Lihat FAQ untuk lebih jelasnya.</value>
|
||||
<comment>{0} will be replaced by our maximum recommended bots count (number)</comment>
|
||||
</data>
|
||||
<data name="PluginLoaded" xml:space="preserve">
|
||||
<value>{0} telah berhasil memuat!</value>
|
||||
<comment>{0} will be replaced by the name of the custom ASF plugin</comment>
|
||||
@@ -595,7 +620,9 @@
|
||||
<data name="NothingFound" xml:space="preserve">
|
||||
<value>Tidak ditemukan!</value>
|
||||
</data>
|
||||
|
||||
<data name="PluginsWarning" xml:space="preserve">
|
||||
<value>Anda telah memuat satu atau beberapa plugin khusus ke ASF. Karena kami tidak dapat menawarkan dukungan untuk pengaturan yang dimodifikasi, harap hubungi pengembang plugin yang sesuai yang Anda putuskan untuk digunakan jika terjadi masalah.</value>
|
||||
</data>
|
||||
<data name="PleaseWait" xml:space="preserve">
|
||||
<value>Harap tunggu...</value>
|
||||
</data>
|
||||
@@ -605,20 +632,45 @@
|
||||
<data name="Executing" xml:space="preserve">
|
||||
<value>Mengeksekusi...</value>
|
||||
</data>
|
||||
|
||||
<data name="InteractiveConsoleEnabled" xml:space="preserve">
|
||||
<value>Konsol interaktif sekarang aktif, ketik 'c' untuk masuk ke mode perintah.</value>
|
||||
</data>
|
||||
<data name="BotGamesToRedeemInBackgroundCount" xml:space="preserve">
|
||||
<value>Bot mempunyai {0} game tersisa di dalam antrian.</value>
|
||||
<comment>{0} will be replaced by remaining number of games in BGR's queue</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<data name="ErrorSingleInstanceRequired" xml:space="preserve">
|
||||
<value>Proses ASF sudah berjalan untuk direktori kerja ini, dibatalkan!</value>
|
||||
</data>
|
||||
<data name="BotHandledConfirmations" xml:space="preserve">
|
||||
<value>Berhasil menangani {0} konfirmasi!</value>
|
||||
<comment>{0} will be replaced by number of confirmations</comment>
|
||||
</data>
|
||||
<data name="BotExtraIdlingCooldown" xml:space="preserve">
|
||||
<value>Menunggu hingga {0} untuk memastikan bahwa kita bisa memulai farming...</value>
|
||||
<comment>{0} will be replaced by translated TimeSpan string (such as "1 minute")</comment>
|
||||
</data>
|
||||
<data name="UpdateCleanup" xml:space="preserve">
|
||||
<value>Membersihkan file lama setelah pembaruan...</value>
|
||||
</data>
|
||||
<data name="BotGeneratingSteamParentalCode" xml:space="preserve">
|
||||
<value>Seadng membuat Steam parental code, ini bisa memakan waktu cukup lama, pertimbangkan untuk memasukkannya ke dalam konfigurasi...</value>
|
||||
</data>
|
||||
<data name="IPCConfigChanged" xml:space="preserve">
|
||||
<value>Konfigurasi IPC telah diubah!</value>
|
||||
</data>
|
||||
<data name="BotTradeOfferResult" xml:space="preserve">
|
||||
<value>Penawaran trade {0} ditentukan menjadi {1} karena {2}.</value>
|
||||
<comment>{0} will be replaced by trade offer ID (number), {1} will be replaced by internal ASF enum name, {2} will be replaced by technical reason why the trade was determined to be in this state</comment>
|
||||
</data>
|
||||
<data name="BotInvalidPasswordDuringLogin" xml:space="preserve">
|
||||
<value>Menerima kode kesalahan InvalidPassword {0} kali berturut-turut. Kata sandi Anda untuk akun ini kemungkinan besar salah, batalkan!</value>
|
||||
<comment>{0} will be replaced by maximum allowed number of failed login attempts</comment>
|
||||
</data>
|
||||
<data name="Result" xml:space="preserve">
|
||||
<value>Hasil: {0}</value>
|
||||
<comment>{0} will be replaced by generic result of various functions that use this string</comment>
|
||||
</data>
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -89,7 +89,10 @@
|
||||
{2}</value>
|
||||
<comment>{0} will be replaced by function name, {1} will be replaced by exception message, {2} will be replaced by entire stack trace. Please note that this string should include newlines for formatting.</comment>
|
||||
</data>
|
||||
|
||||
<data name="ErrorExitingWithNonZeroErrorCode" xml:space="preserve">
|
||||
<value>Завершення роботи з кодом помилки {0}!</value>
|
||||
<comment>{0} will be replaced by error code (number)</comment>
|
||||
</data>
|
||||
<data name="ErrorFailingRequest" xml:space="preserve">
|
||||
<value>Помилка запиту до: {0}</value>
|
||||
<comment>{0} will be replaced by URL of the request</comment>
|
||||
|
||||
@@ -1970,37 +1970,10 @@ public sealed class Bot : IAsyncDisposable, IDisposable {
|
||||
throw new ArgumentNullException(nameof(gamesToRedeemInBackground));
|
||||
}
|
||||
|
||||
HashSet<object> invalidKeys = [];
|
||||
HashSet<object> invalidKeys = gamesToRedeemInBackground.Cast<DictionaryEntry>().Where(static game => !BotDatabase.IsValidGameToRedeemInBackground(game)).Select(static game => game.Key).ToHashSet();
|
||||
|
||||
foreach (DictionaryEntry game in gamesToRedeemInBackground) {
|
||||
bool invalid = false;
|
||||
|
||||
string? key = game.Key as string;
|
||||
|
||||
if (string.IsNullOrEmpty(key)) {
|
||||
invalid = true;
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(key)));
|
||||
} else if (!Utilities.IsValidCdKey(key)) {
|
||||
invalid = true;
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, key));
|
||||
}
|
||||
|
||||
string? name = game.Value as string;
|
||||
|
||||
if (string.IsNullOrEmpty(name)) {
|
||||
invalid = true;
|
||||
ASF.ArchiLogger.LogGenericWarning(string.Format(CultureInfo.CurrentCulture, Strings.ErrorIsInvalid, nameof(name)));
|
||||
}
|
||||
|
||||
if (invalid && (key != null)) {
|
||||
invalidKeys.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (invalidKeys.Count > 0) {
|
||||
foreach (string invalidKey in invalidKeys) {
|
||||
gamesToRedeemInBackground.Remove(invalidKey);
|
||||
}
|
||||
foreach (object invalidKey in invalidKeys) {
|
||||
gamesToRedeemInBackground.Remove(invalidKey);
|
||||
}
|
||||
|
||||
return gamesToRedeemInBackground;
|
||||
|
||||
@@ -78,6 +78,30 @@ public sealed class Actions : IAsyncDisposable, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<(EResult Result, IReadOnlyCollection<uint>? GrantedApps, IReadOnlyCollection<uint>? GrantedPackages)> AddFreeLicenseApp(uint appID) {
|
||||
ArgumentOutOfRangeException.ThrowIfZero(appID);
|
||||
|
||||
SteamApps.FreeLicenseCallback callback;
|
||||
|
||||
try {
|
||||
callback = await Bot.SteamApps.RequestFreeLicense(appID).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
|
||||
return (EResult.Timeout, null, null);
|
||||
}
|
||||
|
||||
return (callback.Result, callback.GrantedApps, callback.GrantedPackages);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public async Task<(EResult Result, EPurchaseResultDetail PurchaseResultDetail)> AddFreeLicensePackage(uint subID) {
|
||||
ArgumentOutOfRangeException.ThrowIfZero(subID);
|
||||
|
||||
return await Bot.ArchiWebHandler.AddFreeLicense(subID).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
public static string? Encrypt(ArchiCryptoHelper.ECryptoMethod cryptoMethod, string stringToEncrypt) {
|
||||
if (!Enum.IsDefined(cryptoMethod)) {
|
||||
|
||||
@@ -623,7 +623,7 @@ public sealed class Commands {
|
||||
}
|
||||
|
||||
switch (type.ToUpperInvariant()) {
|
||||
case "A" or "APP":
|
||||
case "A" or "APP": {
|
||||
HashSet<uint>? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(gameID, Bot.OwnedPackageIDs.Keys, 1);
|
||||
|
||||
if (packageIDs is { Count: > 0 }) {
|
||||
@@ -632,32 +632,34 @@ public sealed class Commands {
|
||||
break;
|
||||
}
|
||||
|
||||
SteamApps.FreeLicenseCallback callback;
|
||||
(EResult result, IReadOnlyCollection<uint>? grantedApps, IReadOnlyCollection<uint>? grantedPackages) = await Bot.Actions.AddFreeLicenseApp(gameID).ConfigureAwait(false);
|
||||
|
||||
try {
|
||||
callback = await Bot.SteamApps.RequestFreeLicense(gameID).ToLongRunningTask().ConfigureAwait(false);
|
||||
} catch (Exception e) {
|
||||
Bot.ArchiLogger.LogGenericWarningException(e);
|
||||
response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, $"app/{gameID}", EResult.Timeout)));
|
||||
if (((grantedApps == null) || (grantedApps.Count == 0)) && ((grantedPackages == null) || (grantedPackages.Count == 0))) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, $"app/{gameID}", result)));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
response.AppendLine(FormatBotResponse((callback.GrantedApps.Count > 0) || (callback.GrantedPackages.Count > 0) ? string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicenseWithItems, $"app/{gameID}", callback.Result, string.Join(", ", callback.GrantedApps.Select(static appID => $"app/{appID}").Union(callback.GrantedPackages.Select(static subID => $"sub/{subID}")))) : string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, $"app/{gameID}", callback.Result)));
|
||||
grantedApps ??= Array.Empty<uint>();
|
||||
grantedPackages ??= Array.Empty<uint>();
|
||||
|
||||
response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicenseWithItems, $"app/{gameID}", result, string.Join(", ", grantedApps.Select(static appID => $"app/{appID}").Union(grantedPackages.Select(static subID => $"sub/{subID}"))))));
|
||||
|
||||
break;
|
||||
default:
|
||||
}
|
||||
default: {
|
||||
if (Bot.OwnedPackageIDs.ContainsKey(gameID)) {
|
||||
response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, $"sub/{gameID}", $"{EResult.Fail}/{EPurchaseResultDetail.AlreadyPurchased}")));
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
(EResult result, EPurchaseResultDetail purchaseResult) = await Bot.ArchiWebHandler.AddFreeLicense(gameID).ConfigureAwait(false);
|
||||
(EResult result, EPurchaseResultDetail purchaseResult) = await Bot.Actions.AddFreeLicensePackage(gameID).ConfigureAwait(false);
|
||||
|
||||
response.AppendLine(FormatBotResponse(string.Format(CultureInfo.CurrentCulture, Strings.BotAddLicense, $"sub/{gameID}", $"{result}/{purchaseResult}")));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -242,18 +242,17 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
throw new ArgumentNullException(nameof(games));
|
||||
}
|
||||
|
||||
bool save = false;
|
||||
|
||||
lock (GamesToRedeemInBackground) {
|
||||
foreach (DictionaryEntry game in games.OfType<DictionaryEntry>().Where(game => !GamesToRedeemInBackground.Contains(game.Key))) {
|
||||
GamesToRedeemInBackground.Add(game.Key, game.Value);
|
||||
save = true;
|
||||
foreach (DictionaryEntry game in games) {
|
||||
if (!IsValidGameToRedeemInBackground(game)) {
|
||||
throw new InvalidOperationException(nameof(game));
|
||||
}
|
||||
|
||||
GamesToRedeemInBackground[game.Key] = game.Value;
|
||||
}
|
||||
}
|
||||
|
||||
if (save) {
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
|
||||
internal static async Task<BotDatabase?> CreateOrLoad(string filePath) {
|
||||
@@ -287,6 +286,16 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
return null;
|
||||
}
|
||||
|
||||
(bool valid, string? errorMessage) = botDatabase.CheckValidation();
|
||||
|
||||
if (!valid) {
|
||||
if (!string.IsNullOrEmpty(errorMessage)) {
|
||||
ASF.ArchiLogger.LogGenericError(errorMessage);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
botDatabase.FilePath = filePath;
|
||||
|
||||
return botDatabase;
|
||||
@@ -295,13 +304,35 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
internal (string? Key, string? Name) GetGameToRedeemInBackground() {
|
||||
lock (GamesToRedeemInBackground) {
|
||||
foreach (DictionaryEntry game in GamesToRedeemInBackground) {
|
||||
return (game.Key as string, game.Value as string);
|
||||
return game.Value switch {
|
||||
string name => (game.Key as string, name),
|
||||
JsonElement { ValueKind: JsonValueKind.String } jsonElement => (game.Key as string, jsonElement.GetString()),
|
||||
_ => throw new InvalidOperationException(nameof(game.Value))
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
internal static bool IsValidGameToRedeemInBackground(DictionaryEntry game) {
|
||||
ArgumentNullException.ThrowIfNull(game);
|
||||
|
||||
string? key = game.Key as string;
|
||||
|
||||
if (string.IsNullOrEmpty(key) || !Utilities.IsValidCdKey(key)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (game.Value) {
|
||||
case string name when !string.IsNullOrEmpty(name):
|
||||
case JsonElement { ValueKind: JsonValueKind.String } jsonElement when !string.IsNullOrEmpty(jsonElement.GetString()):
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal void PerformMaintenance() {
|
||||
DateTime now = DateTime.UtcNow;
|
||||
|
||||
@@ -324,6 +355,8 @@ public sealed class BotDatabase : GenericDatabase {
|
||||
Utilities.InBackground(Save);
|
||||
}
|
||||
|
||||
private (bool Valid, string? ErrorMessage) CheckValidation() => GamesToRedeemInBackground.Cast<DictionaryEntry>().Any(static game => !IsValidGameToRedeemInBackground(game)) ? (false, string.Format(CultureInfo.CurrentCulture, Strings.ErrorConfigPropertyInvalid, nameof(GamesToRedeemInBackground), string.Join("", GamesToRedeemInBackground))) : (true, null);
|
||||
|
||||
private async void OnObjectModified(object? sender, EventArgs e) {
|
||||
if (string.IsNullOrEmpty(FilePath)) {
|
||||
return;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<Version>6.0.0.2</Version>
|
||||
<Version>6.0.0.3</Version>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
2
wiki
2
wiki
Submodule wiki updated: fe15ec23f0...187c258786
Reference in New Issue
Block a user