diff --git a/ASFEnhance/ASFEnhance.cs b/ASFEnhance/ASFEnhance.cs index 38271a7d..60ca9bcb 100644 --- a/ASFEnhance/ASFEnhance.cs +++ b/ASFEnhance/ASFEnhance.cs @@ -44,6 +44,15 @@ public void OnLoaded() case "CA": return await Cart.Command.ResponseGetCartGames(steamID, "ASF").ConfigureAwait(false); + //EVENT + case "EVENT": + case "E": + return await Event.Command.ResponseSteamEvents(bot, steamID, "").ConfigureAwait(false); + + case "EVENTCHECK": + case "EC": + return await Event.Command.ResponseCheckSummerBadge(bot, steamID).ConfigureAwait(false); + //Cart case "CART": case "C": @@ -96,6 +105,18 @@ public void OnLoaded() case "P": return await bot.Commands.Response(steamID, "POINTS " + Utilities.GetArgsAsText(message, 1)).ConfigureAwait(false); + //Event + case "EVENT" when args.Length > 2: + case "E" when args.Length > 2: + return await Event.Command.ResponseSteamEvents(steamID, args[1], Utilities.GetArgsAsText(message, 2)).ConfigureAwait(false); + case "EVENT": + case "E": + return await Event.Command.ResponseSteamEvents(bot, steamID, args[1]).ConfigureAwait(false); + + case "EVENTCHECK": + case "EC": + return await Event.Command.ResponseCheckSummerBadge(steamID, Utilities.GetArgsAsText(message, 1)).ConfigureAwait(false); + //WishList case "ADDWISHLIST" when args.Length > 2: case "AW" when args.Length > 2: diff --git a/ASFEnhance/ASFEnhance.csproj b/ASFEnhance/ASFEnhance.csproj index 881f1658..40a9a87d 100644 --- a/ASFEnhance/ASFEnhance.csproj +++ b/ASFEnhance/ASFEnhance.csproj @@ -37,6 +37,9 @@ true + + + diff --git a/ASFEnhance/AssemblyInfo.cs b/ASFEnhance/AssemblyInfo.cs index b28dcdd4..cd91d1e9 100644 --- a/ASFEnhance/AssemblyInfo.cs +++ b/ASFEnhance/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; [assembly: System.CLSCompliant(false)] -[assembly: AssemblyVersion("1.5.7.194")] -[assembly: AssemblyFileVersion("1.5.7.194")] +[assembly: AssemblyVersion("1.5.8.208")] +[assembly: AssemblyFileVersion("1.5.8.208")] [assembly: AssemblyCopyright("Copyright © 2021 Chr_")] [assembly: AssemblyProduct("ASFEnhance")] diff --git a/ASFEnhance/Data/AppDetails.cs b/ASFEnhance/Data/AppDetails.cs new file mode 100644 index 00000000..a509179d --- /dev/null +++ b/ASFEnhance/Data/AppDetails.cs @@ -0,0 +1,53 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using SteamKit2; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +namespace Chrxw.ASFEnhance.Data +{ + [SuppressMessage("ReSharper", "ClassCannotBeInstantiated")] + internal sealed class AppDetailsResponse + { + [JsonExtensionData] + private IDictionary _additionalData; + } + internal sealed class AppDetailsPayload + { + [JsonProperty(PropertyName = "success", Required = Required.Always)] + public EResult Result { get; private set; } + + [JsonProperty(PropertyName = "data", Required = Required.DisallowNull)] + public AppDetailsData Data { get; private set; } + + [JsonProperty(PropertyName = "dlc", Required = Required.DisallowNull)] + public AppDetailsDlcs Dlc { get; private set; } + + [JsonProperty(PropertyName = "discount", Required = Required.DisallowNull)] + public int Discount { get; private set; } + } + + internal sealed class AppDetailsData + { + [JsonProperty(PropertyName = "type", Required = Required.Always)] + public EResult Result { get; private set; } + + [JsonProperty(PropertyName = "name", Required = Required.DisallowNull)] + public int BasePrice { get; private set; } + + [JsonProperty(PropertyName = "steam_appid", Required = Required.DisallowNull)] + public int Tax { get; private set; } + + [JsonProperty(PropertyName = "required_age", Required = Required.DisallowNull)] + public int Discount { get; private set; } + + [JsonProperty(PropertyName = "is_free", Required = Required.DisallowNull)] + public bool IsFree { get; private set; } + } + internal sealed class AppDetailsDlcs + { + [JsonExtensionData] + private IDictionary Dlcs; + } + +} diff --git a/ASFEnhance/Event/Command.cs b/ASFEnhance/Event/Command.cs new file mode 100644 index 00000000..d1d99845 --- /dev/null +++ b/ASFEnhance/Event/Command.cs @@ -0,0 +1,167 @@ +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + +using ArchiSteamFarm.Core; +using ArchiSteamFarm.Localization; +using ArchiSteamFarm.Steam; +using ArchiSteamFarm.Steam.Storage; +using Chrxw.ASFEnhance.Localization; +using SteamKit2; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using static Chrxw.ASFEnhance.Event.Response; +using static Chrxw.ASFEnhance.Utils; + +namespace Chrxw.ASFEnhance.Event +{ + internal static class Command + { + internal static string Boolen2String(bool value) + { + return value ? "√" : "×"; + } + // 秋促投票 + internal static async Task ResponseSteamEvents(Bot bot, ulong steamID, string gameIDs) + { + if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) + { + throw new ArgumentOutOfRangeException(nameof(steamID)); + } + + if (!bot.HasAccess(steamID, BotConfig.EAccess.Operator)) + { + return null; + } + + if (!bot.IsConnectedAndLoggedOn) + { + return FormatBotResponse(bot, Strings.BotNotConnected); + } + + string[] entries = gameIDs.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + + List intGamsIDs = new(); + + foreach (string entry in entries) + { + if (uint.TryParse(entry, out uint choice)) + { + intGamsIDs.Add(choice); + if (intGamsIDs.Count >= 10) + { + break; + } + } + } + + if (intGamsIDs.Count < 10) //不足10个游戏自动补齐 + { + Random rd = new(); + uint[] defaultGames = new uint[] { 1639930, 1506980, 1374480, 585020, 1639230, 1584090, 1111460, 977880, 1366540, 1398740, 1369630, 1195290 }; + while (intGamsIDs.Count < 10) + { + intGamsIDs.Add(defaultGames[rd.Next(defaultGames.Length)]); + } + } + + for (int i = 0; i < 10; i++) + { + int categoryID = 61 + i; + await WebRequest.MakeVote(bot, intGamsIDs[i], categoryID).ConfigureAwait(false); + } + + SummerBadgeResponse? summerBadgeStatus = await WebRequest.CheckSummerBadge(bot).ConfigureAwait(false); + + if (summerBadgeStatus == null) + { + return FormatBotResponse(bot, Langs.EventReadBadgeStatusFailed); + } + + return FormatBotResponse(bot, string.Format(CurrentCulture, Langs.EventVoteResponse, Boolen2String(summerBadgeStatus.VoteOne), Boolen2String(summerBadgeStatus.VoteAll))); + } + + // 秋促投票(多个Bot) + internal static async Task ResponseSteamEvents(ulong steamID, string botNames, string choose) + { + if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) + { + throw new ArgumentOutOfRangeException(nameof(steamID)); + } + + if (string.IsNullOrEmpty(botNames)) + { + throw new ArgumentNullException(nameof(botNames)); + } + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) + { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(CurrentCulture, Strings.BotNotFound, botNames)) : null; + } + + IList results = await Utilities.InParallel(bots.Select(bot => ResponseSteamEvents(bot, steamID, choose))).ConfigureAwait(false); + + List responses = new(results.Where(result => !string.IsNullOrEmpty(result))!); + + return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; + } + + // 检查秋促徽章 + internal static async Task ResponseCheckSummerBadge(Bot bot, ulong steamID) + { + if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) + { + throw new ArgumentOutOfRangeException(nameof(steamID)); + } + + if (!bot.HasAccess(steamID, BotConfig.EAccess.Operator)) + { + return null; + } + + if (!bot.IsConnectedAndLoggedOn) + { + return FormatBotResponse(bot, Strings.BotNotConnected); + } + + SummerBadgeResponse? summerBadgeStatus = await WebRequest.CheckSummerBadge(bot).ConfigureAwait(false); + + if (summerBadgeStatus == null) + { + return FormatBotResponse(bot, Langs.EventReadBadgeStatusFailed); + } + + return FormatBotResponse(bot, string.Format(CurrentCulture, Langs.EventCheckResponse, Boolen2String(summerBadgeStatus.VoteOne), Boolen2String(summerBadgeStatus.VoteAll), Boolen2String(summerBadgeStatus.PlayOne), Boolen2String(summerBadgeStatus.ReviewOne))); + } + + // 秋促投票(多个Bot) + internal static async Task ResponseCheckSummerBadge(ulong steamID, string botNames) + { + if ((steamID == 0) || !new SteamID(steamID).IsIndividualAccount) + { + throw new ArgumentOutOfRangeException(nameof(steamID)); + } + + if (string.IsNullOrEmpty(botNames)) + { + throw new ArgumentNullException(nameof(botNames)); + } + + HashSet? bots = Bot.GetBots(botNames); + + if ((bots == null) || (bots.Count == 0)) + { + return ASF.IsOwner(steamID) ? FormatStaticResponse(string.Format(CurrentCulture, Strings.BotNotFound, botNames)) : null; + } + + IList results = await Utilities.InParallel(bots.Select(bot => ResponseCheckSummerBadge(bot, steamID))).ConfigureAwait(false); + + List responses = new(results.Where(result => !string.IsNullOrEmpty(result))!); + + return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null; + } + + } +} diff --git a/ASFEnhance/Event/Response.cs b/ASFEnhance/Event/Response.cs new file mode 100644 index 00000000..c95a2237 --- /dev/null +++ b/ASFEnhance/Event/Response.cs @@ -0,0 +1,25 @@ +namespace Chrxw.ASFEnhance.Event +{ + internal class Response + { + //秋促徽章信息 + internal class SummerBadgeResponse + { + //提名一项奖项 + public bool VoteOne { get; } + //提名全部奖项 + public bool VoteAll { get; } + //玩一款提名游戏 + public bool PlayOne { get; } + //评测一款提名游戏 + public bool ReviewOne { get; } + public SummerBadgeResponse(bool VoteOne = false, bool VoteAll = false, bool PlayOne = false, bool ReviewOne = false) + { + this.VoteOne = VoteOne; + this.VoteAll = VoteAll; + this.PlayOne = PlayOne; + this.ReviewOne = ReviewOne; + } + } + } +} diff --git a/ASFEnhance/Event/WebRequest.cs b/ASFEnhance/Event/WebRequest.cs new file mode 100644 index 00000000..6d95edf2 --- /dev/null +++ b/ASFEnhance/Event/WebRequest.cs @@ -0,0 +1,76 @@ +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + +using AngleSharp.Dom; +using ArchiSteamFarm.Core; +using ArchiSteamFarm.Steam; +using ArchiSteamFarm.Web.Responses; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using static Chrxw.ASFEnhance.Event.Response; +using static Chrxw.ASFEnhance.Utils; + +namespace Chrxw.ASFEnhance.Event +{ + internal static class WebRequest + { + // 秋促投票 + internal static async Task MakeVote(Bot bot, uint gameID, int categoryID) + { + Uri request = new(SteamStoreURL, "/steamawards/nominategame"); + Uri referer = new(SteamStoreURL, "/steamawards/category/63"); + + string? sessionID = bot.ArchiWebHandler.WebBrowser.CookieContainer.GetCookieValue(SteamStoreURL, "sessionid"); + + if (string.IsNullOrEmpty(sessionID)) + { + bot.ArchiLogger.LogNullError(nameof(sessionID)); + return false; + } + + Dictionary data = new(5, StringComparer.Ordinal) + { + { "sessionid", sessionID! }, + { "nominatedid", gameID.ToString() }, + { "categoryid", categoryID.ToString() }, + { "source", "3" }, + }; + + await bot.ArchiWebHandler.UrlPostWithSession(request, data: data, referer: referer).ConfigureAwait(false); + + return true; + } + + // 检查秋促徽章 + internal static async Task CheckSummerBadge(Bot bot) + { + Uri request = new(SteamCommunityURL, "/profiles/" + bot.SteamID.ToString() + "/badges/56"); + + HtmlDocumentResponse? response = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request, referer: SteamCommunityURL).ConfigureAwait(false); + + if (response == null) + { + ASF.ArchiLogger.LogGenericInfo("null"); + return null; + } + + bool[] taskStatus = new bool[] { false, false, false, false }; + + for (int i = 0; i < 4; i++) + { + string xpath = string.Format("//div[@class='badge_task'][{0}]/img", i + 1); + IElement? eleTask = response.Content.SelectSingleNode(xpath); + string taskSrc = eleTask?.GetAttribute("src") ?? ""; + + if (string.IsNullOrEmpty(taskSrc)) + { + ASF.ArchiLogger.LogNullError(string.Format("{0}", i)); + continue; + } + + taskStatus[i] = taskSrc.EndsWith("_on.png"); + } + return new SummerBadgeResponse(taskStatus[0], taskStatus[1], taskStatus[2], taskStatus[3]); + } + } +} diff --git a/ASFEnhance/Localization/Langs.Designer.cs b/ASFEnhance/Localization/Langs.Designer.cs index f5c5b8fa..fc71322f 100644 --- a/ASFEnhance/Localization/Langs.Designer.cs +++ b/ASFEnhance/Localization/Langs.Designer.cs @@ -19,7 +19,7 @@ namespace Chrxw.ASFEnhance.Localization { // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen // (以 /str 作为命令选项),或重新生成 VS 项目。 - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Langs { @@ -195,6 +195,33 @@ internal static string Error { } } + /// + /// 查找类似 提名至少一款游戏: {0} 为各奖项提名一款游戏:{1} 玩一款您提名的游戏: {2} 为一款您提名的游戏新发表或更新评测: {3} 的本地化字符串。 + /// + internal static string EventCheckResponse { + get { + return ResourceManager.GetString("EventCheckResponse", resourceCulture); + } + } + + /// + /// 查找类似 读取秋促徽章信息失败 的本地化字符串。 + /// + internal static string EventReadBadgeStatusFailed { + get { + return ResourceManager.GetString("EventReadBadgeStatusFailed", resourceCulture); + } + } + + /// + /// 查找类似 提名至少一款游戏: {0} 为各奖项提名一款游戏: {1} 的本地化字符串。 + /// + internal static string EventVoteResponse { + get { + return ResourceManager.GetString("EventVoteResponse", resourceCulture); + } + } + /// /// 查找类似 失败 的本地化字符串。 /// diff --git a/ASFEnhance/Localization/Langs.en-US.resx b/ASFEnhance/Localization/Langs.en-US.resx index fb053ad0..48344e3d 100644 --- a/ASFEnhance/Localization/Langs.en-US.resx +++ b/ASFEnhance/Localization/Langs.en-US.resx @@ -267,4 +267,13 @@ Unknown wallet area + + Read badge status failed + + + Nominate at least 1 game: {0} Nominate a game in each category: {1} Play a game you nominated: {2} Review or update your review of a game you nominated: {3} + + + Nominate at least 1 game: {0} Nominate a game in each category: {1} + \ No newline at end of file diff --git a/ASFEnhance/Localization/Langs.resx b/ASFEnhance/Localization/Langs.resx index e5189e9e..2daea6f5 100644 --- a/ASFEnhance/Localization/Langs.resx +++ b/ASFEnhance/Localization/Langs.resx @@ -267,4 +267,13 @@ 未找到商店页 + + 读取秋促徽章信息失败 + + + 提名至少一款游戏: {0} 为各奖项提名一款游戏: {1} + + + 提名至少一款游戏: {0} 为各奖项提名一款游戏:{1} 玩一款您提名的游戏: {2} 为一款您提名的游戏新发表或更新评测: {3} + \ No newline at end of file diff --git a/ASFEnhance/Localization/Langs.zh-CN.resx b/ASFEnhance/Localization/Langs.zh-CN.resx index 96e336f5..54bcf2a9 100644 --- a/ASFEnhance/Localization/Langs.zh-CN.resx +++ b/ASFEnhance/Localization/Langs.zh-CN.resx @@ -267,4 +267,13 @@ 钱包区域未知 + + 读取秋促徽章信息失败 + + + 提名至少一款游戏: {0} 为各奖项提名一款游戏: {1} + + + 提名至少一款游戏: {0} 为各奖项提名一款游戏:{1} 玩一款您提名的游戏: {2} 为一款您提名的游戏新发表或更新评测: {3} + \ No newline at end of file diff --git a/README.md b/README.md index 2561021c..e94a1d6d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,25 @@ Post link: [https://keylol.com/t716051-1-1](https://keylol.com/t716051-1-1) ## New Commands +### 秋季促销相关 + +> 本组命令将在秋促结束后删除, 自动投票命令会发送10个请求, 批量使用请注意间隔时间 + +| Command | Shorthand | Access | Description | +| ----------------------- | --------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| `EVENT [Bots] [AppIDs]` | `E` | `Operator` | Vote Steam Awards, gameIDs use "," to split, totaly 10 games needed, if you don't give enough games, it will use built-in random games instead | +| `EVENTCHECK [Bots]` | `EC` | `Operator` | Check summer sale badge tasks status | + +EVENT Usage: + +```txt +> EVENT ASF 1091500,1523370,578080,1426210,1506980,1195290,1369630,1639930,1111460,1366540 +< Nominate at least 1 game: √ Nominate a game in each category: √ + +> EVENTCHECK ASF +< Nominate at least 1 game: √ Nominate a game in each category: √ Play a game you nominated: × Review or update your review of a game you nominated: √ +``` + ### Common Commands | Command | Shorthand | Access | Description | @@ -50,6 +69,7 @@ Post link: [https://keylol.com/t716051-1-1](https://keylol.com/t716051-1-1) | `PURCHASE [Bots]` | `PC` | `Master` | Purchase bot's items for it self | > Steam allows duplicate purchases, please check cart before using PURCHASE command. + ## Shorthand Commands | Shorthand | Equivalent Command | Description | diff --git a/README.zh-CN.md b/README.zh-CN.md index b89a6e24..ad6a8240 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -2,7 +2,7 @@ [![Codacy Badge][codacy_b]][Codacy] [![release][release_b]][Release] [![Download][download_b]][Release] [![License][license_b]][License] -[英文版](README.md) +[English](README.md) > 扩展ASF的功能, 增加几条实用命令 > @@ -12,6 +12,22 @@ ## 新增命令 +### 秋季促销相关 + +> 本组命令将在秋促结束后删除, 自动投票命令会发送10个请求, 批量使用请注意间隔时间 + +| 命令 | 缩写 | 权限 | 说明 | +| ----------------------- | ---- | ---------- | ---------------------------------------------------------------------------------------- | +| `EVENT [Bots] [AppIDs]` | `E` | `Operator` | Steam大奖投票, 需要指定要投票的游戏, 用","分隔, 共10项, 未指定的情况会随机选择内置的游戏 | +| `EVENTCHECK [Bots]` | `EC` | `Operator` | 检查秋促徽章上的任务完成情况 | + +EVENT命令使用举例: + +```txt +> EVENT ASF 1091500,1523370,578080,1426210,1506980,1195290,1369630,1639930,1111460,1366540 +< 提名至少一款游戏: √ 为各奖项提名一款游戏: √ +``` + ### 实用功能 | 命令 | 缩写 | 权限 | 说明 |