* Start working on nullable checks

help me

* Update GlobalConfig.cs

* Finish initial fixup round

* nullability code review
This commit is contained in:
Łukasz Domeradzki 2020-08-22 21:41:01 +02:00 committed by GitHub
parent e5f64ec9dd
commit 9fc1ea65a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
91 changed files with 1996 additions and 2808 deletions

View file

@ -22,34 +22,30 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// This is example class that shows how you can call third-party services within your plugin
// You always wanted from your ASF to post cats, right? Now is your chance!
// P.S. The code is almost 1:1 copy from the one I use in ArchiBoT, you can thank me later
// You've always wanted from your ASF to post cats, right? Now is your chance!
// P.S. The code is almost 1:1 copy from the one I use in ArchiBot, you can thank me later
internal static class CatAPI {
private const string URL = "https://aws.random.cat";
[ItemCanBeNull]
internal static async Task<string> GetRandomCatURL([JetBrains.Annotations.NotNull] WebBrowser webBrowser) {
internal static async Task<string?> GetRandomCatURL(WebBrowser webBrowser) {
if (webBrowser == null) {
throw new ArgumentNullException(nameof(webBrowser));
}
const string request = URL + "/meow";
WebBrowser.ObjectResponse<MeowResponse> response = await webBrowser.UrlGetToJsonObject<MeowResponse>(request).ConfigureAwait(false);
WebBrowser.ObjectResponse<MeowResponse>? response = await webBrowser.UrlGetToJsonObject<MeowResponse>(request).ConfigureAwait(false);
if (response?.Content == null) {
return null;
}
if (string.IsNullOrEmpty(response.Content.Link)) {
ASF.ArchiLogger.LogNullError(nameof(response.Content.Link));
return null;
throw new ArgumentNullException(nameof(response.Content.Link));
}
return Uri.EscapeUriString(response.Content.Link);
@ -59,7 +55,7 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
private sealed class MeowResponse {
#pragma warning disable 649
[JsonProperty(PropertyName = "file", Required = Required.Always)]
internal readonly string Link;
internal readonly string? Link;
#pragma warning restore 649
[JsonConstructor]

View file

@ -19,6 +19,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Net;
using System.Threading.Tasks;
using ArchiSteamFarm.IPC.Controllers.Api;
@ -38,7 +39,11 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> CatGet() {
string link = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
if (ASF.WebBrowser == null) {
throw new ArgumentNullException(nameof(ASF.WebBrowser));
}
string? link = await CatAPI.GetRandomCatURL(ASF.WebBrowser).ConfigureAwait(false);
return !string.IsNullOrEmpty(link) ? Ok(new GenericResponse<string>(link)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false));
}

View file

@ -55,7 +55,7 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// 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 void OnASFInit(IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
public void OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
if (additionalConfigProperties == null) {
return;
}
@ -80,14 +80,14 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// Since ASF already had to do initial parsing in order to determine that the command is unknown, args[] are splitted using standard ASF delimiters
// If by any chance you want to handle message in its raw format, you also have it available, although for usual ASF pattern you can most likely stick with args[] exclusively. The message has CommandPrefix already stripped for your convenience
// If you do not recognize the command, just return null/empty and allow ASF to gracefully return "unknown command" to user on usual basis
public async Task<string> OnBotCommand(Bot bot, ulong steamID, string message, string[] args) {
public async Task<string?> OnBotCommand(Bot bot, ulong steamID, string message, string[] args) {
// In comparison with OnBotMessage(), we're using asynchronous CatAPI call here, so we declare our method as async and return the message as usual
// Notice how we handle access here as well, it'll work only for FamilySharing+
switch (args[0].ToUpperInvariant()) {
case "CAT" when bot.HasPermission(steamID, BotConfig.EPermission.FamilySharing):
// Notice how we can decide whether to use bot's AWH WebBrowser or ASF's one. For Steam-related requests, AWH's one should always be used, for third-party requests like those it doesn't really matter
// Still, it makes sense to pass AWH's one, so in case you get some errors or alike, you know from which bot instance they come from. It's similar to using Bot's ArchiLogger compared to ASF's one
string randomCatURL = await CatAPI.GetRandomCatURL(bot.ArchiWebHandler.WebBrowser).ConfigureAwait(false);
string? randomCatURL = await CatAPI.GetRandomCatURL(bot.ArchiWebHandler.WebBrowser).ConfigureAwait(false);
return !string.IsNullOrEmpty(randomCatURL) ? randomCatURL : "God damn it, we're out of cats, care to notify my master? Thanks!";
default:
@ -126,7 +126,7 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// 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 his bot configs during runtime
// Take a look at OnASFInit() for example parsing code
public async void OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
public async void OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
// ASF marked this message as synchronous, in case we have async code to execute, we can just use async void return
// 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
@ -143,19 +143,22 @@ namespace ArchiSteamFarm.CustomPlugins.ExamplePlugin {
// Keep in mind that there is no guarantee what is the actual access of steamID, so you should do the appropriate access checking yourself
// You can use either ASF's default functions for that, or implement your own logic as you please
// If you do not intend to return any response to user, just return null/empty and ASF will proceed with the silence as usual
public Task<string> OnBotMessage(Bot bot, ulong steamID, string message) {
public Task<string?> OnBotMessage(Bot bot, ulong steamID, string message) {
// Normally ASF will expect from you async-capable responses, such as Task<string>. This allows you to make your code fully asynchronous which is a core foundation on which ASF is built upon
// Since in this method we're not doing any async stuff, instead of defining this method as async (pointless), we just need to wrap our responses in Task.FromResult<>()
if (Bot.BotsReadOnly == null) {
throw new ArgumentNullException(nameof(Bot.BotsReadOnly));
}
// As a starter, we can for example ignore messages sent from our own bots, since otherwise they can run into a possible infinite loop of answering themselves
if (Bot.BotsReadOnly.Values.Any(existingBot => existingBot.SteamID == steamID)) {
return Task.FromResult<string>(null);
return Task.FromResult<string?>(null);
}
// If this message doesn't come from one of our bots, we can reply to the user in some pre-defined way
bot.ArchiLogger.LogGenericTrace("Hey boss, we got some unknown message here!");
return Task.FromResult("I didn't get that, did you mean to use a command?");
return Task.FromResult((string?) "I didn't get that, did you mean to use a command?");
}
// This method is called when bot receives a trade offer that ASF isn't willing to accept (ignored and rejected trades)

View file

@ -48,7 +48,7 @@ namespace ArchiSteamFarm.CustomPlugins.PeriodicGC {
}
}
private static void PerformGC(object state) {
private static void PerformGC(object? state) {
ASF.ArchiLogger.LogGenericWarning("Performing GC, current memory: " + (GC.GetTotalMemory(false) / 1024) + " KB.");
lock (PeriodicGCTimer) {

View file

@ -27,13 +27,11 @@ using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Collections;
using ArchiSteamFarm.Helpers;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal sealed class GlobalCache : SerializableFile {
[NotNull]
private static string SharedFilePath => Path.Combine(ArchiSteamFarm.SharedInfo.ConfigDirectory, nameof(SteamTokenDumper) + ".cache");
[JsonProperty(Required = Required.DisallowNull)]
@ -64,22 +62,16 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal ulong GetAppToken(uint appID) => AppTokens[appID];
[NotNull]
internal Dictionary<uint, ulong> GetAppTokensForSubmission() => AppTokens.Where(appToken => !SubmittedAppIDs.Contains(appToken.Key)).ToDictionary(appToken => appToken.Key, appToken => appToken.Value);
[NotNull]
internal Dictionary<uint, string> GetDepotKeysForSubmission() => DepotKeys.Where(depotKey => !SubmittedDepotIDs.Contains(depotKey.Key)).ToDictionary(depotKey => depotKey.Key, depotKey => depotKey.Value);
[NotNull]
internal Dictionary<uint, ulong> GetPackageTokensForSubmission() => PackageTokens.Where(packageToken => !SubmittedPackageIDs.Contains(packageToken.Key)).ToDictionary(packageToken => packageToken.Key, packageToken => packageToken.Value);
[ItemNotNull]
internal static async Task<GlobalCache> Load() {
if (!File.Exists(SharedFilePath)) {
return new GlobalCache();
}
GlobalCache globalCache = null;
GlobalCache? globalCache = null;
try {
string json = await RuntimeCompatibility.File.ReadAllTextAsync(SharedFilePath).ConfigureAwait(false);
@ -100,7 +92,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
return globalCache;
}
internal async Task OnPICSChanges(uint currentChangeNumber, [NotNull] IReadOnlyCollection<KeyValuePair<uint, SteamApps.PICSChangesCallback.PICSChangeData>> appChanges) {
internal async Task OnPICSChanges(uint currentChangeNumber, IReadOnlyCollection<KeyValuePair<uint, SteamApps.PICSChangesCallback.PICSChangeData>> appChanges) {
if ((currentChangeNumber == 0) || (appChanges == null)) {
throw new ArgumentNullException(nameof(appChanges));
}
@ -127,7 +119,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal async Task OnPICSChangesRestart(uint currentChangeNumber) {
if (currentChangeNumber == 0) {
throw new ArgumentNullException();
throw new ArgumentNullException(nameof(currentChangeNumber));
}
if (currentChangeNumber <= LastChangeNumber) {
@ -146,7 +138,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal bool ShouldRefreshAppInfo(uint appID) => !AppChangeNumbers.ContainsKey(appID);
internal bool ShouldRefreshDepotKey(uint depotID) => !DepotKeys.ContainsKey(depotID);
internal async Task UpdateAppChangeNumbers([NotNull] IReadOnlyCollection<KeyValuePair<uint, uint>> appChangeNumbers) {
internal async Task UpdateAppChangeNumbers(IReadOnlyCollection<KeyValuePair<uint, uint>> appChangeNumbers) {
if (appChangeNumbers == null) {
throw new ArgumentNullException(nameof(appChangeNumbers));
}
@ -167,7 +159,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
}
}
internal async Task UpdateAppTokens([NotNull] IReadOnlyCollection<KeyValuePair<uint, ulong>> appTokens, [NotNull] IReadOnlyCollection<uint> publicAppIDs) {
internal async Task UpdateAppTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> appTokens, IReadOnlyCollection<uint> publicAppIDs) {
if ((appTokens == null) || (publicAppIDs == null)) {
throw new ArgumentNullException(nameof(appTokens) + " || " + nameof(publicAppIDs));
}
@ -207,7 +199,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
}
}
internal async Task UpdateDepotKeys([NotNull] ICollection<SteamApps.DepotKeyCallback> depotKeyResults) {
internal async Task UpdateDepotKeys(ICollection<SteamApps.DepotKeyCallback> depotKeyResults) {
if (depotKeyResults == null) {
throw new ArgumentNullException(nameof(depotKeyResults));
}
@ -221,7 +213,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
string depotKey = BitConverter.ToString(depotKeyResult.DepotKey).Replace("-", "");
if (DepotKeys.TryGetValue(depotKeyResult.DepotID, out string previousDepotKey) && (previousDepotKey == depotKey)) {
if (DepotKeys.TryGetValue(depotKeyResult.DepotID, out string? previousDepotKey) && (previousDepotKey == depotKey)) {
continue;
}
@ -240,7 +232,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
}
}
internal async Task UpdatePackageTokens([NotNull] IReadOnlyCollection<KeyValuePair<uint, ulong>> packageTokens) {
internal async Task UpdatePackageTokens(IReadOnlyCollection<KeyValuePair<uint, ulong>> packageTokens) {
if (packageTokens == null) {
throw new ArgumentNullException(nameof(packageTokens));
}
@ -267,7 +259,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
}
}
internal async Task UpdateSubmittedData([NotNull] IReadOnlyCollection<uint> appIDs, [NotNull] IReadOnlyCollection<uint> packageIDs, [NotNull] IReadOnlyCollection<uint> depotIDs) {
internal async Task UpdateSubmittedData(IReadOnlyCollection<uint> appIDs, IReadOnlyCollection<uint> packageIDs, IReadOnlyCollection<uint> depotIDs) {
if ((appIDs == null) || (packageIDs == null) || (depotIDs == null)) {
throw new ArgumentNullException(nameof(appIDs) + " || " + nameof(packageIDs) + " || " + nameof(depotIDs));
}

View file

@ -22,7 +22,6 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2;
@ -40,7 +39,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
#pragma warning disable IDE0052
[JsonProperty(PropertyName = "guid", Required = Required.Always)]
private readonly string Guid = ASF.GlobalDatabase.Guid.ToString("N");
private readonly string Guid = ASF.GlobalDatabase?.Guid.ToString("N") ?? throw new ArgumentNullException(nameof(ASF.GlobalDatabase.Guid));
#pragma warning restore IDE0052
private readonly ulong SteamID;
@ -62,11 +61,10 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "steamid", Required = Required.Always)]
[NotNull]
private string SteamIDText => new SteamID(SteamID).Render();
#pragma warning restore IDE0051
internal RequestData(ulong steamID, [NotNull] IEnumerable<KeyValuePair<uint, ulong>> apps, [NotNull] IEnumerable<KeyValuePair<uint, ulong>> accessTokens, [NotNull] IEnumerable<KeyValuePair<uint, string>> depots) {
internal RequestData(ulong steamID, IEnumerable<KeyValuePair<uint, ulong>> apps, IEnumerable<KeyValuePair<uint, ulong>> accessTokens, IEnumerable<KeyValuePair<uint, string>> depots) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount || (apps == null) || (accessTokens == null) || (depots == null)) {
throw new ArgumentNullException(nameof(steamID) + " || " + nameof(apps) + " || " + nameof(accessTokens) + " || " + nameof(depots));
}

View file

@ -27,7 +27,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
internal sealed class ResponseData {
#pragma warning disable 649
[JsonProperty(PropertyName = "data", Required = Required.Always)]
internal readonly InternalData Data;
internal readonly InternalData? Data;
#pragma warning restore 649
#pragma warning disable 649

View file

@ -29,21 +29,19 @@ using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Localization;
using ArchiSteamFarm.Plugins;
using JetBrains.Annotations;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using SteamKit2;
namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
[Export(typeof(IPlugin))]
[UsedImplicitly]
internal sealed class SteamTokenDumperPlugin : OfficialPlugin, IASF, IBot, IBotSteamClient, ISteamPICSChanges {
private static readonly ConcurrentDictionary<Bot, IDisposable> BotSubscriptions = new ConcurrentDictionary<Bot, IDisposable>();
private static readonly ConcurrentDictionary<Bot, (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer)> BotSynchronizations = new ConcurrentDictionary<Bot, (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer)>();
private static readonly SemaphoreSlim SubmissionSemaphore = new SemaphoreSlim(1, 1);
private static readonly Timer SubmissionTimer = new Timer(async e => await SubmitData().ConfigureAwait(false));
private static GlobalCache GlobalCache;
private static GlobalCache? GlobalCache;
[JsonProperty]
private static bool IsEnabled;
@ -56,7 +54,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
public Task<uint> GetPreferredChangeNumberToStartFrom() => Task.FromResult(IsEnabled ? GlobalCache?.LastChangeNumber ?? 0 : 0);
public void OnASFInit(IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
public void OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
if (!SharedInfo.HasValidToken) {
ASF.ArchiLogger.LogGenericError($"{Name} has been disabled due to missing build token.");
@ -103,7 +101,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
throw new ArgumentNullException(nameof(bot));
}
if (BotSubscriptions.TryRemove(bot, out IDisposable subscription)) {
if (BotSubscriptions.TryRemove(bot, out IDisposable? subscription)) {
subscription.Dispose();
}
@ -138,7 +136,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
throw new ArgumentNullException(nameof(bot) + " || " + nameof(callbackManager));
}
if (BotSubscriptions.TryRemove(bot, out IDisposable subscription)) {
if (BotSubscriptions.TryRemove(bot, out IDisposable? subscription)) {
subscription.Dispose();
}
@ -153,7 +151,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
}
}
public IReadOnlyCollection<ClientMsgHandler> OnBotSteamHandlersInit(Bot bot) => null;
public IReadOnlyCollection<ClientMsgHandler>? OnBotSteamHandlersInit(Bot bot) => null;
public override void OnLoaded() { }
@ -189,7 +187,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
await GlobalCache.OnPICSChangesRestart(currentChangeNumber).ConfigureAwait(false);
}
private static async void OnLicenseList([NotNull] Bot bot, [NotNull] SteamApps.LicenseListCallback callback) {
private static async void OnLicenseList(Bot bot, SteamApps.LicenseListCallback callback) {
if ((bot == null) || (callback == null)) {
throw new ArgumentNullException(nameof(callback));
}
@ -208,7 +206,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
await Refresh(bot, packageTokens.Keys).ConfigureAwait(false);
}
private static async Task Refresh([NotNull] Bot bot, IReadOnlyCollection<uint> packageIDs = null) {
private static async Task Refresh(Bot bot, IReadOnlyCollection<uint>? packageIDs = null) {
if (bot == null) {
throw new ArgumentNullException(nameof(bot));
}
@ -217,8 +215,8 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
return;
}
if (GlobalCache == null) {
throw new ArgumentNullException(nameof(GlobalCache));
if ((GlobalCache == null) || (ASF.GlobalDatabase == null)) {
throw new ArgumentNullException(nameof(GlobalCache) + " || " + nameof(ASF.GlobalDatabase));
}
if (!BotSynchronizations.TryGetValue(bot, out (SemaphoreSlim RefreshSemaphore, Timer RefreshTimer) synchronization)) {
@ -239,7 +237,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
HashSet<uint> appIDsToRefresh = new HashSet<uint>();
foreach (uint packageID in packageIDs) {
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint> AppIDs) packageData) || (packageData.AppIDs == null)) {
if (!ASF.GlobalDatabase.PackagesDataReadOnly.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) || (packageData.AppIDs == null)) {
// ASF might not have the package info for us at the moment, we'll retry later
continue;
}
@ -388,14 +386,18 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
}
private static async Task SubmitData() {
if (Bot.Bots == null) {
throw new ArgumentNullException(nameof(Bot.Bots));
}
const string request = SharedInfo.ServerURL + "/submit";
if (!IsEnabled) {
return;
}
if (GlobalCache == null) {
throw new ArgumentNullException(nameof(GlobalCache));
if ((ASF.GlobalConfig == null) || (ASF.WebBrowser == null) || (GlobalCache == null)) {
throw new ArgumentNullException(nameof(ASF.GlobalConfig) + " || " + nameof(ASF.WebBrowser) + " || " + nameof(GlobalCache));
}
if (!await SubmissionSemaphore.WaitAsync(0).ConfigureAwait(false)) {
@ -415,7 +417,7 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
return;
}
ulong contributorSteamID = (ASF.GlobalConfig.SteamOwnerID > 0) && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.BotsReadOnly.Values.Where(bot => bot.SteamID > 0).OrderByDescending(bot => bot.OwnedPackageIDsReadOnly.Count).FirstOrDefault()?.SteamID ?? 0;
ulong contributorSteamID = (ASF.GlobalConfig.SteamOwnerID > 0) && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : Bot.Bots.Values.Where(bot => bot.SteamID > 0).OrderByDescending(bot => bot.OwnedPackageIDsReadOnly.Count).FirstOrDefault()?.SteamID ?? 0;
if (contributorSteamID == 0) {
ASF.ArchiLogger.LogGenericError($"Skipped {nameof(SubmitData)} trigger because there is no valid steamID we could classify as a contributor. Consider setting up {nameof(ASF.GlobalConfig.SteamOwnerID)} property.");
@ -427,9 +429,9 @@ namespace ArchiSteamFarm.OfficialPlugins.SteamTokenDumper {
ASF.ArchiLogger.LogGenericInfo($"Submitting registered apps/subs/depots: {appTokens.Count}/{packageTokens.Count}/{depotKeys.Count}...");
WebBrowser.ObjectResponse<ResponseData> response = await ASF.WebBrowser.UrlPostToJsonObject<ResponseData, RequestData>(request, requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
WebBrowser.ObjectResponse<ResponseData>? response = await ASF.WebBrowser.UrlPostToJsonObject<ResponseData, RequestData>(request, requestData, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
if ((response?.Content == null) || response.StatusCode.IsClientErrorCode()) {
if ((response?.Content?.Data == null) || response.StatusCode.IsClientErrorCode()) {
ASF.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
#if NETFRAMEWORK

View file

@ -51,58 +51,53 @@ namespace ArchiSteamFarm {
public static byte LoadBalancingDelay => Math.Max(GlobalConfig?.LoginLimiterDelay ?? 0, GlobalConfig.DefaultLoginLimiterDelay);
[PublicAPI]
public static GlobalConfig GlobalConfig { get; private set; }
public static GlobalConfig? GlobalConfig { get; private set; }
[PublicAPI]
public static GlobalDatabase GlobalDatabase { get; private set; }
public static GlobalDatabase? GlobalDatabase { get; private set; }
[PublicAPI]
public static WebBrowser WebBrowser { get; internal set; }
public static WebBrowser? WebBrowser { get; internal set; }
internal static ICrossProcessSemaphore ConfirmationsSemaphore { get; private set; }
internal static ICrossProcessSemaphore GiftsSemaphore { get; private set; }
internal static ICrossProcessSemaphore InventorySemaphore { get; private set; }
internal static ICrossProcessSemaphore LoginRateLimitingSemaphore { get; private set; }
internal static ICrossProcessSemaphore LoginSemaphore { get; private set; }
internal static ImmutableDictionary<string, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)> WebLimitingSemaphores { get; private set; }
internal static ICrossProcessSemaphore? ConfirmationsSemaphore { get; private set; }
internal static ICrossProcessSemaphore? GiftsSemaphore { get; private set; }
internal static ICrossProcessSemaphore? InventorySemaphore { get; private set; }
internal static ICrossProcessSemaphore? LoginRateLimitingSemaphore { get; private set; }
internal static ICrossProcessSemaphore? LoginSemaphore { get; private set; }
internal static ImmutableDictionary<string, (ICrossProcessSemaphore RateLimitingSemaphore, SemaphoreSlim OpenConnectionsSemaphore)>? WebLimitingSemaphores { get; private set; }
private static readonly SemaphoreSlim UpdateSemaphore = new SemaphoreSlim(1, 1);
private static Timer AutoUpdatesTimer;
private static FileSystemWatcher FileSystemWatcher;
private static ConcurrentDictionary<string, object> LastWriteEvents;
private static Timer? AutoUpdatesTimer;
private static FileSystemWatcher? FileSystemWatcher;
private static ConcurrentDictionary<string, object>? LastWriteEvents;
[PublicAPI]
public static bool IsOwner(ulong steamID) {
if (steamID == 0) {
ArchiLogger.LogNullError(nameof(steamID));
return false;
throw new ArgumentNullException(nameof(steamID));
}
return (steamID == GlobalConfig.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID));
return (steamID == GlobalConfig?.SteamOwnerID) || (Debugging.IsDebugBuild && (steamID == SharedInfo.ArchiSteamID));
}
internal static string GetFilePath(EFileType fileType) {
if (!Enum.IsDefined(typeof(EFileType), fileType)) {
ArchiLogger.LogNullError(nameof(fileType));
return null;
throw new ArgumentNullException(nameof(fileType));
}
switch (fileType) {
case EFileType.Config:
return Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName);
case EFileType.Database:
return Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName);
default:
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(fileType), fileType));
return null;
}
return fileType switch {
EFileType.Config => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalConfigFileName),
EFileType.Database => Path.Combine(SharedInfo.ConfigDirectory, SharedInfo.GlobalDatabaseFileName),
_ => throw new ArgumentOutOfRangeException(nameof(fileType))
};
}
internal static async Task Init() {
if (GlobalConfig == null) {
throw new ArgumentNullException(nameof(GlobalConfig));
}
if (!PluginsCore.InitPlugins()) {
await Task.Delay(10000).ConfigureAwait(false);
}
@ -192,6 +187,10 @@ namespace ArchiSteamFarm {
}
internal static async Task RestartOrExit() {
if (GlobalConfig == null) {
throw new ArgumentNullException(nameof(GlobalConfig));
}
if (Program.RestartAllowed && GlobalConfig.AutoRestart) {
ArchiLogger.LogGenericInfo(Strings.Restarting);
await Task.Delay(5000).ConfigureAwait(false);
@ -203,8 +202,11 @@ namespace ArchiSteamFarm {
}
}
[ItemCanBeNull]
internal static async Task<Version> Update(bool updateOverride = false) {
internal static async Task<Version?> Update(bool updateOverride = false) {
if ((GlobalConfig == null) || (WebBrowser == null)) {
throw new ArgumentNullException(nameof(GlobalConfig) + " || " + nameof(WebBrowser));
}
if (!SharedInfo.BuildInfo.CanUpdate || (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None)) {
return null;
}
@ -234,7 +236,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(Strings.UpdateCheckingNewVersion);
GitHub.ReleaseResponse releaseResponse = await GitHub.GetLatestRelease(GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable).ConfigureAwait(false);
GitHub.ReleaseResponse? releaseResponse = await GitHub.GetLatestRelease(GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.Stable).ConfigureAwait(false);
if (releaseResponse == null) {
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateCheckFailed);
@ -276,7 +278,7 @@ namespace ArchiSteamFarm {
}
string targetFile = SharedInfo.ASF + "-" + SharedInfo.BuildInfo.Variant + ".zip";
GitHub.ReleaseResponse.Asset binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => asset.Name.Equals(targetFile, StringComparison.OrdinalIgnoreCase));
GitHub.ReleaseResponse.Asset? binaryAsset = releaseResponse.Assets.FirstOrDefault(asset => !string.IsNullOrEmpty(asset.Name) && asset.Name!.Equals(targetFile, StringComparison.OrdinalIgnoreCase));
if (binaryAsset == null) {
ArchiLogger.LogGenericWarning(Strings.ErrorUpdateNoAssetForThisVersion);
@ -291,12 +293,12 @@ namespace ArchiSteamFarm {
}
if (!string.IsNullOrEmpty(releaseResponse.ChangelogPlainText)) {
ArchiLogger.LogGenericInfo(releaseResponse.ChangelogPlainText);
ArchiLogger.LogGenericInfo(releaseResponse.ChangelogPlainText!);
}
ArchiLogger.LogGenericInfo(string.Format(Strings.UpdateDownloadingNewVersion, newVersion, binaryAsset.Size / 1024 / 1024));
WebBrowser.BinaryResponse response = await WebBrowser.UrlGetToBinaryWithProgress(binaryAsset.DownloadURL).ConfigureAwait(false);
WebBrowser.BinaryResponse? response = await WebBrowser.UrlGetToBinaryWithProgress(binaryAsset.DownloadURL!).ConfigureAwait(false);
if (response?.Content == null) {
return null;
@ -311,10 +313,11 @@ namespace ArchiSteamFarm {
}
try {
#if !NETFRAMEWORK
await
#if NETFRAMEWORK
using MemoryStream memoryStream = new MemoryStream(response.Content);
#else
await using MemoryStream memoryStream = new MemoryStream(response.Content);
#endif
using MemoryStream memoryStream = new MemoryStream(response.Content);
using ZipArchive zipArchive = new ZipArchive(memoryStream);
@ -344,10 +347,8 @@ namespace ArchiSteamFarm {
}
private static async Task<bool> CanHandleWriteEvent(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ArchiLogger.LogNullError(nameof(filePath));
return false;
if (string.IsNullOrEmpty(filePath) || (LastWriteEvents == null)) {
throw new ArgumentNullException(nameof(filePath) + " || " + nameof(LastWriteEvents));
}
// Save our event in dictionary
@ -358,14 +359,12 @@ namespace ArchiSteamFarm {
await Task.Delay(1000).ConfigureAwait(false);
// We're allowed to handle this event if the one that is saved after full second is our event and we succeed in clearing it (we don't care what we're clearing anymore, it doesn't have to be atomic operation)
return LastWriteEvents.TryGetValue(filePath, out object savedWriteEvent) && (currentWriteEvent == savedWriteEvent) && LastWriteEvents.TryRemove(filePath, out _);
return LastWriteEvents.TryGetValue(filePath, out object? savedWriteEvent) && (currentWriteEvent == savedWriteEvent) && LastWriteEvents.TryRemove(filePath, out _);
}
private static void InitBotsComparer(StringComparer botsComparer) {
if (botsComparer == null) {
ArchiLogger.LogNullError(nameof(botsComparer));
return;
throw new ArgumentNullException(nameof(botsComparer));
}
if (Bot.Bots != null) {
@ -394,9 +393,7 @@ namespace ArchiSteamFarm {
private static bool IsValidBotName(string botName) {
if (string.IsNullOrEmpty(botName)) {
ArchiLogger.LogNullError(nameof(botName));
return false;
throw new ArgumentNullException(nameof(botName));
}
if (botName[0] == '.') {
@ -408,9 +405,7 @@ namespace ArchiSteamFarm {
private static async void OnChanged(object sender, FileSystemEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
throw new ArgumentNullException(nameof(sender) + " || " + nameof(e));
}
await OnChangedFile(e.Name, e.FullPath).ConfigureAwait(false);
@ -418,9 +413,7 @@ namespace ArchiSteamFarm {
private static async Task OnChangedConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
await OnCreatedConfigFile(name, fullPath).ConfigureAwait(false);
@ -428,9 +421,7 @@ namespace ArchiSteamFarm {
private static async Task OnChangedConfigFile(string name) {
if (string.IsNullOrEmpty(name)) {
ArchiLogger.LogNullError(nameof(name));
return;
throw new ArgumentNullException(nameof(name));
}
if (!name.Equals(SharedInfo.IPCConfigFile) || (GlobalConfig?.IPC != true)) {
@ -447,6 +438,10 @@ namespace ArchiSteamFarm {
}
private static async Task OnChangedFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
string extension = Path.GetExtension(name);
switch (extension) {
@ -464,9 +459,7 @@ namespace ArchiSteamFarm {
private static async Task OnChangedKeysFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
await OnCreatedKeysFile(name, fullPath).ConfigureAwait(false);
@ -474,9 +467,7 @@ namespace ArchiSteamFarm {
private static async void OnCreated(object sender, FileSystemEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
throw new ArgumentNullException(nameof(sender) + " || " + nameof(e));
}
await OnCreatedFile(e.Name, e.FullPath).ConfigureAwait(false);
@ -484,9 +475,7 @@ namespace ArchiSteamFarm {
private static async Task OnCreatedConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
string extension = Path.GetExtension(name);
@ -505,9 +494,7 @@ namespace ArchiSteamFarm {
private static async Task OnCreatedFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
string extension = Path.GetExtension(name);
@ -526,10 +513,8 @@ namespace ArchiSteamFarm {
}
private static async Task OnCreatedJsonFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath) || (Bot.Bots == null)) {
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath) + " || " + nameof(Bot.Bots));
}
string botName = Path.GetFileNameWithoutExtension(name);
@ -553,7 +538,7 @@ namespace ArchiSteamFarm {
return;
}
if (Bot.Bots.TryGetValue(botName, out Bot bot)) {
if (Bot.Bots.TryGetValue(botName, out Bot? bot)) {
await bot.OnConfigChanged(false).ConfigureAwait(false);
} else {
await Bot.RegisterBot(botName).ConfigureAwait(false);
@ -565,10 +550,8 @@ namespace ArchiSteamFarm {
}
private static async Task OnCreatedKeysFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath) || (Bot.Bots == null)) {
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath) + " || " + nameof(Bot.Bots));
}
string botName = Path.GetFileNameWithoutExtension(name);
@ -581,7 +564,7 @@ namespace ArchiSteamFarm {
return;
}
if (!Bot.Bots.TryGetValue(botName, out Bot bot)) {
if (!Bot.Bots.TryGetValue(botName, out Bot? bot)) {
return;
}
@ -590,9 +573,7 @@ namespace ArchiSteamFarm {
private static async void OnDeleted(object sender, FileSystemEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
throw new ArgumentNullException(nameof(sender) + " || " + nameof(e));
}
await OnDeletedFile(e.Name, e.FullPath).ConfigureAwait(false);
@ -600,9 +581,7 @@ namespace ArchiSteamFarm {
private static async Task OnDeletedConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
string extension = Path.GetExtension(name);
@ -621,9 +600,7 @@ namespace ArchiSteamFarm {
private static async Task OnDeletedFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath));
}
string extension = Path.GetExtension(name);
@ -638,10 +615,8 @@ namespace ArchiSteamFarm {
}
private static async Task OnDeletedJsonConfigFile(string name, string fullPath) {
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath)) {
ArchiLogger.LogNullError(nameof(name) + " || " + nameof(fullPath));
return;
if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(fullPath) || (Bot.Bots == null)) {
throw new ArgumentNullException(nameof(name) + " || " + nameof(fullPath) + " || " + nameof(Bot.Bots));
}
string botName = Path.GetFileNameWithoutExtension(name);
@ -677,16 +652,14 @@ namespace ArchiSteamFarm {
return;
}
if (Bot.Bots.TryGetValue(botName, out Bot bot)) {
if (Bot.Bots.TryGetValue(botName, out Bot? bot)) {
await bot.OnConfigChanged(true).ConfigureAwait(false);
}
}
private static async void OnRenamed(object sender, RenamedEventArgs e) {
if ((sender == null) || (e == null)) {
ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
return;
throw new ArgumentNullException(nameof(sender) + " || " + nameof(e));
}
await OnDeletedFile(e.OldName, e.OldFullPath).ConfigureAwait(false);
@ -694,8 +667,8 @@ namespace ArchiSteamFarm {
}
private static async Task RegisterBots() {
if (Bot.Bots.Count > 0) {
return;
if ((GlobalConfig == null) || (GlobalDatabase == null) || (WebBrowser == null)) {
throw new ArgumentNullException(nameof(GlobalConfig) + " || " + nameof(GlobalDatabase) + " || " + nameof(WebBrowser));
}
// Ensure that we ask for a list of servers if we don't have any saved servers available
@ -720,7 +693,7 @@ namespace ArchiSteamFarm {
HashSet<string> botNames;
try {
botNames = Directory.Exists(SharedInfo.ConfigDirectory) ? Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*" + SharedInfo.JsonConfigExtension).Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).ToHashSet(Bot.BotsComparer) : new HashSet<string>(0);
botNames = Directory.Exists(SharedInfo.ConfigDirectory) ? Directory.EnumerateFiles(SharedInfo.ConfigDirectory, "*" + SharedInfo.JsonConfigExtension).Select(Path.GetFileNameWithoutExtension).Where(botName => !string.IsNullOrEmpty(botName) && IsValidBotName(botName)).ToHashSet(Bot.BotsComparer)! : new HashSet<string>(0);
} catch (Exception e) {
ArchiLogger.LogGenericException(e);
@ -742,6 +715,10 @@ namespace ArchiSteamFarm {
}
private static async Task UpdateAndRestart() {
if (GlobalConfig == null) {
throw new ArgumentNullException(nameof(GlobalConfig));
}
if (!SharedInfo.BuildInfo.CanUpdate || (GlobalConfig.UpdateChannel == GlobalConfig.EUpdateChannel.None)) {
return;
}
@ -759,7 +736,7 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericInfo(string.Format(Strings.AutoUpdateCheckInfo, autoUpdatePeriod.ToHumanReadable()));
}
Version newVersion = await Update().ConfigureAwait(false);
Version? newVersion = await Update().ConfigureAwait(false);
if ((newVersion == null) || (newVersion <= SharedInfo.Version)) {
return;
@ -770,9 +747,7 @@ namespace ArchiSteamFarm {
private static bool UpdateFromArchive(ZipArchive archive, string targetDirectory) {
if ((archive == null) || string.IsNullOrEmpty(targetDirectory)) {
ArchiLogger.LogNullError(nameof(archive) + " || " + nameof(targetDirectory));
return false;
throw new ArgumentNullException(nameof(archive) + " || " + nameof(targetDirectory));
}
// Firstly we'll move all our existing files to a backup directory
@ -795,7 +770,7 @@ namespace ArchiSteamFarm {
return false;
}
string relativeDirectoryName = Path.GetDirectoryName(relativeFilePath);
string? relativeDirectoryName = Path.GetDirectoryName(relativeFilePath);
switch (relativeDirectoryName) {
case null:
@ -854,7 +829,7 @@ namespace ArchiSteamFarm {
// Check if this file requires its own folder
if (zipFile.Name != zipFile.FullName) {
string directory = Path.GetDirectoryName(file);
string? directory = Path.GetDirectoryName(file);
if (string.IsNullOrEmpty(directory)) {
ArchiLogger.LogNullError(nameof(directory));

View file

@ -40,11 +40,11 @@ namespace ArchiSteamFarm {
private readonly ConcurrentHashSet<ulong> HandledGifts = new ConcurrentHashSet<ulong>();
private readonly SemaphoreSlim TradingSemaphore = new SemaphoreSlim(1, 1);
private Timer CardsFarmerResumeTimer;
private Timer? CardsFarmerResumeTimer;
private bool ProcessingGiftsScheduled;
private bool TradingScheduled;
internal Actions([NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
internal Actions(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
public async ValueTask DisposeAsync() {
// Those are objects that are always being created if constructor doesn't throw exception
@ -70,17 +70,16 @@ namespace ArchiSteamFarm {
}
[PublicAPI]
public async Task<(bool Success, string Token, string Message)> GenerateTwoFactorAuthenticationToken() {
if (!Bot.HasMobileAuthenticator) {
public async Task<(bool Success, string? Token, string Message)> GenerateTwoFactorAuthenticationToken() {
if (Bot.BotDatabase.MobileAuthenticator == null) {
return (false, null, Strings.BotNoASFAuthenticator);
}
string token = await Bot.BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false);
string? token = await Bot.BotDatabase.MobileAuthenticator.GenerateToken().ConfigureAwait(false);
return (true, token, Strings.Success);
}
[ItemNotNull]
[PublicAPI]
public async Task<IDisposable> GetTradingLock() {
await TradingSemaphore.WaitAsync().ConfigureAwait(false);
@ -89,8 +88,8 @@ namespace ArchiSteamFarm {
}
[PublicAPI]
public async Task<(bool Success, string Message)> HandleTwoFactorAuthenticationConfirmations(bool accept, MobileAuthenticator.Confirmation.EType? acceptedType = null, IReadOnlyCollection<ulong> acceptedCreatorIDs = null, bool waitIfNeeded = false) {
if (!Bot.HasMobileAuthenticator) {
public async Task<(bool Success, string Message)> HandleTwoFactorAuthenticationConfirmations(bool accept, MobileAuthenticator.Confirmation.EType? acceptedType = null, IReadOnlyCollection<ulong>? acceptedCreatorIDs = null, bool waitIfNeeded = false) {
if (Bot.BotDatabase.MobileAuthenticator == null) {
return (false, Strings.BotNoASFAuthenticator);
}
@ -99,14 +98,14 @@ namespace ArchiSteamFarm {
}
ushort handledConfirmationsCount = 0;
HashSet<ulong> handledCreatorIDs = null;
HashSet<ulong>? handledCreatorIDs = null;
for (byte i = 0; (i == 0) || ((i < WebBrowser.MaxTries) && waitIfNeeded); i++) {
if (i > 0) {
await Task.Delay(1000).ConfigureAwait(false);
}
HashSet<MobileAuthenticator.Confirmation> confirmations = await Bot.BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false);
HashSet<MobileAuthenticator.Confirmation>? confirmations = await Bot.BotDatabase.MobileAuthenticator.GetConfirmations().ConfigureAwait(false);
if ((confirmations == null) || (confirmations.Count == 0)) {
continue;
@ -135,7 +134,7 @@ namespace ArchiSteamFarm {
handledConfirmationsCount += (ushort) confirmations.Count;
if ((acceptedCreatorIDs != null) && (acceptedCreatorIDs.Count > 0)) {
IEnumerable<ulong> handledCreatorIDsThisRound = confirmations.Select(confirmation => confirmation.Creator).Where(acceptedCreatorIDs.Contains);
IEnumerable<ulong> handledCreatorIDsThisRound = confirmations.Select(confirmation => confirmation.Creator).Where(acceptedCreatorIDs.Contains!);
if (handledCreatorIDs != null) {
handledCreatorIDs.UnionWith(handledCreatorIDsThisRound);
@ -186,11 +185,9 @@ namespace ArchiSteamFarm {
}
[PublicAPI]
public async Task<(bool Success, string Message)> Play(IEnumerable<uint> gameIDs, string gameName = null) {
public async Task<(bool Success, string Message)> Play(IEnumerable<uint> gameIDs, string? gameName = null) {
if (gameIDs == null) {
Bot.ArchiLogger.LogNullError(nameof(gameIDs));
return (false, string.Format(Strings.ErrorObjectIsNull, nameof(gameIDs)));
throw new ArgumentNullException(nameof(gameIDs));
}
if (!Bot.IsConnectedAndLoggedOn) {
@ -207,7 +204,7 @@ namespace ArchiSteamFarm {
}
[PublicAPI]
public async Task<ArchiHandler.PurchaseResponseCallback> RedeemKey(string key) {
public async Task<ArchiHandler.PurchaseResponseCallback?> RedeemKey(string key) {
await LimitGiftsRequestsAsync().ConfigureAwait(false);
return await Bot.ArchiHandler.RedeemKey(key).ConfigureAwait(false);
@ -238,11 +235,9 @@ namespace ArchiSteamFarm {
}
[PublicAPI]
public async Task<(bool Success, string Message)> SendInventory(uint appID = Steam.Asset.SteamAppID, ulong contextID = Steam.Asset.SteamCommunityContextID, ulong targetSteamID = 0, string tradeToken = null, Func<Steam.Asset, bool> filterFunction = null) {
public async Task<(bool Success, string Message)> SendInventory(uint appID = Steam.Asset.SteamAppID, ulong contextID = Steam.Asset.SteamCommunityContextID, ulong targetSteamID = 0, string? tradeToken = null, Func<Steam.Asset, bool>? filterFunction = null) {
if ((appID == 0) || (contextID == 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(contextID));
return (false, string.Format(Strings.ErrorObjectIsNull, nameof(appID) + " || " + nameof(contextID)));
throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID));
}
if (!Bot.IsConnectedAndLoggedOn) {
@ -304,14 +299,14 @@ namespace ArchiSteamFarm {
}
if (string.IsNullOrEmpty(tradeToken) && (Bot.SteamFriends.GetFriendRelationship(targetSteamID) != EFriendRelationship.Friend)) {
Bot targetBot = Bot.Bots.Values.FirstOrDefault(bot => bot.SteamID == targetSteamID);
Bot? targetBot = Bot.Bots?.Values.FirstOrDefault(bot => bot.SteamID == targetSteamID);
if (targetBot?.IsConnectedAndLoggedOn == true) {
tradeToken = await targetBot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
}
}
(bool success, HashSet<ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(targetSteamID, inventory, token: tradeToken).ConfigureAwait(false);
(bool success, HashSet<ulong>? mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(targetSteamID, inventory, token: tradeToken).ConfigureAwait(false);
if ((mobileTradeOfferIDs != null) && (mobileTradeOfferIDs.Count > 0) && Bot.HasMobileAuthenticator) {
(bool twoFactorSuccess, _) = await HandleTwoFactorAuthenticationConfirmations(true, MobileAuthenticator.Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false);
@ -354,8 +349,8 @@ namespace ArchiSteamFarm {
}
[PublicAPI]
public static async Task<(bool Success, string Message, Version Version)> Update() {
Version version = await ASF.Update(true).ConfigureAwait(false);
public static async Task<(bool Success, string? Message, Version? Version)> Update() {
Version? version = await ASF.Update(true).ConfigureAwait(false);
if (version == null) {
return (false, null, null);
@ -386,7 +381,7 @@ namespace ArchiSteamFarm {
ProcessingGiftsScheduled = false;
}
HashSet<ulong> giftCardIDs = await Bot.ArchiWebHandler.GetDigitalGiftCards().ConfigureAwait(false);
HashSet<ulong>? giftCardIDs = await Bot.ArchiWebHandler.GetDigitalGiftCards().ConfigureAwait(false);
if ((giftCardIDs == null) || (giftCardIDs.Count == 0)) {
return;
@ -413,9 +408,7 @@ namespace ArchiSteamFarm {
internal async Task AcceptGuestPasses(IReadOnlyCollection<ulong> guestPassIDs) {
if ((guestPassIDs == null) || (guestPassIDs.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(guestPassIDs));
return;
throw new ArgumentNullException(nameof(guestPassIDs));
}
foreach (ulong guestPassID in guestPassIDs.Where(guestPassID => !HandledGifts.Contains(guestPassID))) {
@ -424,7 +417,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.BotAcceptingGift, guestPassID));
await LimitGiftsRequestsAsync().ConfigureAwait(false);
ArchiHandler.RedeemGuestPassResponseCallback response = await Bot.ArchiHandler.RedeemGuestPass(guestPassID).ConfigureAwait(false);
ArchiHandler.RedeemGuestPassResponseCallback? response = await Bot.ArchiHandler.RedeemGuestPass(guestPassID).ConfigureAwait(false);
if (response != null) {
if (response.Result == EResult.OK) {
@ -441,19 +434,25 @@ namespace ArchiSteamFarm {
internal void OnDisconnected() => HandledGifts.Clear();
private ulong GetFirstSteamMasterID() {
ulong steamMasterID = Bot.BotConfig.SteamUserPermissions.Where(kv => (kv.Key != 0) && (kv.Key != Bot.SteamID) && new SteamID(kv.Key).IsIndividualAccount && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderBy(steamID => steamID).FirstOrDefault();
ulong steamMasterID = Bot.BotConfig.SteamUserPermissions.Where(kv => (kv.Key > 0) && (kv.Key != Bot.SteamID) && new SteamID(kv.Key).IsIndividualAccount && (kv.Value == BotConfig.EPermission.Master)).Select(kv => kv.Key).OrderBy(steamID => steamID).FirstOrDefault();
return steamMasterID > 0 ? steamMasterID : (ASF.GlobalConfig.SteamOwnerID != 0) && new SteamID(ASF.GlobalConfig.SteamOwnerID).IsIndividualAccount ? ASF.GlobalConfig.SteamOwnerID : 0;
if (steamMasterID > 0) {
return steamMasterID;
}
ulong steamOwnerID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
return (steamOwnerID > 0) && new SteamID(steamOwnerID).IsIndividualAccount ? steamOwnerID : 0;
}
private static async Task LimitGiftsRequestsAsync() {
if (ASF.GiftsSemaphore == null) {
ASF.ArchiLogger.LogNullError(nameof(ASF.GiftsSemaphore));
return;
throw new ArgumentNullException(nameof(ASF.GiftsSemaphore));
}
if (ASF.GlobalConfig.GiftsLimiterDelay == 0) {
byte giftsLimiterDelay = ASF.GlobalConfig?.GiftsLimiterDelay ?? GlobalConfig.DefaultGiftsLimiterDelay;
if (giftsLimiterDelay == 0) {
return;
}
@ -461,7 +460,7 @@ namespace ArchiSteamFarm {
Utilities.InBackground(
async () => {
await Task.Delay(ASF.GlobalConfig.GiftsLimiterDelay * 1000).ConfigureAwait(false);
await Task.Delay(giftsLimiterDelay * 1000).ConfigureAwait(false);
ASF.GiftsSemaphore.Release();
}
);

View file

@ -24,9 +24,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using ArchiSteamFarm.Localization;
using CryptSharp.Utility;
using JetBrains.Annotations;
using SteamKit2;
namespace ArchiSteamFarm {
@ -35,10 +33,8 @@ namespace ArchiSteamFarm {
private const byte SteamParentalSCryptBlocksCount = 8;
private const ushort SteamParentalSCryptIterations = 8192;
[NotNull]
private static IEnumerable<byte> SteamParentalCharacters => Enumerable.Range('0', 10).Select(character => (byte) character);
[NotNull]
private static IEnumerable<byte[]> SteamParentalCodes {
get {
HashSet<byte> steamParentalCharacters = SteamParentalCharacters.ToHashSet();
@ -49,53 +45,35 @@ namespace ArchiSteamFarm {
private static byte[] EncryptionKey = Encoding.UTF8.GetBytes(nameof(ArchiSteamFarm));
internal static string Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
internal static string? Decrypt(ECryptoMethod cryptoMethod, string encrypted) {
if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod) || string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(cryptoMethod) + " || " + nameof(encrypted));
return null;
throw new ArgumentNullException(nameof(cryptoMethod) + " || " + nameof(encrypted));
}
switch (cryptoMethod) {
case ECryptoMethod.PlainText:
return encrypted;
case ECryptoMethod.AES:
return DecryptAES(encrypted);
case ECryptoMethod.ProtectedDataForCurrentUser:
return DecryptProtectedDataForCurrentUser(encrypted);
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(cryptoMethod), cryptoMethod));
return null;
}
return cryptoMethod switch {
ECryptoMethod.PlainText => encrypted,
ECryptoMethod.AES => DecryptAES(encrypted),
ECryptoMethod.ProtectedDataForCurrentUser => DecryptProtectedDataForCurrentUser(encrypted),
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
};
}
internal static string Encrypt(ECryptoMethod cryptoMethod, string decrypted) {
internal static string? Encrypt(ECryptoMethod cryptoMethod, string decrypted) {
if (!Enum.IsDefined(typeof(ECryptoMethod), cryptoMethod) || string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(cryptoMethod) + " || " + nameof(decrypted));
return null;
throw new ArgumentNullException(nameof(cryptoMethod) + " || " + nameof(decrypted));
}
switch (cryptoMethod) {
case ECryptoMethod.PlainText:
return decrypted;
case ECryptoMethod.AES:
return EncryptAES(decrypted);
case ECryptoMethod.ProtectedDataForCurrentUser:
return EncryptProtectedDataForCurrentUser(decrypted);
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(cryptoMethod), cryptoMethod));
return null;
}
return cryptoMethod switch {
ECryptoMethod.PlainText => decrypted,
ECryptoMethod.AES => EncryptAES(decrypted),
ECryptoMethod.ProtectedDataForCurrentUser => EncryptProtectedDataForCurrentUser(decrypted),
_ => throw new ArgumentOutOfRangeException(nameof(cryptoMethod))
};
}
internal static IEnumerable<byte> GenerateSteamParentalHash(byte[] password, byte[] salt, byte hashLength, ESteamParentalAlgorithm steamParentalAlgorithm) {
internal static IEnumerable<byte>? GenerateSteamParentalHash(byte[] password, byte[] salt, byte hashLength, ESteamParentalAlgorithm steamParentalAlgorithm) {
if ((password == null) || (salt == null) || (hashLength == 0) || !Enum.IsDefined(typeof(ESteamParentalAlgorithm), steamParentalAlgorithm)) {
ASF.ArchiLogger.LogNullError(nameof(password) + " || " + nameof(salt) + " || " + nameof(hashLength) + " || " + nameof(steamParentalAlgorithm));
return null;
throw new ArgumentNullException(nameof(password) + " || " + nameof(salt) + " || " + nameof(hashLength) + " || " + nameof(steamParentalAlgorithm));
}
switch (steamParentalAlgorithm) {
@ -106,39 +84,31 @@ namespace ArchiSteamFarm {
case ESteamParentalAlgorithm.SCrypt:
return SCrypt.ComputeDerivedKey(password, salt, SteamParentalSCryptIterations, SteamParentalSCryptBlocksCount, 1, null, hashLength);
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(steamParentalAlgorithm), steamParentalAlgorithm));
return null;
throw new ArgumentOutOfRangeException(nameof(steamParentalAlgorithm));
}
}
internal static string RecoverSteamParentalCode(byte[] passwordHash, byte[] salt, ESteamParentalAlgorithm steamParentalAlgorithm) {
internal static string? RecoverSteamParentalCode(byte[] passwordHash, byte[] salt, ESteamParentalAlgorithm steamParentalAlgorithm) {
if ((passwordHash == null) || (salt == null) || !Enum.IsDefined(typeof(ESteamParentalAlgorithm), steamParentalAlgorithm)) {
ASF.ArchiLogger.LogNullError(nameof(passwordHash) + " || " + nameof(salt) + " || " + nameof(steamParentalAlgorithm));
return null;
throw new ArgumentNullException(nameof(passwordHash) + " || " + nameof(salt) + " || " + nameof(steamParentalAlgorithm));
}
byte[] password = SteamParentalCodes.AsParallel().FirstOrDefault(passwordToTry => GenerateSteamParentalHash(passwordToTry, salt, (byte) passwordHash.Length, steamParentalAlgorithm)?.SequenceEqual(passwordHash) == true);
byte[]? password = SteamParentalCodes.AsParallel().FirstOrDefault(passwordToTry => GenerateSteamParentalHash(passwordToTry, salt, (byte) passwordHash.Length, steamParentalAlgorithm)?.SequenceEqual(passwordHash) == true);
return password != null ? Encoding.UTF8.GetString(password) : null;
}
internal static void SetEncryptionKey(string key) {
if (string.IsNullOrEmpty(key)) {
ASF.ArchiLogger.LogNullError(nameof(key));
return;
throw new ArgumentNullException(nameof(key));
}
EncryptionKey = Encoding.UTF8.GetBytes(key);
}
private static string DecryptAES(string encrypted) {
private static string? DecryptAES(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted));
return null;
throw new ArgumentNullException(nameof(encrypted));
}
try {
@ -159,11 +129,9 @@ namespace ArchiSteamFarm {
}
}
private static string DecryptProtectedDataForCurrentUser(string encrypted) {
private static string? DecryptProtectedDataForCurrentUser(string encrypted) {
if (string.IsNullOrEmpty(encrypted)) {
ASF.ArchiLogger.LogNullError(nameof(encrypted));
return null;
throw new ArgumentNullException(nameof(encrypted));
}
try {
@ -185,11 +153,9 @@ namespace ArchiSteamFarm {
}
}
private static string EncryptAES(string decrypted) {
private static string? EncryptAES(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted));
return null;
throw new ArgumentNullException(nameof(decrypted));
}
try {
@ -210,11 +176,9 @@ namespace ArchiSteamFarm {
}
}
private static string EncryptProtectedDataForCurrentUser(string decrypted) {
private static string? EncryptProtectedDataForCurrentUser(string decrypted) {
if (string.IsNullOrEmpty(decrypted)) {
ASF.ArchiLogger.LogNullError(nameof(decrypted));
return null;
throw new ArgumentNullException(nameof(decrypted));
}
try {

View file

@ -47,7 +47,7 @@ namespace ArchiSteamFarm {
internal DateTime LastPacketReceived { get; private set; }
internal ArchiHandler([JetBrains.Annotations.NotNull] ArchiLogger archiLogger, [JetBrains.Annotations.NotNull] SteamUnifiedMessages steamUnifiedMessages) {
internal ArchiHandler(ArchiLogger archiLogger, SteamUnifiedMessages steamUnifiedMessages) {
if ((archiLogger == null) || (steamUnifiedMessages == null)) {
throw new ArgumentNullException(nameof(archiLogger) + " || " + nameof(steamUnifiedMessages));
}
@ -62,14 +62,8 @@ namespace ArchiSteamFarm {
}
public override void HandleMsg(IPacketMsg packetMsg) {
if (packetMsg == null) {
ArchiLogger.LogNullError(nameof(packetMsg));
return;
}
if (Client == null) {
ArchiLogger.LogNullError(nameof(Client));
if ((packetMsg == null) || (Client == null)) {
ArchiLogger.LogNullError(nameof(packetMsg) + " || " + nameof(Client));
return;
}
@ -122,9 +116,7 @@ namespace ArchiSteamFarm {
internal void AckChatMessage(ulong chatGroupID, ulong chatID, uint timestamp) {
if ((chatGroupID == 0) || (chatID == 0) || (timestamp == 0)) {
ArchiLogger.LogNullError(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(timestamp));
return;
throw new ArgumentNullException(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(timestamp));
}
if (Client == null) {
@ -148,9 +140,7 @@ namespace ArchiSteamFarm {
internal void AckMessage(ulong steamID, uint timestamp) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount || (timestamp == 0)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(timestamp));
return;
throw new ArgumentNullException(nameof(steamID) + " || " + nameof(timestamp));
}
if (Client == null) {
@ -173,9 +163,7 @@ namespace ArchiSteamFarm {
internal void AcknowledgeClanInvite(ulong steamID, bool acceptInvite) {
if ((steamID == 0) || !new SteamID(steamID).IsClanAccount) {
ArchiLogger.LogNullError(nameof(steamID));
return;
throw new ArgumentNullException(nameof(steamID));
}
if (Client == null) {
@ -200,9 +188,7 @@ namespace ArchiSteamFarm {
internal async Task<bool> AddFriend(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
ArchiLogger.LogNullError(nameof(steamID));
return false;
throw new ArgumentNullException(nameof(steamID));
}
if (Client == null) {
@ -232,9 +218,7 @@ namespace ArchiSteamFarm {
internal async Task<ulong> GetClanChatGroupID(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsClanAccount) {
ArchiLogger.LogNullError(nameof(steamID));
return 0;
throw new ArgumentNullException(nameof(steamID));
}
if (Client == null) {
@ -302,7 +286,7 @@ namespace ArchiSteamFarm {
return body.player_level;
}
internal async Task<HashSet<ulong>> GetMyChatGroupIDs() {
internal async Task<HashSet<ulong>?> GetMyChatGroupIDs() {
if (Client == null) {
ArchiLogger.LogNullError(nameof(Client));
@ -334,7 +318,7 @@ namespace ArchiSteamFarm {
return body.chat_room_groups.Select(chatRoom => chatRoom.group_summary.chat_group_id).ToHashSet();
}
internal async Task<CPrivacySettings> GetPrivacySettings() {
internal async Task<CPrivacySettings?> GetPrivacySettings() {
if (Client == null) {
ArchiLogger.LogNullError(nameof(Client));
@ -366,7 +350,7 @@ namespace ArchiSteamFarm {
return body.privacy_settings;
}
internal async Task<string> GetTradeToken() {
internal async Task<string?> GetTradeToken() {
if (Client == null) {
ArchiLogger.LogNullError(nameof(Client));
@ -398,11 +382,9 @@ namespace ArchiSteamFarm {
return body.trade_offer_access_token;
}
internal async Task<string> GetTwoFactorDeviceIdentifier(ulong steamID) {
internal async Task<string?> GetTwoFactorDeviceIdentifier(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
ArchiLogger.LogNullError(nameof(steamID));
return null;
throw new ArgumentNullException(nameof(steamID));
}
if (Client == null) {
@ -440,9 +422,7 @@ namespace ArchiSteamFarm {
internal async Task<bool> JoinChatRoomGroup(ulong chatGroupID) {
if (chatGroupID == 0) {
ArchiLogger.LogNullError(nameof(chatGroupID));
return false;
throw new ArgumentNullException(nameof(chatGroupID));
}
if (Client == null) {
@ -470,11 +450,9 @@ namespace ArchiSteamFarm {
return response.Result == EResult.OK;
}
internal async Task PlayGames(IEnumerable<uint> gameIDs, string gameName = null) {
internal async Task PlayGames(IEnumerable<uint> gameIDs, string? gameName = null) {
if (gameIDs == null) {
ArchiLogger.LogNullError(nameof(gameIDs));
return;
throw new ArgumentNullException(nameof(gameIDs));
}
if (Client == null) {
@ -526,11 +504,9 @@ namespace ArchiSteamFarm {
Client.Send(request);
}
internal async Task<RedeemGuestPassResponseCallback> RedeemGuestPass(ulong guestPassID) {
internal async Task<RedeemGuestPassResponseCallback?> RedeemGuestPass(ulong guestPassID) {
if (guestPassID == 0) {
ArchiLogger.LogNullError(nameof(guestPassID));
return null;
throw new ArgumentNullException(nameof(guestPassID));
}
if (Client == null) {
@ -559,11 +535,9 @@ namespace ArchiSteamFarm {
}
}
internal async Task<PurchaseResponseCallback> RedeemKey(string key) {
internal async Task<PurchaseResponseCallback?> RedeemKey(string key) {
if (string.IsNullOrEmpty(key)) {
ArchiLogger.LogNullError(nameof(key));
return null;
throw new ArgumentNullException(nameof(key));
}
if (Client == null) {
@ -594,9 +568,7 @@ namespace ArchiSteamFarm {
internal async Task<bool> RemoveFriend(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
ArchiLogger.LogNullError(nameof(steamID));
return false;
throw new ArgumentNullException(nameof(steamID));
}
if (Client == null) {
@ -641,9 +613,7 @@ namespace ArchiSteamFarm {
internal async Task<EResult> SendMessage(ulong steamID, string message) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount || string.IsNullOrEmpty(message)) {
ArchiLogger.LogNullError(nameof(steamID) + " || " + nameof(message));
return EResult.Invalid;
throw new ArgumentNullException(nameof(steamID) + " || " + nameof(message));
}
if (Client == null) {
@ -678,9 +648,7 @@ namespace ArchiSteamFarm {
internal async Task<EResult> SendMessage(ulong chatGroupID, ulong chatID, string message) {
if ((chatGroupID == 0) || (chatID == 0) || string.IsNullOrEmpty(message)) {
ArchiLogger.LogNullError(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(message));
return EResult.Invalid;
throw new ArgumentNullException(nameof(chatGroupID) + " || " + nameof(chatID) + " || " + nameof(message));
}
if (Client == null) {
@ -714,9 +682,7 @@ namespace ArchiSteamFarm {
internal async Task<EResult> SendTypingStatus(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
ArchiLogger.LogNullError(nameof(steamID));
return EResult.Invalid;
throw new ArgumentNullException(nameof(steamID));
}
if (Client == null) {
@ -749,9 +715,7 @@ namespace ArchiSteamFarm {
internal void SetCurrentMode(uint chatMode) {
if (chatMode == 0) {
ArchiLogger.LogNullError(nameof(chatMode));
return;
throw new ArgumentNullException(nameof(chatMode));
}
if (Client == null) {
@ -770,7 +734,7 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "MemberCanBeInternal")]
public sealed class PurchaseResponseCallback : CallbackMsg {
public readonly Dictionary<uint, string> Items;
public readonly Dictionary<uint, string>? Items;
public EPurchaseResultDetail PurchaseResultDetail { get; internal set; }
public EResult Result { get; internal set; }
@ -784,7 +748,7 @@ namespace ArchiSteamFarm {
PurchaseResultDetail = purchaseResult;
}
internal PurchaseResponseCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientPurchaseResponse msg) {
internal PurchaseResponseCallback(JobID jobID, CMsgClientPurchaseResponse msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -832,7 +796,7 @@ namespace ArchiSteamFarm {
}
}
string gameName = lineItem["ItemDescription"].Value;
string? gameName = lineItem["ItemDescription"].AsString();
if (string.IsNullOrEmpty(gameName)) {
ASF.ArchiLogger.LogNullError(nameof(gameName));
@ -850,7 +814,7 @@ namespace ArchiSteamFarm {
public sealed class UserNotificationsCallback : CallbackMsg {
internal readonly Dictionary<EUserNotification, uint> Notifications;
internal UserNotificationsCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientUserNotifications msg) {
internal UserNotificationsCallback(JobID jobID, CMsgClientUserNotifications msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -890,7 +854,7 @@ namespace ArchiSteamFarm {
}
}
internal UserNotificationsCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientItemAnnouncements msg) {
internal UserNotificationsCallback(JobID jobID, CMsgClientItemAnnouncements msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -899,7 +863,7 @@ namespace ArchiSteamFarm {
Notifications = new Dictionary<EUserNotification, uint>(1) { { EUserNotification.Items, msg.count_new_items } };
}
internal UserNotificationsCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientCommentNotifications msg) {
internal UserNotificationsCallback(JobID jobID, CMsgClientCommentNotifications msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -911,24 +875,23 @@ namespace ArchiSteamFarm {
[PublicAPI]
public enum EUserNotification : byte {
Unknown,
Trading,
GameTurns,
ModeratorMessages,
Comments,
Items,
Invites,
Unknown7, // No clue what 7 stands for, and I doubt we can find out
Gifts,
Chat,
HelpRequestReplies,
AccountAlerts
Trading = 1,
GameTurns = 2,
ModeratorMessages = 3,
Comments = 4,
Items = 5,
Invites = 6,
Gifts = 8,
Chat = 9,
HelpRequestReplies = 10,
AccountAlerts = 11
}
}
internal sealed class PlayingSessionStateCallback : CallbackMsg {
internal readonly bool PlayingBlocked;
internal PlayingSessionStateCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientPlayingSessionState msg) {
internal PlayingSessionStateCallback(JobID jobID, CMsgClientPlayingSessionState msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -941,7 +904,7 @@ namespace ArchiSteamFarm {
internal sealed class RedeemGuestPassResponseCallback : CallbackMsg {
internal readonly EResult Result;
internal RedeemGuestPassResponseCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientRedeemGuestPassResponse msg) {
internal RedeemGuestPassResponseCallback(JobID jobID, CMsgClientRedeemGuestPassResponse msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -954,7 +917,7 @@ namespace ArchiSteamFarm {
internal sealed class SharedLibraryLockStatusCallback : CallbackMsg {
internal readonly ulong LibraryLockedBySteamID;
internal SharedLibraryLockStatusCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientSharedLibraryLockStatus msg) {
internal SharedLibraryLockStatusCallback(JobID jobID, CMsgClientSharedLibraryLockStatus msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}
@ -972,7 +935,7 @@ namespace ArchiSteamFarm {
internal sealed class VanityURLChangedCallback : CallbackMsg {
internal readonly string VanityURL;
internal VanityURLChangedCallback([JetBrains.Annotations.NotNull] JobID jobID, [JetBrains.Annotations.NotNull] CMsgClientVanityURLChangedNotification msg) {
internal VanityURLChangedCallback(JobID jobID, CMsgClientVanityURLChangedNotification msg) {
if ((jobID == null) || (msg == null)) {
throw new ArgumentNullException(nameof(jobID) + " || " + nameof(msg));
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -83,10 +83,10 @@ namespace ArchiSteamFarm {
public readonly EBotBehaviour BotBehaviour = DefaultBotBehaviour;
[JsonProperty]
public readonly string CustomGamePlayedWhileFarming = DefaultCustomGamePlayedWhileFarming;
public readonly string? CustomGamePlayedWhileFarming = DefaultCustomGamePlayedWhileFarming;
[JsonProperty]
public readonly string CustomGamePlayedWhileIdle = DefaultCustomGamePlayedWhileIdle;
public readonly string? CustomGamePlayedWhileIdle = DefaultCustomGamePlayedWhileIdle;
[JsonProperty(Required = Required.DisallowNull)]
public readonly bool Enabled = DefaultEnabled;
@ -134,7 +134,7 @@ namespace ArchiSteamFarm {
public readonly bool ShutdownOnFarmingFinished = DefaultShutdownOnFarmingFinished;
[JsonProperty]
public readonly string SteamTradeToken = DefaultSteamTradeToken;
public readonly string? SteamTradeToken = DefaultSteamTradeToken;
[JsonProperty(ObjectCreationHandling = ObjectCreationHandling.Replace, Required = Required.DisallowNull)]
public readonly ImmutableDictionary<ulong, EPermission> SteamUserPermissions = DefaultSteamUserPermissions;
@ -152,13 +152,13 @@ namespace ArchiSteamFarm {
public ulong SteamMasterClanID { get; private set; } = DefaultSteamMasterClanID;
[JsonExtensionData]
internal Dictionary<string, JToken> AdditionalProperties {
internal Dictionary<string, JToken>? AdditionalProperties {
get;
[UsedImplicitly]
set;
}
internal string DecryptedSteamPassword {
internal string? DecryptedSteamPassword {
get {
if (string.IsNullOrEmpty(SteamPassword)) {
return null;
@ -168,7 +168,7 @@ namespace ArchiSteamFarm {
return SteamPassword;
}
string result = ArchiCryptoHelper.Decrypt(PasswordFormat, SteamPassword);
string? result = ArchiCryptoHelper.Decrypt(PasswordFormat, SteamPassword!);
if (string.IsNullOrEmpty(result)) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorIsInvalid, nameof(SteamPassword)));
@ -181,7 +181,7 @@ namespace ArchiSteamFarm {
set {
if (!string.IsNullOrEmpty(value) && (PasswordFormat != ArchiCryptoHelper.ECryptoMethod.PlainText)) {
value = ArchiCryptoHelper.Encrypt(PasswordFormat, value);
value = ArchiCryptoHelper.Encrypt(PasswordFormat, value!);
}
SteamPassword = value;
@ -196,7 +196,7 @@ namespace ArchiSteamFarm {
internal bool ShouldSerializeSensitiveDetails { private get; set; }
[JsonProperty]
internal string SteamLogin {
internal string? SteamLogin {
get => BackingSteamLogin;
set {
@ -206,7 +206,7 @@ namespace ArchiSteamFarm {
}
[JsonProperty]
internal string SteamParentalCode {
internal string? SteamParentalCode {
get => BackingSteamParentalCode;
set {
@ -216,7 +216,7 @@ namespace ArchiSteamFarm {
}
[JsonProperty]
internal string SteamPassword {
internal string? SteamPassword {
get => BackingSteamPassword;
set {
@ -225,12 +225,12 @@ namespace ArchiSteamFarm {
}
}
private string BackingSteamLogin = DefaultSteamLogin;
private string BackingSteamParentalCode = DefaultSteamParentalCode;
private string BackingSteamPassword = DefaultSteamPassword;
private string? BackingSteamLogin = DefaultSteamLogin;
private string? BackingSteamParentalCode = DefaultSteamParentalCode;
private string? BackingSteamPassword = DefaultSteamPassword;
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamMasterClanID), Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string SSteamMasterClanID {
get => SteamMasterClanID.ToString();
@ -248,7 +248,7 @@ namespace ArchiSteamFarm {
[JsonConstructor]
private BotConfig() { }
internal (bool Valid, string ErrorMessage) CheckValidation() {
internal (bool Valid, string? ErrorMessage) CheckValidation() {
if (BotBehaviour > EBotBehaviour.All) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(BotBehaviour), BotBehaviour));
}
@ -285,11 +285,11 @@ namespace ArchiSteamFarm {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamMasterClanID), SteamMasterClanID));
}
if (!string.IsNullOrEmpty(SteamParentalCode) && (SteamParentalCode != "0") && (SteamParentalCode.Length != SteamParentalCodeLength)) {
if (!string.IsNullOrEmpty(SteamParentalCode) && (SteamParentalCode != "0") && (SteamParentalCode!.Length != SteamParentalCodeLength)) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamParentalCode), SteamParentalCode));
}
if (!string.IsNullOrEmpty(SteamTradeToken) && (SteamTradeToken.Length != SteamTradeTokenLength)) {
if (!string.IsNullOrEmpty(SteamTradeToken) && (SteamTradeToken!.Length != SteamTradeTokenLength)) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamTradeToken), SteamTradeToken));
}
@ -303,15 +303,12 @@ namespace ArchiSteamFarm {
}
}
return TradingPreferences <= ETradingPreferences.All ? (true, null) : (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(TradingPreferences), TradingPreferences));
return TradingPreferences <= ETradingPreferences.All ? (true, (string?) null) : (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(TradingPreferences), TradingPreferences));
}
[ItemCanBeNull]
internal static async Task<BotConfig> Load(string filePath) {
internal static async Task<BotConfig?> Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
return null;
throw new ArgumentNullException(nameof(filePath));
}
if (!File.Exists(filePath)) {
@ -342,10 +339,12 @@ namespace ArchiSteamFarm {
return null;
}
(bool valid, string errorMessage) = botConfig.CheckValidation();
(bool valid, string? errorMessage) = botConfig.CheckValidation();
if (!valid) {
ASF.ArchiLogger.LogGenericError(errorMessage);
if (!string.IsNullOrEmpty(errorMessage)) {
ASF.ArchiLogger.LogGenericError(errorMessage!);
}
return null;
}
@ -355,9 +354,7 @@ namespace ArchiSteamFarm {
internal static async Task<bool> Write(string filePath, BotConfig botConfig) {
if (string.IsNullOrEmpty(filePath) || (botConfig == null)) {
ASF.ArchiLogger.LogNullError(nameof(filePath) + " || " + nameof(botConfig));
return false;
throw new ArgumentNullException(nameof(filePath) + " || " + nameof(botConfig));
}
string json = JsonConvert.SerializeObject(botConfig, Formatting.Indented);

View file

@ -29,7 +29,6 @@ using System.Threading.Tasks;
using ArchiSteamFarm.Collections;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2;
@ -58,7 +57,7 @@ namespace ArchiSteamFarm {
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<uint> IdlingPriorityAppIDs = new ConcurrentHashSet<uint>();
internal string LoginKey {
internal string? LoginKey {
get => BackingLoginKey;
set {
@ -71,7 +70,7 @@ namespace ArchiSteamFarm {
}
}
internal MobileAuthenticator MobileAuthenticator {
internal MobileAuthenticator? MobileAuthenticator {
get => BackingMobileAuthenticator;
set {
@ -85,12 +84,12 @@ namespace ArchiSteamFarm {
}
[JsonProperty(PropertyName = "_" + nameof(LoginKey))]
private string BackingLoginKey;
private string? BackingLoginKey;
[JsonProperty(PropertyName = "_" + nameof(MobileAuthenticator))]
private MobileAuthenticator BackingMobileAuthenticator;
private MobileAuthenticator? BackingMobileAuthenticator;
private BotDatabase([NotNull] string filePath) {
private BotDatabase(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
@ -109,9 +108,7 @@ namespace ArchiSteamFarm {
internal void AddBlacklistedFromTradesSteamIDs(IReadOnlyCollection<ulong> steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
return;
throw new ArgumentNullException(nameof(steamIDs));
}
if (BlacklistedFromTradesSteamIDs.AddRange(steamIDs)) {
@ -121,9 +118,7 @@ namespace ArchiSteamFarm {
internal void AddGamesToRedeemInBackground(IOrderedDictionary games) {
if ((games == null) || (games.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(games));
return;
throw new ArgumentNullException(nameof(games));
}
bool save = false;
@ -142,9 +137,7 @@ namespace ArchiSteamFarm {
internal void AddIdlingBlacklistedAppIDs(IReadOnlyCollection<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
throw new ArgumentNullException(nameof(appIDs));
}
if (IdlingBlacklistedAppIDs.AddRange(appIDs)) {
@ -154,9 +147,7 @@ namespace ArchiSteamFarm {
internal void AddIdlingPriorityAppIDs(IReadOnlyCollection<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
throw new ArgumentNullException(nameof(appIDs));
}
if (IdlingPriorityAppIDs.AddRange(appIDs)) {
@ -164,12 +155,9 @@ namespace ArchiSteamFarm {
}
}
[ItemCanBeNull]
internal static async Task<BotDatabase> CreateOrLoad(string filePath) {
internal static async Task<BotDatabase?> CreateOrLoad(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
return null;
throw new ArgumentNullException(nameof(filePath));
}
if (!File.Exists(filePath)) {
@ -207,10 +195,10 @@ namespace ArchiSteamFarm {
internal IReadOnlyCollection<ulong> GetBlacklistedFromTradesSteamIDs() => BlacklistedFromTradesSteamIDs;
internal (string Key, string Name) GetGameToRedeemInBackground() {
internal (string? Key, string? Name) GetGameToRedeemInBackground() {
lock (GamesToRedeemInBackground) {
foreach (DictionaryEntry game in GamesToRedeemInBackground) {
return (game.Key as string, game.Value as string);
foreach (DictionaryEntry? game in GamesToRedeemInBackground) {
return (game?.Key as string, game?.Value as string);
}
}
@ -222,9 +210,7 @@ namespace ArchiSteamFarm {
internal bool IsBlacklistedFromIdling(uint appID) {
if (appID == 0) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return false;
throw new ArgumentNullException(nameof(appID));
}
return IdlingBlacklistedAppIDs.Contains(appID);
@ -232,9 +218,7 @@ namespace ArchiSteamFarm {
internal bool IsBlacklistedFromTrades(ulong steamID) {
if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) {
ASF.ArchiLogger.LogNullError(nameof(steamID));
return false;
throw new ArgumentNullException(nameof(steamID));
}
return BlacklistedFromTradesSteamIDs.Contains(steamID);
@ -242,9 +226,7 @@ namespace ArchiSteamFarm {
internal bool IsPriorityIdling(uint appID) {
if (appID == 0) {
ASF.ArchiLogger.LogNullError(nameof(appID));
return false;
throw new ArgumentNullException(nameof(appID));
}
return IdlingPriorityAppIDs.Contains(appID);
@ -252,9 +234,7 @@ namespace ArchiSteamFarm {
internal void RemoveBlacklistedFromTradesSteamIDs(IReadOnlyCollection<ulong> steamIDs) {
if ((steamIDs == null) || (steamIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(steamIDs));
return;
throw new ArgumentNullException(nameof(steamIDs));
}
if (BlacklistedFromTradesSteamIDs.RemoveRange(steamIDs)) {
@ -264,9 +244,7 @@ namespace ArchiSteamFarm {
internal void RemoveGameToRedeemInBackground(string key) {
if (string.IsNullOrEmpty(key)) {
ASF.ArchiLogger.LogNullError(nameof(key));
return;
throw new ArgumentNullException(nameof(key));
}
lock (GamesToRedeemInBackground) {
@ -282,9 +260,7 @@ namespace ArchiSteamFarm {
internal void RemoveIdlingBlacklistedAppIDs(IReadOnlyCollection<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
throw new ArgumentNullException(nameof(appIDs));
}
if (IdlingBlacklistedAppIDs.RemoveRange(appIDs)) {
@ -294,9 +270,7 @@ namespace ArchiSteamFarm {
internal void RemoveIdlingPriorityAppIDs(IReadOnlyCollection<uint> appIDs) {
if ((appIDs == null) || (appIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(appIDs));
return;
throw new ArgumentNullException(nameof(appIDs));
}
if (IdlingPriorityAppIDs.RemoveRange(appIDs)) {

View file

@ -76,7 +76,7 @@ namespace ArchiSteamFarm {
private readonly SemaphoreSlim FarmingInitializationSemaphore = new SemaphoreSlim(1, 1);
private readonly SemaphoreSlim FarmingResetSemaphore = new SemaphoreSlim(0, 1);
private readonly ConcurrentList<Game> GamesToFarm = new ConcurrentList<Game>();
private readonly Timer IdleFarmingTimer;
private readonly Timer? IdleFarmingTimer;
private readonly ConcurrentDictionary<uint, DateTime> LocallyIgnoredAppIDs = new ConcurrentDictionary<uint, DateTime>();
private IEnumerable<ConcurrentDictionary<uint, DateTime>> SourcesOfIgnoredAppIDs {
@ -97,15 +97,17 @@ namespace ArchiSteamFarm {
private bool PermanentlyPaused;
private bool ShouldResumeFarming = true;
internal CardsFarmer([NotNull] Bot bot) {
internal CardsFarmer(Bot bot) {
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
if (ASF.GlobalConfig.IdleFarmingPeriod > 0) {
byte idleFarmingPeriod = ASF.GlobalConfig?.IdleFarmingPeriod ?? GlobalConfig.DefaultIdleFarmingPeriod;
if (idleFarmingPeriod > 0) {
IdleFarmingTimer = new Timer(
async e => await CheckGamesForFarming().ConfigureAwait(false),
null,
TimeSpan.FromHours(ASF.GlobalConfig.IdleFarmingPeriod) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots.Count), // Delay
TimeSpan.FromHours(ASF.GlobalConfig.IdleFarmingPeriod) // Period
TimeSpan.FromHours(idleFarmingPeriod) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
TimeSpan.FromHours(idleFarmingPeriod) // Period
);
}
}
@ -344,9 +346,7 @@ namespace ArchiSteamFarm {
private async Task CheckGame(uint appID, string name, float hours, byte badgeLevel) {
if ((appID == 0) || string.IsNullOrEmpty(name) || (hours < 0)) {
Bot.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours));
return;
throw new ArgumentNullException(nameof(appID) + " || " + nameof(name) + " || " + nameof(hours));
}
ushort? cardsRemaining = await GetCardsRemaining(appID).ConfigureAwait(false);
@ -374,9 +374,7 @@ namespace ArchiSteamFarm {
private async Task CheckPage(IDocument htmlDocument, ISet<uint> parsedAppIDs) {
if ((htmlDocument == null) || (parsedAppIDs == null)) {
Bot.ArchiLogger.LogNullError(nameof(htmlDocument) + " || " + nameof(parsedAppIDs));
return;
throw new ArgumentNullException(nameof(htmlDocument) + " || " + nameof(parsedAppIDs));
}
List<IElement> htmlNodes = htmlDocument.SelectNodes("//div[@class='badge_row_inner']");
@ -386,18 +384,18 @@ namespace ArchiSteamFarm {
return;
}
HashSet<Task> backgroundTasks = null;
HashSet<Task>? backgroundTasks = null;
foreach (IElement htmlNode in htmlNodes) {
IElement statsNode = htmlNode.SelectSingleElementNode(".//div[@class='badge_title_stats_content']");
IElement appIDNode = statsNode?.SelectSingleElementNode(".//div[@class='card_drop_info_dialog']");
IElement? statsNode = htmlNode.SelectSingleElementNode(".//div[@class='badge_title_stats_content']");
IElement? appIDNode = statsNode?.SelectSingleElementNode(".//div[@class='card_drop_info_dialog']");
if (appIDNode == null) {
// It's just a badge, nothing more
continue;
}
string appIDText = appIDNode.GetAttributeValue("id");
string? appIDText = appIDNode.GetAttribute("id");
if (string.IsNullOrEmpty(appIDText)) {
Bot.ArchiLogger.LogNullError(nameof(appIDText));
@ -426,7 +424,7 @@ namespace ArchiSteamFarm {
continue;
}
if (SalesBlacklist.Contains(appID) || ASF.GlobalConfig.Blacklist.Contains(appID) || Bot.IsBlacklistedFromIdling(appID) || (Bot.BotConfig.IdlePriorityQueueOnly && !Bot.IsPriorityIdling(appID))) {
if (SalesBlacklist.Contains(appID) || (ASF.GlobalConfig?.Blacklist.Contains(appID) == true) || Bot.IsBlacklistedFromIdling(appID) || (Bot.BotConfig.IdlePriorityQueueOnly && !Bot.IsPriorityIdling(appID))) {
// We're configured to ignore this appID, so skip it
continue;
}
@ -454,7 +452,7 @@ namespace ArchiSteamFarm {
}
// Cards
IElement progressNode = statsNode.SelectSingleElementNode(".//span[@class='progress_info_bold']");
IElement? progressNode = statsNode?.SelectSingleElementNode(".//span[@class='progress_info_bold']");
if (progressNode == null) {
Bot.ArchiLogger.LogNullError(nameof(progressNode));
@ -493,7 +491,7 @@ namespace ArchiSteamFarm {
}
// To save us on extra work, check cards earned so far first
IElement cardsEarnedNode = statsNode.SelectSingleElementNode(".//div[@class='card_drop_info_header']");
IElement? cardsEarnedNode = statsNode?.SelectSingleElementNode(".//div[@class='card_drop_info_header']");
if (cardsEarnedNode == null) {
Bot.ArchiLogger.LogNullError(nameof(cardsEarnedNode));
@ -538,7 +536,7 @@ namespace ArchiSteamFarm {
}
// Hours
IElement timeNode = statsNode.SelectSingleElementNode(".//div[@class='badge_title_stats_playtime']");
IElement? timeNode = statsNode?.SelectSingleElementNode(".//div[@class='badge_title_stats_playtime']");
if (timeNode == null) {
Bot.ArchiLogger.LogNullError(nameof(timeNode));
@ -567,7 +565,7 @@ namespace ArchiSteamFarm {
}
// Names
IElement nameNode = statsNode.SelectSingleElementNode("(.//div[@class='card_drop_info_body'])[last()]");
IElement? nameNode = statsNode?.SelectSingleElementNode("(.//div[@class='card_drop_info_body'])[last()]");
if (nameNode == null) {
Bot.ArchiLogger.LogNullError(nameof(nameNode));
@ -619,7 +617,7 @@ namespace ArchiSteamFarm {
// Levels
byte badgeLevel = 0;
IElement levelNode = htmlNode.SelectSingleElementNode(".//div[@class='badge_info_description']/div[2]");
IElement? levelNode = htmlNode.SelectSingleElementNode(".//div[@class='badge_info_description']/div[2]");
if (levelNode != null) {
// There is no levelNode if we didn't craft that badge yet (level 0)
@ -664,7 +662,7 @@ namespace ArchiSteamFarm {
} else {
Task task = CheckGame(appID, name, hours, badgeLevel);
switch (ASF.GlobalConfig.OptimizationMode) {
switch (ASF.GlobalConfig?.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
await task.ConfigureAwait(false);
@ -687,12 +685,10 @@ namespace ArchiSteamFarm {
private async Task CheckPage(byte page, ISet<uint> parsedAppIDs) {
if ((page == 0) || (parsedAppIDs == null)) {
Bot.ArchiLogger.LogNullError(nameof(page) + " || " + nameof(parsedAppIDs));
return;
throw new ArgumentNullException(nameof(page) + " || " + nameof(parsedAppIDs));
}
using IDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(page).ConfigureAwait(false);
if (htmlDocument == null) {
return;
@ -802,9 +798,7 @@ namespace ArchiSteamFarm {
private async Task<bool> FarmCards(Game game) {
if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game));
return false;
throw new ArgumentNullException(nameof(game));
}
if (game.AppID != game.PlayableAppID) {
@ -814,14 +808,14 @@ namespace ArchiSteamFarm {
await Bot.IdleGame(game).ConfigureAwait(false);
bool success = true;
DateTime endFarmingDate = DateTime.UtcNow.AddHours(ASF.GlobalConfig.MaxFarmingTime);
DateTime endFarmingDate = DateTime.UtcNow.AddHours(ASF.GlobalConfig?.MaxFarmingTime ?? GlobalConfig.DefaultMaxFarmingTime);
while ((DateTime.UtcNow < endFarmingDate) && (await ShouldFarm(game).ConfigureAwait(false)).GetValueOrDefault(true)) {
Bot.ArchiLogger.LogGenericInfo(string.Format(Strings.StillIdling, game.AppID, game.GameName));
DateTime startFarmingPeriod = DateTime.UtcNow;
if (await FarmingResetSemaphore.WaitAsync((ASF.GlobalConfig.FarmingDelay * 60 * 1000) + (ExtraFarmingDelaySeconds * 1000)).ConfigureAwait(false)) {
if (await FarmingResetSemaphore.WaitAsync((ASF.GlobalConfig?.FarmingDelay ?? GlobalConfig.DefaultFarmingDelay * 60 * 1000) + (ExtraFarmingDelaySeconds * 1000)).ConfigureAwait(false)) {
success = KeepFarming;
}
@ -840,9 +834,7 @@ namespace ArchiSteamFarm {
private async Task<bool> FarmHours(IReadOnlyCollection<Game> games) {
if ((games == null) || (games.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
throw new ArgumentNullException(nameof(games));
}
float maxHour = games.Max(game => game.HoursPlayed);
@ -868,7 +860,7 @@ namespace ArchiSteamFarm {
DateTime startFarmingPeriod = DateTime.UtcNow;
if (await FarmingResetSemaphore.WaitAsync((ASF.GlobalConfig.FarmingDelay * 60 * 1000) + (ExtraFarmingDelaySeconds * 1000)).ConfigureAwait(false)) {
if (await FarmingResetSemaphore.WaitAsync((ASF.GlobalConfig?.FarmingDelay ?? GlobalConfig.DefaultFarmingDelay * 60 * 1000) + (ExtraFarmingDelaySeconds * 1000)).ConfigureAwait(false)) {
success = KeepFarming;
}
@ -893,9 +885,7 @@ namespace ArchiSteamFarm {
private async Task<bool> FarmMultiple(IReadOnlyCollection<Game> games) {
if ((games == null) || (games.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(games));
return false;
throw new ArgumentNullException(nameof(games));
}
CurrentGamesFarming.ReplaceWith(games);
@ -910,9 +900,7 @@ namespace ArchiSteamFarm {
private async Task<bool> FarmSolo(Game game) {
if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game));
return true;
throw new ArgumentNullException(nameof(game));
}
CurrentGamesFarming.Add(game);
@ -935,14 +923,12 @@ namespace ArchiSteamFarm {
private async Task<ushort?> GetCardsRemaining(uint appID) {
if (appID == 0) {
Bot.ArchiLogger.LogNullError(nameof(appID));
return 0;
throw new ArgumentNullException(nameof(appID));
}
using IDocument htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetGameCardsPage(appID).ConfigureAwait(false);
IElement progressNode = htmlDocument?.SelectSingleNode("//span[@class='progress_info_bold']");
IElement? progressNode = htmlDocument?.SelectSingleNode("//span[@class='progress_info_bold']");
if (progressNode == null) {
return null;
@ -975,7 +961,7 @@ namespace ArchiSteamFarm {
// Find the number of badge pages
Bot.ArchiLogger.LogGenericInfo(Strings.CheckingFirstBadgePage);
using IDocument htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetBadgePage(1).ConfigureAwait(false);
if (htmlDocument == null) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningCouldNotCheckBadges);
@ -985,7 +971,7 @@ namespace ArchiSteamFarm {
byte maxPages = 1;
IElement htmlNode = htmlDocument.SelectSingleNode("(//a[@class='pagelink'])[last()]");
IElement? htmlNode = htmlDocument.SelectSingleNode("(//a[@class='pagelink'])[last()]");
if (htmlNode != null) {
string lastPage = htmlNode.TextContent;
@ -1009,7 +995,7 @@ namespace ArchiSteamFarm {
Task mainTask = CheckPage(htmlDocument, parsedAppIDs);
switch (ASF.GlobalConfig.OptimizationMode) {
switch (ASF.GlobalConfig?.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
await mainTask.ConfigureAwait(false);
@ -1054,9 +1040,7 @@ namespace ArchiSteamFarm {
private async Task<bool> IsPlayableGame(Game game) {
if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game));
return false;
throw new ArgumentNullException(nameof(game));
}
(uint playableAppID, DateTime ignoredUntil, bool ignoredGlobally) = await Bot.GetAppDataForIdling(game.AppID, game.HoursPlayed).ConfigureAwait(false);
@ -1077,9 +1061,7 @@ namespace ArchiSteamFarm {
private async Task<bool?> ShouldFarm(Game game) {
if (game == null) {
Bot.ArchiLogger.LogNullError(nameof(game));
return false;
throw new ArgumentNullException(nameof(game));
}
ushort? cardsRemaining = await GetCardsRemaining(game.AppID).ConfigureAwait(false);
@ -1131,16 +1113,18 @@ namespace ArchiSteamFarm {
break;
case BotConfig.EFarmingOrder.MarketableAscending:
case BotConfig.EFarmingOrder.MarketableDescending:
HashSet<uint> marketableAppIDs = await Bot.GetMarketableAppIDs().ConfigureAwait(false);
HashSet<uint>? marketableAppIDs = await Bot.GetMarketableAppIDs().ConfigureAwait(false);
if ((marketableAppIDs != null) && (marketableAppIDs.Count > 0)) {
ImmutableHashSet<uint> immutableMarketableAppIDs = marketableAppIDs.ToImmutableHashSet();
switch (farmingOrder) {
case BotConfig.EFarmingOrder.MarketableAscending:
orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => marketableAppIDs.Contains(game.AppID));
orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => immutableMarketableAppIDs.Contains(game.AppID));
break;
case BotConfig.EFarmingOrder.MarketableDescending:
orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => marketableAppIDs.Contains(game.AppID));
orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => immutableMarketableAppIDs.Contains(game.AppID));
break;
default:
@ -1177,7 +1161,7 @@ namespace ArchiSteamFarm {
foreach (Game game in GamesToFarm) {
DateTime redeemDate = DateTime.MinValue;
HashSet<uint> packageIDs = ASF.GlobalDatabase.GetPackageIDs(game.AppID, Bot.OwnedPackageIDs.Keys);
HashSet<uint>? packageIDs = ASF.GlobalDatabase?.GetPackageIDs(game.AppID, Bot.OwnedPackageIDs.Keys);
if (packageIDs != null) {
foreach (uint packageID in packageIDs) {
@ -1196,13 +1180,17 @@ namespace ArchiSteamFarm {
redeemDates[game.AppID] = redeemDate;
}
ImmutableDictionary<uint, DateTime> immutableRedeemDates = redeemDates.ToImmutableDictionary();
switch (farmingOrder) {
case BotConfig.EFarmingOrder.RedeemDateTimesAscending:
orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => redeemDates[game.AppID]);
// ReSharper disable once AccessToModifiedClosure - you're wrong
orderedGamesToFarm = orderedGamesToFarm.ThenBy(game => immutableRedeemDates[game.AppID]);
break;
case BotConfig.EFarmingOrder.RedeemDateTimesDescending:
orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => redeemDates[game.AppID]);
// ReSharper disable once AccessToModifiedClosure - you're wrong
orderedGamesToFarm = orderedGamesToFarm.ThenByDescending(game => immutableRedeemDates[game.AppID]);
break;
default:
@ -1241,7 +1229,7 @@ namespace ArchiSteamFarm {
internal uint PlayableAppID { get; set; }
internal Game(uint appID, [NotNull] string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) {
internal Game(uint appID, string gameName, float hoursPlayed, ushort cardsRemaining, byte badgeLevel) {
if ((appID == 0) || string.IsNullOrEmpty(gameName) || (hoursPlayed < 0) || (cardsRemaining == 0)) {
throw new ArgumentOutOfRangeException(nameof(appID) + " || " + nameof(gameName) + " || " + nameof(hoursPlayed) + " || " + nameof(cardsRemaining));
}
@ -1255,8 +1243,8 @@ namespace ArchiSteamFarm {
PlayableAppID = appID;
}
public bool Equals(Game other) => (other != null) && (ReferenceEquals(other, this) || ((AppID == other.AppID) && (BadgeLevel == other.BadgeLevel) && (GameName == other.GameName)));
public override bool Equals(object obj) => (obj != null) && ((obj == this) || (obj is Game game && Equals(game)));
public bool Equals(Game? other) => (other != null) && (ReferenceEquals(other, this) || ((AppID == other.AppID) && (BadgeLevel == other.BadgeLevel) && (GameName == other.GameName)));
public override bool Equals(object? obj) => (obj != null) && ((obj == this) || (obj is Game game && Equals(game)));
public override int GetHashCode() => RuntimeCompatibility.HashCode.Combine(AppID, BadgeLevel, GameName);
}
}

View file

@ -22,7 +22,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Collections {
internal sealed class ConcurrentEnumerator<T> : IEnumerator<T> {
@ -31,9 +30,9 @@ namespace ArchiSteamFarm.Collections {
private readonly IEnumerator<T> Enumerator;
private readonly IDisposable Lock;
object IEnumerator.Current => Current;
object? IEnumerator.Current => Current;
internal ConcurrentEnumerator([NotNull] IReadOnlyCollection<T> collection, [NotNull] IDisposable @lock) {
internal ConcurrentEnumerator(IReadOnlyCollection<T> collection, IDisposable @lock) {
if ((collection == null) || (@lock == null)) {
throw new ArgumentNullException(nameof(collection) + " || " + nameof(@lock));
}

View file

@ -23,12 +23,11 @@ using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Collections {
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> {
public sealed class ConcurrentHashSet<T> : IReadOnlyCollection<T>, ISet<T> where T : notnull {
public int Count => BackingCollection.Count;
public bool IsReadOnly => false;
@ -36,7 +35,7 @@ namespace ArchiSteamFarm.Collections {
public ConcurrentHashSet() => BackingCollection = new ConcurrentDictionary<T, bool>();
public ConcurrentHashSet([JetBrains.Annotations.NotNull] IEqualityComparer<T> comparer) {
public ConcurrentHashSet(IEqualityComparer<T> comparer) {
if (comparer == null) {
throw new ArgumentNullException(nameof(comparer));
}
@ -47,7 +46,6 @@ namespace ArchiSteamFarm.Collections {
public bool Add(T item) => BackingCollection.TryAdd(item, true);
public void Clear() => BackingCollection.Clear();
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
public bool Contains(T item) => BackingCollection.ContainsKey(item);
public void CopyTo(T[] array, int arrayIndex) => BackingCollection.Keys.CopyTo(array, arrayIndex);
@ -98,7 +96,6 @@ namespace ArchiSteamFarm.Collections {
return otherSet.Any(Contains);
}
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
public bool Remove(T item) => BackingCollection.TryRemove(item, out _);
public bool SetEquals(IEnumerable<T> other) {
@ -127,22 +124,20 @@ namespace ArchiSteamFarm.Collections {
}
}
[SuppressMessage("ReSharper", "AnnotationConflictInHierarchy")]
[SuppressMessage("ReSharper", "AssignNullToNotNullAttribute")]
void ICollection<T>.Add([JetBrains.Annotations.NotNull] T item) => Add(item);
void ICollection<T>.Add(T item) => Add(item);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
// We use Count() and not Any() because we must ensure full loop pass
[PublicAPI]
public bool AddRange([JetBrains.Annotations.NotNull] IEnumerable<T> items) => items.Count(Add) > 0;
public bool AddRange(IEnumerable<T> items) => items.Count(Add) > 0;
// We use Count() and not Any() because we must ensure full loop pass
[PublicAPI]
public bool RemoveRange([JetBrains.Annotations.NotNull] IEnumerable<T> items) => items.Count(Remove) > 0;
public bool RemoveRange(IEnumerable<T> items) => items.Count(Remove) > 0;
[PublicAPI]
public bool ReplaceIfNeededWith([JetBrains.Annotations.NotNull] IReadOnlyCollection<T> other) {
public bool ReplaceIfNeededWith(IReadOnlyCollection<T> other) {
if (SetEquals(other)) {
return false;
}
@ -153,7 +148,7 @@ namespace ArchiSteamFarm.Collections {
}
[PublicAPI]
public void ReplaceWith([JetBrains.Annotations.NotNull] IEnumerable<T> other) {
public void ReplaceWith(IEnumerable<T> other) {
BackingCollection.Clear();
foreach (T item in other) {

View file

@ -21,7 +21,6 @@
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Nito.AsyncEx;
namespace ArchiSteamFarm.Collections {
@ -80,8 +79,6 @@ namespace ArchiSteamFarm.Collections {
}
}
[JetBrains.Annotations.NotNull]
[SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
public IEnumerator<T> GetEnumerator() => new ConcurrentEnumerator<T>(BackingCollection, Lock.ReaderLock());
public int IndexOf(T item) {
@ -108,11 +105,9 @@ namespace ArchiSteamFarm.Collections {
}
}
[JetBrains.Annotations.NotNull]
[SuppressMessage("ReSharper", "AnnotationRedundancyInHierarchy")]
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
internal void ReplaceWith([JetBrains.Annotations.NotNull] IEnumerable<T> collection) {
internal void ReplaceWith(IEnumerable<T> collection) {
using (Lock.WriterLock()) {
BackingCollection.Clear();
BackingCollection.AddRange(collection);

File diff suppressed because it is too large Load diff

View file

@ -19,6 +19,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using SteamKit2;
namespace ArchiSteamFarm {
@ -29,16 +30,14 @@ namespace ArchiSteamFarm {
internal static bool IsDebugBuild => false;
#endif
internal static bool IsDebugConfigured => ASF.GlobalConfig.Debug;
internal static bool IsDebugConfigured => ASF.GlobalConfig?.Debug ?? throw new ArgumentNullException(nameof(ASF.GlobalConfig));
internal static bool IsUserDebugging => IsDebugBuild || IsDebugConfigured;
internal sealed class DebugListener : IDebugListener {
public void WriteLine(string category, string msg) {
if (string.IsNullOrEmpty(category) && string.IsNullOrEmpty(msg)) {
ASF.ArchiLogger.LogNullError(nameof(category) + " && " + nameof(msg));
return;
throw new ArgumentNullException(nameof(category) + " && " + nameof(msg));
}
ASF.ArchiLogger.LogGenericDebug(category + " | " + msg);

View file

@ -26,7 +26,7 @@ using ArchiSteamFarm.Localization;
namespace ArchiSteamFarm {
internal static class Events {
internal static async Task OnBotShutdown() {
if (Program.ProcessRequired || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
if (Program.ProcessRequired || ((Bot.Bots != null) && Bot.Bots.Values.Any(bot => bot.KeepRunning))) {
return;
}
@ -35,7 +35,7 @@ namespace ArchiSteamFarm {
// We give user extra 5 seconds for eventual config changes
await Task.Delay(5000).ConfigureAwait(false);
if (Program.ProcessRequired || Bot.Bots.Values.Any(bot => bot.KeepRunning)) {
if (Program.ProcessRequired || ((Bot.Bots != null) && Bot.Bots.Values.Any(bot => bot.KeepRunning))) {
return;
}

View file

@ -25,7 +25,6 @@ using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using JetBrains.Annotations;
using Markdig;
using Markdig.Renderers;
using Markdig.Syntax;
@ -34,35 +33,29 @@ using Newtonsoft.Json;
namespace ArchiSteamFarm {
internal static class GitHub {
[ItemCanBeNull]
internal static async Task<ReleaseResponse> GetLatestRelease(bool stable = true) {
internal static async Task<ReleaseResponse?> GetLatestRelease(bool stable = true) {
string releaseURL = SharedInfo.GithubReleaseURL + (stable ? "/latest" : "?per_page=1");
if (stable) {
return await GetReleaseFromURL(releaseURL).ConfigureAwait(false);
}
ImmutableList<ReleaseResponse> response = await GetReleasesFromURL(releaseURL).ConfigureAwait(false);
ImmutableList<ReleaseResponse>? response = await GetReleasesFromURL(releaseURL).ConfigureAwait(false);
return response?.FirstOrDefault();
}
[ItemCanBeNull]
internal static async Task<ReleaseResponse> GetRelease(string version) {
internal static async Task<ReleaseResponse?> GetRelease(string version) {
if (string.IsNullOrEmpty(version)) {
ASF.ArchiLogger.LogNullError(nameof(version));
return null;
throw new ArgumentNullException(nameof(version));
}
return await GetReleaseFromURL(SharedInfo.GithubReleaseURL + "/tags/" + version).ConfigureAwait(false);
}
private static MarkdownDocument ExtractChangelogFromBody(string markdownText) {
private static MarkdownDocument? ExtractChangelogFromBody(string markdownText) {
if (string.IsNullOrEmpty(markdownText)) {
ASF.ArchiLogger.LogNullError(nameof(markdownText));
return null;
throw new ArgumentNullException(nameof(markdownText));
}
MarkdownDocument markdownDocument = Markdown.Parse(markdownText);
@ -77,28 +70,22 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
private static async Task<ReleaseResponse> GetReleaseFromURL(string releaseURL) {
if (string.IsNullOrEmpty(releaseURL)) {
ASF.ArchiLogger.LogNullError(nameof(releaseURL));
return null;
private static async Task<ReleaseResponse?> GetReleaseFromURL(string releaseURL) {
if ((ASF.WebBrowser == null) || string.IsNullOrEmpty(releaseURL)) {
throw new ArgumentNullException(nameof(ASF.WebBrowser) + " || " + nameof(releaseURL));
}
WebBrowser.ObjectResponse<ReleaseResponse> objectResponse = await ASF.WebBrowser.UrlGetToJsonObject<ReleaseResponse>(releaseURL).ConfigureAwait(false);
WebBrowser.ObjectResponse<ReleaseResponse>? objectResponse = await ASF.WebBrowser.UrlGetToJsonObject<ReleaseResponse>(releaseURL).ConfigureAwait(false);
return objectResponse?.Content;
}
[ItemCanBeNull]
private static async Task<ImmutableList<ReleaseResponse>> GetReleasesFromURL(string releaseURL) {
if (string.IsNullOrEmpty(releaseURL)) {
ASF.ArchiLogger.LogNullError(nameof(releaseURL));
return null;
private static async Task<ImmutableList<ReleaseResponse>?> GetReleasesFromURL(string releaseURL) {
if ((ASF.WebBrowser == null) || string.IsNullOrEmpty(releaseURL)) {
throw new ArgumentNullException(nameof(ASF.WebBrowser) + " || " + nameof(releaseURL));
}
WebBrowser.ObjectResponse<ImmutableList<ReleaseResponse>> objectResponse = await ASF.WebBrowser.UrlGetToJsonObject<ImmutableList<ReleaseResponse>>(releaseURL).ConfigureAwait(false);
WebBrowser.ObjectResponse<ImmutableList<ReleaseResponse>>? objectResponse = await ASF.WebBrowser.UrlGetToJsonObject<ImmutableList<ReleaseResponse>>(releaseURL).ConfigureAwait(false);
return objectResponse?.Content;
}
@ -106,7 +93,7 @@ namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class ReleaseResponse {
[JsonProperty(PropertyName = "assets", Required = Required.Always)]
internal readonly ImmutableHashSet<Asset> Assets;
internal readonly ImmutableHashSet<Asset>? Assets;
[JsonProperty(PropertyName = "prerelease", Required = Required.Always)]
internal readonly bool IsPreRelease;
@ -115,9 +102,9 @@ namespace ArchiSteamFarm {
internal readonly DateTime PublishedAt;
[JsonProperty(PropertyName = "tag_name", Required = Required.Always)]
internal readonly string Tag;
internal readonly string? Tag;
internal string ChangelogHTML {
internal string? ChangelogHTML {
get {
if (BackingChangelogHTML != null) {
return BackingChangelogHTML;
@ -139,7 +126,7 @@ namespace ArchiSteamFarm {
}
}
internal string ChangelogPlainText {
internal string? ChangelogPlainText {
get {
if (BackingChangelogPlainText != null) {
return BackingChangelogPlainText;
@ -167,32 +154,38 @@ namespace ArchiSteamFarm {
#pragma warning disable 649
[JsonProperty(PropertyName = "body", Required = Required.Always)]
private readonly string MarkdownBody;
private readonly string? MarkdownBody;
#pragma warning restore 649
private MarkdownDocument Changelog {
private MarkdownDocument? Changelog {
get {
if (BackingChangelog != null) {
return BackingChangelog;
}
return BackingChangelog = ExtractChangelogFromBody(MarkdownBody);
if (string.IsNullOrEmpty(MarkdownBody)) {
ASF.ArchiLogger.LogNullError(nameof(MarkdownBody));
return null;
}
return BackingChangelog = ExtractChangelogFromBody(MarkdownBody!);
}
}
private MarkdownDocument BackingChangelog;
private string BackingChangelogHTML;
private string BackingChangelogPlainText;
private MarkdownDocument? BackingChangelog;
private string? BackingChangelogHTML;
private string? BackingChangelogPlainText;
[JsonConstructor]
private ReleaseResponse() { }
internal sealed class Asset {
[JsonProperty(PropertyName = "browser_download_url", Required = Required.Always)]
internal readonly string DownloadURL;
internal readonly string? DownloadURL;
[JsonProperty(PropertyName = "name", Required = Required.Always)]
internal readonly string Name;
internal readonly string? Name;
[JsonProperty(PropertyName = "size", Required = Required.Always)]
internal readonly uint Size;

View file

@ -36,31 +36,31 @@ using SteamKit2;
namespace ArchiSteamFarm {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
public sealed class GlobalConfig {
internal const string DefaultCommandPrefix = "!";
internal const byte DefaultConfirmationsLimiterDelay = 10;
internal const byte DefaultConnectionTimeout = 90;
internal const byte DefaultFarmingDelay = 15;
internal const byte DefaultGiftsLimiterDelay = 1;
internal const bool DefaultHeadless = false;
internal const byte DefaultIdleFarmingPeriod = 8;
internal const byte DefaultInventoryLimiterDelay = 3;
internal const string DefaultIPCPassword = null;
internal const byte DefaultLoginLimiterDelay = 10;
internal const byte DefaultMaxFarmingTime = 10;
internal const byte DefaultMaxTradeHoldDuration = 15;
internal const string DefaultSteamMessagePrefix = "/me ";
internal const ulong DefaultSteamOwnerID = 0;
internal const ushort DefaultWebLimiterDelay = 300;
private const bool DefaultAutoRestart = true;
private const string DefaultCommandPrefix = "!";
private const byte DefaultConfirmationsLimiterDelay = 10;
private const byte DefaultConnectionTimeout = 90;
private const string DefaultCurrentCulture = null;
private const bool DefaultDebug = false;
private const byte DefaultFarmingDelay = 15;
private const byte DefaultGiftsLimiterDelay = 1;
private const bool DefaultHeadless = false;
private const byte DefaultIdleFarmingPeriod = 8;
private const byte DefaultInventoryLimiterDelay = 3;
private const bool DefaultIPC = false;
private const string DefaultIPCPassword = null;
private const byte DefaultMaxFarmingTime = 10;
private const byte DefaultMaxTradeHoldDuration = 15;
private const EOptimizationMode DefaultOptimizationMode = EOptimizationMode.MaxPerformance;
private const bool DefaultStatistics = true;
private const string DefaultSteamMessagePrefix = "/me ";
private const ulong DefaultSteamOwnerID = 0;
private const ProtocolTypes DefaultSteamProtocols = ProtocolTypes.All;
private const EUpdateChannel DefaultUpdateChannel = EUpdateChannel.Stable;
private const byte DefaultUpdatePeriod = 24;
private const ushort DefaultWebLimiterDelay = 300;
private const string DefaultWebProxyPassword = null;
private const string DefaultWebProxyText = null;
private const string DefaultWebProxyUsername = null;
@ -75,7 +75,7 @@ namespace ArchiSteamFarm {
public readonly ImmutableHashSet<uint> Blacklist = DefaultBlacklist;
[JsonProperty]
public readonly string CommandPrefix = DefaultCommandPrefix;
public readonly string? CommandPrefix = DefaultCommandPrefix;
[JsonProperty(Required = Required.DisallowNull)]
public readonly byte ConfirmationsLimiterDelay = DefaultConfirmationsLimiterDelay;
@ -84,7 +84,7 @@ namespace ArchiSteamFarm {
public readonly byte ConnectionTimeout = DefaultConnectionTimeout;
[JsonProperty]
public readonly string CurrentCulture = DefaultCurrentCulture;
public readonly string? CurrentCulture = DefaultCurrentCulture;
[JsonProperty(Required = Required.DisallowNull)]
public readonly bool Debug = DefaultDebug;
@ -108,7 +108,7 @@ namespace ArchiSteamFarm {
public readonly bool IPC = DefaultIPC;
[JsonProperty]
public readonly string IPCPassword = DefaultIPCPassword;
public readonly string? IPCPassword = DefaultIPCPassword;
[JsonProperty(Required = Required.DisallowNull)]
public readonly byte LoginLimiterDelay = DefaultLoginLimiterDelay;
@ -126,7 +126,7 @@ namespace ArchiSteamFarm {
public readonly bool Statistics = DefaultStatistics;
[JsonProperty]
public readonly string SteamMessagePrefix = DefaultSteamMessagePrefix;
public readonly string? SteamMessagePrefix = DefaultSteamMessagePrefix;
[JsonProperty(Required = Required.DisallowNull)]
public readonly EUpdateChannel UpdateChannel = DefaultUpdateChannel;
@ -138,14 +138,14 @@ namespace ArchiSteamFarm {
public readonly ushort WebLimiterDelay = DefaultWebLimiterDelay;
[JsonProperty(PropertyName = nameof(WebProxy))]
public readonly string WebProxyText = DefaultWebProxyText;
public readonly string? WebProxyText = DefaultWebProxyText;
[JsonProperty]
public readonly string WebProxyUsername = DefaultWebProxyUsername;
public readonly string? WebProxyUsername = DefaultWebProxyUsername;
[JsonIgnore]
[PublicAPI]
public WebProxy WebProxy {
public WebProxy? WebProxy {
get {
if (BackingWebProxy != null) {
return BackingWebProxy;
@ -197,7 +197,7 @@ namespace ArchiSteamFarm {
public ProtocolTypes SteamProtocols { get; private set; } = DefaultSteamProtocols;
[JsonExtensionData]
internal Dictionary<string, JToken> AdditionalProperties {
internal Dictionary<string, JToken>? AdditionalProperties {
get;
[UsedImplicitly]
set;
@ -209,7 +209,7 @@ namespace ArchiSteamFarm {
internal bool ShouldSerializeSensitiveDetails { private get; set; }
[JsonProperty]
internal string WebProxyPassword {
internal string? WebProxyPassword {
get => BackingWebProxyPassword;
set {
@ -218,11 +218,11 @@ namespace ArchiSteamFarm {
}
}
private WebProxy BackingWebProxy;
private string BackingWebProxyPassword = DefaultWebProxyPassword;
private WebProxy? BackingWebProxy;
private string? BackingWebProxyPassword = DefaultWebProxyPassword;
[JsonProperty(PropertyName = SharedInfo.UlongCompatibilityStringPrefix + nameof(SteamOwnerID), Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string SSteamOwnerID {
get => SteamOwnerID.ToString();
@ -240,7 +240,7 @@ namespace ArchiSteamFarm {
[JsonConstructor]
internal GlobalConfig() { }
internal (bool Valid, string ErrorMessage) CheckValidation() {
internal (bool Valid, string? ErrorMessage) CheckValidation() {
if (ConnectionTimeout == 0) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(ConnectionTimeout), ConnectionTimeout));
}
@ -257,7 +257,7 @@ namespace ArchiSteamFarm {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(OptimizationMode), OptimizationMode));
}
if (!string.IsNullOrEmpty(SteamMessagePrefix) && (SteamMessagePrefix.Length > Bot.MaxMessagePrefixLength)) {
if (!string.IsNullOrEmpty(SteamMessagePrefix) && (SteamMessagePrefix!.Length > Bot.MaxMessagePrefixLength)) {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamMessagePrefix), SteamMessagePrefix));
}
@ -269,15 +269,12 @@ namespace ArchiSteamFarm {
return (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(SteamProtocols), SteamProtocols));
}
return Enum.IsDefined(typeof(EUpdateChannel), UpdateChannel) ? (true, null) : (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(UpdateChannel), UpdateChannel));
return Enum.IsDefined(typeof(EUpdateChannel), UpdateChannel) ? (true, (string?) null) : (false, string.Format(Strings.ErrorConfigPropertyInvalid, nameof(UpdateChannel), UpdateChannel));
}
[ItemCanBeNull]
internal static async Task<GlobalConfig> Load(string filePath) {
internal static async Task<GlobalConfig?> Load(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
return null;
throw new ArgumentNullException(nameof(filePath));
}
if (!File.Exists(filePath)) {
@ -308,10 +305,12 @@ namespace ArchiSteamFarm {
return null;
}
(bool valid, string errorMessage) = globalConfig.CheckValidation();
(bool valid, string? errorMessage) = globalConfig.CheckValidation();
if (!valid) {
ASF.ArchiLogger.LogGenericError(errorMessage);
if (!string.IsNullOrEmpty(errorMessage)) {
ASF.ArchiLogger.LogGenericError(errorMessage!);
}
return null;
}
@ -321,9 +320,7 @@ namespace ArchiSteamFarm {
internal static async Task<bool> Write(string filePath, GlobalConfig globalConfig) {
if (string.IsNullOrEmpty(filePath) || (globalConfig == null)) {
ASF.ArchiLogger.LogNullError(nameof(filePath) + " || " + nameof(globalConfig));
return false;
throw new ArgumentNullException(nameof(filePath) + " || " + nameof(globalConfig));
}
string json = JsonConvert.SerializeObject(globalConfig, Formatting.Indented);

View file

@ -44,7 +44,7 @@ namespace ArchiSteamFarm {
[JsonIgnore]
[PublicAPI]
public IReadOnlyDictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> PackagesDataReadOnly => PackagesData;
public IReadOnlyDictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)> PackagesDataReadOnly => PackagesData;
[JsonProperty(Required = Required.DisallowNull)]
internal readonly InMemoryServerListProvider ServerListProvider = new InMemoryServerListProvider();
@ -53,7 +53,7 @@ namespace ArchiSteamFarm {
private readonly ConcurrentDictionary<uint, ulong> PackagesAccessTokens = new ConcurrentDictionary<uint, ulong>();
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> PackagesData = new ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)>();
private readonly ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)> PackagesData = new ConcurrentDictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)>();
private readonly SemaphoreSlim PackagesRefreshSemaphore = new SemaphoreSlim(1, 1);
@ -73,7 +73,7 @@ namespace ArchiSteamFarm {
[JsonProperty(PropertyName = "_" + nameof(CellID), Required = Required.DisallowNull)]
private uint BackingCellID;
private GlobalDatabase([NotNull] string filePath) : this() {
private GlobalDatabase(string filePath) : this() {
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(filePath));
}
@ -95,12 +95,9 @@ namespace ArchiSteamFarm {
base.Dispose();
}
[ItemCanBeNull]
internal static async Task<GlobalDatabase> CreateOrLoad(string filePath) {
internal static async Task<GlobalDatabase?> CreateOrLoad(string filePath) {
if (string.IsNullOrEmpty(filePath)) {
ASF.ArchiLogger.LogNullError(nameof(filePath));
return null;
throw new ArgumentNullException(nameof(filePath));
}
if (!File.Exists(filePath)) {
@ -138,15 +135,13 @@ namespace ArchiSteamFarm {
internal HashSet<uint> GetPackageIDs(uint appID, IEnumerable<uint> packageIDs) {
if ((appID == 0) || (packageIDs == null)) {
ASF.ArchiLogger.LogNullError(nameof(appID) + " || " + nameof(packageIDs));
return null;
throw new ArgumentNullException(nameof(appID) + " || " + nameof(packageIDs));
}
HashSet<uint> result = new HashSet<uint>();
foreach (uint packageID in packageIDs.Where(packageID => packageID != 0)) {
if (!PackagesData.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint> AppIDs) packagesData) || (packagesData.AppIDs?.Contains(appID) != true)) {
if (!PackagesData.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) packagesData) || (packagesData.AppIDs?.Contains(appID) != true)) {
continue;
}
@ -158,9 +153,7 @@ namespace ArchiSteamFarm {
internal void RefreshPackageAccessTokens(IReadOnlyDictionary<uint, ulong> packageAccessTokens) {
if ((packageAccessTokens == null) || (packageAccessTokens.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(packageAccessTokens));
return;
throw new ArgumentNullException(nameof(packageAccessTokens));
}
bool save = false;
@ -179,21 +172,19 @@ namespace ArchiSteamFarm {
internal async Task RefreshPackages(Bot bot, IReadOnlyDictionary<uint, uint> packages) {
if ((bot == null) || (packages == null) || (packages.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(packages));
return;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(packages));
}
await PackagesRefreshSemaphore.WaitAsync().ConfigureAwait(false);
try {
HashSet<uint> packageIDs = packages.Where(package => (package.Key != 0) && (!PackagesData.TryGetValue(package.Key, out (uint ChangeNumber, HashSet<uint> AppIDs) packageData) || (packageData.ChangeNumber < package.Value))).Select(package => package.Key).ToHashSet();
HashSet<uint> packageIDs = packages.Where(package => (package.Key != 0) && (!PackagesData.TryGetValue(package.Key, out (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) || (packageData.ChangeNumber < package.Value))).Select(package => package.Key).ToHashSet();
if (packageIDs.Count == 0) {
return;
}
Dictionary<uint, (uint ChangeNumber, HashSet<uint> AppIDs)> packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
Dictionary<uint, (uint ChangeNumber, HashSet<uint>? AppIDs)>? packagesData = await bot.GetPackagesData(packageIDs).ConfigureAwait(false);
if (packagesData == null) {
bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);
@ -203,8 +194,8 @@ namespace ArchiSteamFarm {
bool save = false;
foreach ((uint packageID, (uint ChangeNumber, HashSet<uint> AppIDs) packageData) in packagesData) {
if (PackagesData.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint> AppIDs) previousData) && (packageData.ChangeNumber < previousData.ChangeNumber)) {
foreach ((uint packageID, (uint ChangeNumber, HashSet<uint>? AppIDs) packageData) in packagesData) {
if (PackagesData.TryGetValue(packageID, out (uint ChangeNumber, HashSet<uint>? AppIDs) previousData) && (packageData.ChangeNumber < previousData.ChangeNumber)) {
continue;
}
@ -220,7 +211,7 @@ namespace ArchiSteamFarm {
}
}
private async void OnServerListUpdated(object sender, EventArgs e) => await Save().ConfigureAwait(false);
private async void OnServerListUpdated(object? sender, EventArgs e) => await Save().ConfigureAwait(false);
// ReSharper disable UnusedMember.Global
public bool ShouldSerializeCellID() => CellID != 0;

View file

@ -26,19 +26,19 @@ using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Helpers {
public sealed class ArchiCacheable<T> : IDisposable {
public sealed class ArchiCacheable<T> : IDisposable where T : class {
private readonly TimeSpan CacheLifetime;
private readonly SemaphoreSlim InitSemaphore = new SemaphoreSlim(1, 1);
private readonly Func<Task<(bool Success, T Result)>> ResolveFunction;
private readonly Func<Task<(bool Success, T? Result)>> ResolveFunction;
private bool IsInitialized => InitializedAt > DateTime.MinValue;
private bool IsPermanentCache => CacheLifetime == Timeout.InfiniteTimeSpan;
private bool IsRecent => IsPermanentCache || (DateTime.UtcNow.Subtract(InitializedAt) < CacheLifetime);
private DateTime InitializedAt;
private T InitializedValue;
private T? InitializedValue;
public ArchiCacheable([NotNull] Func<Task<(bool Success, T Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
public ArchiCacheable(Func<Task<(bool Success, T? Result)>> resolveFunction, TimeSpan? cacheLifetime = null) {
ResolveFunction = resolveFunction ?? throw new ArgumentNullException(nameof(resolveFunction));
CacheLifetime = cacheLifetime ?? Timeout.InfiniteTimeSpan;
}
@ -46,11 +46,9 @@ namespace ArchiSteamFarm.Helpers {
public void Dispose() => InitSemaphore.Dispose();
[PublicAPI]
public async Task<(bool Success, T Result)> GetValue(EFallback fallback = EFallback.DefaultForType) {
public async Task<(bool Success, T? Result)> GetValue(EFallback fallback = EFallback.DefaultForType) {
if (!Enum.IsDefined(typeof(EFallback), fallback)) {
ASF.ArchiLogger.LogNullError(nameof(fallback));
return (false, default);
throw new ArgumentNullException(nameof(fallback));
}
if (IsInitialized && IsRecent) {
@ -64,7 +62,7 @@ namespace ArchiSteamFarm.Helpers {
return (true, InitializedValue);
}
(bool success, T result) = await ResolveFunction().ConfigureAwait(false);
(bool success, T? result) = await ResolveFunction().ConfigureAwait(false);
if (!success) {
switch (fallback) {

View file

@ -25,7 +25,6 @@ using System.IO;
using System.Security.AccessControl;
using System.Threading;
using System.Threading.Tasks;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Helpers {
internal sealed class CrossProcessFileBasedSemaphore : ICrossProcessSemaphore {
@ -34,9 +33,9 @@ namespace ArchiSteamFarm.Helpers {
private readonly string FilePath;
private readonly SemaphoreSlim LocalSemaphore = new SemaphoreSlim(1, 1);
private FileStream FileLock;
private FileStream? FileLock;
internal CrossProcessFileBasedSemaphore([NotNull] string name) {
internal CrossProcessFileBasedSemaphore(string name) {
if (string.IsNullOrEmpty(name)) {
throw new ArgumentNullException(nameof(name));
}
@ -151,7 +150,7 @@ namespace ArchiSteamFarm.Helpers {
return;
}
string directoryPath = Path.GetDirectoryName(FilePath);
string? directoryPath = Path.GetDirectoryName(FilePath);
if (string.IsNullOrEmpty(directoryPath)) {
ASF.ArchiLogger.LogNullError(nameof(directoryPath));

View file

@ -21,13 +21,12 @@
using System;
using System.Threading;
using JetBrains.Annotations;
namespace ArchiSteamFarm.Helpers {
internal sealed class SemaphoreLock : IDisposable {
private readonly SemaphoreSlim Semaphore;
internal SemaphoreLock([NotNull] SemaphoreSlim semaphore) => Semaphore = semaphore ?? throw new ArgumentNullException(nameof(semaphore));
internal SemaphoreLock(SemaphoreSlim semaphore) => Semaphore = semaphore ?? throw new ArgumentNullException(nameof(semaphore));
public void Dispose() => Semaphore.Release();
}

View file

@ -29,7 +29,7 @@ namespace ArchiSteamFarm.Helpers {
public abstract class SerializableFile : IDisposable {
private readonly SemaphoreSlim FileSemaphore = new SemaphoreSlim(1, 1);
protected string FilePath { private get; set; }
protected string? FilePath { private get; set; }
private bool ReadOnly;
private bool SavingScheduled;
@ -37,7 +37,11 @@ namespace ArchiSteamFarm.Helpers {
public virtual void Dispose() => FileSemaphore.Dispose();
protected async Task Save() {
if (ReadOnly || string.IsNullOrEmpty(FilePath)) {
if (string.IsNullOrEmpty(FilePath)) {
throw new ArgumentNullException(nameof(FilePath));
}
if (ReadOnly) {
return;
}

View file

@ -31,23 +31,23 @@ using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NLog.Web;
#if !NETFRAMEWORK
using Microsoft.Extensions.Hosting;
#endif
namespace ArchiSteamFarm.IPC {
internal static class ArchiKestrel {
internal static HistoryTarget HistoryTarget { get; private set; }
internal static HistoryTarget? HistoryTarget { get; private set; }
internal static string WebsiteDirectory { get; private set; } = Path.Combine(AppContext.BaseDirectory, SharedInfo.WebsiteDirectory);
#if NETFRAMEWORK
private static IWebHost KestrelWebHost;
private static IWebHost? KestrelWebHost;
#else
private static IHost KestrelWebHost;
private static IHost? KestrelWebHost;
#endif
internal static void OnNewHistoryTarget(HistoryTarget historyTarget = null) {
internal static void OnNewHistoryTarget(HistoryTarget? historyTarget = null) {
if (HistoryTarget != null) {
HistoryTarget.NewHistoryEntry -= NLogController.OnNewHistoryEntry;
HistoryTarget = null;
@ -140,9 +140,9 @@ namespace ArchiSteamFarm.IPC {
// Start the server
#if NETFRAMEWORK
IWebHost kestrelWebHost = null;
IWebHost? kestrelWebHost = null;
#else
IHost kestrelWebHost = null;
IHost? kestrelWebHost = null;
#endif
try {

View file

@ -39,6 +39,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[HttpGet]
[ProducesResponseType(typeof(GenericResponse<ASFResponse>), (int) HttpStatusCode.OK)]
public ActionResult<GenericResponse<ASFResponse>> ASFGet() {
if (ASF.GlobalConfig == null) {
throw new ArgumentNullException(nameof(ASF.GlobalConfig));
}
uint memoryUsage = (uint) GC.GetTotalMemory(false) / 1024;
ASFResponse result = new ASFResponse(SharedInfo.BuildInfo.Variant, ASF.GlobalConfig, memoryUsage, RuntimeCompatibility.ProcessStartTime, SharedInfo.Version);
@ -54,13 +58,15 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> ASFPost([FromBody] ASFRequest request) {
if (request == null) {
ASF.ArchiLogger.LogNullError(nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
if ((request == null) || (ASF.GlobalConfig == null)) {
throw new ArgumentNullException(nameof(request) + " || " + nameof(ASF.GlobalConfig));
}
(bool valid, string errorMessage) = request.GlobalConfig.CheckValidation();
if (request.GlobalConfig == null) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.GlobalConfig))));
}
(bool valid, string? errorMessage) = request.GlobalConfig.CheckValidation();
if (!valid) {
return BadRequest(new GenericResponse(false, errorMessage));
@ -125,13 +131,13 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[HttpPost("Update")]
[ProducesResponseType(typeof(GenericResponse<string>), (int) HttpStatusCode.OK)]
public async Task<ActionResult<GenericResponse<string>>> UpdatePost() {
(bool success, string message, Version version) = await Actions.Update().ConfigureAwait(false);
(bool success, string? message, Version? version) = await Actions.Update().ConfigureAwait(false);
if (string.IsNullOrEmpty(message)) {
message = success ? Strings.Success : Strings.WarningFailed;
}
return Ok(new GenericResponse<string>(success, message, version?.ToString()));
return Ok(new GenericResponse<string>(success, message!, version?.ToString()));
}
}
}

View file

@ -42,12 +42,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> BotDelete(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -66,18 +64,16 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public ActionResult<GenericResponse> BotGet(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if (bots == null) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(bots))));
}
return Ok(new GenericResponse<IReadOnlyDictionary<string, Bot>>(bots.ToDictionary(bot => bot.BotName, bot => bot, Bot.BotsComparer)));
return Ok(new GenericResponse<IReadOnlyDictionary<string, Bot>>(bots.Where(bot => !string.IsNullOrEmpty(bot.BotName)).ToDictionary(bot => bot.BotName, bot => bot, Bot.BotsComparer)!));
}
/// <summary>
@ -88,13 +84,15 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse<IReadOnlyDictionary<string, bool>>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> BotPost(string botNames, [FromBody] BotRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botNames) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
if (string.IsNullOrEmpty(botNames) || (request == null) || (Bot.Bots == null)) {
throw new ArgumentNullException(nameof(botNames) + " || " + nameof(request) + " || " + nameof(Bot.Bots));
}
(bool valid, string errorMessage) = request.BotConfig.CheckValidation();
if (request.BotConfig == null) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.BotConfig))));
}
(bool valid, string? errorMessage) = request.BotConfig.CheckValidation();
if (!valid) {
return BadRequest(new GenericResponse(false, errorMessage));
@ -109,7 +107,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
Dictionary<string, bool> result = new Dictionary<string, bool>(bots.Count, Bot.BotsComparer);
foreach (string botName in bots) {
if (Bot.Bots.TryGetValue(botName, out Bot bot)) {
if (Bot.Bots.TryGetValue(botName, out Bot? bot)) {
if (!request.BotConfig.IsSteamLoginSet && bot.BotConfig.IsSteamLoginSet) {
request.BotConfig.SteamLogin = bot.BotConfig.SteamLogin;
}
@ -155,12 +153,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundDelete(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -179,23 +175,21 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundGet(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
}
IList<(Dictionary<string, string> UnusedKeys, Dictionary<string, string> UsedKeys)> results = await Utilities.InParallel(bots.Select(bot => bot.GetUsedAndUnusedKeys())).ConfigureAwait(false);
IList<(Dictionary<string, string>? UnusedKeys, Dictionary<string, string>? UsedKeys)> results = await Utilities.InParallel(bots.Select(bot => bot.GetUsedAndUnusedKeys())).ConfigureAwait(false);
Dictionary<string, GamesToRedeemInBackgroundResponse> result = new Dictionary<string, GamesToRedeemInBackgroundResponse>(bots.Count, Bot.BotsComparer);
foreach (Bot bot in bots) {
(Dictionary<string, string> unusedKeys, Dictionary<string, string> usedKeys) = results[result.Count];
(Dictionary<string, string>? unusedKeys, Dictionary<string, string>? usedKeys) = results[result.Count];
result[bot.BotName] = new GamesToRedeemInBackgroundResponse(unusedKeys, usedKeys);
}
@ -211,16 +205,14 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> GamesToRedeemInBackgroundPost(string botNames, [FromBody] BotGamesToRedeemInBackgroundRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botNames) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
throw new ArgumentNullException(nameof(botNames) + " || " + nameof(request));
}
if (request.GamesToRedeemInBackground.Count == 0) {
if ((request.GamesToRedeemInBackground == null) || (request.GamesToRedeemInBackground.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.GamesToRedeemInBackground))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -252,22 +244,20 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> InputPost(string botNames, [FromBody] BotInputRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botNames) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
throw new ArgumentNullException(nameof(botNames) + " || " + nameof(request));
}
if ((request.Type == ASF.EUserInputType.None) || !Enum.IsDefined(typeof(ASF.EUserInputType), request.Type) || string.IsNullOrEmpty(request.Value)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(request.Type) + " || " + nameof(request.Value))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
}
IList<bool> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.SetUserInput(request.Type, request.Value)))).ConfigureAwait(false);
IList<bool> results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => bot.SetUserInput(request.Type, request.Value!)))).ConfigureAwait(false);
return Ok(results.All(result => result) ? new GenericResponse(true) : new GenericResponse(false, Strings.WarningFailed));
}
@ -281,12 +271,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> PausePost(string botNames, [FromBody] BotPauseRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botNames) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
throw new ArgumentNullException(nameof(botNames) + " || " + nameof(request));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -310,29 +298,27 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> RedeemPost(string botNames, [FromBody] BotRedeemRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botNames) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
throw new ArgumentNullException(nameof(botNames) + " || " + nameof(request));
}
if (request.KeysToRedeem.Count == 0) {
if ((request.KeysToRedeem == null) || (request.KeysToRedeem.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.KeysToRedeem))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
}
IList<ArchiHandler.PurchaseResponseCallback> results = await Utilities.InParallel(bots.Select(bot => request.KeysToRedeem.Select(key => bot.Actions.RedeemKey(key))).SelectMany(task => task)).ConfigureAwait(false);
IList<ArchiHandler.PurchaseResponseCallback?> results = await Utilities.InParallel(bots.Select(bot => request.KeysToRedeem.Select(key => bot.Actions.RedeemKey(key))).SelectMany(task => task)).ConfigureAwait(false);
Dictionary<string, IReadOnlyDictionary<string, ArchiHandler.PurchaseResponseCallback>> result = new Dictionary<string, IReadOnlyDictionary<string, ArchiHandler.PurchaseResponseCallback>>(bots.Count, Bot.BotsComparer);
Dictionary<string, IReadOnlyDictionary<string, ArchiHandler.PurchaseResponseCallback?>> result = new Dictionary<string, IReadOnlyDictionary<string, ArchiHandler.PurchaseResponseCallback?>>(bots.Count, Bot.BotsComparer);
int count = 0;
foreach (Bot bot in bots) {
Dictionary<string, ArchiHandler.PurchaseResponseCallback> responses = new Dictionary<string, ArchiHandler.PurchaseResponseCallback>(request.KeysToRedeem.Count, StringComparer.Ordinal);
Dictionary<string, ArchiHandler.PurchaseResponseCallback?> responses = new Dictionary<string, ArchiHandler.PurchaseResponseCallback?>(request.KeysToRedeem.Count, StringComparer.Ordinal);
result[bot.BotName] = responses;
foreach (string key in request.KeysToRedeem) {
@ -340,7 +326,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
}
}
return Ok(new GenericResponse<IReadOnlyDictionary<string, IReadOnlyDictionary<string, ArchiHandler.PurchaseResponseCallback>>>(result.Values.SelectMany(responses => responses.Values).All(value => value != null), result));
return Ok(new GenericResponse<IReadOnlyDictionary<string, IReadOnlyDictionary<string, ArchiHandler.PurchaseResponseCallback?>>>(result.Values.SelectMany(responses => responses.Values).All(value => value != null), result));
}
/// <summary>
@ -351,17 +337,15 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> RenamePost(string botName, [FromBody] BotRenameRequest request) {
if (string.IsNullOrEmpty(botName) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botName) + " || " + nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botName) + " || " + nameof(request))));
if (string.IsNullOrEmpty(botName) || (request == null) || (Bot.Bots == null)) {
throw new ArgumentNullException(nameof(botName) + " || " + nameof(request) + " || " + nameof(Bot.Bots));
}
if (string.IsNullOrEmpty(request.NewName) || request.NewName.Equals(SharedInfo.ASF) || Bot.Bots.ContainsKey(request.NewName)) {
if (string.IsNullOrEmpty(request.NewName) || request.NewName!.Equals(SharedInfo.ASF) || Bot.Bots.ContainsKey(request.NewName)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(request.NewName))));
}
if (!Bot.Bots.TryGetValue(botName, out Bot bot)) {
if (!Bot.Bots.TryGetValue(botName, out Bot? bot)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botName)));
}
@ -378,12 +362,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> ResumePost(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -402,12 +384,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> StartPost(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -426,12 +406,10 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> StopPost(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.BotNotFound, botNames)));
@ -450,16 +428,14 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationConfirmationsPost(string botNames, [FromBody] TwoFactorAuthenticationConfirmationsRequest request) {
if (string.IsNullOrEmpty(botNames) || (request == null)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames) + " || " + nameof(request))));
throw new ArgumentNullException(nameof(botNames));
}
if (request.AcceptedType.HasValue && ((request.AcceptedType.Value == MobileAuthenticator.Confirmation.EType.Unknown) || !Enum.IsDefined(typeof(MobileAuthenticator.Confirmation.EType), request.AcceptedType.Value))) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(request.AcceptedType))));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse>>(false, string.Format(Strings.BotNotFound, botNames)));
@ -485,23 +461,21 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> TwoFactorAuthenticationTokenGet(string botNames) {
if (string.IsNullOrEmpty(botNames)) {
ASF.ArchiLogger.LogNullError(nameof(botNames));
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse<string>>>(false, string.Format(Strings.ErrorIsEmpty, nameof(botNames))));
throw new ArgumentNullException(nameof(botNames));
}
HashSet<Bot> bots = Bot.GetBots(botNames);
HashSet<Bot>? bots = Bot.GetBots(botNames);
if ((bots == null) || (bots.Count == 0)) {
return BadRequest(new GenericResponse<IReadOnlyDictionary<string, GenericResponse<string>>>(false, string.Format(Strings.BotNotFound, botNames)));
}
IList<(bool Success, string Token, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.GenerateTwoFactorAuthenticationToken())).ConfigureAwait(false);
IList<(bool Success, string? Token, string Message)> results = await Utilities.InParallel(bots.Select(bot => bot.Actions.GenerateTwoFactorAuthenticationToken())).ConfigureAwait(false);
Dictionary<string, GenericResponse<string>> result = new Dictionary<string, GenericResponse<string>>(bots.Count, Bot.BotsComparer);
foreach (Bot bot in bots) {
(bool success, string token, string message) = results[result.Count];
(bool success, string? token, string message) = results[result.Count];
result[bot.BotName] = new GenericResponse<string>(success, message, token);
}

View file

@ -44,36 +44,37 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public async Task<ActionResult<GenericResponse>> CommandPost([FromBody] CommandRequest request) {
if (request == null) {
ASF.ArchiLogger.LogNullError(nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
throw new ArgumentNullException(nameof(request));
}
if (string.IsNullOrEmpty(request.Command)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request.Command))));
}
if (ASF.GlobalConfig.SteamOwnerID == 0) {
ulong steamOwnerID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
if (steamOwnerID == 0) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(ASF.GlobalConfig.SteamOwnerID))));
}
Bot targetBot = Bot.Bots.OrderBy(bot => bot.Key, Bot.BotsComparer).Select(bot => bot.Value).FirstOrDefault();
Bot? targetBot = Bot.Bots.OrderBy(bot => bot.Key, Bot.BotsComparer).Select(bot => bot.Value).FirstOrDefault();
if (targetBot == null) {
return BadRequest(new GenericResponse(false, Strings.ErrorNoBotsDefined));
}
string command = request.Command;
string command = request.Command!;
string? commandPrefix = ASF.GlobalConfig?.CommandPrefix ?? GlobalConfig.DefaultCommandPrefix;
if (!string.IsNullOrEmpty(ASF.GlobalConfig.CommandPrefix) && command.StartsWith(ASF.GlobalConfig.CommandPrefix, StringComparison.Ordinal)) {
command = command.Substring(ASF.GlobalConfig.CommandPrefix.Length);
if (!string.IsNullOrEmpty(commandPrefix) && command.StartsWith(commandPrefix, StringComparison.Ordinal)) {
command = command.Substring(commandPrefix!.Length);
if (string.IsNullOrEmpty(command)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(command))));
}
}
string response = await targetBot.Commands.Response(ASF.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false);
string? response = await targetBot.Commands.Response(steamOwnerID, command).ConfigureAwait(false);
return Ok(new GenericResponse<string>(response));
}

View file

@ -19,6 +19,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
@ -86,7 +87,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
break;
}
} finally {
if (ActiveLogWebSockets.TryRemove(webSocket, out SemaphoreSlim closedSemaphore)) {
if (ActiveLogWebSockets.TryRemove(webSocket, out SemaphoreSlim? closedSemaphore)) {
await closedSemaphore.WaitAsync().ConfigureAwait(false); // Ensure that our semaphore is truly closed by now
closedSemaphore.Dispose();
}
@ -98,11 +99,9 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return new EmptyResult();
}
internal static async void OnNewHistoryEntry(object sender, HistoryTarget.NewHistoryEntryArgs newHistoryEntryArgs) {
if ((sender == null) || (newHistoryEntryArgs == null)) {
ASF.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(newHistoryEntryArgs));
return;
internal static async void OnNewHistoryEntry(object? sender, HistoryTarget.NewHistoryEntryArgs newHistoryEntryArgs) {
if (newHistoryEntryArgs == null) {
throw new ArgumentNullException(nameof(newHistoryEntryArgs));
}
if (ActiveLogWebSockets.Count == 0) {
@ -110,14 +109,13 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
}
string json = JsonConvert.SerializeObject(new GenericResponse<string>(newHistoryEntryArgs.Message));
await Task.WhenAll(ActiveLogWebSockets.Where(kv => kv.Key.State == WebSocketState.Open).Select(kv => PostLoggedJsonUpdate(kv.Key, kv.Value, json))).ConfigureAwait(false);
}
private static async Task PostLoggedJsonUpdate(WebSocket webSocket, SemaphoreSlim sendSemaphore, string json) {
if ((webSocket == null) || (sendSemaphore == null) || string.IsNullOrEmpty(json)) {
ASF.ArchiLogger.LogNullError(nameof(webSocket) + " || " + nameof(sendSemaphore) + " || " + nameof(json));
return;
throw new ArgumentNullException(nameof(webSocket) + " || " + nameof(sendSemaphore) + " || " + nameof(json));
}
if (webSocket.State != WebSocketState.Open) {
@ -141,9 +139,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
private static async Task PostLoggedMessageUpdate(WebSocket webSocket, SemaphoreSlim sendSemaphore, string loggedMessage) {
if ((webSocket == null) || (sendSemaphore == null) || string.IsNullOrEmpty(loggedMessage)) {
ASF.ArchiLogger.LogNullError(nameof(webSocket) + " || " + nameof(sendSemaphore) + " || " + nameof(loggedMessage));
return;
throw new ArgumentNullException(nameof(webSocket) + " || " + nameof(sendSemaphore) + " || " + nameof(loggedMessage));
}
if (webSocket.State != WebSocketState.Open) {
@ -151,6 +147,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
}
string response = JsonConvert.SerializeObject(new GenericResponse<string>(loggedMessage));
await PostLoggedJsonUpdate(webSocket, sendSemaphore, response).ConfigureAwait(false);
}
}

View file

@ -39,18 +39,16 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public ActionResult<GenericResponse> StructureGet(string structure) {
if (string.IsNullOrEmpty(structure)) {
ASF.ArchiLogger.LogNullError(nameof(structure));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(structure))));
throw new ArgumentNullException(nameof(structure));
}
Type targetType = WebUtilities.ParseType(structure);
Type? targetType = WebUtilities.ParseType(structure);
if (targetType == null) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, structure)));
}
object obj;
object? obj;
try {
obj = Activator.CreateInstance(targetType, true);

View file

@ -43,45 +43,55 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
public ActionResult<GenericResponse> TypeGet(string type) {
if (string.IsNullOrEmpty(type)) {
ASF.ArchiLogger.LogNullError(nameof(type));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(type))));
throw new ArgumentNullException(nameof(type));
}
Type targetType = WebUtilities.ParseType(type);
Type? targetType = WebUtilities.ParseType(type);
if (targetType == null) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, type)));
}
string baseType = targetType.BaseType?.GetUnifiedName();
HashSet<string> customAttributes = targetType.CustomAttributes.Select(attribute => attribute.AttributeType.GetUnifiedName()).ToHashSet(StringComparer.Ordinal);
string underlyingType = null;
string? baseType = targetType.BaseType?.GetUnifiedName();
HashSet<string> customAttributes = targetType.CustomAttributes.Select(attribute => attribute.AttributeType.GetUnifiedName()).Where(customAttribute => !string.IsNullOrEmpty(customAttribute)).ToHashSet(StringComparer.Ordinal)!;
string? underlyingType = null;
Dictionary<string, string> body = new Dictionary<string, string>(StringComparer.Ordinal);
if (targetType.IsClass) {
foreach (FieldInfo field in targetType.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(field => !field.IsPrivate)) {
JsonPropertyAttribute jsonProperty = field.GetCustomAttribute<JsonPropertyAttribute>();
JsonPropertyAttribute? jsonProperty = field.GetCustomAttribute<JsonPropertyAttribute>();
if (jsonProperty != null) {
body[jsonProperty.PropertyName ?? field.Name] = field.FieldType.GetUnifiedName();
string? unifiedName = field.FieldType.GetUnifiedName();
if (!string.IsNullOrEmpty(unifiedName)) {
body[jsonProperty.PropertyName ?? field.Name] = unifiedName!;
}
}
}
foreach (PropertyInfo property in targetType.GetProperties(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public).Where(property => property.CanRead && (property.GetMethod?.IsPrivate == false))) {
JsonPropertyAttribute jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
JsonPropertyAttribute? jsonProperty = property.GetCustomAttribute<JsonPropertyAttribute>();
if (jsonProperty != null) {
body[jsonProperty.PropertyName ?? property.Name] = property.PropertyType.GetUnifiedName();
string? unifiedName = property.PropertyType.GetUnifiedName();
if (!string.IsNullOrEmpty(unifiedName)) {
body[jsonProperty.PropertyName ?? property.Name] = unifiedName!;
}
}
}
} else if (targetType.IsEnum) {
Type enumType = Enum.GetUnderlyingType(targetType);
underlyingType = enumType.GetUnifiedName();
foreach (object value in Enum.GetValues(targetType)) {
string valueText = value.ToString();
foreach (object? value in Enum.GetValues(targetType)) {
if (value == null) {
continue;
}
string? valueText = value.ToString();
if (string.IsNullOrEmpty(valueText)) {
ASF.ArchiLogger.LogNullError(nameof(valueText));
@ -89,7 +99,13 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorObjectIsNull, nameof(valueText))));
}
body[valueText] = Convert.ChangeType(value, enumType).ToString();
string? valueObjText = Convert.ChangeType(value, enumType)?.ToString();
if (string.IsNullOrEmpty(valueObjText)) {
continue;
}
body[valueText] = valueObjText!;
}
}

View file

@ -45,9 +45,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.InternalServerError)]
public ActionResult<GenericResponse> DirectoryGet(string directory) {
if (string.IsNullOrEmpty(directory)) {
ASF.ArchiLogger.LogNullError(nameof(directory));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(directory))));
throw new ArgumentNullException(directory);
}
string directoryPath = Path.Combine(ArchiKestrel.WebsiteDirectory, directory);
@ -64,7 +62,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
return StatusCode((int) HttpStatusCode.InternalServerError, new GenericResponse(false, string.Format(Strings.ErrorParsingObject, nameof(files)) + Environment.NewLine + e));
}
HashSet<string> result = files.Select(Path.GetFileName).ToHashSet();
HashSet<string> result = files.Select(Path.GetFileName).Where(fileName => !string.IsNullOrEmpty(fileName)).ToHashSet()!;
return Ok(new GenericResponse<IReadOnlyCollection<string>>(result));
}
@ -79,7 +77,7 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse<GitHubReleaseResponse>), (int) HttpStatusCode.OK)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> GitHubReleaseGet() {
GitHub.ReleaseResponse releaseResponse = await GitHub.GetLatestRelease(false).ConfigureAwait(false);
GitHub.ReleaseResponse? releaseResponse = await GitHub.GetLatestRelease(false).ConfigureAwait(false);
return releaseResponse != null ? Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
}
@ -96,10 +94,25 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> GitHubReleaseGet(string version) {
if (string.IsNullOrEmpty(version)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(version))));
throw new ArgumentNullException(nameof(version));
}
GitHub.ReleaseResponse releaseResponse = version.Equals("latest", StringComparison.OrdinalIgnoreCase) ? await GitHub.GetLatestRelease().ConfigureAwait(false) : await GitHub.GetRelease(version).ConfigureAwait(false);
GitHub.ReleaseResponse? releaseResponse;
switch (version.ToUpperInvariant()) {
case "LATEST":
releaseResponse = await GitHub.GetLatestRelease().ConfigureAwait(false);
break;
default:
if (!Version.TryParse(version, out Version? parsedVersion)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(version))));
}
releaseResponse = await GitHub.GetRelease(parsedVersion.ToString(4)).ConfigureAwait(false);
break;
}
return releaseResponse != null ? Ok(new GenericResponse<GitHubReleaseResponse>(new GitHubReleaseResponse(releaseResponse))) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
}
@ -116,17 +129,15 @@ namespace ArchiSteamFarm.IPC.Controllers.Api {
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.BadRequest)]
[ProducesResponseType(typeof(GenericResponse), (int) HttpStatusCode.ServiceUnavailable)]
public async Task<ActionResult<GenericResponse>> SendPost([FromBody] WWWSendRequest request) {
if (request == null) {
ASF.ArchiLogger.LogNullError(nameof(request));
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsEmpty, nameof(request))));
if ((request == null) || (ASF.WebBrowser == null)) {
throw new ArgumentNullException(nameof(request) + " || " + nameof(ASF.WebBrowser));
}
if (string.IsNullOrEmpty(request.URL) || !Uri.TryCreate(request.URL, UriKind.Absolute, out Uri uri) || !uri.Scheme.Equals(Uri.UriSchemeHttps)) {
if (string.IsNullOrEmpty(request.URL) || !Uri.TryCreate(request.URL, UriKind.Absolute, out Uri? uri) || !uri.Scheme.Equals(Uri.UriSchemeHttps)) {
return BadRequest(new GenericResponse(false, string.Format(Strings.ErrorIsInvalid, nameof(request.URL))));
}
WebBrowser.StringResponse urlResponse = await ASF.WebBrowser.UrlGetToString(request.URL).ConfigureAwait(false);
WebBrowser.StringResponse? urlResponse = await ASF.WebBrowser.UrlGetToString(request.URL!).ConfigureAwait(false);
return urlResponse?.Content != null ? Ok(new GenericResponse<string>(urlResponse.Content)) : StatusCode((int) HttpStatusCode.ServiceUnavailable, new GenericResponse(false, string.Format(Strings.ErrorRequestFailedTooManyTimes, WebBrowser.MaxTries)));
}

View file

@ -41,11 +41,11 @@ namespace ArchiSteamFarm.IPC.Integration {
private static readonly SemaphoreSlim AuthorizationSemaphore = new SemaphoreSlim(1, 1);
private static readonly ConcurrentDictionary<IPAddress, byte> FailedAuthorizations = new ConcurrentDictionary<IPAddress, byte>();
private static Timer ClearFailedAuthorizationsTimer;
private static Timer? ClearFailedAuthorizationsTimer;
private readonly RequestDelegate Next;
public ApiAuthenticationMiddleware([JetBrains.Annotations.NotNull] RequestDelegate next) {
public ApiAuthenticationMiddleware(RequestDelegate next) {
Next = next ?? throw new ArgumentNullException(nameof(next));
lock (FailedAuthorizations) {
@ -61,9 +61,7 @@ namespace ArchiSteamFarm.IPC.Integration {
[PublicAPI]
public async Task InvokeAsync(HttpContext context) {
if (context == null) {
ASF.ArchiLogger.LogNullError(nameof(context));
return;
throw new ArgumentNullException(nameof(context));
}
HttpStatusCode authenticationStatus = await GetAuthenticationStatus(context).ConfigureAwait(false);
@ -78,13 +76,13 @@ namespace ArchiSteamFarm.IPC.Integration {
}
private static async Task<HttpStatusCode> GetAuthenticationStatus(HttpContext context) {
if (context == null) {
ASF.ArchiLogger.LogNullError(nameof(context));
return HttpStatusCode.InternalServerError;
if ((context == null) || (ClearFailedAuthorizationsTimer == null)) {
throw new ArgumentNullException(nameof(context) + " || " + nameof(ClearFailedAuthorizationsTimer));
}
if (string.IsNullOrEmpty(ASF.GlobalConfig.IPCPassword)) {
string? ipcPassword = ASF.GlobalConfig?.IPCPassword ?? GlobalConfig.DefaultIPCPassword;
if (string.IsNullOrEmpty(ipcPassword)) {
return HttpStatusCode.OK;
}
@ -100,13 +98,13 @@ namespace ArchiSteamFarm.IPC.Integration {
return HttpStatusCode.Unauthorized;
}
string inputPassword = passwords.FirstOrDefault(password => !string.IsNullOrEmpty(password));
string? inputPassword = passwords.FirstOrDefault(password => !string.IsNullOrEmpty(password));
if (string.IsNullOrEmpty(inputPassword)) {
return HttpStatusCode.Unauthorized;
}
bool authorized = inputPassword == ASF.GlobalConfig.IPCPassword;
bool authorized = inputPassword == ipcPassword;
await AuthorizationSemaphore.WaitAsync().ConfigureAwait(false);

View file

@ -29,9 +29,11 @@ using Swashbuckle.AspNetCore.SwaggerGen;
namespace ArchiSteamFarm.IPC.Integration {
[UsedImplicitly]
internal sealed class EnumSchemaFilter : ISchemaFilter {
public void Apply([NotNull] OpenApiSchema schema, [NotNull] SchemaFilterContext context) {
public void Apply(OpenApiSchema schema, SchemaFilterContext context) {
if ((schema == null) || (context == null)) {
throw new ArgumentNullException(nameof(schema) + " || " + nameof(context));
ASF.ArchiLogger.LogNullError(nameof(schema) + " || " + nameof(context));
return;
}
if (!context.Type.IsEnum) {
@ -44,8 +46,12 @@ namespace ArchiSteamFarm.IPC.Integration {
OpenApiObject definition = new OpenApiObject();
foreach (object enumValue in context.Type.GetEnumValues()) {
string enumName = Enum.GetName(context.Type, enumValue);
foreach (object? enumValue in context.Type.GetEnumValues()) {
if (enumValue == null) {
continue;
}
string? enumName = Enum.GetName(context.Type, enumValue);
if (string.IsNullOrEmpty(enumName)) {
enumName = enumValue.ToString();
@ -74,7 +80,11 @@ namespace ArchiSteamFarm.IPC.Integration {
schema.AddExtension("x-definition", definition);
}
private static bool TryCast<T>(object value, out T typedValue) {
private static bool TryCast<T>(object value, out T typedValue) where T : struct {
if (value == null) {
throw new ArgumentNullException(nameof(value));
}
try {
typedValue = (T) Convert.ChangeType(value, typeof(T));

View file

@ -31,7 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly GlobalConfig GlobalConfig;
public readonly GlobalConfig? GlobalConfig;
[JsonConstructor]
private ASFRequest() { }

View file

@ -36,7 +36,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </remarks>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly OrderedDictionary GamesToRedeemInBackground;
public readonly OrderedDictionary? GamesToRedeemInBackground;
[JsonConstructor]
private BotGamesToRedeemInBackgroundRequest() { }

View file

@ -35,7 +35,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// Specifies the value for given input type (declared in <see cref="Type" />)
/// </summary>
[JsonProperty(Required = Required.Always)]
public readonly string Value;
public readonly string? Value;
[JsonConstructor]
private BotInputRequest() { }

View file

@ -32,7 +32,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly ImmutableHashSet<string> KeysToRedeem;
public readonly ImmutableHashSet<string>? KeysToRedeem;
[JsonConstructor]
private BotRedeemRequest() { }

View file

@ -31,7 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string NewName;
public readonly string? NewName;
[JsonConstructor]
private BotRenameRequest() { }

View file

@ -31,7 +31,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly BotConfig BotConfig;
public readonly BotConfig? BotConfig;
[JsonConstructor]
private BotRequest() { }

View file

@ -32,9 +32,9 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string Command;
public readonly string? Command;
internal CommandRequest([JetBrains.Annotations.NotNull] string command) {
internal CommandRequest(string command) {
if (string.IsNullOrEmpty(command)) {
throw new ArgumentNullException(nameof(command));
}

View file

@ -50,7 +50,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// Specifies IDs of the confirmations that we're supposed to handle. CreatorID of the confirmation is equal to ID of the object that triggered it - e.g. ID of the trade offer, or ID of the market listing. If not provided, or empty array, all confirmation IDs are considered for an action.
/// </summary>
[JsonProperty(Required = Required.DisallowNull)]
public ImmutableHashSet<ulong> AcceptedCreatorIDs { get; private set; }
public ImmutableHashSet<ulong>? AcceptedCreatorIDs { get; private set; }
/// <summary>
/// A helper property which works the same as <see cref="AcceptedCreatorIDs" /> but with values written as strings - for javascript compatibility purposes. Use either this one, or <see cref="AcceptedCreatorIDs" />, not both.

View file

@ -34,7 +34,7 @@ namespace ArchiSteamFarm.IPC.Requests {
/// </remarks>
[Required]
[JsonProperty(Required = Required.Always)]
public readonly string URL;
public readonly string? URL;
[JsonConstructor]
private WWWSendRequest() { }

View file

@ -21,7 +21,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
@ -61,7 +60,7 @@ namespace ArchiSteamFarm.IPC.Responses {
[Required]
public readonly Version Version;
internal ASFResponse([NotNull] string buildVariant, [NotNull] GlobalConfig globalConfig, uint memoryUsage, DateTime processStartTime, [NotNull] Version version) {
internal ASFResponse(string buildVariant, GlobalConfig globalConfig, uint memoryUsage, DateTime processStartTime, Version version) {
if (string.IsNullOrEmpty(buildVariant) || (globalConfig == null) || (memoryUsage == 0) || (processStartTime == DateTime.MinValue) || (version == null)) {
throw new ArgumentNullException(nameof(buildVariant) + " || " + nameof(globalConfig) + " || " + nameof(memoryUsage) + " || " + nameof(processStartTime) + " || " + nameof(version));
}

View file

@ -28,15 +28,15 @@ namespace ArchiSteamFarm.IPC.Responses {
/// Keys that were redeemed and not used during the process, if available.
/// </summary>
[JsonProperty]
public readonly Dictionary<string, string> UnusedKeys;
public readonly Dictionary<string, string>? UnusedKeys;
/// <summary>
/// Keys that were redeemed and used during the process, if available.
/// </summary>
[JsonProperty]
public readonly Dictionary<string, string> UsedKeys;
public readonly Dictionary<string, string>? UsedKeys;
internal GamesToRedeemInBackgroundResponse(Dictionary<string, string> unusedKeys = null, Dictionary<string, string> usedKeys = null) {
internal GamesToRedeemInBackgroundResponse(Dictionary<string, string>? unusedKeys = null, Dictionary<string, string>? usedKeys = null) {
UnusedKeys = unusedKeys;
UsedKeys = usedKeys;
}

View file

@ -32,12 +32,12 @@ namespace ArchiSteamFarm.IPC.Responses {
/// The type of the result depends on the API endpoint that you've called.
/// </remarks>
[JsonProperty]
public readonly T Result;
public readonly T? Result;
public GenericResponse(T result) : base(result != null) => Result = result;
public GenericResponse(T? result) : base(result != null) => Result = result;
public GenericResponse(bool success, string message) : base(success, message) { }
public GenericResponse(bool success, T result) : base(success) => Result = result;
public GenericResponse(bool success, string message, T result) : base(success, message) => Result = result;
public GenericResponse(bool success, T? result) : base(success) => Result = result;
public GenericResponse(bool success, string message, T? result) : base(success, message) => Result = result;
}
public class GenericResponse {
@ -57,16 +57,10 @@ namespace ArchiSteamFarm.IPC.Responses {
[Required]
public readonly bool Success;
public GenericResponse(bool success, string message = null) {
public GenericResponse(bool success, string? message = null) {
Success = success;
if (!string.IsNullOrEmpty(message)) {
Message = message;
return;
}
Message = success ? "OK" : Strings.WarningFailed;
Message = !string.IsNullOrEmpty(message) ? message! : success ? "OK" : Strings.WarningFailed;
}
}
}

View file

@ -21,7 +21,6 @@
using System;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
@ -31,7 +30,7 @@ namespace ArchiSteamFarm.IPC.Responses {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string ChangelogHTML;
public readonly string? ChangelogHTML;
/// <summary>
/// Date of the release.
@ -52,9 +51,9 @@ namespace ArchiSteamFarm.IPC.Responses {
/// </summary>
[JsonProperty(Required = Required.Always)]
[Required]
public readonly string Version;
public readonly string? Version;
internal GitHubReleaseResponse([NotNull] GitHub.ReleaseResponse releaseResponse) {
internal GitHubReleaseResponse(GitHub.ReleaseResponse releaseResponse) {
if (releaseResponse == null) {
throw new ArgumentNullException(nameof(releaseResponse));
}

View file

@ -22,7 +22,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm.IPC.Responses {
@ -46,7 +45,7 @@ namespace ArchiSteamFarm.IPC.Responses {
[Required]
public readonly TypeProperties Properties;
internal TypeResponse([NotNull] Dictionary<string, string> body, [NotNull] TypeProperties properties) {
internal TypeResponse(Dictionary<string, string> body, TypeProperties properties) {
if ((body == null) || (properties == null)) {
throw new ArgumentNullException(nameof(body) + " || " + nameof(properties));
}
@ -63,7 +62,7 @@ namespace ArchiSteamFarm.IPC.Responses {
/// This can be used for determining how <see cref="Body" /> should be interpreted.
/// </remarks>
[JsonProperty]
public readonly string BaseType;
public readonly string? BaseType;
/// <summary>
/// Custom attributes of given type, if available.
@ -72,7 +71,7 @@ namespace ArchiSteamFarm.IPC.Responses {
/// This can be used for determining main enum type if <see cref="BaseType" /> is <see cref="Enum" />.
/// </remarks>
[JsonProperty]
public readonly HashSet<string> CustomAttributes;
public readonly HashSet<string>? CustomAttributes;
/// <summary>
/// Underlying type of given type, if available.
@ -81,9 +80,9 @@ namespace ArchiSteamFarm.IPC.Responses {
/// This can be used for determining underlying enum type if <see cref="BaseType" /> is <see cref="Enum" />.
/// </remarks>
[JsonProperty]
public readonly string UnderlyingType;
public readonly string? UnderlyingType;
internal TypeProperties(string baseType = null, HashSet<string> customAttributes = null, string underlyingType = null) {
internal TypeProperties(string? baseType = null, HashSet<string>? customAttributes = null, string? underlyingType = null) {
BaseType = baseType;
CustomAttributes = customAttributes;
UnderlyingType = underlyingType;

View file

@ -39,13 +39,14 @@ using Newtonsoft.Json.Serialization;
#if NETFRAMEWORK
using Newtonsoft.Json.Converters;
#endif
namespace ArchiSteamFarm.IPC {
internal sealed class Startup {
private readonly IConfiguration Configuration;
public Startup([NotNull] IConfiguration configuration) => Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
public Startup(IConfiguration configuration) => Configuration = configuration ?? throw new ArgumentNullException(nameof(configuration));
#if NETFRAMEWORK
[UsedImplicitly]
@ -55,9 +56,7 @@ namespace ArchiSteamFarm.IPC {
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
#endif
if ((app == null) || (env == null)) {
ASF.ArchiLogger.LogNullError(nameof(app) + " || " + nameof(env));
return;
throw new ArgumentNullException(nameof(app) + " || " + nameof(env));
}
if (Debugging.IsUserDebugging) {
@ -93,7 +92,9 @@ namespace ArchiSteamFarm.IPC {
app.UseRouting();
#endif
if (!string.IsNullOrEmpty(ASF.GlobalConfig.IPCPassword)) {
string? ipcPassword = ASF.GlobalConfig?.IPCPassword ?? GlobalConfig.DefaultIPCPassword;
if (!string.IsNullOrEmpty(ipcPassword)) {
// We need ApiAuthenticationMiddleware for IPCPassword
app.UseWhen(context => context.Request.Path.StartsWithSegments("/Api", StringComparison.OrdinalIgnoreCase), appBuilder => appBuilder.UseMiddleware<ApiAuthenticationMiddleware>());
@ -125,9 +126,7 @@ namespace ArchiSteamFarm.IPC {
public void ConfigureServices(IServiceCollection services) {
if (services == null) {
ASF.ArchiLogger.LogNullError(nameof(services));
return;
throw new ArgumentNullException(nameof(services));
}
// The order of dependency injection matters, pay attention to it
@ -138,8 +137,10 @@ namespace ArchiSteamFarm.IPC {
// Add support for response compression
services.AddResponseCompression();
string? ipcPassword = ASF.GlobalConfig?.IPCPassword ?? GlobalConfig.DefaultIPCPassword;
// Add CORS to allow userscripts and third-party apps
if (!string.IsNullOrEmpty(ASF.GlobalConfig.IPCPassword)) {
if (!string.IsNullOrEmpty(ipcPassword)) {
services.AddCors(options => options.AddDefaultPolicy(policyBuilder => policyBuilder.AllowAnyOrigin()));
}
@ -209,7 +210,7 @@ namespace ArchiSteamFarm.IPC {
#endif
// Add support for controllers declared in custom plugins
HashSet<Assembly> assemblies = PluginsCore.LoadAssemblies();
HashSet<Assembly>? assemblies = PluginsCore.LoadAssemblies();
if (assemblies != null) {
foreach (Assembly assembly in assemblies) {

View file

@ -1,4 +1,4 @@
// _ _ _ ____ _ _____
// _ _ _ ____ _ _____
// / \ _ __ ___ | |__ (_)/ ___| | |_ ___ __ _ _ __ ___ | ___|__ _ _ __ _ __ ___
// / _ \ | '__|/ __|| '_ \ | |\___ \ | __|/ _ \ / _` || '_ ` _ \ | |_ / _` || '__|| '_ ` _ \
// / ___ \ | | | (__ | | | || | ___) || |_| __/| (_| || | | | | || _|| (_| || | | | | | | |
@ -29,9 +29,7 @@ namespace ArchiSteamFarm.IPC {
internal static class WebUtilities {
internal static async Task Generate(this HttpResponse httpResponse, HttpStatusCode statusCode) {
if (httpResponse == null) {
ASF.ArchiLogger.LogNullError(nameof(httpResponse));
return;
throw new ArgumentNullException(nameof(httpResponse));
}
ushort statusCodeNumber = (ushort) statusCode;
@ -40,24 +38,20 @@ namespace ArchiSteamFarm.IPC {
await httpResponse.WriteAsync(statusCodeNumber + " - " + statusCode).ConfigureAwait(false);
}
internal static string GetUnifiedName(this Type type) {
internal static string? GetUnifiedName(this Type type) {
if (type == null) {
ASF.ArchiLogger.LogNullError(nameof(type));
return null;
throw new ArgumentNullException(nameof(type));
}
return type.GenericTypeArguments.Length == 0 ? type.FullName : type.Namespace + "." + type.Name + string.Join("", type.GenericTypeArguments.Select(innerType => '[' + innerType.GetUnifiedName() + ']'));
}
internal static Type ParseType(string typeText) {
internal static Type? ParseType(string typeText) {
if (string.IsNullOrEmpty(typeText)) {
ASF.ArchiLogger.LogNullError(nameof(typeText));
return null;
throw new ArgumentNullException(nameof(typeText));
}
Type targetType = Type.GetType(typeText);
Type? targetType = Type.GetType(typeText);
if (targetType != null) {
return targetType;

View file

@ -42,7 +42,7 @@ namespace ArchiSteamFarm.Json {
[JsonIgnore]
[PublicAPI]
public ImmutableDictionary<string, JToken> AdditionalProperties { get; internal set; }
public ImmutableDictionary<string, JToken>? AdditionalProperties { get; internal set; }
[JsonIgnore]
[PublicAPI]
@ -81,7 +81,7 @@ namespace ArchiSteamFarm.Json {
[JsonIgnore]
[PublicAPI]
public ImmutableHashSet<Tag> Tags { get; internal set; }
public ImmutableHashSet<Tag>? Tags { get; internal set; }
[JsonIgnore]
[PublicAPI]
@ -93,7 +93,6 @@ namespace ArchiSteamFarm.Json {
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "amount", Required = Required.Always)]
[JetBrains.Annotations.NotNull]
private string AmountText {
get => Amount.ToString();
@ -117,7 +116,6 @@ namespace ArchiSteamFarm.Json {
#pragma warning disable IDE0052
[JsonProperty(PropertyName = "assetid", Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string AssetIDText {
get => AssetID.ToString();
@ -141,7 +139,6 @@ namespace ArchiSteamFarm.Json {
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "classid", Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string ClassIDText {
set {
if (string.IsNullOrEmpty(value)) {
@ -161,7 +158,6 @@ namespace ArchiSteamFarm.Json {
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "contextid", Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string ContextIDText {
get => ContextID.ToString();
@ -185,7 +181,6 @@ namespace ArchiSteamFarm.Json {
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "id", Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string IDText {
set => AssetIDText = value;
}
@ -193,7 +188,6 @@ namespace ArchiSteamFarm.Json {
#pragma warning disable IDE0051
[JsonProperty(PropertyName = "instanceid", Required = Required.DisallowNull)]
[JetBrains.Annotations.NotNull]
private string InstanceIDText {
set {
if (string.IsNullOrEmpty(value)) {
@ -212,7 +206,7 @@ namespace ArchiSteamFarm.Json {
#pragma warning restore IDE0051
// Constructed from trades being received or plugins
public Asset(uint appID, ulong contextID, ulong classID, uint amount, ulong instanceID = 0, ulong assetID = 0, bool marketable = true, bool tradable = true, ImmutableHashSet<Tag> tags = null, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) {
public Asset(uint appID, ulong contextID, ulong classID, uint amount, ulong instanceID = 0, ulong assetID = 0, bool marketable = true, bool tradable = true, ImmutableHashSet<Tag>? tags = null, uint realAppID = 0, EType type = EType.Unknown, ERarity rarity = ERarity.Unknown) {
if ((appID == 0) || (contextID == 0) || (classID == 0) || (amount == 0)) {
throw new ArgumentNullException(nameof(appID) + " || " + nameof(contextID) + " || " + nameof(classID) + " || " + nameof(amount));
}
@ -237,19 +231,18 @@ namespace ArchiSteamFarm.Json {
[JsonConstructor]
private Asset() { }
[JetBrains.Annotations.NotNull]
internal Asset CreateShallowCopy() => (Asset) MemberwiseClone();
public sealed class Tag {
[JsonProperty(PropertyName = "category", Required = Required.Always)]
[PublicAPI]
public readonly string Identifier;
public readonly string? Identifier;
[JsonProperty(PropertyName = "internal_name", Required = Required.Always)]
[PublicAPI]
public readonly string Value;
public readonly string? Value;
internal Tag([JetBrains.Annotations.NotNull] string identifier, [JetBrains.Annotations.NotNull] string value) {
internal Tag(string identifier, string value) {
if (string.IsNullOrEmpty(identifier) || string.IsNullOrEmpty(value)) {
throw new ArgumentNullException(nameof(identifier) + " || " + nameof(value));
}
@ -354,13 +347,13 @@ namespace ArchiSteamFarm.Json {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class InventoryResponse : EResultResponse {
[JsonProperty(PropertyName = "assets", Required = Required.DisallowNull)]
internal readonly ImmutableHashSet<Asset> Assets;
internal readonly ImmutableHashSet<Asset>? Assets;
[JsonProperty(PropertyName = "descriptions", Required = Required.DisallowNull)]
internal readonly ImmutableHashSet<Description> Descriptions;
internal readonly ImmutableHashSet<Description>? Descriptions;
[JsonProperty(PropertyName = "error", Required = Required.DisallowNull)]
internal readonly string Error;
internal readonly string? Error;
[JsonProperty(PropertyName = "total_inventory_count", Required = Required.DisallowNull)]
internal readonly uint TotalInventoryCount;
@ -439,7 +432,7 @@ namespace ArchiSteamFarm.Json {
foreach (Asset.Tag tag in Tags) {
switch (tag.Identifier) {
case "Game":
if ((tag.Value.Length <= 4) || !tag.Value.StartsWith("app_", StringComparison.Ordinal)) {
if (string.IsNullOrEmpty(tag.Value) || (tag.Value!.Length <= 4) || !tag.Value.StartsWith("app_", StringComparison.Ordinal)) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(tag.Value), tag.Value));
break;
@ -528,7 +521,7 @@ namespace ArchiSteamFarm.Json {
}
[JsonExtensionData]
internal Dictionary<string, JToken> AdditionalProperties {
internal Dictionary<string, JToken>? AdditionalProperties {
get;
[UsedImplicitly]
set;
@ -542,7 +535,7 @@ namespace ArchiSteamFarm.Json {
internal bool Marketable { get; set; }
[JsonProperty(PropertyName = "tags", Required = Required.DisallowNull)]
internal ImmutableHashSet<Asset.Tag> Tags { get; set; }
internal ImmutableHashSet<Asset.Tag>? Tags { get; set; }
internal bool Tradable { get; set; }
@ -608,7 +601,7 @@ namespace ArchiSteamFarm.Json {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class NewDiscoveryQueueResponse {
[JsonProperty(PropertyName = "queue", Required = Required.Always)]
internal readonly ImmutableHashSet<uint> Queue;
internal readonly ImmutableHashSet<uint>? Queue;
[JsonConstructor]
private NewDiscoveryQueueResponse() { }
@ -617,7 +610,7 @@ namespace ArchiSteamFarm.Json {
[SuppressMessage("ReSharper", "ClassCannotBeInstantiated")]
internal sealed class RedeemWalletResponse : EResultResponse {
[JsonProperty(PropertyName = "wallet", Required = Required.DisallowNull)]
internal readonly InternalKeyDetails KeyDetails;
internal readonly InternalKeyDetails? KeyDetails;
[JsonProperty(PropertyName = "detail", Required = Required.DisallowNull)]
internal readonly EPurchaseResultDetail? PurchaseResultDetail;
@ -697,10 +690,10 @@ namespace ArchiSteamFarm.Json {
internal readonly ECommentPermission CommentPermission;
[JsonProperty(PropertyName = "PrivacySettings", Required = Required.Always)]
internal readonly PrivacySettings Settings;
internal readonly PrivacySettings? Settings;
// Constructed from privacy change request
internal UserPrivacy([JetBrains.Annotations.NotNull] PrivacySettings settings, ECommentPermission commentPermission) {
internal UserPrivacy(PrivacySettings settings, ECommentPermission commentPermission) {
Settings = settings ?? throw new ArgumentNullException(nameof(settings));
CommentPermission = commentPermission;
}

View file

@ -53,36 +53,41 @@ namespace ArchiSteamFarm {
#pragma warning disable 649
[JsonProperty(PropertyName = "identity_secret", Required = Required.Always)]
private readonly string IdentitySecret;
private readonly string? IdentitySecret;
#pragma warning restore 649
#pragma warning disable 649
[JsonProperty(PropertyName = "shared_secret", Required = Required.Always)]
private readonly string SharedSecret;
private readonly string? SharedSecret;
#pragma warning restore 649
private Bot Bot;
private Bot? Bot;
[JsonConstructor]
private MobileAuthenticator() => CachedDeviceID = new ArchiCacheable<string>(ResolveDeviceID);
public void Dispose() => CachedDeviceID.Dispose();
internal async Task<string> GenerateToken() {
internal async Task<string?> GenerateToken() {
if (Bot == null) {
throw new ArgumentNullException(nameof(Bot));
}
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
throw new ArgumentNullException(nameof(time));
}
return GenerateTokenForTime(time);
}
[ItemCanBeNull]
internal async Task<HashSet<Confirmation>> GetConfirmations() {
(bool success, string deviceID) = await CachedDeviceID.GetValue().ConfigureAwait(false);
internal async Task<HashSet<Confirmation>?> GetConfirmations() {
if (Bot == null) {
throw new ArgumentNullException(nameof(Bot));
}
(bool success, string? deviceID) = await CachedDeviceID.GetValue().ConfigureAwait(false);
if (!success || string.IsNullOrEmpty(deviceID)) {
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, nameof(deviceID)));
@ -93,12 +98,10 @@ namespace ArchiSteamFarm {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
throw new ArgumentNullException(nameof(time));
}
string confirmationHash = GenerateConfirmationHash(time, "conf");
string? confirmationHash = GenerateConfirmationHash(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Bot.ArchiLogger.LogNullError(nameof(confirmationHash));
@ -108,7 +111,7 @@ namespace ArchiSteamFarm {
await LimitConfirmationsRequestsAsync().ConfigureAwait(false);
using IDocument htmlDocument = await Bot.ArchiWebHandler.GetConfirmations(deviceID, confirmationHash, time).ConfigureAwait(false);
using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetConfirmations(deviceID!, confirmationHash!, time).ConfigureAwait(false);
if (htmlDocument == null) {
return null;
@ -123,7 +126,7 @@ namespace ArchiSteamFarm {
}
foreach (IElement confirmationNode in confirmationNodes) {
string idText = confirmationNode.GetAttributeValue("data-confid");
string? idText = confirmationNode.GetAttribute("data-confid");
if (string.IsNullOrEmpty(idText)) {
Bot.ArchiLogger.LogNullError(nameof(idText));
@ -137,7 +140,7 @@ namespace ArchiSteamFarm {
return null;
}
string keyText = confirmationNode.GetAttributeValue("data-key");
string? keyText = confirmationNode.GetAttribute("data-key");
if (string.IsNullOrEmpty(keyText)) {
Bot.ArchiLogger.LogNullError(nameof(keyText));
@ -151,7 +154,7 @@ namespace ArchiSteamFarm {
return null;
}
string creatorText = confirmationNode.GetAttributeValue("data-creator");
string? creatorText = confirmationNode.GetAttribute("data-creator");
if (string.IsNullOrEmpty(creatorText)) {
Bot.ArchiLogger.LogNullError(nameof(creatorText));
@ -165,7 +168,7 @@ namespace ArchiSteamFarm {
return null;
}
string typeText = confirmationNode.GetAttributeValue("data-type");
string? typeText = confirmationNode.GetAttribute("data-type");
if (string.IsNullOrEmpty(typeText)) {
Bot.ArchiLogger.LogNullError(nameof(typeText));
@ -192,13 +195,11 @@ namespace ArchiSteamFarm {
}
internal async Task<bool> HandleConfirmations(IReadOnlyCollection<Confirmation> confirmations, bool accept) {
if ((confirmations == null) || (confirmations.Count == 0)) {
Bot.ArchiLogger.LogNullError(nameof(confirmations));
return false;
if ((confirmations == null) || (confirmations.Count == 0) || (Bot == null)) {
throw new ArgumentNullException(nameof(confirmations) + " || " + nameof(Bot));
}
(bool success, string deviceID) = await CachedDeviceID.GetValue().ConfigureAwait(false);
(bool success, string? deviceID) = await CachedDeviceID.GetValue().ConfigureAwait(false);
if (!success || string.IsNullOrEmpty(deviceID)) {
Bot.ArchiLogger.LogGenericError(string.Format(Strings.WarningFailedWithError, nameof(deviceID)));
@ -209,12 +210,10 @@ namespace ArchiSteamFarm {
uint time = await GetSteamTime().ConfigureAwait(false);
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return false;
throw new ArgumentNullException(nameof(time));
}
string confirmationHash = GenerateConfirmationHash(time, "conf");
string? confirmationHash = GenerateConfirmationHash(time, "conf");
if (string.IsNullOrEmpty(confirmationHash)) {
Bot.ArchiLogger.LogNullError(nameof(confirmationHash));
@ -222,7 +221,7 @@ namespace ArchiSteamFarm {
return false;
}
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(deviceID, confirmationHash, time, confirmations, accept).ConfigureAwait(false);
bool? result = await Bot.ArchiWebHandler.HandleConfirmations(deviceID!, confirmationHash!, time, confirmations, accept).ConfigureAwait(false);
if (!result.HasValue) {
// Request timed out
@ -238,7 +237,7 @@ namespace ArchiSteamFarm {
// In this case, we'll accept all pending confirmations one-by-one, synchronously (as Steam can't handle them in parallel)
// We totally ignore actual result returned by those calls, abort only if request timed out
foreach (Confirmation confirmation in confirmations) {
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(deviceID, confirmationHash, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
bool? confirmationResult = await Bot.ArchiWebHandler.HandleConfirmation(deviceID!, confirmationHash!, time, confirmation.ID, confirmation.Key, accept).ConfigureAwait(false);
if (!confirmationResult.HasValue) {
return false;
@ -248,13 +247,11 @@ namespace ArchiSteamFarm {
return true;
}
internal void Init([JetBrains.Annotations.NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
internal void Init(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
private string GenerateConfirmationHash(uint time, string tag = null) {
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
private string? GenerateConfirmationHash(uint time, string? tag = null) {
if ((time == 0) || (Bot == null) || string.IsNullOrEmpty(IdentitySecret)) {
throw new ArgumentNullException(nameof(time) + " || " + nameof(Bot) + " || " + nameof(IdentitySecret));
}
byte[] identitySecret;
@ -271,7 +268,7 @@ namespace ArchiSteamFarm {
byte bufferSize = 8;
if (!string.IsNullOrEmpty(tag)) {
bufferSize += (byte) Math.Min(32, tag.Length);
bufferSize += (byte) Math.Min(32, tag!.Length);
}
byte[] timeArray = BitConverter.GetBytes((ulong) time);
@ -295,11 +292,9 @@ namespace ArchiSteamFarm {
return Convert.ToBase64String(hash);
}
private string GenerateTokenForTime(uint time) {
if (time == 0) {
Bot.ArchiLogger.LogNullError(nameof(time));
return null;
private string? GenerateTokenForTime(uint time) {
if ((time == 0) || (Bot == null) || string.IsNullOrEmpty(SharedSecret)) {
throw new ArgumentNullException(nameof(time) + " || " + nameof(Bot) + " || " + nameof(SharedSecret));
}
byte[] sharedSecret;
@ -351,6 +346,10 @@ namespace ArchiSteamFarm {
}
private async Task<uint> GetSteamTime() {
if (Bot == null) {
throw new ArgumentNullException(nameof(Bot));
}
if (SteamTimeDifference.HasValue && (DateTime.UtcNow.Subtract(LastSteamTimeCheck).TotalHours < SteamTimeTTL)) {
return (uint) (Utilities.GetUnixTime() + SteamTimeDifference.Value);
}
@ -379,12 +378,12 @@ namespace ArchiSteamFarm {
private static async Task LimitConfirmationsRequestsAsync() {
if (ASF.ConfirmationsSemaphore == null) {
ASF.ArchiLogger.LogNullError(nameof(ASF.ConfirmationsSemaphore));
return;
throw new ArgumentNullException(nameof(ASF.ConfirmationsSemaphore));
}
if (ASF.GlobalConfig.ConfirmationsLimiterDelay == 0) {
byte confirmationsLimiterDelay = ASF.GlobalConfig?.ConfirmationsLimiterDelay ?? GlobalConfig.DefaultConfirmationsLimiterDelay;
if (confirmationsLimiterDelay == 0) {
return;
}
@ -392,14 +391,18 @@ namespace ArchiSteamFarm {
Utilities.InBackground(
async () => {
await Task.Delay(ASF.GlobalConfig.ConfirmationsLimiterDelay * 1000).ConfigureAwait(false);
await Task.Delay(confirmationsLimiterDelay * 1000).ConfigureAwait(false);
ASF.ConfirmationsSemaphore.Release();
}
);
}
private async Task<(bool Success, string Result)> ResolveDeviceID() {
string deviceID = await Bot.ArchiHandler.GetTwoFactorDeviceIdentifier(Bot.SteamID).ConfigureAwait(false);
private async Task<(bool Success, string? Result)> ResolveDeviceID() {
if (Bot == null) {
throw new ArgumentNullException(nameof(Bot));
}
string? deviceID = await Bot.ArchiHandler.GetTwoFactorDeviceIdentifier(Bot.SteamID).ConfigureAwait(false);
if (string.IsNullOrEmpty(deviceID)) {
Bot.ArchiLogger.LogGenericWarning(Strings.WarningFailed);

View file

@ -31,7 +31,7 @@ namespace ArchiSteamFarm.NLog {
public sealed class ArchiLogger {
private readonly Logger Logger;
public ArchiLogger([NotNull] string name) {
public ArchiLogger(string name) {
if (string.IsNullOrEmpty(name)) {
throw new ArgumentNullException(nameof(name));
}
@ -40,22 +40,18 @@ namespace ArchiSteamFarm.NLog {
}
[PublicAPI]
public void LogGenericDebug(string message, [CallerMemberName] string previousMethodName = null) {
public void LogGenericDebug(string message, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
Logger.Debug($"{previousMethodName}() {message}");
}
[PublicAPI]
public void LogGenericDebuggingException(Exception exception, [CallerMemberName] string previousMethodName = null) {
public void LogGenericDebuggingException(Exception exception, [CallerMemberName] string? previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
throw new ArgumentNullException(nameof(exception));
}
if (!Debugging.IsUserDebugging) {
@ -66,85 +62,71 @@ namespace ArchiSteamFarm.NLog {
}
[PublicAPI]
public void LogGenericError(string message, [CallerMemberName] string previousMethodName = null) {
public void LogGenericError(string message, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
Logger.Error($"{previousMethodName}() {message}");
}
[PublicAPI]
public void LogGenericException(Exception exception, [CallerMemberName] string previousMethodName = null) {
public void LogGenericException(Exception exception, [CallerMemberName] string? previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
throw new ArgumentNullException(nameof(exception));
}
Logger.Error(exception, $"{previousMethodName}()");
}
[PublicAPI]
public void LogGenericInfo(string message, [CallerMemberName] string previousMethodName = null) {
public void LogGenericInfo(string message, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
Logger.Info($"{previousMethodName}() {message}");
}
[PublicAPI]
public void LogGenericTrace(string message, [CallerMemberName] string previousMethodName = null) {
public void LogGenericTrace(string message, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
Logger.Trace($"{previousMethodName}() {message}");
}
[PublicAPI]
public void LogGenericWarning(string message, [CallerMemberName] string previousMethodName = null) {
public void LogGenericWarning(string message, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(message)) {
LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
Logger.Warn($"{previousMethodName}() {message}");
}
[PublicAPI]
public void LogGenericWarningException(Exception exception, [CallerMemberName] string previousMethodName = null) {
public void LogGenericWarningException(Exception exception, [CallerMemberName] string? previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
throw new ArgumentNullException(nameof(exception));
}
Logger.Warn(exception, $"{previousMethodName}()");
}
[PublicAPI]
public void LogNullError(string nullObjectName, [CallerMemberName] string previousMethodName = null) {
public void LogNullError(string nullObjectName, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(nullObjectName)) {
return;
throw new ArgumentNullException(nameof(nullObjectName));
}
LogGenericError(string.Format(Strings.ErrorObjectIsNull, nullObjectName), previousMethodName);
}
internal void LogChatMessage(bool echo, string message, ulong chatGroupID = 0, ulong chatID = 0, ulong steamID = 0, [CallerMemberName] string previousMethodName = null) {
internal void LogChatMessage(bool echo, string message, ulong chatGroupID = 0, ulong chatID = 0, ulong steamID = 0, [CallerMemberName] string? previousMethodName = null) {
if (string.IsNullOrEmpty(message) || (((chatGroupID == 0) || (chatID == 0)) && (steamID == 0))) {
LogNullError(nameof(message) + " || " + "((" + nameof(chatGroupID) + " || " + nameof(chatID) + ") && " + nameof(steamID) + ")");
return;
throw new ArgumentNullException(nameof(message) + " || " + "((" + nameof(chatGroupID) + " || " + nameof(chatID) + ") && " + nameof(steamID) + ")");
}
StringBuilder loggedMessage = new StringBuilder(previousMethodName + "() " + message + " " + (echo ? "->" : "<-") + " ");
@ -169,11 +151,9 @@ namespace ArchiSteamFarm.NLog {
Logger.Log(logEventInfo);
}
internal async Task LogFatalException(Exception exception, [CallerMemberName] string previousMethodName = null) {
internal async Task LogFatalException(Exception exception, [CallerMemberName] string? previousMethodName = null) {
if (exception == null) {
LogNullError(nameof(exception));
return;
throw new ArgumentNullException(nameof(exception));
}
Logger.Fatal(exception, $"{previousMethodName}()");

View file

@ -75,12 +75,12 @@ namespace ArchiSteamFarm.NLog {
NewHistoryEntry?.Invoke(this, new NewHistoryEntryArgs(message));
}
internal event EventHandler<NewHistoryEntryArgs> NewHistoryEntry;
internal event EventHandler<NewHistoryEntryArgs>? NewHistoryEntry;
internal sealed class NewHistoryEntryArgs : EventArgs {
internal readonly string Message;
internal NewHistoryEntryArgs([NotNull] string message) => Message = message ?? throw new ArgumentNullException(nameof(message));
internal NewHistoryEntryArgs(string message) => Message = message ?? throw new ArgumentNullException(nameof(message));
}
}
}

View file

@ -28,7 +28,6 @@ using System.Threading.Tasks;
using ArchiSteamFarm.Collections;
using ArchiSteamFarm.IPC;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
using NLog;
using NLog.Config;
using NLog.Targets;
@ -64,14 +63,12 @@ namespace ArchiSteamFarm.NLog {
}
}
internal static async Task<string> GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF) {
internal static async Task<string?> GetUserInput(ASF.EUserInputType userInputType, string botName = SharedInfo.ASF) {
if ((userInputType == ASF.EUserInputType.None) || !Enum.IsDefined(typeof(ASF.EUserInputType), userInputType) || string.IsNullOrEmpty(botName)) {
ASF.ArchiLogger.LogNullError(nameof(userInputType) + " || " + nameof(botName));
return null;
throw new ArgumentNullException(nameof(userInputType) + " || " + nameof(botName));
}
if (ASF.GlobalConfig.Headless) {
if (ASF.GlobalConfig?.Headless ?? GlobalConfig.DefaultHeadless) {
ASF.ArchiLogger.LogGenericWarning(Strings.ErrorUserInputRunningInHeadlessMode);
return null;
@ -196,7 +193,7 @@ namespace ArchiSteamFarm.NLog {
return;
}
HistoryTarget historyTarget = LogManager.Configuration.AllTargets.OfType<HistoryTarget>().FirstOrDefault();
HistoryTarget? historyTarget = LogManager.Configuration.AllTargets.OfType<HistoryTarget>().FirstOrDefault();
if ((historyTarget == null) && !IsUsingCustomConfiguration) {
historyTarget = new HistoryTarget("History") {
@ -214,7 +211,7 @@ namespace ArchiSteamFarm.NLog {
}
internal static void StartInteractiveConsole() {
if (ASF.GlobalConfig.SteamOwnerID == 0) {
if ((ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID) == 0) {
ASF.ArchiLogger.LogGenericWarning(string.Format(Strings.InteractiveConsoleNotAvailable, nameof(ASF.GlobalConfig.SteamOwnerID)));
return;
@ -230,7 +227,6 @@ namespace ArchiSteamFarm.NLog {
return Console.ReadLine();
}
[NotNull]
private static string ConsoleReadLineMasked(char mask = '*') {
StringBuilder result = new StringBuilder();
@ -287,15 +283,17 @@ namespace ArchiSteamFarm.NLog {
continue;
}
if (!string.IsNullOrEmpty(ASF.GlobalConfig.CommandPrefix) && command.StartsWith(ASF.GlobalConfig.CommandPrefix, StringComparison.Ordinal)) {
command = command.Substring(ASF.GlobalConfig.CommandPrefix.Length);
string commandPrefix = ASF.GlobalConfig?.CommandPrefix ?? GlobalConfig.DefaultCommandPrefix;
if (!string.IsNullOrEmpty(commandPrefix) && command.StartsWith(commandPrefix, StringComparison.Ordinal)) {
command = command.Substring(commandPrefix.Length);
if (string.IsNullOrEmpty(command)) {
continue;
}
}
Bot targetBot = Bot.Bots.OrderBy(bot => bot.Key, Bot.BotsComparer).Select(bot => bot.Value).FirstOrDefault();
Bot? targetBot = Bot.Bots.OrderBy(bot => bot.Key, Bot.BotsComparer).Select(bot => bot.Value).FirstOrDefault();
if (targetBot == null) {
Console.WriteLine(@"<< " + Strings.ErrorNoBotsDefined);
@ -305,7 +303,9 @@ namespace ArchiSteamFarm.NLog {
Console.WriteLine(@"<> " + Strings.Executing);
string response = await targetBot.Commands.Response(ASF.GlobalConfig.SteamOwnerID, command).ConfigureAwait(false);
ulong steamOwnerID = ASF.GlobalConfig?.SteamOwnerID ?? GlobalConfig.DefaultSteamOwnerID;
string? response = await targetBot.Commands.Response(steamOwnerID, command).ConfigureAwait(false);
if (string.IsNullOrEmpty(response)) {
ASF.ArchiLogger.LogNullError(nameof(response));
@ -339,9 +339,9 @@ namespace ArchiSteamFarm.NLog {
}
}
private static void OnConfigurationChanged(object sender, LoggingConfigurationChangedEventArgs e) {
if ((sender == null) || (e == null)) {
ASF.ArchiLogger.LogNullError(nameof(sender) + " || " + nameof(e));
private static void OnConfigurationChanged(object? sender, LoggingConfigurationChangedEventArgs e) {
if (e == null) {
ASF.ArchiLogger.LogNullError(nameof(e));
return;
}
@ -352,7 +352,7 @@ namespace ArchiSteamFarm.NLog {
OnUserInputStart();
}
HistoryTarget historyTarget = LogManager.Configuration.AllTargets.OfType<HistoryTarget>().FirstOrDefault();
HistoryTarget? historyTarget = LogManager.Configuration.AllTargets.OfType<HistoryTarget>().FirstOrDefault();
ArchiKestrel.OnNewHistoryTarget(historyTarget);
}

View file

@ -19,6 +19,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Threading.Tasks;
@ -36,7 +37,7 @@ namespace ArchiSteamFarm.NLog {
// This is NLog config property, it must have public get() and set() capabilities
[PublicAPI]
public string BotName { get; set; }
public string? BotName { get; set; }
// This is NLog config property, it must have public get() and set() capabilities
[PublicAPI]
@ -71,10 +72,10 @@ namespace ArchiSteamFarm.NLog {
return;
}
Bot bot = null;
Bot? bot = null;
if (!string.IsNullOrEmpty(BotName)) {
bot = Bot.GetBot(BotName);
bot = Bot.GetBot(BotName!);
if (bot?.IsConnectedAndLoggedOn != true) {
return;
@ -88,15 +89,13 @@ namespace ArchiSteamFarm.NLog {
}
}
private async Task SendGroupMessage(string message, Bot bot = null) {
private async Task SendGroupMessage(string message, Bot? bot = null) {
if (string.IsNullOrEmpty(message)) {
ASF.ArchiLogger.LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
if (bot == null) {
bot = Bot.Bots.Values.FirstOrDefault(targetBot => targetBot.IsConnectedAndLoggedOn);
bot = Bot.Bots?.Values.FirstOrDefault(targetBot => targetBot.IsConnectedAndLoggedOn);
if (bot == null) {
return;
@ -108,15 +107,13 @@ namespace ArchiSteamFarm.NLog {
}
}
private async Task SendPrivateMessage(string message, Bot bot = null) {
private async Task SendPrivateMessage(string message, Bot? bot = null) {
if (string.IsNullOrEmpty(message)) {
ASF.ArchiLogger.LogNullError(nameof(message));
return;
throw new ArgumentNullException(nameof(message));
}
if (bot == null) {
bot = Bot.Bots.Values.FirstOrDefault(targetBot => targetBot.IsConnectedAndLoggedOn && (targetBot.SteamID != SteamID));
bot = Bot.Bots?.Values.FirstOrDefault(targetBot => targetBot.IsConnectedAndLoggedOn && (targetBot.SteamID != SteamID));
if (bot == null) {
return;

View file

@ -29,7 +29,6 @@ using System.Text.RegularExpressions;
using System.Threading;
using ArchiSteamFarm.Helpers;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
namespace ArchiSteamFarm {
internal static class OS {
@ -37,13 +36,11 @@ namespace ArchiSteamFarm {
internal static readonly string ProcessFileName = Process.GetCurrentProcess().MainModule?.FileName ?? throw new ArgumentNullException(nameof(ProcessFileName));
internal static bool IsUnix => RuntimeInformation.IsOSPlatform(OSPlatform.Linux) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX);
[NotNull]
internal static string Variant => RuntimeInformation.OSDescription.Trim();
private static bool IsWindows => RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
private static Mutex SingleInstance;
private static Mutex? SingleInstance;
internal static void CoreInit() {
if (IsWindows && !Console.IsOutputRedirected) {
@ -64,9 +61,7 @@ namespace ArchiSteamFarm {
internal static ICrossProcessSemaphore CreateCrossProcessSemaphore(string objectName) {
if (string.IsNullOrEmpty(objectName)) {
ASF.ArchiLogger.LogNullError(nameof(objectName));
return null;
throw new ArgumentNullException(nameof(objectName));
}
string resourceName = GetOsResourceName(objectName);
@ -76,9 +71,7 @@ namespace ArchiSteamFarm {
internal static void Init(bool systemRequired, GlobalConfig.EOptimizationMode optimizationMode) {
if (!Enum.IsDefined(typeof(GlobalConfig.EOptimizationMode), optimizationMode)) {
ASF.ArchiLogger.LogNullError(nameof(optimizationMode));
return;
throw new ArgumentNullException(nameof(optimizationMode));
}
if (IsWindows) {
@ -97,9 +90,7 @@ namespace ArchiSteamFarm {
break;
default:
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(optimizationMode), optimizationMode));
return;
throw new ArgumentOutOfRangeException(nameof(optimizationMode));
}
}
@ -132,9 +123,7 @@ namespace ArchiSteamFarm {
internal static void UnixSetFileAccess(string path, EUnixPermission permission) {
if (string.IsNullOrEmpty(path)) {
ASF.ArchiLogger.LogNullError(nameof(path));
return;
throw new ArgumentNullException(nameof(path));
}
if (!IsUnix) {
@ -166,9 +155,7 @@ namespace ArchiSteamFarm {
private static string GetOsResourceName(string objectName) {
if (string.IsNullOrEmpty(objectName)) {
ASF.ArchiLogger.LogNullError(nameof(objectName));
return null;
throw new ArgumentNullException(nameof(objectName));
}
return SharedInfo.AssemblyName + "-" + objectName;

View file

@ -31,6 +31,6 @@ namespace ArchiSteamFarm.Plugins {
/// ASF will call this method right after global config initialization.
/// </summary>
/// <param name="additionalConfigProperties">Extra config properties made out of <see cref="JsonExtensionDataAttribute" />. Can be null if no extra properties are found.</param>
void OnASFInit([CanBeNull] IReadOnlyDictionary<string, JToken> additionalConfigProperties = null);
void OnASFInit(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null);
}
}

View file

@ -30,13 +30,13 @@ namespace ArchiSteamFarm.Plugins {
/// Doing so will allow the garbage collector to dispose the bot afterwards, refraining from doing so will create a "memory leak" by keeping the reference alive.
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
void OnBotDestroy([NotNull] Bot bot);
void OnBotDestroy(Bot bot);
/// <summary>
/// ASF will call this method after creating the bot object, e.g. after config creation.
/// Bot config is not yet available at this stage. This function will execute only once for every bot object.
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
void OnBotInit([NotNull] Bot bot);
void OnBotInit(Bot bot);
}
}

View file

@ -29,18 +29,18 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="farmedSomething">Bool value indicating whether the module has finished successfully, so when there was at least one card to drop, and nothing has interrupted us in the meantime.</param>
void OnBotFarmingFinished([NotNull] Bot bot, bool farmedSomething);
void OnBotFarmingFinished(Bot bot, bool farmedSomething);
/// <summary>
/// ASF will call this method when cards farming module is started on given bot instance. The module is started only when there are valid cards to drop, so this method won't be called when there is nothing to idle.
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
void OnBotFarmingStarted([NotNull] Bot bot);
void OnBotFarmingStarted(Bot bot);
/// <summary>
/// ASF will call this method when cards farming module is stopped on given bot instance. The stop could be a result of a natural finish, or other situations (e.g. Steam networking issues, user commands).
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
void OnBotFarmingStopped([NotNull] Bot bot);
void OnBotFarmingStopped(Bot bot);
}
}

View file

@ -33,8 +33,6 @@ namespace ArchiSteamFarm.Plugins {
/// <param name="message">Command message in its raw format, stripped of <see cref="GlobalConfig.CommandPrefix" />.</param>
/// <param name="args">Pre-parsed message using standard ASF delimiters.</param>
/// <returns>Response to the command, or null/empty (as the task value) if the command isn't handled by this plugin.</returns>
[ItemCanBeNull]
[NotNull]
Task<string> OnBotCommand([NotNull] Bot bot, ulong steamID, [NotNull] string message, [ItemNotNull] [NotNull] string[] args);
Task<string?> OnBotCommand(Bot bot, ulong steamID, string message, string[] args);
}
}

View file

@ -30,12 +30,12 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="reason">Reason for disconnection, or <see cref="EResult.OK" /> if the disconnection was initiated by ASF (e.g. as a result of a command).</param>
void OnBotDisconnected([NotNull] Bot bot, EResult reason);
void OnBotDisconnected(Bot bot, EResult reason);
/// <summary>
/// ASF will call this method when bot successfully connects to Steam network.
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
void OnBotLoggedOn([NotNull] Bot bot);
void OnBotLoggedOn(Bot bot);
}
}

View file

@ -31,7 +31,6 @@ namespace ArchiSteamFarm.Plugins {
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="steamID">64-bit Steam identificator of the user that sent a friend request, or a group that the bot has been invited to.</param>
/// <returns>True if the request should be accepted as part of this plugin, false otherwise.</returns>
[NotNull]
Task<bool> OnBotFriendRequest([NotNull] Bot bot, ulong steamID);
Task<bool> OnBotFriendRequest(Bot bot, ulong steamID);
}
}

View file

@ -32,8 +32,6 @@ namespace ArchiSteamFarm.Plugins {
/// <param name="steamID">64-bit long unsigned integer of steamID executing the command.</param>
/// <param name="message">Message in its raw format.</param>
/// <returns>Response to the message, or null/empty (as the task value) for silence.</returns>
[ItemCanBeNull]
[NotNull]
Task<string> OnBotMessage([NotNull] Bot bot, ulong steamID, [NotNull] string message);
Task<string?> OnBotMessage(Bot bot, ulong steamID, string message);
}
}

View file

@ -32,6 +32,6 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="additionalConfigProperties">Extra config properties made out of <see cref="JsonExtensionDataAttribute" />. Can be null if no extra properties are found.</param>
void OnBotInitModules([NotNull] Bot bot, [CanBeNull] IReadOnlyDictionary<string, JToken> additionalConfigProperties = null);
void OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null);
}
}

View file

@ -31,14 +31,13 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="callbackManager">Callback manager object which can be used for establishing subscriptions to standard and custom callbacks.</param>
void OnBotSteamCallbacksInit([NotNull] Bot bot, [NotNull] CallbackManager callbackManager);
void OnBotSteamCallbacksInit(Bot bot, CallbackManager callbackManager);
/// <summary>
/// ASF will call this method right after bot initialization in order to allow you hooking custom SK2 client handlers into the SteamClient.
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <returns>Collection of custom client handlers that are supposed to be hooked into the SteamClient by ASF. If you do not require any, just return null.</returns>
[CanBeNull]
IReadOnlyCollection<ClientMsgHandler> OnBotSteamHandlersInit([NotNull] Bot bot);
/// <returns>Collection of custom client handlers that are supposed to be hooked into the SteamClient by ASF. If you do not require any, just return null or empty collection.</returns>
IReadOnlyCollection<ClientMsgHandler>? OnBotSteamHandlersInit(Bot bot);
}
}

View file

@ -32,7 +32,6 @@ namespace ArchiSteamFarm.Plugins {
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="tradeOffer">Trade offer related to this callback.</param>
/// <returns>True if the trade offer should be accepted as part of this plugin, false otherwise.</returns>
[NotNull]
Task<bool> OnBotTradeOffer([NotNull] Bot bot, [NotNull] Steam.TradeOffer tradeOffer);
Task<bool> OnBotTradeOffer(Bot bot, Steam.TradeOffer tradeOffer);
}
}

View file

@ -30,6 +30,6 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="tradeResults">Trade results related to this callback.</param>
void OnBotTradeOfferResults([NotNull] Bot bot, [NotNull] IReadOnlyCollection<Trading.ParseTradeResult> tradeResults);
void OnBotTradeOfferResults(Bot bot, IReadOnlyCollection<Trading.ParseTradeResult> tradeResults);
}
}

View file

@ -30,6 +30,6 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="newNotifications">Collection containing those notification types that are new (that is, when new count > previous count of that notification type).</param>
void OnBotUserNotifications([NotNull] Bot bot, [NotNull] IReadOnlyCollection<ArchiHandler.UserNotificationsCallback.EUserNotification> newNotifications);
void OnBotUserNotifications(Bot bot, IReadOnlyCollection<ArchiHandler.UserNotificationsCallback.EUserNotification> newNotifications);
}
}

View file

@ -30,7 +30,6 @@ namespace ArchiSteamFarm.Plugins {
/// Unless you know what you're doing, you should not implement this property yourself and let ASF decide.
/// </summary>
/// <returns>Comparer that will be used for the bots, as well as bot regexes.</returns>
[NotNull]
StringComparer BotsComparer { get; }
}
}

View file

@ -31,7 +31,6 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <returns>String that will be used as the name of this plugin.</returns>
[JsonProperty]
[NotNull]
string Name { get; }
/// <summary>
@ -40,7 +39,6 @@ namespace ArchiSteamFarm.Plugins {
/// </summary>
/// <returns>Version that will be shown to the user when plugin is loaded.</returns>
[JsonProperty]
[NotNull]
Version Version { get; }
/// <summary>

View file

@ -31,7 +31,6 @@ namespace ArchiSteamFarm.Plugins {
/// ASF uses this method for determining the point in time from which it should keep history going upon a restart. The actual point in time that will be used is calculated as the lowest change number from all loaded plugins, to guarantee that no plugin will miss any changes, while allowing possible duplicates for those plugins that were already synchronized with newer changes. If you don't care about persistent state and just want to receive the ongoing history, you should return 0 (which is equal to "I'm fine with any"). If there won't be any plugin asking for a specific point in time, ASF will start returning entries since the start of the program.
/// </summary>
/// <returns>The most recent change number from which you're fine to receive <see cref="OnPICSChanges" /></returns>
[NotNull]
Task<uint> GetPreferredChangeNumberToStartFrom();
/// <summary>
@ -40,7 +39,7 @@ namespace ArchiSteamFarm.Plugins {
/// <param name="currentChangeNumber">The change number of current callback.</param>
/// <param name="appChanges">App changes that happened since the previous call of this method. Can be empty.</param>
/// <param name="packageChanges">Package changes that happened since the previous call of this method. Can be empty.</param>
void OnPICSChanges(uint currentChangeNumber, [NotNull] IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> appChanges, [NotNull] IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> packageChanges);
void OnPICSChanges(uint currentChangeNumber, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> appChanges, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> packageChanges);
/// <summary>
/// ASF will call this method when it'll be necessary to restart the history of PICS changes. This can happen due to Steam limitation in which we're unable to keep history going if we're too far behind (approx 5k changeNumbers). If you're relying on continuous history of app/package PICS changes sent by <see cref="OnPICSChanges" />, ASF can no longer guarantee that upon calling this method, therefore you should start clean.

View file

@ -31,7 +31,6 @@ using System.Reflection;
using System.Threading.Tasks;
using ArchiSteamFarm.Json;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
using Newtonsoft.Json.Linq;
using SteamKit2;
@ -40,11 +39,10 @@ namespace ArchiSteamFarm.Plugins {
internal static bool HasCustomPluginsLoaded => HasActivePluginsLoaded && ActivePlugins.Any(plugin => !(plugin is OfficialPlugin officialPlugin) || !officialPlugin.HasSameVersion());
[ImportMany]
internal static ImmutableHashSet<IPlugin> ActivePlugins { get; private set; }
internal static ImmutableHashSet<IPlugin>? ActivePlugins { get; private set; }
private static bool HasActivePluginsLoaded => ActivePlugins?.Count > 0;
[ItemNotNull]
internal static async Task<StringComparer> GetBotsComparer() {
if (!HasActivePluginsLoaded) {
return StringComparer.Ordinal;
@ -60,7 +58,7 @@ namespace ArchiSteamFarm.Plugins {
return StringComparer.Ordinal;
}
StringComparer result = results.FirstOrDefault(comparer => comparer != null);
StringComparer? result = results.FirstOrDefault(comparer => comparer != null);
return result ?? StringComparer.Ordinal;
}
@ -94,7 +92,7 @@ namespace ArchiSteamFarm.Plugins {
return false;
}
HashSet<Assembly> assemblies = LoadAssemblies();
HashSet<Assembly>? assemblies = LoadAssemblies();
if ((assemblies == null) || (assemblies.Count == 0)) {
ASF.ArchiLogger.LogGenericTrace(Strings.NothingFound);
@ -160,13 +158,13 @@ namespace ArchiSteamFarm.Plugins {
return invalidPlugins.Count == 0;
}
internal static HashSet<Assembly> LoadAssemblies() {
HashSet<Assembly> assemblies = null;
internal static HashSet<Assembly>? LoadAssemblies() {
HashSet<Assembly>? assemblies = null;
string pluginsPath = Path.Combine(SharedInfo.HomeDirectory, SharedInfo.PluginsDirectory);
if (Directory.Exists(pluginsPath)) {
HashSet<Assembly> loadedAssemblies = LoadAssembliesFrom(pluginsPath);
HashSet<Assembly>? loadedAssemblies = LoadAssembliesFrom(pluginsPath);
if ((loadedAssemblies != null) && (loadedAssemblies.Count > 0)) {
assemblies = loadedAssemblies;
@ -176,7 +174,7 @@ namespace ArchiSteamFarm.Plugins {
string customPluginsPath = Path.Combine(Directory.GetCurrentDirectory(), SharedInfo.PluginsDirectory);
if (Directory.Exists(customPluginsPath)) {
HashSet<Assembly> loadedAssemblies = LoadAssembliesFrom(customPluginsPath);
HashSet<Assembly>? loadedAssemblies = LoadAssembliesFrom(customPluginsPath);
if ((loadedAssemblies != null) && (loadedAssemblies.Count > 0)) {
if ((assemblies != null) && (assemblies.Count > 0)) {
@ -190,7 +188,7 @@ namespace ArchiSteamFarm.Plugins {
return assemblies;
}
internal static async Task OnASFInitModules(IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
internal static async Task OnASFInitModules(IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
if (!HasActivePluginsLoaded) {
return;
}
@ -202,19 +200,16 @@ namespace ArchiSteamFarm.Plugins {
}
}
[ItemCanBeNull]
internal static async Task<string> OnBotCommand(Bot bot, ulong steamID, string message, string[] args) {
internal static async Task<string?> OnBotCommand(Bot bot, ulong steamID, string message, string[] args) {
if ((bot == null) || (steamID == 0) || !new SteamID(steamID).IsIndividualAccount || string.IsNullOrEmpty(message) || (args == null) || (args.Length == 0)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(message) + " || " + nameof(args));
return null;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(message) + " || " + nameof(args));
}
if (!HasActivePluginsLoaded) {
return null;
}
IList<string> responses;
IList<string?> responses;
try {
responses = await Utilities.InParallel(ActivePlugins.OfType<IBotCommand>().Select(plugin => plugin.OnBotCommand(bot, steamID, message, args))).ConfigureAwait(false);
@ -229,9 +224,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotDestroy(Bot bot) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -247,9 +240,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotDisconnected(Bot bot, EResult reason) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -265,9 +256,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotFarmingFinished(Bot bot, bool farmedSomething) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -283,9 +272,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotFarmingStarted(Bot bot) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -301,9 +288,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotFarmingStopped(Bot bot) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -319,9 +304,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task<bool> OnBotFriendRequest(Bot bot, ulong steamID) {
if ((bot == null) || (steamID == 0) || !new SteamID(steamID).IsValid) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID));
return false;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(steamID));
}
if (!HasActivePluginsLoaded) {
@ -343,9 +326,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotInit(Bot bot) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -359,11 +340,9 @@ namespace ArchiSteamFarm.Plugins {
}
}
internal static async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken> additionalConfigProperties = null) {
internal static async Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JToken>? additionalConfigProperties = null) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -379,9 +358,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotLoggedOn(Bot bot) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
@ -395,19 +372,16 @@ namespace ArchiSteamFarm.Plugins {
}
}
[ItemCanBeNull]
internal static async Task<string> OnBotMessage(Bot bot, ulong steamID, string message) {
internal static async Task<string?> OnBotMessage(Bot bot, ulong steamID, string message) {
if ((bot == null) || (steamID == 0) || !new SteamID(steamID).IsIndividualAccount || string.IsNullOrEmpty(message)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(message));
return null;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(steamID) + " || " + nameof(message));
}
if (!HasActivePluginsLoaded) {
return null;
}
IList<string> responses;
IList<string?> responses;
try {
responses = await Utilities.InParallel(ActivePlugins.OfType<IBotMessage>().Select(plugin => plugin.OnBotMessage(bot, steamID, message))).ConfigureAwait(false);
@ -422,9 +396,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotSteamCallbacksInit(Bot bot, CallbackManager callbackManager) {
if ((bot == null) || (callbackManager == null)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(callbackManager));
return;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(callbackManager));
}
if (!HasActivePluginsLoaded) {
@ -438,18 +410,16 @@ namespace ArchiSteamFarm.Plugins {
}
}
internal static async Task<HashSet<ClientMsgHandler>> OnBotSteamHandlersInit(Bot bot) {
internal static async Task<HashSet<ClientMsgHandler>?> OnBotSteamHandlersInit(Bot bot) {
if (bot == null) {
ASF.ArchiLogger.LogNullError(nameof(bot));
return null;
throw new ArgumentNullException(nameof(bot));
}
if (!HasActivePluginsLoaded) {
return null;
}
IList<IReadOnlyCollection<ClientMsgHandler>> responses;
IList<IReadOnlyCollection<ClientMsgHandler>?> responses;
try {
responses = await Utilities.InParallel(ActivePlugins.OfType<IBotSteamClient>().Select(plugin => Task.Run(() => plugin.OnBotSteamHandlersInit(bot)))).ConfigureAwait(false);
@ -464,9 +434,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task<bool> OnBotTradeOffer(Bot bot, Steam.TradeOffer tradeOffer) {
if ((bot == null) || (tradeOffer == null)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(tradeOffer));
return false;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(tradeOffer));
}
if (!HasActivePluginsLoaded) {
@ -488,9 +456,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection<Trading.ParseTradeResult> tradeResults) {
if ((bot == null) || (tradeResults == null) || (tradeResults.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(tradeResults));
return;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(tradeResults));
}
if (!HasActivePluginsLoaded) {
@ -506,9 +472,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnBotUserNotifications(Bot bot, IReadOnlyCollection<ArchiHandler.UserNotificationsCallback.EUserNotification> newNotifications) {
if ((bot == null) || (newNotifications == null) || (newNotifications.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(bot) + " || " + nameof(newNotifications));
return;
throw new ArgumentNullException(nameof(bot) + " || " + nameof(newNotifications));
}
if (!HasActivePluginsLoaded) {
@ -524,9 +488,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnPICSChanges(uint currentChangeNumber, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> appChanges, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> packageChanges) {
if ((currentChangeNumber == 0) || (appChanges == null) || (packageChanges == null)) {
ASF.ArchiLogger.LogNullError(nameof(currentChangeNumber) + " || " + nameof(appChanges) + " || " + nameof(packageChanges));
return;
throw new ArgumentNullException(nameof(currentChangeNumber) + " || " + nameof(appChanges) + " || " + nameof(packageChanges));
}
if (!HasActivePluginsLoaded) {
@ -542,9 +504,7 @@ namespace ArchiSteamFarm.Plugins {
internal static async Task OnPICSChangesRestart(uint currentChangeNumber) {
if (currentChangeNumber == 0) {
ASF.ArchiLogger.LogNullError(nameof(currentChangeNumber));
return;
throw new ArgumentNullException(nameof(currentChangeNumber));
}
if (!HasActivePluginsLoaded) {
@ -558,11 +518,9 @@ namespace ArchiSteamFarm.Plugins {
}
}
private static HashSet<Assembly> LoadAssembliesFrom(string path) {
private static HashSet<Assembly>? LoadAssembliesFrom(string path) {
if (string.IsNullOrEmpty(path)) {
ASF.ArchiLogger.LogNullError(nameof(path));
return null;
throw new ArgumentNullException(nameof(path));
}
if (!Directory.Exists(path)) {

View file

@ -38,12 +38,13 @@ using SteamKit2;
namespace ArchiSteamFarm {
internal static class Program {
internal static string NetworkGroup { get; private set; }
internal static string? NetworkGroup { get; private set; }
internal static bool ProcessRequired { get; private set; }
internal static bool RestartAllowed { get; private set; } = true;
internal static bool ShutdownSequenceInitialized { get; private set; }
private static readonly TaskCompletionSource<byte> ShutdownResetEvent = new TaskCompletionSource<byte>();
private static bool SystemRequired;
internal static async Task Exit(byte exitCode = 0) {
@ -60,12 +61,10 @@ namespace ArchiSteamFarm {
return;
}
string executableName = Path.GetFileNameWithoutExtension(OS.ProcessFileName);
string? executableName = Path.GetFileNameWithoutExtension(OS.ProcessFileName);
if (string.IsNullOrEmpty(executableName)) {
ASF.ArchiLogger.LogNullError(nameof(executableName));
return;
throw new ArgumentNullException(nameof(executableName));
}
IEnumerable<string> arguments = Environment.GetCommandLineArgs().Skip(executableName.Equals(SharedInfo.AssemblyName) ? 1 : 0);
@ -85,9 +84,7 @@ namespace ArchiSteamFarm {
private static void HandleCryptKeyArgument(string cryptKey) {
if (string.IsNullOrEmpty(cryptKey)) {
ASF.ArchiLogger.LogNullError(nameof(cryptKey));
return;
throw new ArgumentNullException(nameof(cryptKey));
}
ArchiCryptoHelper.SetEncryptionKey(cryptKey);
@ -95,9 +92,7 @@ namespace ArchiSteamFarm {
private static void HandleNetworkGroupArgument(string networkGroup) {
if (string.IsNullOrEmpty(networkGroup)) {
ASF.ArchiLogger.LogNullError(nameof(networkGroup));
return;
throw new ArgumentNullException(nameof(networkGroup));
}
NetworkGroup = networkGroup;
@ -105,9 +100,7 @@ namespace ArchiSteamFarm {
private static void HandlePathArgument(string path) {
if (string.IsNullOrEmpty(path)) {
ASF.ArchiLogger.LogNullError(nameof(path));
return;
throw new ArgumentNullException(nameof(path));
}
try {
@ -117,7 +110,7 @@ namespace ArchiSteamFarm {
}
}
private static async Task Init(IReadOnlyCollection<string> args) {
private static async Task Init(IReadOnlyCollection<string>? args) {
AppDomain.CurrentDomain.ProcessExit += OnProcessExit;
AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
@ -135,13 +128,19 @@ namespace ArchiSteamFarm {
await InitASF(args).ConfigureAwait(false);
}
private static async Task InitASF(IReadOnlyCollection<string> args) {
private static async Task InitASF(IReadOnlyCollection<string>? args) {
OS.CoreInit();
Console.Title = SharedInfo.ProgramIdentifier;
ASF.ArchiLogger.LogGenericInfo(SharedInfo.ProgramIdentifier);
await InitGlobalConfigAndLanguage().ConfigureAwait(false);
if (!await InitGlobalConfigAndLanguage().ConfigureAwait(false)) {
return;
}
if (ASF.GlobalConfig == null) {
throw new ArgumentNullException(nameof(ASF.GlobalConfig));
}
// Parse post-init args
if (args != null) {
@ -154,7 +153,7 @@ namespace ArchiSteamFarm {
await ASF.Init().ConfigureAwait(false);
}
private static async Task<bool> InitCore(IReadOnlyCollection<string> args) {
private static async Task<bool> InitCore(IReadOnlyCollection<string>? args) {
Directory.SetCurrentDirectory(SharedInfo.HomeDirectory);
// Allow loading configs from source tree if it's a debug build
@ -192,16 +191,14 @@ namespace ArchiSteamFarm {
return true;
}
private static async Task InitGlobalConfigAndLanguage() {
private static async Task<bool> InitGlobalConfigAndLanguage() {
string globalConfigFile = ASF.GetFilePath(ASF.EFileType.Config);
if (string.IsNullOrEmpty(globalConfigFile)) {
ASF.ArchiLogger.LogNullError(nameof(globalConfigFile));
return;
throw new ArgumentNullException(nameof(globalConfigFile));
}
GlobalConfig globalConfig;
GlobalConfig? globalConfig;
if (File.Exists(globalConfigFile)) {
globalConfig = await GlobalConfig.Load(globalConfigFile).ConfigureAwait(false);
@ -211,7 +208,7 @@ namespace ArchiSteamFarm {
await Task.Delay(5 * 1000).ConfigureAwait(false);
await Exit(1).ConfigureAwait(false);
return;
return false;
}
} else {
globalConfig = new GlobalConfig();
@ -223,10 +220,10 @@ namespace ArchiSteamFarm {
ASF.ArchiLogger.LogGenericDebug(globalConfigFile + ": " + JsonConvert.SerializeObject(ASF.GlobalConfig, Formatting.Indented));
}
if (!string.IsNullOrEmpty(ASF.GlobalConfig.CurrentCulture)) {
if (!string.IsNullOrEmpty(ASF.GlobalConfig?.CurrentCulture)) {
try {
// GetCultureInfo() would be better but we can't use it for specifying neutral cultures such as "en"
CultureInfo culture = CultureInfo.CreateSpecificCulture(ASF.GlobalConfig.CurrentCulture);
CultureInfo culture = CultureInfo.CreateSpecificCulture(ASF.GlobalConfig!.CurrentCulture);
CultureInfo.DefaultThreadCurrentCulture = CultureInfo.DefaultThreadCurrentUICulture = culture;
} catch (Exception e) {
ASF.ArchiLogger.LogGenericWarningException(e);
@ -239,16 +236,16 @@ namespace ArchiSteamFarm {
switch (CultureInfo.CurrentUICulture.TwoLetterISOLanguageName) {
case "en":
case "iv":
return;
return true;
}
// We can't dispose this resource set, as we can't be sure if it isn't used somewhere else, rely on GC in this case
ResourceSet defaultResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.GetCultureInfo("en-US"), true, true);
ResourceSet? defaultResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.GetCultureInfo("en-US"), true, true);
if (defaultResourceSet == null) {
ASF.ArchiLogger.LogNullError(nameof(defaultResourceSet));
return;
return true;
}
HashSet<DictionaryEntry> defaultStringObjects = defaultResourceSet.Cast<DictionaryEntry>().ToHashSet();
@ -256,16 +253,16 @@ namespace ArchiSteamFarm {
if (defaultStringObjects.Count == 0) {
ASF.ArchiLogger.LogNullError(nameof(defaultStringObjects));
return;
return true;
}
// We can't dispose this resource set, as we can't be sure if it isn't used somewhere else, rely on GC in this case
ResourceSet currentResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
ResourceSet? currentResourceSet = Strings.ResourceManager.GetResourceSet(CultureInfo.CurrentUICulture, true, true);
if (currentResourceSet == null) {
ASF.ArchiLogger.LogNullError(nameof(currentResourceSet));
return;
return true;
}
HashSet<DictionaryEntry> currentStringObjects = currentResourceSet.Cast<DictionaryEntry>().ToHashSet();
@ -286,15 +283,15 @@ namespace ArchiSteamFarm {
float translationCompleteness = currentStringObjects.Count / (float) defaultStringObjects.Count;
ASF.ArchiLogger.LogGenericInfo(string.Format(Strings.TranslationIncomplete, CultureInfo.CurrentUICulture.Name, translationCompleteness.ToString("P1")));
}
return true;
}
private static async Task InitGlobalDatabaseAndServices() {
string globalDatabaseFile = ASF.GetFilePath(ASF.EFileType.Database);
if (string.IsNullOrEmpty(globalDatabaseFile)) {
ASF.ArchiLogger.LogNullError(nameof(globalDatabaseFile));
return;
throw new ArgumentNullException(nameof(globalDatabaseFile));
}
if (!File.Exists(globalDatabaseFile)) {
@ -304,7 +301,7 @@ namespace ArchiSteamFarm {
await Task.Delay(5 * 1000).ConfigureAwait(false);
}
GlobalDatabase globalDatabase = await GlobalDatabase.CreateOrLoad(globalDatabaseFile).ConfigureAwait(false);
GlobalDatabase? globalDatabase = await GlobalDatabase.CreateOrLoad(globalDatabaseFile).ConfigureAwait(false);
if (globalDatabase == null) {
ASF.ArchiLogger.LogGenericError(string.Format(Strings.ErrorDatabaseInvalid, globalDatabaseFile));
@ -377,7 +374,7 @@ namespace ArchiSteamFarm {
return true;
}
private static async Task<int> Main(string[] args) {
private static async Task<int> Main(string[]? args) {
// Initialize
await Init(args).ConfigureAwait(false);
@ -385,24 +382,20 @@ namespace ArchiSteamFarm {
return await ShutdownResetEvent.Task.ConfigureAwait(false);
}
private static async void OnProcessExit(object sender, EventArgs e) => await Shutdown().ConfigureAwait(false);
private static async void OnProcessExit(object? sender, EventArgs e) => await Shutdown().ConfigureAwait(false);
private static async void OnUnhandledException(object sender, UnhandledExceptionEventArgs e) {
private static async void OnUnhandledException(object? sender, UnhandledExceptionEventArgs e) {
if (e?.ExceptionObject == null) {
ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.ExceptionObject));
return;
throw new ArgumentNullException(nameof(e) + " || " + nameof(e.ExceptionObject));
}
await ASF.ArchiLogger.LogFatalException((Exception) e.ExceptionObject).ConfigureAwait(false);
await Exit(1).ConfigureAwait(false);
}
private static async void OnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) {
private static async void OnUnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e) {
if (e?.Exception == null) {
ASF.ArchiLogger.LogNullError(nameof(e) + " || " + nameof(e.Exception));
return;
throw new ArgumentNullException(nameof(e) + " || " + nameof(e.Exception));
}
await ASF.ArchiLogger.LogFatalException(e.Exception).ConfigureAwait(false);
@ -414,13 +407,11 @@ namespace ArchiSteamFarm {
private static void ParsePostInitArgs(IReadOnlyCollection<string> args) {
if (args == null) {
ASF.ArchiLogger.LogNullError(nameof(args));
return;
throw new ArgumentNullException(nameof(args));
}
try {
string envCryptKey = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariableCryptKey);
string? envCryptKey = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariableCryptKey);
if (!string.IsNullOrEmpty(envCryptKey)) {
HandleCryptKeyArgument(envCryptKey);
@ -464,19 +455,17 @@ namespace ArchiSteamFarm {
private static void ParsePreInitArgs(IReadOnlyCollection<string> args) {
if (args == null) {
ASF.ArchiLogger.LogNullError(nameof(args));
return;
throw new ArgumentNullException(nameof(args));
}
try {
string envNetworkGroup = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariableNetworkGroup);
string? envNetworkGroup = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariableNetworkGroup);
if (!string.IsNullOrEmpty(envNetworkGroup)) {
HandleNetworkGroupArgument(envNetworkGroup);
}
string envPath = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariablePath);
string? envPath = Environment.GetEnvironmentVariable(SharedInfo.EnvironmentVariablePath);
if (!string.IsNullOrEmpty(envPath)) {
HandlePathArgument(envPath);

View file

@ -29,6 +29,7 @@ using Microsoft.AspNetCore.Hosting;
using System.Collections.Generic;
using System.Net.WebSockets;
using System.Threading;
#endif
namespace ArchiSteamFarm {
@ -38,7 +39,11 @@ namespace ArchiSteamFarm {
private static readonly DateTime SavedProcessStartTime = DateTime.UtcNow;
#endif
#if NETFRAMEWORK
public static bool IsRunningOnMono => Type.GetType("Mono.Runtime") != null;
#else
public static bool IsRunningOnMono => false;
#endif
public static DateTime ProcessStartTime {
get {
@ -57,7 +62,7 @@ namespace ArchiSteamFarm {
#pragma warning disable 1998
[PublicAPI]
public static class File {
public static async Task AppendAllTextAsync([NotNull] string path, string contents) =>
public static async Task AppendAllTextAsync(string path, string contents) =>
#if NETFRAMEWORK
System.IO.File.AppendAllText(path, contents);
#else
@ -65,7 +70,7 @@ namespace ArchiSteamFarm {
#endif
#pragma warning disable IDE0022
public static void Move([NotNull] string sourceFileName, [NotNull] string destFileName, bool overwrite) {
public static void Move(string sourceFileName, string destFileName, bool overwrite) {
#if NETFRAMEWORK
if (overwrite && System.IO.File.Exists(destFileName)) {
System.IO.File.Delete(destFileName);
@ -78,23 +83,21 @@ namespace ArchiSteamFarm {
}
#pragma warning restore IDE0022
[ItemNotNull]
public static async Task<byte[]> ReadAllBytesAsync([NotNull] string path) =>
public static async Task<byte[]> ReadAllBytesAsync(string path) =>
#if NETFRAMEWORK
System.IO.File.ReadAllBytes(path);
#else
await System.IO.File.ReadAllBytesAsync(path).ConfigureAwait(false);
#endif
[ItemNotNull]
public static async Task<string> ReadAllTextAsync([NotNull] string path) =>
public static async Task<string> ReadAllTextAsync(string path) =>
#if NETFRAMEWORK
System.IO.File.ReadAllText(path);
#else
await System.IO.File.ReadAllTextAsync(path).ConfigureAwait(false);
#endif
public static async Task WriteAllTextAsync([NotNull] string path, string contents) =>
public static async Task WriteAllTextAsync(string path, string contents) =>
#if NETFRAMEWORK
System.IO.File.WriteAllText(path, contents);
#else
@ -115,8 +118,7 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static class Path {
[NotNull]
public static string GetRelativePath([NotNull] string relativeTo, [NotNull] string path) {
public static string GetRelativePath(string relativeTo, string path) {
#if NETFRAMEWORK
if (!path.StartsWith(relativeTo, StringComparison.Ordinal)) {
throw new NotImplementedException();
@ -134,8 +136,7 @@ namespace ArchiSteamFarm {
}
#if NETFRAMEWORK
[NotNull]
internal static IWebHostBuilder ConfigureWebHostDefaults([NotNull] this IWebHostBuilder builder, [NotNull] Action<IWebHostBuilder> configure) {
internal static IWebHostBuilder ConfigureWebHostDefaults(this IWebHostBuilder builder, Action<IWebHostBuilder> configure) {
configure(builder);
return builder;
@ -147,17 +148,16 @@ namespace ArchiSteamFarm {
value = kv.Value;
}
public static ValueTask DisposeAsync([NotNull] this IDisposable disposable) {
public static ValueTask DisposeAsync(this IDisposable disposable) {
disposable.Dispose();
return default;
}
public static async Task<WebSocketReceiveResult> ReceiveAsync([NotNull] this WebSocket webSocket, [NotNull] byte[] buffer, CancellationToken cancellationToken) => await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken).ConfigureAwait(false);
public static async Task SendAsync([NotNull] this WebSocket webSocket, [NotNull] byte[] buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => await webSocket.SendAsync(new ArraySegment<byte>(buffer), messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
public static async Task<WebSocketReceiveResult> ReceiveAsync(this WebSocket webSocket, byte[] buffer, CancellationToken cancellationToken) => await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), cancellationToken).ConfigureAwait(false);
public static async Task SendAsync(this WebSocket webSocket, byte[] buffer, WebSocketMessageType messageType, bool endOfMessage, CancellationToken cancellationToken) => await webSocket.SendAsync(new ArraySegment<byte>(buffer), messageType, endOfMessage, cancellationToken).ConfigureAwait(false);
[NotNull]
public static string[] Split([NotNull] this string text, char separator, StringSplitOptions options = StringSplitOptions.None) => text.Split(new[] { separator }, options);
public static string[] Split(this string text, char separator, StringSplitOptions options = StringSplitOptions.None) => text.Split(new[] { separator }, options);
public static void TrimExcess<TKey, TValue>(this Dictionary<TKey, TValue> _) { } // no-op
#endif

View file

@ -67,7 +67,7 @@ namespace ArchiSteamFarm {
internal static string HomeDirectory {
get {
if (!string.IsNullOrEmpty(CachedHomeDirectory)) {
return CachedHomeDirectory;
return CachedHomeDirectory!;
}
// We're aiming to handle two possible cases here, classic publish and single-file publish which is possible with OS-specific builds
@ -75,70 +75,47 @@ namespace ArchiSteamFarm {
// We can't just return our base directory since it could lead to the (wrong) temporary directory of extracted files in a single-publish scenario
// If the path goes to our own binary, the user is using OS-specific build, single-file or not, we'll use path to location of that binary then
// Otherwise, this path goes to some third-party binary, likely dotnet/mono, the user is using our generic build or other custom binary, we need to trust our base directory then
CachedHomeDirectory = Path.GetFileNameWithoutExtension(OS.ProcessFileName) == AssemblyName ? Path.GetDirectoryName(OS.ProcessFileName) : AppContext.BaseDirectory;
CachedHomeDirectory = Path.GetFileNameWithoutExtension(OS.ProcessFileName) == AssemblyName ? Path.GetDirectoryName(OS.ProcessFileName) ?? AppContext.BaseDirectory : AppContext.BaseDirectory;
return CachedHomeDirectory;
}
}
[NotNull]
internal static string ProgramIdentifier => PublicIdentifier + " V" + Version + " (" + BuildInfo.Variant + "/" + ModuleVersion + " | " + OS.Variant + ")";
[NotNull]
internal static string PublicIdentifier => AssemblyName + (BuildInfo.IsCustomBuild ? "-custom" : PluginsCore.HasCustomPluginsLoaded ? "-modded" : "");
[NotNull]
internal static Version Version => Assembly.GetEntryAssembly()?.GetName().Version ?? throw new ArgumentNullException(nameof(Version));
private static Guid ModuleVersion => Assembly.GetEntryAssembly()?.ManifestModule.ModuleVersionId ?? throw new ArgumentNullException(nameof(ModuleVersion));
private static string CachedHomeDirectory;
private static string? CachedHomeDirectory;
internal static class BuildInfo {
#if ASF_VARIANT_DOCKER
internal static bool CanUpdate => false;
[NotNull]
internal static string Variant => "docker";
#elif ASF_VARIANT_GENERIC
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "generic";
#elif ASF_VARIANT_GENERIC_NETF
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "generic-netf";
#elif ASF_VARIANT_LINUX_ARM
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "linux-arm";
#elif ASF_VARIANT_LINUX_ARM64
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "linux-arm64";
#elif ASF_VARIANT_LINUX_X64
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "linux-x64";
#elif ASF_VARIANT_OSX_X64
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "osx-x64";
#elif ASF_VARIANT_WIN_X64
internal static bool CanUpdate => true;
[NotNull]
internal static string Variant => "win-x64";
#else
internal static bool CanUpdate => false;
[NotNull]
internal static string Variant => SourceVariant;
#endif

View file

@ -29,7 +29,6 @@ using System.Threading;
using System.Threading.Tasks;
using ArchiSteamFarm.Json;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
using Newtonsoft.Json;
namespace ArchiSteamFarm {
@ -60,13 +59,13 @@ namespace ArchiSteamFarm {
private DateTime LastPersonaStateRequest;
private bool ShouldSendHeartBeats;
internal Statistics([JetBrains.Annotations.NotNull] Bot bot) {
internal Statistics(Bot bot) {
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
MatchActivelyTimer = new Timer(
async e => await MatchActively().ConfigureAwait(false),
null,
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots.Count), // Delay
TimeSpan.FromHours(1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
TimeSpan.FromHours(8) // Period
);
}
@ -99,11 +98,11 @@ namespace ArchiSteamFarm {
const string request = URL + "/Api/HeartBeat";
Dictionary<string, string> data = new Dictionary<string, string>(2, StringComparer.Ordinal) {
{ "Guid", ASF.GlobalDatabase.Guid.ToString("N") },
{ "Guid", (ASF.GlobalDatabase?.Guid ?? Guid.NewGuid()).ToString("N") },
{ "SteamID", Bot.SteamID.ToString() }
};
WebBrowser.BasicResponse response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
WebBrowser.BasicResponse? response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
if (response == null) {
return;
@ -128,7 +127,7 @@ namespace ArchiSteamFarm {
}
}
internal async Task OnPersonaState(string nickname = null, string avatarHash = null) {
internal async Task OnPersonaState(string? nickname = null, string? avatarHash = null) {
if ((DateTime.UtcNow < LastAnnouncementCheck.AddHours(MinAnnouncementCheckTTL)) && (ShouldSendHeartBeats || (LastHeartBeat == DateTime.MinValue))) {
return;
}
@ -157,7 +156,7 @@ namespace ArchiSteamFarm {
return;
}
string tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
string? tradeToken = await Bot.ArchiHandler.GetTradeToken().ConfigureAwait(false);
if (string.IsNullOrEmpty(tradeToken)) {
// This is actually network failure, so we'll stop sending heartbeats but not record it as valid check
@ -210,16 +209,16 @@ namespace ArchiSteamFarm {
Dictionary<string, string> data = new Dictionary<string, string>(9, StringComparer.Ordinal) {
{ "AvatarHash", avatarHash ?? "" },
{ "GamesCount", inventory.Select(item => item.RealAppID).Distinct().Count().ToString() },
{ "Guid", ASF.GlobalDatabase.Guid.ToString("N") },
{ "Guid", (ASF.GlobalDatabase?.Guid ?? Guid.NewGuid()).ToString("N") },
{ "ItemsCount", inventory.Count.ToString() },
{ "MatchableTypes", JsonConvert.SerializeObject(acceptedMatchableTypes) },
{ "MatchEverything", Bot.BotConfig.TradingPreferences.HasFlag(BotConfig.ETradingPreferences.MatchEverything) ? "1" : "0" },
{ "Nickname", nickname ?? "" },
{ "SteamID", Bot.SteamID.ToString() },
{ "TradeToken", tradeToken }
{ "TradeToken", tradeToken! }
};
WebBrowser.BasicResponse response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
WebBrowser.BasicResponse? response = await Bot.ArchiWebHandler.WebBrowser.UrlPost(request, data, requestOptions: WebBrowser.ERequestOptions.ReturnClientErrors).ConfigureAwait(false);
if (response == null) {
return;
@ -239,11 +238,10 @@ namespace ArchiSteamFarm {
}
}
[ItemCanBeNull]
private async Task<ImmutableHashSet<ListedUser>> GetListedUsers() {
private async Task<ImmutableHashSet<ListedUser>?> GetListedUsers() {
const string request = URL + "/Api/Bots";
WebBrowser.ObjectResponse<ImmutableHashSet<ListedUser>> objectResponse = await Bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject<ImmutableHashSet<ListedUser>>(request).ConfigureAwait(false);
WebBrowser.ObjectResponse<ImmutableHashSet<ListedUser>>? objectResponse = await Bot.ArchiWebHandler.WebBrowser.UrlGetToJsonObject<ImmutableHashSet<ListedUser>>(request).ConfigureAwait(false);
return objectResponse?.Content;
}
@ -325,7 +323,7 @@ namespace ArchiSteamFarm {
try {
Bot.ArchiLogger.LogGenericTrace(Strings.Starting);
Dictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)> triedSteamIDs = new Dictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)>();
Dictionary<ulong, (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs)> triedSteamIDs = new Dictionary<ulong, (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs)>();
bool shouldContinueMatching = true;
bool tradedSomething = false;
@ -355,7 +353,7 @@ namespace ArchiSteamFarm {
}
}
private async Task<(bool ShouldContinueMatching, bool TradedSomething)> MatchActivelyRound(IReadOnlyCollection<Steam.Asset.EType> acceptedMatchableTypes, IDictionary<ulong, (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs)> triedSteamIDs) {
private async Task<(bool ShouldContinueMatching, bool TradedSomething)> MatchActivelyRound(IReadOnlyCollection<Steam.Asset.EType> acceptedMatchableTypes, IDictionary<ulong, (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs)> triedSteamIDs) {
if ((acceptedMatchableTypes == null) || (acceptedMatchableTypes.Count == 0) || (triedSteamIDs == null)) {
Bot.ArchiLogger.LogNullError(nameof(acceptedMatchableTypes) + " || " + nameof(triedSteamIDs));
@ -391,7 +389,7 @@ namespace ArchiSteamFarm {
return (false, false);
}
ImmutableHashSet<ListedUser> listedUsers = await GetListedUsers().ConfigureAwait(false);
ImmutableHashSet<ListedUser>? listedUsers = await GetListedUsers().ConfigureAwait(false);
if ((listedUsers == null) || (listedUsers.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(listedUsers)));
@ -399,11 +397,12 @@ namespace ArchiSteamFarm {
return (false, false);
}
byte maxTradeHoldDuration = ASF.GlobalConfig?.MaxTradeHoldDuration ?? GlobalConfig.DefaultMaxTradeHoldDuration;
byte totalMatches = 0;
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> skippedSetsThisRound = new HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)>();
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.MatchEverything).ThenByDescending(listedUser => listedUser.MatchEverything || (listedUser.ItemsCount < MaxItemsForFairBots)).ThenByDescending(listedUser => listedUser.Score)) {
foreach (ListedUser listedUser in listedUsers.Where(listedUser => (listedUser.SteamID != Bot.SteamID) && acceptedMatchableTypes.Any(listedUser.MatchableTypes.Contains) && (!triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs) attempt) || (attempt.Tries < byte.MaxValue)) && !Bot.IsBlacklistedFromTrades(listedUser.SteamID)).OrderBy(listedUser => triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs) attempt) ? attempt.Tries : 0).ThenByDescending(listedUser => listedUser.MatchEverything).ThenByDescending(listedUser => listedUser.MatchEverything || (listedUser.ItemsCount < MaxItemsForFairBots)).ThenByDescending(listedUser => listedUser.Score)) {
HashSet<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity)> wantedSets = ourTradableState.Keys.Where(set => !skippedSetsThisRound.Contains(set) && listedUser.MatchableTypes.Contains(set.Type)).ToHashSet();
if (wantedSets.Count == 0) {
@ -425,8 +424,8 @@ namespace ArchiSteamFarm {
}
if (holdDuration.Value > 0) {
if (holdDuration.Value > ASF.GlobalConfig.MaxTradeHoldDuration) {
Bot.ArchiLogger.LogGenericTrace(holdDuration.Value + " > " + ASF.GlobalConfig.MaxTradeHoldDuration);
if (holdDuration.Value > maxTradeHoldDuration) {
Bot.ArchiLogger.LogGenericTrace(holdDuration.Value + " > " + maxTradeHoldDuration);
continue;
}
@ -467,11 +466,11 @@ namespace ArchiSteamFarm {
Dictionary<ulong, uint> fairClassIDsToReceive = new Dictionary<ulong, uint>();
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, Dictionary<ulong, uint> ourFullItems) in ourFullState.Where(set => !skippedSetsThisUser.Contains(set.Key) && listedUser.MatchableTypes.Contains(set.Key.Type) && set.Value.Values.Any(count => count > 1))) {
if (!ourTradableState.TryGetValue(set, out Dictionary<ulong, uint> ourTradableItems) || (ourTradableItems.Count == 0)) {
if (!ourTradableState.TryGetValue(set, out Dictionary<ulong, uint>? ourTradableItems) || (ourTradableItems.Count == 0)) {
continue;
}
if (!theirTradableState.TryGetValue(set, out Dictionary<ulong, uint> theirTradableItems) || (theirTradableItems.Count == 0)) {
if (!theirTradableState.TryGetValue(set, out Dictionary<ulong, uint>? theirTradableItems) || (theirTradableItems.Count == 0)) {
continue;
}
@ -480,7 +479,7 @@ namespace ArchiSteamFarm {
Dictionary<ulong, uint> ourTradableSet = new Dictionary<ulong, uint>(ourTradableItems);
// We also have to take into account changes that happened in previous trades with this user, so this block will adapt to that
if (inventoryStateChanges.TryGetValue(set, out Dictionary<ulong, uint> pastChanges) && (pastChanges.Count > 0)) {
if (inventoryStateChanges.TryGetValue(set, out Dictionary<ulong, uint>? pastChanges) && (pastChanges.Count > 0)) {
foreach ((ulong classID, uint amount) in pastChanges) {
if (!ourFullSet.TryGetValue(classID, out uint fullAmount) || (fullAmount == 0) || (fullAmount < amount)) {
Bot.ArchiLogger.LogNullError(nameof(fullAmount));
@ -566,7 +565,7 @@ namespace ArchiSteamFarm {
classIDsToGive[ourItem] = classIDsToGive.TryGetValue(ourItem, out uint ourGivenAmount) ? ourGivenAmount + 1 : 1;
ourFullSet[ourItem] = ourFullAmount - 1; // We don't need to remove anything here because we can guarantee that ourItem.Value is at least 2
if (inventoryStateChanges.TryGetValue(set, out Dictionary<ulong, uint> currentChanges)) {
if (inventoryStateChanges.TryGetValue(set, out Dictionary<ulong, uint>? currentChanges)) {
currentChanges[ourItem] = currentChanges.TryGetValue(ourItem, out uint amount) ? amount + 1 : 1;
} else {
inventoryStateChanges[set] = new Dictionary<ulong, uint> {
@ -626,8 +625,8 @@ namespace ArchiSteamFarm {
return (false, skippedSetsThisRound.Count > 0);
}
if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong> GivenAssetIDs, ISet<ulong> ReceivedAssetIDs) previousAttempt)) {
if (itemsToGive.Select(item => item.AssetID).All(previousAttempt.GivenAssetIDs.Contains) && itemsToReceive.Select(item => item.AssetID).All(previousAttempt.ReceivedAssetIDs.Contains)) {
if (triedSteamIDs.TryGetValue(listedUser.SteamID, out (byte Tries, ISet<ulong>? GivenAssetIDs, ISet<ulong>? ReceivedAssetIDs) previousAttempt)) {
if ((previousAttempt.GivenAssetIDs == null) || (previousAttempt.ReceivedAssetIDs == null) || (itemsToGive.Select(item => item.AssetID).All(previousAttempt.GivenAssetIDs.Contains) && itemsToReceive.Select(item => item.AssetID).All(previousAttempt.ReceivedAssetIDs.Contains))) {
// This user didn't respond in our previous round, avoid him for remaining ones
triedSteamIDs[listedUser.SteamID] = (byte.MaxValue, previousAttempt.GivenAssetIDs, previousAttempt.ReceivedAssetIDs);
@ -645,7 +644,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericTrace(Bot.SteamID + " <- " + string.Join(", ", itemsToReceive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " | " + string.Join(", ", itemsToGive.Select(item => item.RealAppID + "/" + item.Type + "-" + item.ClassID + " #" + item.Amount)) + " -> " + listedUser.SteamID);
(bool success, HashSet<ulong> mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false);
(bool success, HashSet<ulong>? mobileTradeOfferIDs) = await Bot.ArchiWebHandler.SendTradeOffer(listedUser.SteamID, itemsToGive, itemsToReceive, listedUser.TradeToken, true).ConfigureAwait(false);
if ((mobileTradeOfferIDs != null) && (mobileTradeOfferIDs.Count > 0) && Bot.HasMobileAuthenticator) {
(bool twoFactorSuccess, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, MobileAuthenticator.Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false);
@ -717,7 +716,7 @@ namespace ArchiSteamFarm {
#pragma warning disable 649
[JsonProperty(PropertyName = "trade_token", Required = Required.Always)]
internal readonly string TradeToken;
internal readonly string? TradeToken;
#pragma warning restore 649
internal float Score => GamesCount / (float) ItemsCount;

View file

@ -24,7 +24,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using ArchiSteamFarm.Collections;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2.Discovery;
@ -33,10 +32,8 @@ namespace ArchiSteamFarm.SteamKit2 {
[JsonProperty(Required = Required.DisallowNull)]
private readonly ConcurrentHashSet<ServerRecordEndPoint> ServerRecords = new ConcurrentHashSet<ServerRecordEndPoint>();
[NotNull]
public Task<IEnumerable<ServerRecord>> FetchServerListAsync() => Task.FromResult(ServerRecords.Select(server => ServerRecord.CreateServer(server.Host, server.Port, server.ProtocolTypes)));
public Task<IEnumerable<ServerRecord>> FetchServerListAsync() => Task.FromResult(ServerRecords.Where(server => !string.IsNullOrEmpty(server.Host) && (server.Port > 0) && (server.ProtocolTypes > 0)).Select(server => ServerRecord.CreateServer(server.Host!, server.Port, server.ProtocolTypes)));
[NotNull]
public Task UpdateServerListAsync(IEnumerable<ServerRecord> endpoints) {
if (endpoints == null) {
ASF.ArchiLogger.LogNullError(nameof(endpoints));
@ -55,6 +52,6 @@ namespace ArchiSteamFarm.SteamKit2 {
public bool ShouldSerializeServerRecords() => ServerRecords.Count > 0;
internal event EventHandler ServerListUpdated;
internal event EventHandler? ServerListUpdated;
}
}

View file

@ -20,14 +20,13 @@
// limitations under the License.
using System;
using JetBrains.Annotations;
using Newtonsoft.Json;
using SteamKit2;
namespace ArchiSteamFarm.SteamKit2 {
internal sealed class ServerRecordEndPoint : IEquatable<ServerRecordEndPoint> {
[JsonProperty(Required = Required.Always)]
internal readonly string Host;
internal readonly string? Host;
[JsonProperty(Required = Required.Always)]
internal readonly ushort Port;
@ -35,7 +34,7 @@ namespace ArchiSteamFarm.SteamKit2 {
[JsonProperty(Required = Required.Always)]
internal readonly ProtocolTypes ProtocolTypes;
internal ServerRecordEndPoint([NotNull] string host, ushort port, ProtocolTypes protocolTypes) {
internal ServerRecordEndPoint(string host, ushort port, ProtocolTypes protocolTypes) {
if (string.IsNullOrEmpty(host) || (port == 0) || (protocolTypes == 0)) {
throw new ArgumentNullException(nameof(host) + " || " + nameof(port) + " || " + nameof(protocolTypes));
}
@ -48,8 +47,8 @@ namespace ArchiSteamFarm.SteamKit2 {
[JsonConstructor]
private ServerRecordEndPoint() { }
public bool Equals(ServerRecordEndPoint other) => (other != null) && (ReferenceEquals(other, this) || ((Host == other.Host) && (Port == other.Port) && (ProtocolTypes == other.ProtocolTypes)));
public override bool Equals(object obj) => (obj != null) && ((obj == this) || (obj is ServerRecordEndPoint serverRecord && Equals(serverRecord)));
public bool Equals(ServerRecordEndPoint? other) => (other != null) && (ReferenceEquals(other, this) || ((Host == other.Host) && (Port == other.Port) && (ProtocolTypes == other.ProtocolTypes)));
public override bool Equals(object? obj) => (obj != null) && ((obj == this) || (obj is ServerRecordEndPoint serverRecord && Equals(serverRecord)));
public override int GetHashCode() => RuntimeCompatibility.HashCode.Combine(Host, Port, ProtocolTypes);
}
}

View file

@ -60,11 +60,11 @@ namespace ArchiSteamFarm {
}
try {
Bot refreshBot = null;
SteamApps.PICSChangesCallback picsChanges = null;
Bot? refreshBot = null;
SteamApps.PICSChangesCallback? picsChanges = null;
for (byte i = 0; (i < WebBrowser.MaxTries) && (picsChanges == null); i++) {
refreshBot = Bot.Bots.Values.FirstOrDefault(bot => bot.IsConnectedAndLoggedOn);
refreshBot = Bot.Bots?.Values.FirstOrDefault(bot => bot.IsConnectedAndLoggedOn);
if (refreshBot == null) {
return;
@ -95,7 +95,7 @@ namespace ArchiSteamFarm {
return;
}
if (picsChanges.PackageChanges.Count > 0) {
if ((picsChanges.PackageChanges.Count > 0) && (ASF.GlobalDatabase != null)) {
await ASF.GlobalDatabase.RefreshPackages(refreshBot, picsChanges.PackageChanges.ToDictionary(package => package.Key, package => package.Value.ChangeNumber)).ConfigureAwait(false);
}

View file

@ -25,7 +25,6 @@ using System.Threading;
using System.Threading.Tasks;
using AngleSharp.Dom;
using ArchiSteamFarm.Localization;
using JetBrains.Annotations;
namespace ArchiSteamFarm {
internal sealed class SteamSaleEvent : IAsyncDisposable {
@ -34,13 +33,13 @@ namespace ArchiSteamFarm {
private readonly Bot Bot;
private readonly Timer SaleEventTimer;
internal SteamSaleEvent([NotNull] Bot bot) {
internal SteamSaleEvent(Bot bot) {
Bot = bot ?? throw new ArgumentNullException(nameof(bot));
SaleEventTimer = new Timer(
async e => await ExploreDiscoveryQueue().ConfigureAwait(false),
null,
TimeSpan.FromHours(1.1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots.Count), // Delay
TimeSpan.FromHours(1.1) + TimeSpan.FromSeconds(ASF.LoadBalancingDelay * Bot.Bots?.Count ?? 0), // Delay
TimeSpan.FromHours(8.1) // Period
);
}
@ -55,7 +54,7 @@ namespace ArchiSteamFarm {
Bot.ArchiLogger.LogGenericTrace(Strings.Starting);
for (byte i = 0; (i < MaxSingleQueuesDaily) && (await IsDiscoveryQueueAvailable().ConfigureAwait(false)).GetValueOrDefault(); i++) {
ImmutableHashSet<uint> queue = await Bot.ArchiWebHandler.GenerateNewDiscoveryQueue().ConfigureAwait(false);
ImmutableHashSet<uint>? queue = await Bot.ArchiWebHandler.GenerateNewDiscoveryQueue().ConfigureAwait(false);
if ((queue == null) || (queue.Count == 0)) {
Bot.ArchiLogger.LogGenericTrace(string.Format(Strings.ErrorIsEmpty, nameof(queue)));
@ -83,13 +82,13 @@ namespace ArchiSteamFarm {
}
private async Task<bool?> IsDiscoveryQueueAvailable() {
using IDocument htmlDocument = await Bot.ArchiWebHandler.GetDiscoveryQueuePage().ConfigureAwait(false);
using IDocument? htmlDocument = await Bot.ArchiWebHandler.GetDiscoveryQueuePage().ConfigureAwait(false);
if (htmlDocument == null) {
return null;
}
IElement htmlNode = htmlDocument.SelectSingleNode("//div[@class='subtext']");
IElement? htmlNode = htmlDocument.SelectSingleNode("//div[@class='subtext']");
if (htmlNode == null) {
// Valid, no cards for exploring the queue available

View file

@ -44,16 +44,14 @@ namespace ArchiSteamFarm {
private bool ParsingScheduled;
internal Trading([NotNull] Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
internal Trading(Bot bot) => Bot = bot ?? throw new ArgumentNullException(nameof(bot));
public void Dispose() => TradesSemaphore.Dispose();
[PublicAPI]
public static bool IsFairExchange(IReadOnlyCollection<Steam.Asset> itemsToGive, IReadOnlyCollection<Steam.Asset> itemsToReceive) {
if ((itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
throw new ArgumentNullException(nameof(itemsToGive) + " || " + nameof(itemsToReceive));
}
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint> itemsToGiveAmounts = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), uint>();
@ -83,9 +81,7 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static bool IsTradeNeutralOrBetter(HashSet<Steam.Asset> inventory, ISet<Steam.Asset> itemsToGive, ISet<Steam.Asset> itemsToReceive) {
if ((inventory == null) || (inventory.Count == 0) || (itemsToGive == null) || (itemsToGive.Count == 0) || (itemsToReceive == null) || (itemsToReceive.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(itemsToGive) + " || " + nameof(itemsToReceive));
return false;
throw new ArgumentNullException(nameof(inventory) + " || " + nameof(itemsToGive) + " || " + nameof(itemsToReceive));
}
// Input of this function is items we're expected to give/receive and our inventory (limited to realAppIDs of itemsToGive/itemsToReceive)
@ -118,9 +114,7 @@ namespace ArchiSteamFarm {
}
if (amountToGive > 0) {
ASF.ArchiLogger.LogNullError(nameof(amountToGive));
return false;
throw new ArgumentNullException(nameof(amountToGive));
}
if (itemsToRemove.Count > 0) {
@ -138,7 +132,7 @@ namespace ArchiSteamFarm {
// Once we have both states, we can check overall fairness
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, List<uint> beforeAmounts) in initialSets) {
if (!finalSets.TryGetValue(set, out List<uint> afterAmounts)) {
if (!finalSets.TryGetValue(set, out List<uint>? afterAmounts)) {
// If we have no info about this set, then it has to be a bad one
return false;
}
@ -188,9 +182,7 @@ namespace ArchiSteamFarm {
internal static (Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> FullState, Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> TradableState) GetDividedInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return (null, null);
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
@ -199,7 +191,7 @@ namespace ArchiSteamFarm {
foreach (Steam.Asset item in inventory) {
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (fullState.TryGetValue(key, out Dictionary<ulong, uint> fullSet)) {
if (fullState.TryGetValue(key, out Dictionary<ulong, uint>? fullSet)) {
fullSet[item.ClassID] = fullSet.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount;
} else {
fullState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
@ -209,7 +201,7 @@ namespace ArchiSteamFarm {
continue;
}
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint> tradableSet)) {
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint>? tradableSet)) {
tradableSet[item.ClassID] = tradableSet.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount;
} else {
tradableState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
@ -221,9 +213,7 @@ namespace ArchiSteamFarm {
internal static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> GetTradableInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return null;
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
@ -231,7 +221,7 @@ namespace ArchiSteamFarm {
foreach (Steam.Asset item in inventory.Where(item => item.Tradable)) {
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint> tradableSet)) {
if (tradableState.TryGetValue(key, out Dictionary<ulong, uint>? tradableSet)) {
tradableSet[item.ClassID] = tradableSet.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount;
} else {
tradableState[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
@ -243,9 +233,7 @@ namespace ArchiSteamFarm {
internal static HashSet<Steam.Asset> GetTradableItemsFromInventory(ISet<Steam.Asset> inventory, IDictionary<ulong, uint> classIDs) {
if ((inventory == null) || (inventory.Count == 0) || (classIDs == null) || (classIDs.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory) + " || " + nameof(classIDs));
return null;
throw new ArgumentNullException(nameof(inventory) + " || " + nameof(classIDs));
}
HashSet<Steam.Asset> result = new HashSet<Steam.Asset>();
@ -273,16 +261,12 @@ namespace ArchiSteamFarm {
internal static bool IsEmptyForMatching(IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> fullState, IReadOnlyDictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> tradableState) {
if ((fullState == null) || (tradableState == null)) {
ASF.ArchiLogger.LogNullError(nameof(fullState) + " || " + nameof(tradableState));
return false;
throw new ArgumentNullException(nameof(fullState) + " || " + nameof(tradableState));
}
foreach (((uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) set, Dictionary<ulong, uint> state) in tradableState) {
if (!fullState.TryGetValue(set, out Dictionary<ulong, uint> fullSet) || (fullSet == null) || (fullSet.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(fullSet));
return false;
if (!fullState.TryGetValue(set, out Dictionary<ulong, uint>? fullSet) || (fullSet == null) || (fullSet.Count == 0)) {
throw new ArgumentNullException(nameof(fullSet));
}
if (!IsEmptyForMatching(fullSet, state)) {
@ -296,24 +280,18 @@ namespace ArchiSteamFarm {
internal static bool IsEmptyForMatching(IReadOnlyDictionary<ulong, uint> fullSet, IReadOnlyDictionary<ulong, uint> tradableSet) {
if ((fullSet == null) || (tradableSet == null)) {
ASF.ArchiLogger.LogNullError(nameof(fullSet) + " || " + nameof(tradableSet));
return false;
throw new ArgumentNullException(nameof(fullSet) + " || " + nameof(tradableSet));
}
foreach ((ulong classID, uint amount) in tradableSet) {
switch (amount) {
case 0:
// No tradable items, this should never happen, dictionary should not have this key to begin with
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(amount), amount));
return false;
throw new ArgumentOutOfRangeException(nameof(amount));
case 1:
// Single tradable item, can be matchable or not depending on the rest of the inventory
if (!fullSet.TryGetValue(classID, out uint fullAmount) || (fullAmount == 0) || (fullAmount < amount)) {
ASF.ArchiLogger.LogNullError(nameof(fullAmount));
return false;
throw new ArgumentNullException(nameof(fullAmount));
}
if (fullAmount > 1) {
@ -369,9 +347,7 @@ namespace ArchiSteamFarm {
private static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), List<uint>> GetInventorySets(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return null;
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> sets = GetInventoryState(inventory);
@ -381,9 +357,7 @@ namespace ArchiSteamFarm {
private static Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> GetInventoryState(IReadOnlyCollection<Steam.Asset> inventory) {
if ((inventory == null) || (inventory.Count == 0)) {
ASF.ArchiLogger.LogNullError(nameof(inventory));
return null;
throw new ArgumentNullException(nameof(inventory));
}
Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>> state = new Dictionary<(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity), Dictionary<ulong, uint>>();
@ -391,7 +365,7 @@ namespace ArchiSteamFarm {
foreach (Steam.Asset item in inventory) {
(uint RealAppID, Steam.Asset.EType Type, Steam.Asset.ERarity Rarity) key = (item.RealAppID, item.Type, item.Rarity);
if (state.TryGetValue(key, out Dictionary<ulong, uint> set)) {
if (state.TryGetValue(key, out Dictionary<ulong, uint>? set)) {
set[item.ClassID] = set.TryGetValue(item.ClassID, out uint amount) ? amount + item.Amount : item.Amount;
} else {
state[key] = new Dictionary<ulong, uint> { { item.ClassID, item.Amount } };
@ -402,7 +376,7 @@ namespace ArchiSteamFarm {
}
private async Task<bool> ParseActiveTrades() {
HashSet<Steam.TradeOffer> tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false);
HashSet<Steam.TradeOffer>? tradeOffers = await Bot.ArchiWebHandler.GetActiveTradeOffers().ConfigureAwait(false);
if ((tradeOffers == null) || (tradeOffers.Count == 0)) {
return false;
@ -412,11 +386,11 @@ namespace ArchiSteamFarm {
HandledTradeOfferIDs.IntersectWith(tradeOffers.Select(tradeOffer => tradeOffer.TradeOfferID));
}
IEnumerable<Task<(ParseTradeResult TradeResult, bool RequiresMobileConfirmation)>> tasks = tradeOffers.Where(tradeOffer => !HandledTradeOfferIDs.Contains(tradeOffer.TradeOfferID)).Select(ParseTrade);
IList<(ParseTradeResult TradeResult, bool RequiresMobileConfirmation)> results = await Utilities.InParallel(tasks).ConfigureAwait(false);
IEnumerable<Task<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)>> tasks = tradeOffers.Where(tradeOffer => !HandledTradeOfferIDs.Contains(tradeOffer.TradeOfferID)).Select(ParseTrade);
IList<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)> results = await Utilities.InParallel(tasks).ConfigureAwait(false);
if (Bot.HasMobileAuthenticator) {
HashSet<ulong> mobileTradeOfferIDs = results.Where(result => (result.TradeResult != null) && (result.TradeResult.Result == ParseTradeResult.EResult.Accepted) && result.RequiresMobileConfirmation).Select(result => result.TradeResult.TradeOfferID).ToHashSet();
HashSet<ulong> mobileTradeOfferIDs = results.Where(result => (result.TradeResult != null) && (result.TradeResult.Result == ParseTradeResult.EResult.Accepted) && result.RequiresMobileConfirmation).Select(result => result.TradeResult!.TradeOfferID).ToHashSet();
if (mobileTradeOfferIDs.Count > 0) {
(bool twoFactorSuccess, _) = await Bot.Actions.HandleTwoFactorAuthenticationConfirmations(true, MobileAuthenticator.Confirmation.EType.Trade, mobileTradeOfferIDs, true).ConfigureAwait(false);
@ -429,16 +403,18 @@ namespace ArchiSteamFarm {
}
}
await PluginsCore.OnBotTradeOfferResults(Bot, results.Select(result => result.TradeResult).ToHashSet()).ConfigureAwait(false);
HashSet<ParseTradeResult> validTradeResults = results.Where(result => result.TradeResult != null).Select(result => result.TradeResult!).ToHashSet();
if (validTradeResults.Count > 0) {
await PluginsCore.OnBotTradeOfferResults(Bot, validTradeResults).ConfigureAwait(false);
}
return results.Any(result => (result.TradeResult != null) && (result.TradeResult.Result == ParseTradeResult.EResult.Accepted) && (!result.RequiresMobileConfirmation || Bot.HasMobileAuthenticator) && (result.TradeResult.ReceivedItemTypes?.Any(receivedItemType => Bot.BotConfig.LootableTypes.Contains(receivedItemType)) == true));
}
private async Task<(ParseTradeResult TradeResult, bool RequiresMobileConfirmation)> ParseTrade(Steam.TradeOffer tradeOffer) {
private async Task<(ParseTradeResult? TradeResult, bool RequiresMobileConfirmation)> ParseTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Bot.ArchiLogger.LogNullError(nameof(tradeOffer));
return (null, false);
throw new ArgumentNullException(nameof(tradeOffer));
}
if (tradeOffer.State != ETradeOfferState.Active) {
@ -518,10 +494,12 @@ namespace ArchiSteamFarm {
}
private async Task<ParseTradeResult.EResult> ShouldAcceptTrade(Steam.TradeOffer tradeOffer) {
if (tradeOffer == null) {
Bot.ArchiLogger.LogNullError(nameof(tradeOffer));
if (Bot.Bots == null) {
throw new ArgumentNullException(nameof(Bot.Bots));
}
return ParseTradeResult.EResult.Unknown;
if ((tradeOffer == null) || (ASF.GlobalConfig == null)) {
throw new ArgumentNullException(nameof(tradeOffer) + " || " + nameof(ASF.GlobalConfig));
}
if (tradeOffer.OtherSteamID64 != 0) {
@ -677,9 +655,9 @@ namespace ArchiSteamFarm {
[PublicAPI]
public readonly ulong TradeOfferID;
internal readonly ImmutableHashSet<Steam.Asset.EType> ReceivedItemTypes;
internal readonly ImmutableHashSet<Steam.Asset.EType>? ReceivedItemTypes;
internal ParseTradeResult(ulong tradeOfferID, EResult result, IReadOnlyCollection<Steam.Asset> itemsToReceive = null) {
internal ParseTradeResult(ulong tradeOfferID, EResult result, IReadOnlyCollection<Steam.Asset>? itemsToReceive = null) {
if ((tradeOfferID == 0) || (result == EResult.Unknown)) {
throw new ArgumentNullException(nameof(tradeOfferID) + " || " + nameof(result));
}

View file

@ -44,9 +44,7 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static string GetArgsAsText(string[] args, byte argsToSkip, string delimiter) {
if ((args == null) || (args.Length <= argsToSkip) || string.IsNullOrEmpty(delimiter)) {
ASF.ArchiLogger.LogNullError(nameof(args) + " || " + nameof(argsToSkip) + " || " + nameof(delimiter));
return null;
throw new ArgumentNullException(nameof(args) + " || " + nameof(argsToSkip) + " || " + nameof(delimiter));
}
return string.Join(delimiter, args.Skip(argsToSkip));
@ -55,36 +53,21 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static string GetArgsAsText(string text, byte argsToSkip) {
if (string.IsNullOrEmpty(text)) {
ASF.ArchiLogger.LogNullError(nameof(text));
return null;
throw new ArgumentNullException(nameof(text));
}
string[] args = text.Split((char[]) null, argsToSkip + 1, StringSplitOptions.RemoveEmptyEntries);
string[] args = text.Split(new char[0], argsToSkip + 1, StringSplitOptions.RemoveEmptyEntries);
return args[^1];
}
[PublicAPI]
public static string GetAttributeValue(this INode node, string attributeName) {
if ((node == null) || string.IsNullOrEmpty(attributeName)) {
ASF.ArchiLogger.LogNullError(nameof(node) + " || " + nameof(attributeName));
return null;
}
return node is IElement element ? element.GetAttribute(attributeName) : null;
}
[PublicAPI]
public static uint GetUnixTime() => (uint) DateTimeOffset.UtcNow.ToUnixTimeSeconds();
[PublicAPI]
public static async void InBackground(Action action, bool longRunning = false) {
if (action == null) {
ASF.ArchiLogger.LogNullError(nameof(action));
return;
throw new ArgumentNullException(nameof(action));
}
TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
@ -99,9 +82,7 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static async void InBackground<T>(Func<T> function, bool longRunning = false) {
if (function == null) {
ASF.ArchiLogger.LogNullError(nameof(function));
return;
throw new ArgumentNullException(nameof(function));
}
TaskCreationOptions options = TaskCreationOptions.DenyChildAttach;
@ -116,14 +97,12 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static async Task<IList<T>> InParallel<T>(IEnumerable<Task<T>> tasks) {
if (tasks == null) {
ASF.ArchiLogger.LogNullError(nameof(tasks));
return null;
throw new ArgumentNullException(nameof(tasks));
}
IList<T> results;
switch (ASF.GlobalConfig.OptimizationMode) {
switch (ASF.GlobalConfig?.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
results = new List<T>();
@ -144,12 +123,10 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static async Task InParallel(IEnumerable<Task> tasks) {
if (tasks == null) {
ASF.ArchiLogger.LogNullError(nameof(tasks));
return;
throw new ArgumentNullException(nameof(tasks));
}
switch (ASF.GlobalConfig.OptimizationMode) {
switch (ASF.GlobalConfig?.OptimizationMode) {
case GlobalConfig.EOptimizationMode.MinMemoryUsage:
foreach (Task task in tasks) {
await task.ConfigureAwait(false);
@ -169,9 +146,7 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static bool IsValidCdKey(string key) {
if (string.IsNullOrEmpty(key)) {
ASF.ArchiLogger.LogNullError(nameof(key));
return false;
throw new ArgumentNullException(nameof(key));
}
return Regex.IsMatch(key, @"^[0-9A-Z]{4,7}-[0-9A-Z]{4,7}-[0-9A-Z]{4,7}(?:(?:-[0-9A-Z]{4,7})?(?:-[0-9A-Z]{4,7}))?$", RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
@ -180,9 +155,7 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static bool IsValidHexadecimalText(string text) {
if (string.IsNullOrEmpty(text)) {
ASF.ArchiLogger.LogNullError(nameof(text));
return false;
throw new ArgumentNullException(nameof(text));
}
return (text.Length % 2 == 0) && text.All(Uri.IsHexDigit);
@ -225,23 +198,17 @@ namespace ArchiSteamFarm {
}
}
[ItemNotNull]
[NotNull]
[PublicAPI]
public static List<IElement> SelectElementNodes([NotNull] this IElement element, string xpath) => element.SelectNodes(xpath).Cast<IElement>().ToList();
public static List<IElement> SelectElementNodes(this IElement element, string xpath) => element.SelectNodes(xpath).Cast<IElement>().ToList();
[ItemNotNull]
[NotNull]
[PublicAPI]
public static List<IElement> SelectNodes([NotNull] this IDocument document, string xpath) => document.Body.SelectNodes(xpath).Cast<IElement>().ToList();
public static List<IElement> SelectNodes(this IDocument document, string xpath) => document.Body.SelectNodes(xpath).Cast<IElement>().ToList();
[CanBeNull]
[PublicAPI]
public static IElement SelectSingleElementNode([NotNull] this IElement element, string xpath) => (IElement) element.SelectSingleNode(xpath);
public static IElement? SelectSingleElementNode(this IElement element, string xpath) => (IElement?) element.SelectSingleNode(xpath);
[CanBeNull]
[PublicAPI]
public static IElement SelectSingleNode([NotNull] this IDocument document, string xpath) => (IElement) document.Body.SelectSingleNode(xpath);
public static IElement? SelectSingleNode(this IDocument document, string xpath) => (IElement?) document.Body.SelectSingleNode(xpath);
[PublicAPI]
public static IEnumerable<T> ToEnumerable<T>(this T item) {
@ -251,9 +218,8 @@ namespace ArchiSteamFarm {
[PublicAPI]
public static string ToHumanReadable(this TimeSpan timeSpan) => timeSpan.Humanize(3, maxUnit: TimeUnit.Year, minUnit: TimeUnit.Second);
[NotNull]
[PublicAPI]
public static Task<T> ToLongRunningTask<T>([NotNull] this AsyncJob<T> job) where T : CallbackMsg {
public static Task<T> ToLongRunningTask<T>(this AsyncJob<T> job) where T : CallbackMsg {
if (job == null) {
throw new ArgumentNullException(nameof(job));
}
@ -263,9 +229,8 @@ namespace ArchiSteamFarm {
return job.ToTask();
}
[NotNull]
[PublicAPI]
public static Task<AsyncJobMultiple<T>.ResultSet> ToLongRunningTask<T>([NotNull] this AsyncJobMultiple<T> job) where T : CallbackMsg {
public static Task<AsyncJobMultiple<T>.ResultSet> ToLongRunningTask<T>(this AsyncJobMultiple<T> job) where T : CallbackMsg {
if (job == null) {
throw new ArgumentNullException(nameof(job));
}
@ -277,9 +242,7 @@ namespace ArchiSteamFarm {
internal static void DeleteEmptyDirectoriesRecursively(string directory) {
if (string.IsNullOrEmpty(directory)) {
ASF.ArchiLogger.LogNullError(nameof(directory));
return;
throw new ArgumentNullException(nameof(directory));
}
if (!Directory.Exists(directory)) {
@ -299,33 +262,19 @@ namespace ArchiSteamFarm {
}
}
internal static string GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
internal static string? GetCookieValue(this CookieContainer cookieContainer, string url, string name) {
if ((cookieContainer == null) || string.IsNullOrEmpty(url) || string.IsNullOrEmpty(name)) {
ASF.ArchiLogger.LogNullError(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name));
return null;
throw new ArgumentNullException(nameof(cookieContainer) + " || " + nameof(url) + " || " + nameof(name));
}
Uri uri;
try {
uri = new Uri(url);
} catch (UriFormatException e) {
ASF.ArchiLogger.LogGenericException(e);
return null;
}
CookieCollection cookies = cookieContainer.GetCookies(uri);
CookieCollection cookies = cookieContainer.GetCookies(new Uri(url));
return cookies.Count > 0 ? (from Cookie cookie in cookies where cookie.Name.Equals(name) select cookie.Value).FirstOrDefault() : null;
}
internal static bool RelativeDirectoryStartsWith(string directory, params string[] prefixes) {
if (string.IsNullOrEmpty(directory) || (prefixes == null) || (prefixes.Length == 0)) {
ASF.ArchiLogger.LogNullError(nameof(directory) + " || " + nameof(prefixes));
return false;
throw new ArgumentNullException(nameof(directory) + " || " + nameof(prefixes));
}
return (from prefix in prefixes where directory.Length > prefix.Length let pathSeparator = directory[prefix.Length] where (pathSeparator == Path.DirectorySeparatorChar) || (pathSeparator == Path.AltDirectorySeparatorChar) select prefix).Any(prefix => directory.StartsWith(prefix, StringComparison.Ordinal));

View file

@ -54,16 +54,16 @@ namespace ArchiSteamFarm {
private readonly HttpClient HttpClient;
private readonly HttpClientHandler HttpClientHandler;
internal WebBrowser([NotNull] ArchiLogger archiLogger, IWebProxy webProxy = null, bool extendedTimeout = false) {
internal WebBrowser(ArchiLogger archiLogger, IWebProxy? webProxy = null, bool extendedTimeout = false) {
ArchiLogger = archiLogger ?? throw new ArgumentNullException(nameof(archiLogger));
HttpClientHandler = new HttpClientHandler {
AllowAutoRedirect = false, // This must be false if we want to handle custom redirection schemes such as "steammobile://"
#if !NETFRAMEWORK
AutomaticDecompression = DecompressionMethods.All,
#else
#if NETFRAMEWORK
AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip,
#else
AutomaticDecompression = DecompressionMethods.All,
#endif
CookieContainer = CookieContainer
@ -86,9 +86,12 @@ namespace ArchiSteamFarm {
HttpClientHandler.Dispose();
}
[NotNull]
[PublicAPI]
public HttpClient GenerateDisposableHttpClient(bool extendedTimeout = false) {
if (ASF.GlobalConfig == null) {
throw new ArgumentNullException(nameof(ASF.GlobalConfig));
}
HttpClient result = new HttpClient(HttpClientHandler, false) {
#if !NETFRAMEWORK
DefaultRequestVersion = HttpVersion.Version20,
@ -103,19 +106,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<HtmlDocumentResponse> UrlGetToHtmlDocument(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
public async Task<HtmlDocumentResponse?> UrlGetToHtmlDocument(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
HtmlDocumentResponse result = null;
HtmlDocumentResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
await using StreamResponse? response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -148,19 +148,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<ObjectResponse<T>> UrlGetToJsonObject<T>(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
public async Task<ObjectResponse<T>?> UrlGetToJsonObject<T>(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
ObjectResponse<T> result = null;
ObjectResponse<T>? result = null;
for (byte i = 0; i < maxTries; i++) {
await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
await using StreamResponse? response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -174,7 +171,7 @@ namespace ArchiSteamFarm {
continue;
}
T obj;
T? obj;
try {
using StreamReader streamReader = new StreamReader(response.Content);
@ -182,6 +179,12 @@ namespace ArchiSteamFarm {
JsonSerializer serializer = new JsonSerializer();
obj = serializer.Deserialize<T>(jsonReader);
if (obj == null) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
@ -199,19 +202,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<XmlDocumentResponse> UrlGetToXmlDocument(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
public async Task<XmlDocumentResponse?> UrlGetToXmlDocument(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
XmlDocumentResponse result = null;
XmlDocumentResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
await using StreamResponse? response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -246,19 +246,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<BasicResponse> UrlHead(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
public async Task<BasicResponse?> UrlHead(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
BasicResponse result = null;
BasicResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
using HttpResponseMessage response = await InternalHead(request, referer).ConfigureAwait(false);
using HttpResponseMessage? response = await InternalHead(request, referer).ConfigureAwait(false);
if (response == null) {
continue;
@ -283,19 +280,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<BasicResponse> UrlPost<T>(string request, T data = null, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
public async Task<BasicResponse?> UrlPost<T>(string request, T? data = null, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
BasicResponse result = null;
BasicResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
using HttpResponseMessage response = await InternalPost(request, data, referer).ConfigureAwait(false);
using HttpResponseMessage? response = await InternalPost(request, data, referer).ConfigureAwait(false);
if (response == null) {
continue;
@ -320,19 +314,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<HtmlDocumentResponse> UrlPostToHtmlDocument<T>(string request, T data = null, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
public async Task<HtmlDocumentResponse?> UrlPostToHtmlDocument<T>(string request, T? data = null, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
HtmlDocumentResponse result = null;
HtmlDocumentResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
await using StreamResponse response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
await using StreamResponse? response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -365,19 +356,16 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
[PublicAPI]
public async Task<ObjectResponse<TResult>> UrlPostToJsonObject<TResult, TData>(string request, TData data = null, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where TResult : class where TData : class {
public async Task<ObjectResponse<TResult>?> UrlPostToJsonObject<TResult, TData>(string request, TData? data = null, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where TResult : class where TData : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
ObjectResponse<TResult> result = null;
ObjectResponse<TResult>? result = null;
for (byte i = 0; i < maxTries; i++) {
await using StreamResponse response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
await using StreamResponse? response = await UrlPostToStream(request, data, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -391,7 +379,7 @@ namespace ArchiSteamFarm {
continue;
}
TResult obj;
TResult? obj;
try {
using StreamReader steamReader = new StreamReader(response.Content);
@ -399,6 +387,12 @@ namespace ArchiSteamFarm {
JsonSerializer serializer = new JsonSerializer();
obj = serializer.Deserialize<TResult>(jsonReader);
if (obj == null) {
ArchiLogger.LogGenericWarning(string.Format(Strings.ErrorIsEmpty, nameof(obj)));
continue;
}
} catch (Exception e) {
ArchiLogger.LogGenericWarningException(e);
@ -432,33 +426,18 @@ namespace ArchiSteamFarm {
}
}
internal static async Task<IDocument> StringToHtmlDocument(string html) {
if (string.IsNullOrEmpty(html)) {
ASF.ArchiLogger.LogNullError(nameof(html));
return null;
}
IBrowsingContext context = BrowsingContext.New();
return await context.OpenAsync(req => req.Content(html)).ConfigureAwait(false);
}
[ItemCanBeNull]
internal async Task<BinaryResponse> UrlGetToBinaryWithProgress(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
internal async Task<BinaryResponse?> UrlGetToBinaryWithProgress(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
BinaryResponse result = null;
BinaryResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
const byte printPercentage = 10;
const byte maxBatches = 99 / printPercentage;
await using StreamResponse response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
await using StreamResponse? response = await UrlGetToStream(request, referer, requestOptions | ERequestOptions.ReturnClientErrors, 1).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -474,10 +453,11 @@ namespace ArchiSteamFarm {
ArchiLogger.LogGenericDebug("0%...");
#if !NETFRAMEWORK
await
#if NETFRAMEWORK
using MemoryStream ms = new MemoryStream((int) response.Length);
#else
await using MemoryStream ms = new MemoryStream((int) response.Length);
#endif
using MemoryStream ms = new MemoryStream((int) response.Length);
try {
byte batch = 0;
@ -525,18 +505,15 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
internal async Task<StringResponse> UrlGetToString(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
internal async Task<StringResponse?> UrlGetToString(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
StringResponse result = null;
StringResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
using HttpResponseMessage response = await InternalGet(request, referer).ConfigureAwait(false);
using HttpResponseMessage? response = await InternalGet(request, referer).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -561,41 +538,33 @@ namespace ArchiSteamFarm {
return result;
}
private async Task<HttpResponseMessage> InternalGet(string request, string referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) {
private async Task<HttpResponseMessage?> InternalGet(string request, string? referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));
return null;
throw new ArgumentNullException(nameof(request));
}
return await InternalRequest<object>(new Uri(request), HttpMethod.Get, null, referer, httpCompletionOption).ConfigureAwait(false);
}
private async Task<HttpResponseMessage> InternalHead(string request, string referer = null) {
private async Task<HttpResponseMessage?> InternalHead(string request, string? referer = null) {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));
return null;
throw new ArgumentNullException(nameof(request));
}
return await InternalRequest<object>(new Uri(request), HttpMethod.Head, null, referer).ConfigureAwait(false);
}
private async Task<HttpResponseMessage> InternalPost<T>(string request, T data = null, string referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) where T : class {
private async Task<HttpResponseMessage?> InternalPost<T>(string request, T? data = null, string? referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead) where T : class {
if (string.IsNullOrEmpty(request)) {
ArchiLogger.LogNullError(nameof(request));
return null;
throw new ArgumentNullException(nameof(request));
}
return await InternalRequest(new Uri(request), HttpMethod.Post, data, referer, httpCompletionOption).ConfigureAwait(false);
}
private async Task<HttpResponseMessage> InternalRequest<T>(Uri requestUri, HttpMethod httpMethod, T data = null, string referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, byte maxRedirections = MaxTries) where T : class {
private async Task<HttpResponseMessage?> InternalRequest<T>(Uri requestUri, HttpMethod httpMethod, T? data = null, string? referer = null, HttpCompletionOption httpCompletionOption = HttpCompletionOption.ResponseContentRead, byte maxRedirections = MaxTries) where T : class {
if ((requestUri == null) || (httpMethod == null)) {
ArchiLogger.LogNullError(nameof(requestUri) + " || " + nameof(httpMethod));
return null;
throw new ArgumentNullException(nameof(requestUri) + " || " + nameof(httpMethod));
}
HttpResponseMessage response;
@ -677,7 +646,7 @@ namespace ArchiSteamFarm {
return response;
default:
// We have no clue about those, but maybe HttpClient can handle them for us
ASF.ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(redirectUri.Scheme), redirectUri.Scheme));
ArchiLogger.LogGenericError(string.Format(Strings.WarningUnknownValuePleaseReport, nameof(redirectUri.Scheme), redirectUri.Scheme));
break;
}
@ -728,18 +697,15 @@ namespace ArchiSteamFarm {
}
}
[ItemCanBeNull]
private async Task<StreamResponse> UrlGetToStream(string request, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
private async Task<StreamResponse?> UrlGetToStream(string request, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
StreamResponse result = null;
StreamResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
HttpResponseMessage response = await InternalGet(request, referer, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
HttpResponseMessage? response = await InternalGet(request, referer, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -764,18 +730,15 @@ namespace ArchiSteamFarm {
return result;
}
[ItemCanBeNull]
private async Task<StreamResponse> UrlPostToStream<T>(string request, T data = null, string referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
private async Task<StreamResponse?> UrlPostToStream<T>(string request, T? data = null, string? referer = null, ERequestOptions requestOptions = ERequestOptions.None, byte maxTries = MaxTries) where T : class {
if (string.IsNullOrEmpty(request) || (maxTries == 0)) {
ArchiLogger.LogNullError(nameof(request) + " || " + nameof(maxTries));
return null;
throw new ArgumentNullException(nameof(request) + " || " + nameof(maxTries));
}
StreamResponse result = null;
StreamResponse? result = null;
for (byte i = 0; i < maxTries; i++) {
HttpResponseMessage response = await InternalPost(request, data, referer, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
HttpResponseMessage? response = await InternalPost(request, data, referer, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
if (response?.StatusCode.IsClientErrorCode() == true) {
if (requestOptions.HasFlag(ERequestOptions.ReturnClientErrors)) {
@ -806,7 +769,7 @@ namespace ArchiSteamFarm {
internal readonly Uri FinalUri;
internal BasicResponse([NotNull] HttpResponseMessage httpResponseMessage) {
internal BasicResponse(HttpResponseMessage httpResponseMessage) {
if (httpResponseMessage == null) {
throw new ArgumentNullException(nameof(httpResponseMessage));
}
@ -815,7 +778,7 @@ namespace ArchiSteamFarm {
StatusCode = httpResponseMessage.StatusCode;
}
internal BasicResponse([NotNull] BasicResponse basicResponse) {
internal BasicResponse(BasicResponse basicResponse) {
if (basicResponse == null) {
throw new ArgumentNullException(nameof(basicResponse));
}
@ -827,15 +790,15 @@ namespace ArchiSteamFarm {
public sealed class HtmlDocumentResponse : BasicResponse, IDisposable {
[PublicAPI]
public readonly IDocument Content;
public readonly IDocument? Content;
internal HtmlDocumentResponse([NotNull] BasicResponse basicResponse) : base(basicResponse) {
internal HtmlDocumentResponse(BasicResponse basicResponse) : base(basicResponse) {
if (basicResponse == null) {
throw new ArgumentNullException(nameof(basicResponse));
}
}
private HtmlDocumentResponse([NotNull] StreamResponse streamResponse, [NotNull] IDocument document) : this(streamResponse) {
private HtmlDocumentResponse(StreamResponse streamResponse, IDocument document) : this(streamResponse) {
if ((streamResponse == null) || (document == null)) {
throw new ArgumentNullException(nameof(streamResponse) + " || " + nameof(document));
}
@ -845,8 +808,7 @@ namespace ArchiSteamFarm {
public void Dispose() => Content?.Dispose();
[ItemCanBeNull]
internal static async Task<HtmlDocumentResponse> Create([NotNull] StreamResponse streamResponse) {
internal static async Task<HtmlDocumentResponse?> Create(StreamResponse streamResponse) {
if (streamResponse == null) {
throw new ArgumentNullException(nameof(streamResponse));
}
@ -865,11 +827,11 @@ namespace ArchiSteamFarm {
}
}
public sealed class ObjectResponse<T> : BasicResponse {
public sealed class ObjectResponse<T> : BasicResponse where T : class {
[PublicAPI]
public readonly T Content;
public readonly T? Content;
internal ObjectResponse([NotNull] StreamResponse streamResponse, T content) : this(streamResponse) {
internal ObjectResponse(StreamResponse streamResponse, T content) : this(streamResponse) {
if (streamResponse == null) {
throw new ArgumentNullException(nameof(streamResponse));
}
@ -877,7 +839,7 @@ namespace ArchiSteamFarm {
Content = content;
}
internal ObjectResponse([NotNull] BasicResponse basicResponse) : base(basicResponse) {
internal ObjectResponse(BasicResponse basicResponse) : base(basicResponse) {
if (basicResponse == null) {
throw new ArgumentNullException(nameof(basicResponse));
}
@ -886,9 +848,9 @@ namespace ArchiSteamFarm {
public sealed class XmlDocumentResponse : BasicResponse {
[PublicAPI]
public readonly XmlDocument Content;
public readonly XmlDocument? Content;
internal XmlDocumentResponse([NotNull] StreamResponse streamResponse, XmlDocument content) : this(streamResponse) {
internal XmlDocumentResponse(StreamResponse streamResponse, XmlDocument content) : this(streamResponse) {
if (streamResponse == null) {
throw new ArgumentNullException(nameof(streamResponse));
}
@ -896,7 +858,7 @@ namespace ArchiSteamFarm {
Content = content;
}
internal XmlDocumentResponse([NotNull] BasicResponse basicResponse) : base(basicResponse) {
internal XmlDocumentResponse(BasicResponse basicResponse) : base(basicResponse) {
if (basicResponse == null) {
throw new ArgumentNullException(nameof(basicResponse));
}
@ -910,9 +872,9 @@ namespace ArchiSteamFarm {
}
internal sealed class BinaryResponse : BasicResponse {
internal readonly byte[] Content;
internal readonly byte[]? Content;
internal BinaryResponse([NotNull] BasicResponse basicResponse, [NotNull] byte[] content) : this(basicResponse) {
internal BinaryResponse(BasicResponse basicResponse, byte[] content) : this(basicResponse) {
if ((basicResponse == null) || (content == null)) {
throw new ArgumentNullException(nameof(basicResponse) + " || " + nameof(content));
}
@ -920,7 +882,7 @@ namespace ArchiSteamFarm {
Content = content;
}
internal BinaryResponse([NotNull] BasicResponse basicResponse) : base(basicResponse) {
internal BinaryResponse(BasicResponse basicResponse) : base(basicResponse) {
if (basicResponse == null) {
throw new ArgumentNullException(nameof(basicResponse));
}
@ -928,12 +890,12 @@ namespace ArchiSteamFarm {
}
internal sealed class StreamResponse : BasicResponse, IAsyncDisposable {
internal readonly Stream Content;
internal readonly Stream? Content;
internal readonly uint Length;
private readonly HttpResponseMessage ResponseMessage;
internal StreamResponse([NotNull] HttpResponseMessage httpResponseMessage, [NotNull] Stream content) : this(httpResponseMessage) {
internal StreamResponse(HttpResponseMessage httpResponseMessage, Stream content) : this(httpResponseMessage) {
if ((httpResponseMessage == null) || (content == null)) {
throw new ArgumentNullException(nameof(httpResponseMessage) + " || " + nameof(content));
}
@ -941,7 +903,7 @@ namespace ArchiSteamFarm {
Content = content;
}
internal StreamResponse([NotNull] HttpResponseMessage httpResponseMessage) : base(httpResponseMessage) {
internal StreamResponse(HttpResponseMessage httpResponseMessage) : base(httpResponseMessage) {
if (httpResponseMessage == null) {
throw new ArgumentNullException(nameof(httpResponseMessage));
}
@ -960,9 +922,9 @@ namespace ArchiSteamFarm {
}
internal sealed class StringResponse : BasicResponse {
internal readonly string Content;
internal readonly string? Content;
internal StringResponse([NotNull] HttpResponseMessage httpResponseMessage, [NotNull] string content) : this(httpResponseMessage) {
internal StringResponse(HttpResponseMessage httpResponseMessage, string content) : this(httpResponseMessage) {
if ((httpResponseMessage == null) || (content == null)) {
throw new ArgumentNullException(nameof(httpResponseMessage) + " || " + nameof(content));
}
@ -970,7 +932,7 @@ namespace ArchiSteamFarm {
Content = content;
}
internal StringResponse([NotNull] HttpResponseMessage httpResponseMessage) : base(httpResponseMessage) {
internal StringResponse(HttpResponseMessage httpResponseMessage) : base(httpResponseMessage) {
if (httpResponseMessage == null) {
throw new ArgumentNullException(nameof(httpResponseMessage));
}

View file

@ -13,6 +13,7 @@
<ErrorReport>none</ErrorReport>
<LangVersion>latest</LangVersion>
<NoWarn>1591</NoWarn>
<Nullable>enable</Nullable>
<PackageIcon>../resources/ASF.ico</PackageIcon>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/JustArchiNET/ArchiSteamFarm</PackageProjectUrl>