This commit is contained in:
Łukasz Domeradzki
2025-10-26 23:15:38 +01:00
parent b362408704
commit 738d949f1f
28 changed files with 46 additions and 46 deletions

View File

@@ -44,7 +44,7 @@ internal sealed class IGitHubPluginUpdates {
private readonly TestContext TestContext;
private CancellationToken CancellationToken => TestContext.CancellationTokenSource.Token;
private CancellationToken CancellationToken => TestContext.CancellationToken;
[UsedImplicitly]
public IGitHubPluginUpdates(TestContext testContext) {

View File

@@ -38,7 +38,7 @@ namespace ArchiSteamFarm.Tests;
internal sealed class SteamChatMessage {
private readonly TestContext TestContext;
private CancellationToken CancellationToken => TestContext.CancellationTokenSource.Token;
private CancellationToken CancellationToken => TestContext.CancellationToken;
[UsedImplicitly]
public SteamChatMessage(TestContext testContext) {

View File

@@ -29,7 +29,7 @@ namespace ArchiSteamFarm.Helpers;
[PublicAPI]
public interface ICrossProcessSemaphore {
void Release();
Task WaitAsync(CancellationToken cancellationToken = default);
Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default);
public void Release();
public Task WaitAsync(CancellationToken cancellationToken = default);
public Task<bool> WaitAsync(int millisecondsTimeout, CancellationToken cancellationToken = default);
}

View File

@@ -40,5 +40,5 @@ public interface IASF : IPlugin {
/// 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>
Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null);
public Task OnASFInit(IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null);
}

View File

@@ -40,12 +40,12 @@ public interface IBot : IPlugin {
/// 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>
Task OnBotDestroy(Bot bot);
public Task 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>
Task OnBotInit(Bot bot);
public Task OnBotInit(Bot bot);
}

View File

@@ -39,17 +39,17 @@ public interface IBotCardsFarmerInfo : IPlugin {
/// </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>
Task OnBotFarmingFinished(Bot bot, bool farmedSomething);
public Task 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>
Task OnBotFarmingStarted(Bot bot);
public Task 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>
Task OnBotFarmingStopped(Bot bot);
public Task OnBotFarmingStopped(Bot bot);
}

View File

@@ -44,5 +44,5 @@ public interface IBotCommand2 : IPlugin {
/// <param name="args">Pre-parsed message using standard ASF delimiters.</param>
/// <param name="steamID">Optionally, steamID of the user who executed the command - may not be available with value of 0 (e.g. ASF API).</param>
/// <returns>Response to the command, or null/empty (as the task value) if the command isn't handled by this plugin.</returns>
Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0);
public Task<string?> OnBotCommand(Bot bot, EAccess access, string message, string[] args, ulong steamID = 0);
}

View File

@@ -39,11 +39,11 @@ public interface IBotConnection : IPlugin {
/// </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>
Task OnBotDisconnected(Bot bot, EResult reason);
public Task 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>
Task OnBotLoggedOn(Bot bot);
public Task OnBotLoggedOn(Bot bot);
}

View File

@@ -41,5 +41,5 @@ public interface IBotCustomMachineInfoProvider : IPlugin {
/// <remarks>This method will be called with very limited amount of bot-related data, as it's used during bot initialization. We recommend to stick with <see cref="Bot.BotName" />, <see cref="Bot.BotConfig" /> and <see cref="Bot.BotDatabase" /> exclusively.</remarks>
/// <param name="bot">Bot object related to this callback.</param>
/// <returns><see cref="IMachineInfoProvider" /> that will be used for the particular bot. You can return null if you want to use default implementation.</returns>
Task<IMachineInfoProvider?> GetMachineInfoProvider(Bot bot);
public Task<IMachineInfoProvider?> GetMachineInfoProvider(Bot bot);
}

View File

@@ -39,5 +39,5 @@ public interface IBotFriendRequest : IPlugin {
/// <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>
Task<bool> OnBotFriendRequest(Bot bot, ulong steamID);
public Task<bool> OnBotFriendRequest(Bot bot, ulong steamID);
}

View File

@@ -41,5 +41,5 @@ public interface IBotIdentity : IPlugin {
/// <param name="data">Full data received by ASF in the callback that relates to this bot (Steam) account.</param>
/// <param name="nickname">Parsed nickname set for this bot (Steam) account.</param>
/// <param name="avatarHash">Parsed hash of the avatar of this bot (Steam) account, as hex.</param>
Task OnSelfPersonaState(Bot bot, SteamFriends.PersonaStateCallback data, string? nickname, string? avatarHash);
public Task OnSelfPersonaState(Bot bot, SteamFriends.PersonaStateCallback data, string? nickname, string? avatarHash);
}

View File

@@ -42,5 +42,5 @@ public interface IBotMessage : IPlugin {
/// <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>
Task<string?> OnBotMessage(Bot bot, ulong steamID, string message);
public Task<string?> OnBotMessage(Bot bot, ulong steamID, string message);
}

View File

@@ -42,5 +42,5 @@ public interface IBotModules : IPlugin {
/// </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>
Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null);
public Task OnBotInitModules(Bot bot, IReadOnlyDictionary<string, JsonElement>? additionalConfigProperties = null);
}

View File

@@ -40,12 +40,12 @@ public interface IBotSteamClient : IPlugin {
/// </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>
Task OnBotSteamCallbacksInit(Bot bot, CallbackManager callbackManager);
public Task 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 or empty collection.</returns>
Task<IReadOnlyCollection<ClientMsgHandler>?> OnBotSteamHandlersInit(Bot bot);
public Task<IReadOnlyCollection<ClientMsgHandler>?> OnBotSteamHandlersInit(Bot bot);
}

View File

@@ -42,5 +42,5 @@ public interface IBotTradeOffer2 : IPlugin {
/// <param name="tradeOffer">Trade offer related to this callback.</param>
/// <param name="asfResult">ASF result in regards to parsing this trade offer, can be useful for determining why it wasn't accepted as part of the core logic.</param>
/// <returns>True if the trade offer should be accepted as part of this plugin, false otherwise.</returns>
Task<bool> OnBotTradeOffer(Bot bot, TradeOffer tradeOffer, ParseTradeResult.EResult asfResult);
public Task<bool> OnBotTradeOffer(Bot bot, TradeOffer tradeOffer, ParseTradeResult.EResult asfResult);
}

View File

@@ -40,5 +40,5 @@ public interface IBotTradeOfferResults : IPlugin {
/// </summary>
/// <param name="bot">Bot object related to this callback.</param>
/// <param name="tradeResults">Trade results related to this callback.</param>
Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection<ParseTradeResult> tradeResults);
public Task OnBotTradeOfferResults(Bot bot, IReadOnlyCollection<ParseTradeResult> tradeResults);
}

View File

@@ -40,5 +40,5 @@ public interface IBotUserNotifications : IPlugin {
/// </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>
Task OnBotUserNotifications(Bot bot, IReadOnlyCollection<UserNotificationsCallback.EUserNotification> newNotifications);
public Task OnBotUserNotifications(Bot bot, IReadOnlyCollection<UserNotificationsCallback.EUserNotification> newNotifications);
}

View File

@@ -37,5 +37,5 @@ public interface IBotsComparer : IPlugin {
/// 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>
StringComparer BotsComparer => StringComparer.Ordinal;
public StringComparer BotsComparer => StringComparer.Ordinal;
}

View File

@@ -39,5 +39,5 @@ public interface ICrossProcessSemaphoreProvider : IPlugin {
/// </summary>
/// <param name="resourceName">Unique resource name provided by ASF for identification purposes.</param>
/// <returns>Concrete implementation of <see cref="ICrossProcessSemaphore" /> providing required functionality. It's allowed to return null if you want to use ASF's default implementation for specified resource instead.</returns>
Task<ICrossProcessSemaphore?> GetCrossProcessSemaphore(string resourceName);
public Task<ICrossProcessSemaphore?> GetCrossProcessSemaphore(string resourceName);
}

View File

@@ -48,14 +48,14 @@ public interface IGitHubPluginUpdates : IPluginUpdates {
/// Boolean value that determines whether your plugin is able to update at the time of calling. You may provide false if, for example, you're inside a critical section and you don't want to update at this time, despite supporting updates otherwise.
/// This effectively skips unnecessary request to GitHub if you're certain that you're not interested in any updates right now.
/// </summary>
bool CanUpdate => true;
public bool CanUpdate => true;
/// <summary>
/// ASF will use this property as a target for GitHub updates. GitHub repository specified here must have valid releases that will be used for updates.
/// </summary>
/// <returns>Repository name in format of {Author}/{Repository}.</returns>
/// <example>JustArchiNET/ArchiSteamFarm</example>
string RepositoryName { get; }
public string RepositoryName { get; }
Task<Uri?> IPluginUpdates.GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, GlobalConfig.EUpdateChannel updateChannel, bool forced) {
ArgumentNullException.ThrowIfNull(asfVersion);
@@ -104,7 +104,7 @@ public interface IGitHubPluginUpdates : IPluginUpdates {
/// - *.zip, if exactly one match is found
/// </remarks>
/// <returns>Target release asset from those provided that should be used for auto-update. You may return null if the update is unavailable, for example, because ASF version/variant is determined unsupported, or due to any other reason.</returns>
Task<ReleaseAsset?> GetTargetReleaseAsset(Version asfVersion, string asfVariant, Version newPluginVersion, IReadOnlyCollection<ReleaseAsset> releaseAssets) {
public Task<ReleaseAsset?> GetTargetReleaseAsset(Version asfVersion, string asfVariant, Version newPluginVersion, IReadOnlyCollection<ReleaseAsset> releaseAssets) {
ArgumentNullException.ThrowIfNull(asfVersion);
ArgumentException.ThrowIfNullOrEmpty(asfVariant);
ArgumentNullException.ThrowIfNull(newPluginVersion);

View File

@@ -38,7 +38,7 @@ public interface IPlugin {
/// </summary>
/// <returns>String that will be used as the name of this plugin.</returns>
[JsonInclude]
string Name { get; }
public string Name { get; }
/// <summary>
/// ASF will use this property as version indicator of your plugin to the user.
@@ -46,10 +46,10 @@ public interface IPlugin {
/// </summary>
/// <returns>Version that will be shown to the user when plugin is loaded.</returns>
[JsonInclude]
Version Version { get; }
public Version Version { get; }
/// <summary>
/// ASF will call this method right after plugin initialization.
/// </summary>
Task OnLoaded();
public Task OnLoaded();
}

View File

@@ -45,15 +45,15 @@ public interface IPluginUpdates : IPlugin {
/// <param name="updateChannel">ASF update channel specified for this request. This might be different from the one specified in <see cref="GlobalConfig" />, as user might've specified other one for this request.</param>
/// <param name="forced">Boolean value specifying whether user has requested forced plugin update, that is, one that should always execute if possible - even with the same and lower version (downgrade).</param>
/// <returns>Target release asset URL that should be used for auto-update. It's permitted to return null if you want to skip update, e.g. because no new version is available.</returns>
Task<Uri?> GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, GlobalConfig.EUpdateChannel updateChannel, bool forced);
public Task<Uri?> GetTargetReleaseURL(Version asfVersion, string asfVariant, bool asfUpdate, GlobalConfig.EUpdateChannel updateChannel, bool forced);
/// <summary>
/// ASF will call this method after update to the new plugin version has been finished, just before restart of the process.
/// </summary>
Task OnPluginUpdateFinished() => Task.CompletedTask;
public Task OnPluginUpdateFinished() => Task.CompletedTask;
/// <summary>
/// ASF will call this method before proceeding with an update to the new plugin version.
/// </summary>
Task OnPluginUpdateProceeding() => Task.CompletedTask;
public Task OnPluginUpdateProceeding() => Task.CompletedTask;
}

View File

@@ -38,7 +38,7 @@ public interface ISteamPICSChanges : IPlugin {
/// 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>
Task<uint> GetPreferredChangeNumberToStartFrom();
public Task<uint> GetPreferredChangeNumberToStartFrom();
/// <summary>
/// ASF will call this method upon receiving any app/package PICS changes. The history is guaranteed to be precise and continuous starting from <see cref="GetPreferredChangeNumberToStartFrom" /> until <see cref="OnPICSChangesRestart" /> is called. It's possible for this method to have duplicated calls across different runs, in particular when some other plugin asks for lower <see cref="GetPreferredChangeNumberToStartFrom" />, therefore you should keep that in mind (and refer to change number of standalone apps/packages).
@@ -46,11 +46,11 @@ public interface ISteamPICSChanges : IPlugin {
/// <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>
Task OnPICSChanges(uint currentChangeNumber, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> appChanges, IReadOnlyDictionary<uint, SteamApps.PICSChangesCallback.PICSChangeData> packageChanges);
public Task 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.
/// </summary>
/// <param name="currentChangeNumber">The change number from which we're restarting the PICS history.</param>
Task OnPICSChangesRestart(uint currentChangeNumber);
public Task OnPICSChangesRestart(uint currentChangeNumber);
}

View File

@@ -38,12 +38,12 @@ public interface IUpdateAware : IPlugin {
/// </summary>
/// <param name="currentVersion">The current (old) version of ASF program.</param>
/// <param name="newVersion">The target (new) version of ASF program.</param>
Task OnUpdateFinished(Version currentVersion, Version newVersion);
public Task OnUpdateFinished(Version currentVersion, Version newVersion);
/// <summary>
/// ASF will call this method before proceeding with an update to a particular ASF version.
/// </summary>
/// <param name="currentVersion">The current (old) version of ASF program.</param>
/// <param name="newVersion">The target (new) version of ASF program.</param>
Task OnUpdateProceeding(Version currentVersion, Version newVersion);
public Task OnUpdateProceeding(Version currentVersion, Version newVersion);
}

View File

@@ -35,7 +35,7 @@ public interface IWebInterface : IPlugin {
/// </summary>
/// <example>www</example>
/// <remarks>You'll need to ship this folder together with your plugin for the interface to work.</remarks>
string PhysicalPath => "www";
public string PhysicalPath => "www";
/// <summary>
/// Specifies web path (address) under which ASF should host your static WWW files in <see cref="PhysicalPath" /> directory. Default value of "/" allows you to override default ASF files and gives you full flexibility in your <see cref="PhysicalPath" /> directory. However, you can instead host your files under some other fixed location specified here, such as "/MyPlugin", which is especially useful if you want to have your own default index.html in addition to the one provided by us (ASF-ui).
@@ -43,5 +43,5 @@ public interface IWebInterface : IPlugin {
/// <example>/MyPlugin</example>
/// <remarks>If you're using path other than default, ensure it does NOT end with a slash.</remarks>
[JsonInclude]
string WebPath => "/";
public string WebPath => "/";
}

View File

@@ -37,11 +37,11 @@ public interface IWebServiceProvider : IPlugin {
/// ASF will call this method during configuration of the IPC endpoints.
/// </summary>
/// <param name="app">Application builder related to this callback.</param>
void OnConfiguringEndpoints(IApplicationBuilder app);
public void OnConfiguringEndpoints(IApplicationBuilder app);
/// <summary>
/// ASF will call this method during configuration of the IPC services.
/// </summary>
/// <param name="services">Service collection related to this callback.</param>
void OnConfiguringServices(IServiceCollection services);
public void OnConfiguringServices(IServiceCollection services);
}

View File

@@ -986,7 +986,7 @@ public sealed class ArchiHandler : ClientMsgHandler, IDisposable {
}
if (gameIDs.Count > 0) {
IReadOnlySet<uint> uniqueGameIDs = gameIDs as IReadOnlySet<uint> ?? gameIDs.ToHashSet();
IEnumerable<uint> uniqueGameIDs = gameIDs as IReadOnlySet<uint> ?? gameIDs.Distinct();
foreach (uint gameID in uniqueGameIDs.Where(static gameID => gameID > 0)) {
if (request.Body.games_played.Count >= MaxGamesPlayedConcurrently) {

View File

@@ -103,7 +103,7 @@ public sealed class BotDatabase : GenericDatabase {
[JsonInclude]
[JsonPropertyName("BackingExtraStorePackagesRefreshedAt")]
internal DateTime ExtraStorePackagesRefreshedAt {
get => field;
get;
set {
if (field == value) {