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
+< 提名至少一款游戏: √ 为各奖项提名一款游戏: √
+```
+
### 实用功能
| 命令 | 缩写 | 权限 | 说明 |