├── .editorconfig
├── .gitattributes
├── .github
├── CODE_OF_CONDUCT.md
├── FUNDING.yml
├── ISSUE_TEMPLATE
│ ├── bug-report---错误汇报.md
│ └── feature-request---新功能提议.md
└── workflows
│ ├── cleanartifacts.yml
│ └── publish.yml
├── .gitignore
├── .gitmodules
├── ASFEnhance.IPC
├── ASFEnhance.IPC.cs
├── ASFEnhance.IPC.csproj
├── Controllers
│ ├── Base
│ │ └── AbstractController.cs
│ ├── CartController.cs
│ ├── CuratorController.cs
│ ├── ProfileController.cs
│ ├── PurchaseController.cs
│ ├── RecommendController.cs
│ ├── WalletController.cs
│ └── WishlistController.cs
└── Data
│ ├── Requests
│ ├── AddCartRequest.cs
│ ├── AppIdListRequest.cs
│ ├── ClainIdListRequest.cs
│ ├── CuratorsRequest.cs
│ ├── OnlyPurchaseRequest.cs
│ ├── PurchaseRequest.cs
│ ├── RecommendRequest.cs
│ └── RedeemWalletCodeRequest.cs
│ └── Responses
│ ├── AppDetailResponse.cs
│ ├── BoolDictResponse.cs
│ ├── BotCartResponse.cs
│ ├── BotSummaryResponse.cs
│ ├── CheckGameDictResponse.cs
│ ├── MixWishlistResponse.cs
│ └── OnlyPurchaseResponse.cs
├── ASFEnhance.sln
├── ASFEnhance
├── ASFEnhance.cs
├── ASFEnhance.csproj
├── AccessTokenNullException.cs
├── Account
│ ├── Command.cs
│ ├── CurrencyHelper.cs
│ ├── HtmlParser.cs
│ └── WebRequest.cs
├── Cart
│ ├── Command.cs
│ ├── HtmlParser.cs
│ └── WebRequest.cs
├── Community
│ ├── Command.cs
│ └── WebRequest.cs
├── Curator
│ ├── Command.cs
│ └── WebRequest.cs
├── Data
│ ├── AbstractResponse.cs
│ ├── AccountHistoryResponse.cs
│ ├── AppDetailsResponse.cs
│ ├── CartItemResponse.cs
│ ├── CheckGameResponse.cs
│ ├── ClaimItemResponse.cs
│ ├── CombineItemStacksResponse.cs
│ ├── Common
│ │ ├── CartData.cs
│ │ ├── CuratorData.cs
│ │ ├── FlagsData.cs
│ │ ├── GiftInfoData.cs
│ │ ├── GiftMessageData.cs
│ │ ├── IdData.cs
│ │ ├── NavdataData.cs
│ │ ├── PendingGiftData.cs
│ │ └── RewardItemData.cs
│ ├── CuratorItem.cs
│ ├── DigitalGiftCardOption.cs
│ ├── ENotificationType.cs
│ ├── EditProfilePayload.cs
│ ├── EditProfileResponse.cs
│ ├── ExchangeAPIResponse.cs
│ ├── ExternalLinkCheckoutPayload.cs
│ ├── FetchProfileSummaryData.cs
│ ├── FinalPriceResponse.cs
│ ├── FinalizeTransactionResponse.cs
│ ├── GetOwnedGamesResponse.cs
│ ├── GetPlayerBansResponse.cs
│ ├── GroupData.cs
│ ├── IAccountCartService
│ │ ├── AddItemsToCartRequest.cs
│ │ ├── AddItemsToCartResponse.cs
│ │ ├── GetCartResponse.cs
│ │ ├── ModifyLineItemRequest.cs
│ │ └── RemoveLineItemRequest.cs
│ ├── IAccountPrivateAppsService
│ │ └── GetPrivateAppListResponse.cs
│ ├── ILoyaltyRewardsService
│ │ └── QueryRewardItemsResponse.cs
│ ├── ISteamNotificationService
│ │ └── GetSteamNotificationsResponse.cs
│ ├── IStoreBrowseService
│ │ ├── GetItemsRequest.cs
│ │ └── GetItemsResponse.cs
│ ├── IStoreService
│ │ └── GetDiscoveryQueueResponse.cs
│ ├── IWishlistService
│ │ ├── GetWishlistCountResponse.cs
│ │ └── GetWishlistResponse.cs
│ ├── InitTransactionResponse.cs
│ ├── JoinGroupStatus.cs
│ ├── LeavelGroupResponse.cs
│ ├── LicensesResponse.cs
│ ├── NotificationOptions.cs
│ ├── Plugin
│ │ ├── AddressConfig.cs
│ │ ├── EmailOptions.cs
│ │ ├── GroupItem.cs
│ │ ├── HistoryParseResponse.cs
│ │ ├── PluginConfig.cs
│ │ ├── SteamGameID.cs
│ │ └── SubModuleInfo.cs
│ ├── PluginUpdateResponse.cs
│ ├── PurchaseResponse.cs
│ ├── RecommendGameResponse.cs
│ ├── SteamReplayResponse.cs
│ ├── TransactionStatusResponse.cs
│ ├── UnpackGiftResponse.cs
│ └── WebApi
│ │ ├── AJaxFollowResponse.cs
│ │ ├── AddWishlistResponse.cs
│ │ ├── AjaxCreateWalletAndCheckFundsResponse.cs
│ │ ├── AjaxGetCuratorsResponse.cs
│ │ ├── AjaxGetInviteTokens.cs
│ │ ├── AjaxRedeemWalletCodeResponse.cs
│ │ ├── BaseResultResponse.cs
│ │ ├── IgnoreGameResponse.cs
│ │ └── TradeOfferAcceptResponse.cs
├── DevFeature
│ └── Command.cs
├── Event
│ ├── Command.cs
│ ├── NominatePayload.cs
│ ├── SteamAwardVoteData.cs
│ └── WebRequest.cs
├── Explorer
│ ├── Command.cs
│ ├── ReflectionHelper.cs
│ └── WebRequest.cs
├── Friend
│ ├── Command.cs
│ ├── HtmlParser.cs
│ └── WebRequest.cs
├── Group
│ ├── Command.cs
│ ├── HtmlParser.cs
│ └── WebRequest.cs
├── Inventory
│ ├── Command.cs
│ └── WebRequest.cs
├── Localization
│ ├── Langs.en-US.resx
│ ├── Langs.resx
│ ├── Langs.ru-RU.resx
│ └── Static.resx
├── Other
│ ├── Command.cs
│ └── CommandHelpData.cs
├── Profile
│ ├── Command.cs
│ ├── ElementExtensions.cs
│ ├── HtmlParser.cs
│ └── WebRequest.cs
├── RegexUtils.cs
├── Store
│ ├── Command.cs
│ ├── HtmlParser.cs
│ └── WebRequest.cs
├── Update
│ ├── Command.cs
│ └── WebRequest.cs
├── Utils.cs
├── Wallet
│ ├── Command.cs
│ └── WebRequest.cs
├── WishList
│ ├── Command.cs
│ └── WebRequest.cs
├── Wishlist
│ └── HtmlParser.cs
└── _Adapter_
│ ├── Endpoint.cs
│ └── ExtensionCore.cs
├── Directory.Build.props
├── Directory.Packages.props
├── LICENSE
├── README.en.md
├── README.md
├── README.ru.md
├── build.ps1
├── crowdin.yml
├── desktop.ini
└── img
├── blacklist.png
└── whitelist.png
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.github/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | * Demonstrating empathy and kindness toward other people
21 | * Being respectful of differing opinions, viewpoints, and experiences
22 | * Giving and gracefully accepting constructive feedback
23 | * Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | * Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | * The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | * Trolling, insulting or derogatory comments, and personal or political attacks
33 | * Public or private harassment
34 | * Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | * Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | chr@chrxw.com.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: chr233
4 | custom: ["https://afdian.com/@chr233", "https://steamcommunity.com/tradeoffer/new/?partner=221260487&token=xgqMgL-i"]
5 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report---错误汇报.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report / 错误汇报
3 | about: If you need help / 如果你需要帮助
4 | title: ""
5 | labels: bug
6 | assignees: ""
7 | ---
8 |
9 | **Describe the bug / Bug 描述**
10 | Describe what the bug is
11 | 简单描述一下是什么 bug
12 |
13 | **Error Log / 错误日志**
14 | Please paste the error log of ASFEnhance, for example
15 | 请在此粘贴 ASFEnhance 的错误日志, 示例如下
16 |
17 | ```txt
18 | ASFenhance 遇到错误, 日志如下
19 | ==========================================
20 | - 原始消息: TEST
21 | - Access: Owner
22 | - ASF 版本: 5.4.0.3
23 | - 插件版本: 1.7.5.0
24 | ==========================================
25 | {
26 | "EULA": true,
27 | "Statistic": true,
28 | "DevFeature": false
29 | }
30 | ==========================================
31 | - 错误类型: System.Exception
32 | - 错误消息: Exception of type 'System.Exception' was thrown.
33 | at ASFEnhance.ASFEnhance.ResponseCommand(Bot bot, EAccess access, String message, String[] args, UInt64 steamId)
34 | at ASFEnhance.ASFEnhance.OnBotCommand(Bot bot, EAccess access, String message, String[] args, UInt64 steamId)
35 | ```
36 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request---新功能提议.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request / 新功能提议
3 | about: I want new command / 我需要新的功能
4 | title: ""
5 | labels: enhancement
6 | assignees: ""
7 | ---
8 |
9 | **Describe what feature you want / 描述需要加入的新功能**
10 | Description of what you want.
11 | 描述一下新功能的用途
12 |
--------------------------------------------------------------------------------
/.github/workflows/cleanartifacts.yml:
--------------------------------------------------------------------------------
1 | name: cleanartifacts
2 |
3 | on:
4 | schedule:
5 | - cron: "0 1 * * *"
6 | workflow_dispatch:
7 |
8 | jobs:
9 | remove-old-artifacts:
10 | runs-on: ubuntu-latest
11 | timeout-minutes: 10
12 |
13 | steps:
14 | - name: Remove old artifacts
15 | uses: c-hive/gha-remove-artifacts@v1
16 | with:
17 | age: "1 seconds"
18 |
--------------------------------------------------------------------------------
/.github/workflows/publish.yml:
--------------------------------------------------------------------------------
1 | name: publish
2 |
3 | on:
4 | push:
5 | tags:
6 | - "*"
7 |
8 | env:
9 | REPO_NAME: "ASFEnhance"
10 | PROJECT_NAME: "ASFEnhance"
11 | PLUGIN_NAME: "ASFEnhance.dll"
12 | PROJECT_NAME2: "ASFEnhance.IPC"
13 | PLUGIN_NAME2: "ASFEnhance.IPC.dll"
14 | DOTNET_SDK_VERSION: 9.0
15 |
16 | jobs:
17 | build:
18 | strategy:
19 | fail-fast: false
20 |
21 | runs-on: windows-latest
22 |
23 | steps:
24 | - name: Checkout code
25 | uses: actions/checkout@v4.1.7
26 | with:
27 | submodules: recursive
28 |
29 | - name: Setup .NET Core
30 | uses: actions/setup-dotnet@v4.0.1
31 | with:
32 | dotnet-version: ${{ env.DOTNET_SDK_VERSION }}
33 |
34 | - name: Verify .NET Core
35 | run: dotnet --info
36 |
37 | - name: Restore packages in preparation for ${{ env.PROJECT_NAME }} publishing
38 | run: dotnet restore ${{ env.PROJECT_NAME }} -p:ContinuousIntegrationBuild=true --nologo
39 |
40 | - name: Publish ${{ env.PROJECT_NAME }}
41 | run: dotnet publish ${{ env.PROJECT_NAME }} -o:"./publish/" -c:Release
42 |
43 | - name: Collect files
44 | shell: pwsh
45 | run: |
46 | if (-Not (Test-Path -Path ./tmp)) {
47 | New-Item -ItemType Directory -Path ./tmp
48 | }
49 | else {
50 | Remove-Item -Path ./tmp/* -Recurse -Force
51 | }
52 |
53 | Copy-Item -Path .\publish\${{ env.PLUGIN_NAME }} -Destination .\tmp\
54 |
55 | $dirs = Get-ChildItem -Path ./publish -Directory
56 | foreach ($dir in $dirs) {
57 | $subFiles = Get-ChildItem -Path $dir.FullName -File -Filter *.resources.dll
58 |
59 | foreach ($file in $subFiles) {
60 | $resourceName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name)
61 | $opDir = "./tmp/$resourceName"
62 | if (-Not (Test-Path -Path $opDir)) {
63 | New-Item -ItemType Directory -Path $opDir
64 | }
65 |
66 | $destinationPath = ".\tmp\$resourceName\$($dir.Name).dll"
67 | Copy-Item -Path $file -Destination $destinationPath
68 |
69 | Write-Output "Copy resource DLL $($file.FullName) -> $destinationPath"
70 | }
71 | }
72 |
73 | - name: Zip files
74 | run: 7z a -bd -slp -tzip -mm=Deflate -mx=5 -mfb=150 -mpass=10 "./dist/${{ env.PROJECT_NAME }}.zip" "./tmp/*"
75 |
76 | - name: Upload ${{ env.PROJECT_NAME }}
77 | continue-on-error: true
78 | uses: actions/upload-artifact@v4.3.6
79 | with:
80 | if-no-files-found: error
81 | name: ${{ env.PROJECT_NAME }}.zip
82 | path: ./dist/${{ env.PROJECT_NAME }}.zip
83 |
84 | # Build ASFEnhance.IPC
85 |
86 | - name: Restore packages in preparation for ${{ env.PROJECT_NAME2 }} publishing
87 | run: dotnet restore ${{ env.PROJECT_NAME2 }} -p:ContinuousIntegrationBuild=true --nologo
88 |
89 | - name: Publish ${{ env.PROJECT_NAME2 }}
90 | run: dotnet publish ${{ env.PROJECT_NAME2 }} -o:"./publish/" -c:Release
91 |
92 | - name: Collect files
93 | shell: pwsh
94 | run: |
95 | if (-Not (Test-Path -Path ./tmp)) {
96 | New-Item -ItemType Directory -Path ./tmp
97 | }
98 | else {
99 | Remove-Item -Path ./tmp/* -Recurse -Force
100 | }
101 |
102 | Copy-Item -Path .\publish\${{ env.PLUGIN_NAME2 }} -Destination .\tmp\
103 |
104 | - name: Zip files
105 | run: 7z a -bd -slp -tzip -mm=Deflate -mx=5 -mfb=150 -mpass=10 "./dist/${{ env.PROJECT_NAME2 }}.zip" "./tmp/*"
106 |
107 | - name: Upload ${{ env.PROJECT_NAME2 }}
108 | continue-on-error: true
109 | uses: actions/upload-artifact@v4.3.6
110 | with:
111 | if-no-files-found: error
112 | name: ${{ env.PROJECT_NAME2 }}.zip
113 | path: ./dist/${{ env.PROJECT_NAME2 }}.zip
114 |
115 | release:
116 | needs: build
117 | runs-on: ubuntu-latest
118 |
119 | steps:
120 | - name: Checkout code
121 | uses: actions/checkout@v4.1.7
122 |
123 | - name: Download ${{ env.PROJECT_NAME }} artifact from windows-latest
124 | uses: actions/download-artifact@v4.1.8
125 | with:
126 | name: ${{ env.PROJECT_NAME }}.zip
127 | path: out
128 |
129 | - name: Download ${{ env.PROJECT_NAME2 }} artifact from windows-latest
130 | uses: actions/download-artifact@v4.1.8
131 | with:
132 | name: ${{ env.PROJECT_NAME2 }}.zip
133 | path: out
134 |
135 | - name: Create ${{ env.PROJECT_NAME }} GitHub release
136 | uses: ncipollo/release-action@v1.14.0
137 | with:
138 | artifacts: "out/*"
139 | makeLatest: false
140 | prerelease: true
141 | tag: ${{ github.ref_name }}
142 | name: ${{ env.PROJECT_NAME }} ${{ github.ref_name }}
143 | body: |
144 |  
145 |
146 | Help improve translation: [](https://crowdin.com/project/asfenhance)
147 |
148 | ---
149 |
150 | ASFEnhance.zip : Plugin with Command / 插件本体
151 | ASFEnhance.IPC.zip : Plugin with IPC Interface (Require install with ASFEnhance) / 插件的 IPC 接口 (需要和 ASFEnhance 一起安装)
152 |
153 | ---
154 |
155 | release created by github actions
156 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "ArchiSteamFarm"]
2 | path = ArchiSteamFarm
3 | branch = main
4 | url = git@github.com:JustArchiNET/ArchiSteamFarm.git
5 | [submodule "wiki"]
6 | path = wiki
7 | url = git@github.com:chr233/ASFEnhance.wiki.git
8 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/ASFEnhance.IPC.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Plugins.Interfaces;
2 | using System.Composition;
3 | using System.Text;
4 |
5 | namespace ASFEnhance.IPC;
6 |
7 | [Export(typeof(IPlugin))]
8 | internal sealed class ASFEnhance_IPC : IGitHubPluginUpdates
9 | {
10 | public string Name => "ASFEnhance.IPC";
11 |
12 | public Version Version => MyVersion;
13 |
14 | public bool CanUpdate => true;
15 | public string RepositoryName => "chr233/ASFEnhance";
16 |
17 | ///
18 | /// 插件加载事件
19 | ///
20 | ///
21 | public Task OnLoaded()
22 | {
23 | var message = new StringBuilder("\n");
24 | message.AppendLine(Static.Line);
25 | message.AppendLine(Name);
26 | message.AppendLine(Static.Line);
27 |
28 | ASFLogger.LogGenericInfo(message.ToString());
29 |
30 | return Task.CompletedTask;
31 | }
32 | }
--------------------------------------------------------------------------------
/ASFEnhance.IPC/ASFEnhance.IPC.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Library
5 |
6 |
7 |
8 |
9 | False
10 | all
11 |
12 |
13 | False
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | True
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Controllers/Base/AbstractController.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.AspNetCore.Mvc;
2 |
3 | namespace ASFEnhance.IPC.Controllers.Base;
4 |
5 | ///
6 | /// 基础控制器
7 | ///
8 | [ApiController]
9 | [Produces("application/json")]
10 | public abstract class AbstractController : ControllerBase { }
11 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Controllers/CuratorController.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.IPC.Responses;
3 | using ArchiSteamFarm.Localization;
4 | using ArchiSteamFarm.Steam;
5 | using ASFEnhance.Data;
6 | using ASFEnhance.IPC.Controllers.Base;
7 | using ASFEnhance.IPC.Data.Requests;
8 | using ASFEnhance.IPC.Data.Responses;
9 | using Microsoft.AspNetCore.Http;
10 | using Microsoft.AspNetCore.Mvc;
11 | using System.Globalization;
12 |
13 | namespace ASFEnhance.IPC.Controllers;
14 |
15 | ///
16 | /// 鉴赏家相关接口
17 | ///
18 | [Route("/Api/[controller]/[action]")]
19 | public sealed class CuratorController : AbstractController
20 | {
21 | ///
22 | /// 关注鉴赏家
23 | ///
24 | ///
25 | ///
26 | ///
27 | ///
28 | [HttpPost("{botNames:required}")]
29 | [EndpointDescription("需要指定ClanId")]
30 | [EndpointSummary("关注鉴赏家")]
31 | public async Task> FollowCurator(string botNames, [FromBody] ClanIdListRequest request)
32 | {
33 | if (string.IsNullOrEmpty(botNames))
34 | {
35 | throw new ArgumentNullException(nameof(botNames));
36 | }
37 | ArgumentNullException.ThrowIfNull(request);
38 |
39 | if (!Config.EULA)
40 | {
41 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
42 | }
43 |
44 | var bots = Bot.GetBots(botNames);
45 |
46 | if (bots == null || bots.Count == 0)
47 | {
48 | return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)));
49 | }
50 |
51 | if (request.ClanIds == null || request.ClanIds.Count == 0)
52 | {
53 | return BadRequest(new GenericResponse(false, "ClanIds 无效"));
54 | }
55 |
56 | var response = bots.ToDictionary(x => x.BotName, x => new BoolDictResponse());
57 |
58 | foreach (var clianid in request.ClanIds)
59 | {
60 | IList<(string, bool)> results = await Utilities.InParallel(bots.Select(
61 | async bot =>
62 | {
63 | if (!bot.IsConnectedAndLoggedOn) { return (bot.BotName, false); }
64 | var result = await Curator.WebRequest.FollowCurator(bot, clianid, true, null).ConfigureAwait(false);
65 | return (bot.BotName, result);
66 | }
67 | )).ConfigureAwait(false);
68 |
69 | foreach (var result in results)
70 | {
71 | response[result.Item1].Add(clianid.ToString(), result.Item2);
72 | }
73 | }
74 |
75 | return Ok(new GenericResponse>(response));
76 | }
77 |
78 | ///
79 | /// 取消关注鉴赏家
80 | ///
81 | ///
82 | ///
83 | ///
84 | ///
85 | [HttpPost("{botNames:required}")]
86 | [EndpointDescription("需要指定ClanId")]
87 | [EndpointSummary("取消关注鉴赏家")]
88 | public async Task> UnFollowCurator(string botNames, [FromBody] ClanIdListRequest request)
89 | {
90 | if (string.IsNullOrEmpty(botNames))
91 | {
92 | throw new ArgumentNullException(nameof(botNames));
93 | }
94 |
95 | ArgumentNullException.ThrowIfNull(request);
96 |
97 | if (!Config.EULA)
98 | {
99 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
100 | }
101 |
102 | var bots = Bot.GetBots(botNames);
103 |
104 | if (bots == null || bots.Count == 0)
105 | {
106 | return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)));
107 | }
108 |
109 | if (request.ClanIds == null || request.ClanIds.Count == 0)
110 | {
111 | return BadRequest(new GenericResponse(false, "ClanIds 无效"));
112 | }
113 |
114 | var response = bots.ToDictionary(x => x.BotName, x => new BoolDictResponse());
115 |
116 | foreach (var clianId in request.ClanIds)
117 | {
118 | IList<(string, bool)> results = await Utilities.InParallel(bots.Select(
119 | async bot =>
120 | {
121 | if (!bot.IsConnectedAndLoggedOn) { return (bot.BotName, false); }
122 | var result = await Curator.WebRequest.FollowCurator(bot, clianId, false, null).ConfigureAwait(false);
123 | return (bot.BotName, result);
124 | }
125 | )).ConfigureAwait(false);
126 |
127 | foreach (var result in results)
128 | {
129 | response[result.Item1].Add(clianId.ToString(), result.Item2);
130 | }
131 | }
132 |
133 | return Ok(new GenericResponse>(response));
134 | }
135 |
136 | ///
137 | /// 已关注的鉴赏家列表
138 | ///
139 | ///
140 | ///
141 | ///
142 | ///
143 | [HttpPost("{botNames:required}")]
144 | [EndpointDescription("Start:起始位置,Count:获取数量")]
145 | [EndpointSummary("获取已关注的鉴赏家列表")]
146 | public async Task> FollowingCurators(string botNames, [FromBody] CuratorsRequest request)
147 | {
148 | if (string.IsNullOrEmpty(botNames))
149 | {
150 | throw new ArgumentNullException(nameof(botNames));
151 | }
152 |
153 | ArgumentNullException.ThrowIfNull(request);
154 |
155 | if (!Config.EULA)
156 | {
157 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
158 | }
159 |
160 | var bots = Bot.GetBots(botNames);
161 |
162 | if (bots == null || bots.Count == 0)
163 | {
164 | return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)));
165 | }
166 |
167 | if (request.Count == 0)
168 | {
169 | return BadRequest(new GenericResponse(false, "Count 无效"));
170 | }
171 |
172 | var response = bots.ToDictionary(x => x.BotName, x => new List());
173 |
174 | var results = await Utilities.InParallel(bots.Select(
175 | async bot =>
176 | {
177 | if (!bot.IsConnectedAndLoggedOn) { return (bot.BotName, []); }
178 |
179 | var result = await Curator.WebRequest.GetFollowingCurators(bot, request.Start, request.Count).ConfigureAwait(false);
180 |
181 | return (bot.BotName, result);
182 | }
183 | )).ConfigureAwait(false);
184 |
185 | foreach (var result in results)
186 | {
187 | response[result.BotName] = result.result ?? [];
188 | }
189 |
190 | return Ok(new GenericResponse>>(response));
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Controllers/ProfileController.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.IPC.Responses;
2 | using ArchiSteamFarm.Localization;
3 | using ArchiSteamFarm.Steam;
4 | using ASFEnhance.IPC.Controllers.Base;
5 | using Microsoft.AspNetCore.Http;
6 | using Microsoft.AspNetCore.Mvc;
7 |
8 | namespace ASFEnhance.IPC.Controllers;
9 |
10 | ///
11 | /// 购物车相关接口
12 | ///
13 | [Route("/Api/[controller]/[action]")]
14 | public sealed class ProfileController : AbstractController
15 | {
16 | ///
17 | /// 批量设置用户名
18 | ///
19 | ///
20 | ///
21 | ///
22 | [HttpPost]
23 | [EndpointDescription("botNames 设定范围, nameList 一行一个昵称")]
24 | [EndpointSummary("批量设置用户名")]
25 | public async Task> BatchEditNickname(Dictionary nicknames)
26 | {
27 | if (nicknames.Count == 0)
28 | {
29 | throw new ArgumentNullException(nameof(nicknames));
30 | }
31 |
32 | if (!Config.EULA)
33 | {
34 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
35 | }
36 |
37 | Dictionary response = [];
38 |
39 | foreach (var (botName, nickname) in nicknames)
40 | {
41 | var bot = Bot.GetBot(botName);
42 |
43 | if (bot == null || !bot.IsConnectedAndLoggedOn)
44 | {
45 | response.Add(botName, bot == null ? Strings.BotNotFound : Strings.BotNotConnected);
46 | continue;
47 | }
48 |
49 | bot.SteamFriends.SetPersonaName(nickname);
50 | await Task.Delay(100).ConfigureAwait(false);
51 |
52 | response.Add(botName, Langs.Success);
53 | }
54 |
55 | return Ok(new GenericResponse>(true, "Ok", response));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Controllers/RecommendController.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.IPC.Responses;
3 | using ArchiSteamFarm.Localization;
4 | using ArchiSteamFarm.Steam;
5 | using ASFEnhance.IPC.Controllers.Base;
6 | using ASFEnhance.IPC.Data.Requests;
7 | using ASFEnhance.IPC.Data.Responses;
8 | using Microsoft.AspNetCore.Http;
9 | using Microsoft.AspNetCore.Mvc;
10 | using System.Globalization;
11 |
12 | namespace ASFEnhance.IPC.Controllers;
13 |
14 | ///
15 | /// 评测相关接口
16 | ///
17 | [Route("/Api/[controller]/[action]")]
18 | public sealed class RecommendController : AbstractController
19 | {
20 | ///
21 | /// 发布游戏评测
22 | ///
23 | ///
24 | ///
25 | ///
26 | ///
27 | [HttpPost("{botNames:required}")]
28 | [EndpointDescription("RateUp:true好评,AllowReply:true允许回复,ForFree:false非免费取得,Public:true评测公开可见,Comment:评测内容")]
29 | [EndpointSummary("发布游戏评测")]
30 | public async Task> PublishReview(string botNames, [FromBody] RecommendRequest request)
31 | {
32 | if (string.IsNullOrEmpty(botNames))
33 | {
34 | throw new ArgumentNullException(nameof(botNames));
35 | }
36 |
37 | ArgumentNullException.ThrowIfNull(request);
38 |
39 | if (!Config.EULA)
40 | {
41 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
42 | }
43 |
44 | var bots = Bot.GetBots(botNames);
45 |
46 | if (bots == null || bots.Count == 0)
47 | {
48 | return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)));
49 | }
50 |
51 | if (request.Recommends == null || request.Recommends.Count == 0)
52 | {
53 | return BadRequest(new GenericResponse(false, "Recommends 无效"));
54 | }
55 |
56 | var response = bots.ToDictionary(x => x.BotName, x => new BoolDictResponse());
57 |
58 | foreach (var recommend in request.Recommends)
59 | {
60 | if (string.IsNullOrEmpty(recommend.Comment))
61 | {
62 | foreach (var bot in bots)
63 | {
64 | response[bot.BotName].Add(recommend.AppId.ToString(), false);
65 | }
66 | continue;
67 | }
68 |
69 | IList<(string, bool)> results = await Utilities.InParallel(bots.Select(
70 | async bot =>
71 | {
72 | if (!bot.IsConnectedAndLoggedOn) { return (bot.BotName, false); }
73 | var result = await Store.WebRequest.PublishReview(bot, recommend.AppId, recommend.Comment, recommend.RateUp, recommend.Public, recommend.AllowReply, recommend.ForFree).ConfigureAwait(false);
74 | return (bot.BotName, result?.Result ?? false);
75 | }
76 | )).ConfigureAwait(false);
77 |
78 | foreach (var result in results)
79 | {
80 | response[result.Item1].Add(recommend.AppId.ToString(), result.Item2);
81 | }
82 | }
83 |
84 | return Ok(new GenericResponse>(response));
85 | }
86 |
87 | ///
88 | /// 删除游戏评测
89 | ///
90 | ///
91 | ///
92 | ///
93 | ///
94 | [HttpPost("{botNames:required}")]
95 | [EndpointDescription("需要指定AppIds列表")]
96 | [EndpointSummary("删除游戏评测")]
97 | public async Task> DeleteReview(string botNames, [FromBody] uint[] appIds)
98 | {
99 | if (string.IsNullOrEmpty(botNames))
100 | {
101 | throw new ArgumentNullException(nameof(botNames));
102 | }
103 |
104 | ArgumentNullException.ThrowIfNull(appIds);
105 |
106 | if (!Config.EULA)
107 | {
108 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
109 | }
110 |
111 | var bots = Bot.GetBots(botNames);
112 |
113 | if (bots == null || bots.Count == 0)
114 | {
115 | return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botNames)));
116 | }
117 |
118 | if (appIds.Length == 0)
119 | {
120 | return BadRequest(new GenericResponse(false, "AppIds 无效"));
121 | }
122 |
123 | var response = bots.ToDictionary(x => x.BotName, x => new BoolDictResponse());
124 |
125 | foreach (var appId in appIds)
126 | {
127 | IList<(string, bool)> results = await Utilities.InParallel(bots.Select(
128 | async bot =>
129 | {
130 | if (!bot.IsConnectedAndLoggedOn) { return (bot.BotName, false); }
131 | var result = await Store.WebRequest.DeleteRecommend(bot, appId).ConfigureAwait(false);
132 | return (bot.BotName, result);
133 | }
134 | )).ConfigureAwait(false);
135 |
136 | foreach (var result in results)
137 | {
138 | response[result.Item1].Add(appId.ToString(), result.Item2);
139 | }
140 | }
141 |
142 | return Ok(new GenericResponse>(response));
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Controllers/WalletController.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.IPC.Responses;
2 | using ArchiSteamFarm.Localization;
3 | using ArchiSteamFarm.Steam;
4 | using ASFEnhance.Data.Plugin;
5 | using ASFEnhance.IPC.Controllers.Base;
6 | using ASFEnhance.IPC.Data.Requests;
7 | using Microsoft.AspNetCore.Http;
8 | using Microsoft.AspNetCore.Mvc;
9 | using SteamKit2;
10 | using System.Globalization;
11 |
12 | namespace ASFEnhance.IPC.Controllers;
13 |
14 | ///
15 | /// 鉴赏家相关接口
16 | ///
17 | [Route("/Api/[controller]/[action]")]
18 | public sealed class WalletController : AbstractController
19 | {
20 | ///
21 | /// 充值钱包兑换码
22 | ///
23 | ///
24 | ///
25 | ///
26 | ///
27 | [HttpPost("{botName:required}")]
28 | [EndpointDescription("Code: 重置码, Address: 地址, 可为空, 为空时使用 ASF.json 中配置的地址替代")]
29 | [EndpointSummary("充值钱包兑换码")]
30 | public async Task> RedeemWalletCode(string botName, [FromBody] RedeemWalletCodeRequest payload)
31 | {
32 | if (string.IsNullOrEmpty(botName))
33 | {
34 | throw new ArgumentNullException(nameof(botName));
35 | }
36 |
37 | if (string.IsNullOrEmpty(payload.Code))
38 | {
39 | return BadRequest(new GenericResponse(false, "参数错误, Code无效"));
40 | }
41 |
42 | if (!Config.EULA)
43 | {
44 | return BadRequest(new GenericResponse(false, Langs.EulaFeatureUnavilable));
45 | }
46 |
47 | var bot = Bot.GetBot(botName);
48 | if (bot == null)
49 | {
50 | return BadRequest(new GenericResponse(false, string.Format(CultureInfo.CurrentCulture, Strings.BotNotFound, botName)));
51 | }
52 |
53 | var result = await Wallet.WebRequest.RedeemWalletCode(bot, payload.Code).ConfigureAwait(false);
54 |
55 | if (result != null)
56 | {
57 | if (result.Success == EResult.OK)
58 | {
59 | return Ok(new GenericResponse(true, Langs.WalletCodeRedeemSuccess));
60 | }
61 | else if (result.Success == EResult.InvalidState)
62 | {
63 | AddressConfig? address = null;
64 |
65 | if (payload.Address != null)
66 | {
67 | address = payload.Address;
68 | }
69 | else if (Config.Addresses?.Count > 0)
70 | {
71 | address = Config.Addresses[Random.Shared.Next(0, Config.Addresses.Count)];
72 | }
73 |
74 | if (address == null)
75 | {
76 | return Ok(new GenericResponse(false, Langs.NoAvilableAddressError));
77 | }
78 |
79 | var result2 = await Wallet.WebRequest.RedeemWalletCode(bot, payload.Code, address).ConfigureAwait(false);
80 |
81 | if (result2 != null)
82 | {
83 | if (result2.Success == EResult.OK)
84 | {
85 | return Ok(new GenericResponse(true, Langs.WalletCodeRedeemSuccess));
86 | }
87 |
88 | return Ok(new GenericResponse(false, string.Format(Langs.WalletCodeRedeemFailed, result2.Success)));
89 | }
90 | }
91 | else
92 | {
93 | return Ok(new GenericResponse(false, string.Format(Langs.WalletCodeRedeemFailed, result.Success)));
94 | }
95 | }
96 |
97 | return Ok(new GenericResponse(false, Langs.NetworkError));
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/AddCartRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Requests;
2 |
3 | ///
4 | /// 下单请求
5 | ///
6 | public sealed record AddCartRequest
7 | {
8 | ///
9 | /// SubID列表
10 | ///
11 | public List? Items { get; set; }
12 |
13 | ///
14 | ///
15 | ///
16 | public sealed record ItemData
17 | {
18 | ///
19 | ///
20 | ///
21 | public uint PackageId { get; set; }
22 | ///
23 | ///
24 | ///
25 | public uint BundleId { get; set; }
26 | ///
27 | ///
28 | ///
29 | public GiftInfoData? GiftInfo { get; set; }
30 | ///
31 | ///
32 | ///
33 | public bool IsPrivate { get; set; }
34 | ///
35 | ///
36 | ///
37 | public bool IsGift { get; set; }
38 |
39 | ///
40 | ///
41 | ///
42 | public sealed record GiftInfoData
43 | {
44 | ///
45 | ///
46 | ///
47 | public ulong AccountIdGiftee { get; set; }
48 | ///
49 | ///
50 | ///
51 | public string? GifteeName { get; set; }
52 | ///
53 | ///
54 | ///
55 | public string? Message { get; set; }
56 | ///
57 | ///
58 | ///
59 | public string? Sentiment { get; set; }
60 | ///
61 | ///
62 | ///
63 | public string? Signature { get; set; }
64 | ///
65 | ///
66 | ///
67 | public ulong TimeScheduledSend { get; set; }
68 | }
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/AppIdListRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Requests;
2 |
3 | ///
4 | /// AppIds列表请求
5 | ///
6 | public sealed record AppIdListRequest
7 | {
8 | ///
9 | /// AppId列表
10 | ///
11 | public uint[]? AppIds { get; set; }
12 | ///
13 | /// SubId列表
14 | ///
15 | public uint[]? PackageIds { get; set; }
16 | ///
17 | /// BundleId列表
18 | ///
19 | public uint[]? BundleIds { get; set; }
20 | }
21 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/ClainIdListRequest.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace ASFEnhance.IPC.Data.Requests;
4 |
5 | ///
6 | /// 鉴赏家Id列表请求
7 | ///
8 | public sealed record ClanIdListRequest
9 | {
10 | ///
11 | /// 鉴赏家ID列表
12 | ///
13 | [Required]
14 | public HashSet? ClanIds { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/CuratorsRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Requests;
2 |
3 | ///
4 | /// 鉴赏家列表请求
5 | ///
6 | public sealed record CuratorsRequest
7 | {
8 | ///
9 | /// 起始位置
10 | ///
11 | public uint Start { get; set; }
12 |
13 | ///
14 | /// 获取数量
15 | ///
16 | public uint Count { get; set; } = 30;
17 | }
18 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/OnlyPurchaseRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Requests;
2 |
3 | ///
4 | /// 下单请求
5 | ///
6 | public sealed record OnlyPurchaseRequest
7 | {
8 | ///
9 | /// 卡单
10 | ///
11 | public bool FakePurchase { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/PurchaseRequest.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Requests;
2 |
3 | ///
4 | /// 下单请求
5 | ///
6 | public sealed record PurchaseRequest
7 | {
8 | ///
9 | /// SubID列表
10 | ///
11 | public HashSet? SubIds { get; set; }
12 |
13 | ///
14 | /// BundleID列表
15 | ///
16 | public HashSet? BundleIds { get; set; }
17 |
18 | ///
19 | /// 跳过已拥有
20 | ///
21 | public bool SkipOwned { get; set; } = true;
22 |
23 | ///
24 | /// 卡单
25 | ///
26 | public bool FakePurchase { get; set; }
27 | }
28 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/RecommendRequest.cs:
--------------------------------------------------------------------------------
1 | using System.ComponentModel.DataAnnotations;
2 |
3 | namespace ASFEnhance.IPC.Data.Requests;
4 |
5 | ///
6 | /// 发布评测请求
7 | ///
8 | public sealed record RecommendRequest
9 | {
10 | ///
11 | /// 评测列表
12 | ///
13 | [Required]
14 | public HashSet? Recommends { get; set; }
15 | }
16 | ///
17 | /// 评测选项
18 | ///
19 | public sealed record RecommendOption
20 | {
21 | ///
22 | /// AppId
23 | ///
24 | [Required]
25 | public uint AppId { get; set; }
26 |
27 | ///
28 | /// 是否推荐
29 | ///
30 |
31 | public bool RateUp { get; set; } = true;
32 |
33 | ///
34 | /// 允许回复
35 | ///
36 |
37 | public bool AllowReply { get; set; } = true;
38 |
39 | ///
40 | /// 免费获取的游戏
41 | ///
42 |
43 | public bool ForFree { get; set; }
44 |
45 | ///
46 | /// 是否公开
47 | ///
48 |
49 | public bool Public { get; set; } = true;
50 |
51 | ///
52 | /// 评测内容
53 | ///
54 | [Required]
55 | public string Comment { get; set; } = "";
56 | }
57 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Requests/RedeemWalletCodeRequest.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Plugin;
2 |
3 | namespace ASFEnhance.IPC.Data.Requests;
4 | ///
5 | ///
6 | ///
7 | public sealed record RedeemWalletCodeRequest
8 | {
9 | ///
10 | ///
11 | ///
12 | public string? Code { get; set; }
13 |
14 | ///
15 | ///
16 | ///
17 | public AddressConfig? Address { get; set; }
18 | }
19 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/AppDetailResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Responses;
2 |
3 | ///
4 | /// App信息响应
5 | ///
6 | public sealed class AppDetailDictResponse : Dictionary
7 | {
8 | }
9 |
10 | ///
11 | /// APP信息
12 | ///
13 | public sealed record AppDetail
14 | {
15 | ///
16 | /// 是否成功
17 | ///
18 | public bool Success { get; set; }
19 | ///
20 | /// AppID
21 | ///
22 | public uint AppId { get; set; }
23 | ///
24 | /// 名称
25 | ///
26 | public string Name { get; set; } = "";
27 | ///
28 | /// 类型
29 | ///
30 | public string Type { get; set; } = "";
31 | ///
32 | /// 说明
33 | ///
34 | public string Desc { get; set; } = "";
35 | ///
36 | /// 是否免费
37 | ///
38 | public bool IsFree { get; set; }
39 | ///
40 | /// 是否已发售
41 | ///
42 | public bool Released { get; set; }
43 | ///
44 | /// SubID列表
45 | ///
46 | public List? Subs { get; set; }
47 | }
48 |
49 | ///
50 | /// Sub信息
51 | ///
52 | public sealed record SubInfo
53 | {
54 | ///
55 | /// SubID
56 | ///
57 | public uint? PackageId { get; set; }
58 | ///
59 | /// BundleId
60 | ///
61 | public uint? BundleId { get; set; }
62 | ///
63 | /// 名称
64 | ///
65 | public string? Name { get; set; }
66 | ///
67 | ///
68 | ///
69 | public string? PriceInCents { get; set; }
70 | ///
71 | ///
72 | ///
73 | public string? PriceFormatted { get; set; }
74 | ///
75 | ///
76 | ///
77 | public bool CanPurchaseAsGift { get; set; }
78 | ///
79 | ///
80 | ///
81 | public int IncludeGameCount { get; set; }
82 | ///
83 | ///
84 | ///
85 | public bool RequiresShipping { get; set; }
86 | }
87 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/BoolDictResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Responses;
2 |
3 | ///
4 | /// 机器人字典响应
5 | ///
6 | public sealed class BoolDictResponse : Dictionary
7 | {
8 | }
9 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/BotCartResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using SteamKit2;
3 |
4 | namespace ASFEnhance.IPC.Data.Responses;
5 | ///
6 | ///
7 | ///
8 | public sealed record BotCartResponse
9 | {
10 | ///
11 | ///
12 | ///
13 | public List? Items { get; set; }
14 | ///
15 | ///
16 | ///
17 | public bool IsValid { get; set; }
18 |
19 | ///
20 | ///
21 | ///
22 | public sealed record CartItemData
23 | {
24 | ///
25 | ///
26 | ///
27 | public uint? PackageId { get; set; }
28 | ///
29 | ///
30 | ///
31 | public uint? BundleId { get; set; }
32 | ///
33 | ///
34 | ///
35 | public string? LineItemId { get; set; }
36 | ///
37 | ///
38 | ///
39 | public string? Name { get; set; }
40 | ///
41 | ///
42 | ///
43 | public bool IsValid { get; set; }
44 | ///
45 | ///
46 | ///
47 | public ulong TimeAdded { get; set; }
48 | ///
49 | ///
50 | ///
51 | public string? PriceCents { get; set; }
52 | ///
53 | ///
54 | ///
55 | public ECurrencyCode CurrencyCode { get; set; }
56 | ///
57 | ///
58 | ///
59 | public string? PriceFormatted { get; set; }
60 | ///
61 | ///
62 | ///
63 | public bool IsGift { get; set; }
64 | ///
65 | ///
66 | ///
67 | public bool IsPrivate { get; set; }
68 | ///
69 | ///
70 | ///
71 | public GiftInfoData? GiftInfo { get; set; }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/BotSummaryResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Responses;
2 |
3 | ///
4 | /// 机器人信息响应
5 | ///
6 | public sealed record BotSummaryResponse
7 | {
8 | ///
9 | /// 是否成功
10 | ///
11 | public bool Success { get; set; }
12 | ///
13 | /// 货币
14 | ///
15 | public string Currency { get; set; } = "";
16 | ///
17 | /// 钱包余额
18 | ///
19 | public long Balance { get; set; }
20 | ///
21 | /// 格式化钱包余额
22 | ///
23 | public string FormatBalance { get; set; } = "";
24 | ///
25 | /// 昵称
26 | ///
27 | public string Nick { get; set; } = "";
28 | ///
29 | /// 个人资料链接
30 | ///
31 | public string ProfileLink { get; set; } = "";
32 | ///
33 | /// SteamId
34 | ///
35 | public ulong SteamId { get; set; }
36 | ///
37 | /// 好友代码
38 | ///
39 | public ulong FriendCode { get; set; }
40 | }
41 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/CheckGameDictResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data;
2 |
3 | namespace ASFEnhance.IPC.Data.Responses;
4 |
5 | ///
6 | /// 获取游戏关注信息响应
7 | ///
8 | public sealed class CheckGameDictResponse : Dictionary
9 | {
10 | }
11 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/MixWishlistResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.IWishlistService;
2 |
3 | namespace ASFEnhance.IPC.Data.Responses;
4 |
5 | ///
6 | /// 愿望单响应
7 | ///
8 | public sealed record MixWishlistResponse
9 | {
10 | ///
11 | ///
12 | ///
13 | ///
14 | ///
15 | public MixWishlistResponse(int count, GetWishlistResponse? data)
16 | {
17 | Count = count;
18 | Items = data?.Items;
19 | }
20 |
21 | ///
22 | ///
23 | ///
24 | ///
25 | public MixWishlistResponse(GetWishlistResponse? data)
26 | {
27 | Count = data?.Items?.Count ?? -1;
28 | Items = data?.Items;
29 | }
30 |
31 | ///
32 | ///
33 | ///
34 | public int Count { get; set; }
35 |
36 | ///
37 | ///
38 | ///
39 | public List? Items { get; set; }
40 | }
41 |
--------------------------------------------------------------------------------
/ASFEnhance.IPC/Data/Responses/OnlyPurchaseResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.IPC.Data.Responses;
2 |
3 | ///
4 | /// 购物结果响应
5 | ///
6 | public sealed record OnlyPurchaseResponse
7 | {
8 | ///
9 | /// 是否成功
10 | ///
11 | public bool Success { get; set; }
12 | ///
13 | /// 花费
14 | ///
15 | public long Cost { get; set; }
16 | ///
17 | /// 货币
18 | ///
19 | public string? Currency { get; set; }
20 | ///
21 | /// 购物前余额
22 | ///
23 | public long BalancePrev { get; set; }
24 | ///
25 | /// 购物后余额
26 | ///
27 | public long BalanceNow { get; set; }
28 | }
29 |
--------------------------------------------------------------------------------
/ASFEnhance.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.2.32505.173
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ASFEnhance", "ASFEnhance\ASFEnhance.csproj", "{32ECAB31-BC19-4030-93D0-77A2A8114694}"
7 | EndProject
8 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{B084B4BE-3062-4C36-BF76-EDC3D6627C79}"
9 | ProjectSection(SolutionItems) = preProject
10 | Directory.Build.props = Directory.Build.props
11 | Directory.Packages.props = Directory.Packages.props
12 | README.en.md = README.en.md
13 | README.md = README.md
14 | README.ru.md = README.ru.md
15 | EndProjectSection
16 | EndProject
17 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ArchiSteamFarm", "ArchiSteamFarm\ArchiSteamFarm\ArchiSteamFarm.csproj", "{3204D646-3154-44F2-9DD1-9344173288E5}"
18 | EndProject
19 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ASFEnhance.IPC", "ASFEnhance.IPC\ASFEnhance.IPC.csproj", "{F98AAFB8-AF6B-4392-BC11-571BC7F52D8E}"
20 | EndProject
21 | Global
22 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
23 | Debug|Any CPU = Debug|Any CPU
24 | Release|Any CPU = Release|Any CPU
25 | EndGlobalSection
26 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
27 | {32ECAB31-BC19-4030-93D0-77A2A8114694}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
28 | {32ECAB31-BC19-4030-93D0-77A2A8114694}.Debug|Any CPU.Build.0 = Debug|Any CPU
29 | {32ECAB31-BC19-4030-93D0-77A2A8114694}.Release|Any CPU.ActiveCfg = Release|Any CPU
30 | {32ECAB31-BC19-4030-93D0-77A2A8114694}.Release|Any CPU.Build.0 = Release|Any CPU
31 | {3204D646-3154-44F2-9DD1-9344173288E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
32 | {3204D646-3154-44F2-9DD1-9344173288E5}.Debug|Any CPU.Build.0 = Debug|Any CPU
33 | {3204D646-3154-44F2-9DD1-9344173288E5}.Release|Any CPU.ActiveCfg = Release|Any CPU
34 | {3204D646-3154-44F2-9DD1-9344173288E5}.Release|Any CPU.Build.0 = Release|Any CPU
35 | {F98AAFB8-AF6B-4392-BC11-571BC7F52D8E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
36 | {F98AAFB8-AF6B-4392-BC11-571BC7F52D8E}.Debug|Any CPU.Build.0 = Debug|Any CPU
37 | {F98AAFB8-AF6B-4392-BC11-571BC7F52D8E}.Release|Any CPU.ActiveCfg = Release|Any CPU
38 | {F98AAFB8-AF6B-4392-BC11-571BC7F52D8E}.Release|Any CPU.Build.0 = Release|Any CPU
39 | EndGlobalSection
40 | GlobalSection(SolutionProperties) = preSolution
41 | HideSolutionNode = FALSE
42 | EndGlobalSection
43 | GlobalSection(ExtensibilityGlobals) = postSolution
44 | SolutionGuid = {3032EA4B-1AE9-4B25-8719-790BF268412F}
45 | EndGlobalSection
46 | EndGlobal
47 |
--------------------------------------------------------------------------------
/ASFEnhance/ASFEnhance.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Library
4 | false
5 |
6 |
7 |
8 | $(DefineConstants);IpcEnable
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | all
22 | runtime; build; native; contentfiles; analyzers; buildtransitive
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 | True
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/ASFEnhance/AccessTokenNullException.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 |
3 | namespace ASFEnhance;
4 | ///
5 | /// AccessToken 为NULL
6 | ///
7 | public class AccessTokenNullException(Bot bot) : Exception(bot.BotName)
8 | {
9 | }
--------------------------------------------------------------------------------
/ASFEnhance/Account/CurrencyHelper.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Account;
2 |
3 | internal static class CurrencyHelper
4 | {
5 | ///
6 | /// 货币ISO名称转成符号
7 | ///
8 | internal static Dictionary Currency2Symbol { get; } = new() {
9 | { "AED", "AED" },
10 | { "ARS", "ARS$" },
11 | { "AUD", "A$" },
12 | { "BRL", "R$" },
13 | { "CAD", "CDN$" },
14 | { "CHF", "CHF" },
15 | { "CLP", "CLP$" },
16 | { "CNY", "¥" },
17 | { "COP", "COL$" },
18 | { "CRC", "₡" },
19 | { "EUR", "€" },
20 | { "GBP", "£" },
21 | { "HKD", "HK$" },
22 | { "IDR", "Rp" },
23 | { "ILS", "₪" },
24 | { "INR", "₹" },
25 | { "JPY", "¥" },
26 | { "KRW", "₩" },
27 | { "KWD", "KD" },
28 | { "KZT", "₸" },
29 | { "MXN", "Mex$" },
30 | { "MYR", "RM" },
31 | { "NOK", "kr" },
32 | { "NZD", "NZ$" },
33 | { "PEN", "S/." },
34 | { "PHP", "₱" },
35 | { "PLN", "zł" },
36 | { "QAR", "QR" },
37 | { "RUB", "₽" },
38 | { "SAR", "SR" },
39 | { "SGD", "S$" },
40 | { "THB", "฿" },
41 | { "TRY", "₺" },
42 | { "TWD", "NT$" },
43 | { "UAH", "₴" },
44 | { "USD", "$" },
45 | { "UYU", "$U" },
46 | { "VND", "₫" },
47 | { "ZAR", "R" },
48 | };
49 |
50 | ///
51 | /// 货币符号转成ISO名称
52 | ///
53 | internal static Dictionary SymbolCurrency { get; } = new() {
54 | { "AED", "AED" },
55 | { "ARS$", "ARS" },
56 | { "A$", "AUD" },
57 | { "R$", "BRL" },
58 | { "CDN$", "CAD" },
59 | { "CHF", "CHF" },
60 | { "CLP$", "CLP" },
61 | { "¥", "CNY" },
62 | { "COL$", "COP" },
63 | { "₡", "CRC" },
64 | { "--€", "EUR" },
65 | { "€", "EUR" },
66 | { "£", "GBP" },
67 | { "HK$", "HKD" },
68 | { "Rp", "IDR" },
69 | { "₪", "ILS" },
70 | { "₹", "INR" },
71 | { "₩", "KRW" },
72 | { "KD", "KWD" },
73 | { "₸", "KZT" },
74 | { "Mex$", "MXN" },
75 | { "RM", "MYR" },
76 | { "kr", "NOK" },
77 | { "NZ$", "NZD" },
78 | { "S/.", "PEN" },
79 | { "₱", "PHP" },
80 | { "zł", "PLN" },
81 | { "QR", "QAR" },
82 | { "pуб.", "RUB" },
83 | { "₽", "RUB" },
84 | { "SR", "SAR" },
85 | { "S$", "SGD" },
86 | { "฿", "THB" },
87 | { "TL", "TRY" },
88 | { "₺", "TRY" },
89 | { "NT$", "TWD" },
90 | { "₴", "UAH" },
91 | { "$", "USD" },
92 | { "$U", "UYU" },
93 | { "₫", "VND" },
94 | { "R", "ZAR" },
95 | };
96 |
97 | ///
98 | /// 使用逗号作为小数点的国家
99 | ///
100 | internal static HashSet DotCurrency { get; } = [
101 | "TRY",
102 | "ARS",
103 | "BRL",
104 | "NOK",
105 | "EUR",
106 | "PLN",
107 | "VND",
108 | ];
109 | }
110 |
--------------------------------------------------------------------------------
/ASFEnhance/Cart/HtmlParser.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Web.Responses;
2 | using ASFEnhance.Data;
3 | using System.Text;
4 |
5 | namespace ASFEnhance.Cart;
6 |
7 | internal static class HtmlParser
8 | {
9 | ///
10 | /// 解析购物车可用区域
11 | ///
12 | ///
13 | ///
14 | internal static string? ParseCartCountries(HtmlDocumentResponse? response)
15 | {
16 | if (response?.Content == null)
17 | {
18 | return null;
19 | }
20 |
21 | var currentCountry = response.Content.QuerySelector("#usercountrycurrency");
22 |
23 | var availableCountries = response.Content.QuerySelectorAll("#usercountrycurrency_droplist>li>a");
24 |
25 | StringBuilder message = new();
26 | message.AppendLine(Langs.MultipleLineResult);
27 |
28 | if (currentCountry != null)
29 | {
30 | message.AppendLine(Langs.MultipleLineResult);
31 | message.AppendLine(Langs.AvailableAreaHeader);
32 |
33 | string? currentCode = currentCountry.GetAttribute("value");
34 |
35 | foreach (var availableCountrie in availableCountries)
36 | {
37 | string? countryCode = availableCountrie.GetAttribute("id");
38 | string countryName = availableCountrie.TextContent;
39 |
40 | if (!string.IsNullOrEmpty(countryCode) && countryCode != "help")
41 | {
42 | message.AppendLineFormat(currentCode == countryCode ? Langs.AreaItemCurrent : Langs.AreaItem, countryCode, countryName);
43 | }
44 | }
45 | }
46 | else
47 | {
48 | message.AppendLine(Langs.NoAvailableArea);
49 | }
50 |
51 | return message.ToString();
52 | }
53 |
54 | internal static List? ParseDigitalGiftCardOptions(HtmlDocumentResponse? response)
55 | {
56 | if (response?.Content == null)
57 | {
58 | return null;
59 | }
60 |
61 | var result = new List();
62 |
63 | var cardsEle = response.Content.QuerySelectorAll("div.giftcard_amounts>.giftcard_selection");
64 |
65 | foreach (var cardEle in cardsEle)
66 | {
67 | var nameEle = cardEle.QuerySelector(".giftcard_text");
68 | var name = nameEle?.TextContent;
69 |
70 | var linkEle = cardEle.QuerySelector("a");
71 | var link = linkEle?.GetAttribute("href");
72 |
73 | if (string.IsNullOrEmpty(name) || string.IsNullOrEmpty(link))
74 | {
75 | continue;
76 | }
77 |
78 | var match = RegexUtils.MatchCardBalance().Match(link);
79 |
80 | if (match.Success)
81 | {
82 | var value = match.Groups[1].Value;
83 | if (uint.TryParse(value, out uint balance))
84 | {
85 | result.Add(new DigitalGiftCardOption
86 | {
87 | Name = name,
88 | Balance = balance,
89 | });
90 | }
91 | }
92 | }
93 |
94 | return result;
95 | }
96 |
97 | internal static Dictionary? FetchPayload(HtmlDocumentResponse? response)
98 | {
99 | if (response?.Content == null)
100 | {
101 | return null;
102 | }
103 |
104 | var result = new Dictionary();
105 |
106 | var inputs = response.Content.QuerySelectorAll("#externalForm>input");
107 |
108 | foreach (var input in inputs)
109 | {
110 | var key = input.GetAttribute("name");
111 | var value = input.GetAttribute("value");
112 | if (!string.IsNullOrEmpty(key) && !string.IsNullOrEmpty(value))
113 | {
114 | result.Add(key, value);
115 | }
116 | }
117 |
118 | return result;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/ASFEnhance/Community/Command.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.Localization;
3 | using ArchiSteamFarm.Steam;
4 | using ASFEnhance.Data;
5 | using System.Text;
6 |
7 |
8 | namespace ASFEnhance.Community;
9 |
10 | internal static class Command
11 | {
12 | ///
13 | /// 清除通知小绿信
14 | ///
15 | ///
16 | ///
17 | internal static async Task ResponseGetNotifications(Bot bot)
18 | {
19 | if (!bot.IsConnectedAndLoggedOn)
20 | {
21 | return bot.FormatBotResponse(Strings.BotNotConnected);
22 | }
23 |
24 | var result = await WebRequest.GetSteamNotificationsResponse(bot).ConfigureAwait(false);
25 | var response = result?.Response;
26 |
27 | if (response?.Notifications == null)
28 | {
29 | return bot.FormatBotResponse(Langs.NetworkError);
30 | }
31 |
32 | var sb = new StringBuilder();
33 | sb.AppendLine(Langs.MultipleLineResult);
34 |
35 | int index = 1;
36 | foreach (var notification in response.Notifications)
37 | {
38 | var type = notification.NotificationType switch
39 | {
40 | (int)ENotificationType.ReceivedGift => Langs.MsgReceivedGift,
41 | (int)ENotificationType.SubscribedDissionReplyed => Langs.MsgReceivedReplyed,
42 | (int)ENotificationType.ReceivedNewItem => Langs.MsgReceivedNewItem,
43 | (int)ENotificationType.ReceivedFriendInvitation => Langs.MsgFriendInviation,
44 | (int)ENotificationType.MajorSaleStart => Langs.MsgMajorSale,
45 | (int)ENotificationType.ItemInWishlistOnSale => Langs.MsgWishlistItemOnSale,
46 | (int)ENotificationType.ReceivedTradeOffer => Langs.MsgTradeOffer,
47 | (int)ENotificationType.ReceivedSteamSupportReply => Langs.MsgSteamSupport,
48 | (int)ENotificationType.SteamTurnNotification => Langs.MsgSteamTurn,
49 | (int)ENotificationType.SteamCommunityMessage => Langs.MsgCommunityMessage,
50 | _ => notification.NotificationType.ToString(),
51 | };
52 |
53 | sb.AppendLineFormat("{0}{1}: {2} {3}", notification.Read ? "" : "*", index++, type, notification.BodyData);
54 |
55 | if (index >= 15)
56 | {
57 | break;
58 | }
59 | }
60 |
61 | if (index == 1)
62 | {
63 | sb.AppendLine(Langs.MsgNoMessage);
64 | }
65 |
66 | sb.AppendLine(Static.Line);
67 | sb.AppendLineFormat(Langs.NocUnreadCount, response.UnreadCount);
68 |
69 | if (response.ConfirmationCount > 0)
70 | {
71 | sb.AppendLineFormat(Langs.NocConfirmationCount, response.ConfirmationCount);
72 | }
73 | if (response.PendingGiftCount > 0)
74 | {
75 | sb.AppendLineFormat(Langs.NocPendingGiftCount, response.PendingGiftCount);
76 | }
77 | if (response.PendingFriendCount > 0)
78 | {
79 | sb.AppendLineFormat(Langs.NocPendingFriendCount, response.PendingFriendCount);
80 | }
81 | if (response.PendingFamilyInviteCount > 0)
82 | {
83 | sb.AppendLineFormat(Langs.NocPendingFamilyInviteCount, response.PendingFamilyInviteCount);
84 | }
85 |
86 | return bot.FormatBotResponse(sb.ToString());
87 | }
88 |
89 | ///
90 | /// 清除通知小绿信 (多个Bot)
91 | ///
92 | ///
93 | ///
94 | ///
95 | internal static async Task ResponseGetNotifications(string botNames)
96 | {
97 | if (string.IsNullOrEmpty(botNames))
98 | {
99 | throw new ArgumentNullException(nameof(botNames));
100 | }
101 |
102 | var bots = Bot.GetBots(botNames);
103 |
104 | if ((bots == null) || (bots.Count == 0))
105 | {
106 | return FormatStaticResponse(Strings.BotNotFound, botNames);
107 | }
108 |
109 | var results = await Utilities.InParallel(bots.Select(bot => ResponseGetNotifications(bot))).ConfigureAwait(false);
110 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
111 |
112 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
113 | }
114 |
115 |
116 | ///
117 | /// 清除通知小绿信
118 | ///
119 | ///
120 | ///
121 | internal static async Task ResponseClearNotification(Bot bot)
122 | {
123 | if (!bot.IsConnectedAndLoggedOn)
124 | {
125 | return bot.FormatBotResponse(Strings.BotNotConnected);
126 | }
127 |
128 | var result = await WebRequest.MarkNotificationsRead(bot).ConfigureAwait(false);
129 |
130 | return bot.FormatBotResponse(result ? Langs.Success : Langs.Failure);
131 | }
132 |
133 | ///
134 | /// 清除通知小绿信 (多个Bot)
135 | ///
136 | ///
137 | ///
138 | ///
139 | internal static async Task ResponseClearNotification(string botNames)
140 | {
141 | if (string.IsNullOrEmpty(botNames))
142 | {
143 | throw new ArgumentNullException(nameof(botNames));
144 | }
145 |
146 | var bots = Bot.GetBots(botNames);
147 |
148 | if ((bots == null) || (bots.Count == 0))
149 | {
150 | return FormatStaticResponse(Strings.BotNotFound, botNames);
151 | }
152 |
153 | var results = await Utilities.InParallel(bots.Select(bot => ResponseClearNotification(bot))).ConfigureAwait(false);
154 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
155 |
156 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/ASFEnhance/Community/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ASFEnhance.Data.ISteamNotificationService;
3 |
4 | namespace ASFEnhance.Community;
5 |
6 | internal static class WebRequest
7 | {
8 | ///
9 | /// 设置留言全部已读
10 | ///
11 | ///
12 | ///
13 | internal static async Task MarkNotificationsRead(Bot bot)
14 | {
15 | var request = new Uri(SteamApiURL, "/ISteamNotificationService/MarkNotificationsRead/v1/");
16 | var data = new Dictionary(2) {
17 | { "access_token", bot.AccessToken ?? throw new AccessTokenNullException(bot) },
18 | { "timestamp", "0" },
19 | { "mark_all_read", "true" },
20 | };
21 |
22 | await bot.ArchiWebHandler.UrlPost(request, data: data, referer: SteamCommunityURL).ConfigureAwait(false);
23 |
24 | return true;
25 | }
26 |
27 | internal static async Task GetSteamNotificationsResponse(Bot bot)
28 | {
29 | var token = bot.AccessToken ?? throw new AccessTokenNullException(bot);
30 |
31 | var request = new Uri(SteamApiURL, $"/ISteamNotificationService/GetSteamNotifications/v1/?access_token={token}&include_hidden=true&language={Langs.Language}&include_confirmation_count=true&include_pinned_counts=true&include_read=true");
32 |
33 | var response = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession(request, referer: SteamCommunityURL).ConfigureAwait(false);
34 |
35 | return response?.Content;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/ASFEnhance/Curator/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Helpers.Json;
2 | using ArchiSteamFarm.Steam;
3 | using ASFEnhance.Data;
4 | using ASFEnhance.Data.WebApi;
5 | using SteamKit2;
6 |
7 |
8 | namespace ASFEnhance.Curator;
9 |
10 |
11 | ///
12 | /// 网络请求
13 | ///
14 | public static class WebRequest
15 | {
16 | ///
17 | /// 关注或者取关鉴赏家
18 | ///
19 | ///
20 | ///
21 | ///
22 | ///
23 | ///
24 | public static async Task FollowCurator(Bot bot, ulong clanId, bool isFollow, SemaphoreSlim? semaphore)
25 | {
26 | if (semaphore != null)
27 | {
28 | await semaphore.WaitAsync().ConfigureAwait(false);
29 | }
30 |
31 | try
32 | {
33 | var request = new Uri(SteamStoreURL, "/curators/ajaxfollow");
34 | var referer = new Uri(SteamStoreURL, $"curator/{clanId}");
35 |
36 | var data = new Dictionary(3) {
37 | { "clanid", clanId.ToString() },
38 | { "follow", isFollow ? "1" : "0" },
39 | };
40 |
41 | var response = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: data, referer: referer).ConfigureAwait(false);
42 |
43 | return response?.Content?.Success?.Result == EResult.OK;
44 | }
45 | finally
46 | {
47 | semaphore?.Release();
48 | }
49 | }
50 |
51 | ///
52 | /// 获取关注的鉴赏家列表
53 | ///
54 | ///
55 | ///
56 | ///
57 | ///
58 | public static async Task?> GetFollowingCurators(Bot bot, uint start, uint count)
59 | {
60 | var request = new Uri(SteamStoreURL, $"/curators/ajaxgetcurators//?query=&start={start}&count={count}&dynamic_data=&filter=mycurators&appid=0");
61 | var referer = new Uri(SteamStoreURL, "/curators/mycurators/");
62 |
63 | var response = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession(request, referer: referer).ConfigureAwait(false);
64 |
65 | var html = response?.Content?.Html;
66 | if (html == null)
67 | {
68 | return null;
69 | }
70 |
71 | var match = RegexUtils.MatchCuratorPayload().Match(html);
72 | if (!match.Success)
73 | {
74 | return null;
75 | }
76 |
77 | try
78 | {
79 | string jsonStr = match.Groups[1].Value;
80 | var data = jsonStr.ToJsonObject>();
81 | return data;
82 | }
83 | catch (Exception ex)
84 | {
85 | ASFLogger.LogGenericError(ex.Message);
86 | return null;
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/AbstractResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 | ///
5 | /// 基础响应
6 | ///
7 | ///
8 | public record AbstractResponse where T : notnull
9 | {
10 | ///
11 | /// 响应
12 | ///
13 | [JsonPropertyName("response")]
14 | public T? Response { get; set; }
15 | }
16 |
17 | ///
18 | public record AbstractResponse : AbstractResponse>
19 | {
20 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/AccountHistoryResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | internal sealed record AccountHistoryResponse
6 | {
7 | [JsonPropertyName("html")]
8 | public string HtmlContent { get; set; } = "";
9 |
10 | [JsonPropertyName("cursor")]
11 | public CursorData Cursor { get; set; } = new();
12 |
13 | internal sealed record CursorData
14 | {
15 | [JsonPropertyName("wallet_txnid")]
16 | public string WalletTxnid { get; set; } = "";
17 |
18 | [JsonPropertyName("timestamp_newest")]
19 | public long TimestampNewest { get; set; }
20 |
21 | [JsonPropertyName("balance")]
22 | public string Balance { get; set; } = "";
23 |
24 | [JsonPropertyName("currency")]
25 | public int Currency { get; set; }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/CartItemResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Plugin;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | ///
6 | /// 购物车信息
7 | ///
8 | [Obsolete("失效")]
9 | internal sealed record CartItemResponse
10 | {
11 | ///
12 | /// 购物车列表
13 | ///
14 | public HashSet CartItems { get; set; }
15 | ///
16 | /// 购物车总价
17 | ///
18 | public int TotalPrice { get; set; }
19 | ///
20 | /// 是否能为自己购买
21 | ///
22 | public bool PurchaseForSelf { get; set; }
23 | ///
24 | /// 是否能作为礼物购买
25 | ///
26 | public bool PurchaseAsGift { get; set; }
27 |
28 | public CartItemResponse()
29 | {
30 | CartItems = [];
31 | }
32 |
33 | public CartItemResponse(HashSet cartItems, int totalPrice, bool purchaseSelf, bool purchaseGift)
34 | {
35 | CartItems = cartItems;
36 | TotalPrice = totalPrice;
37 | PurchaseForSelf = purchaseSelf;
38 | PurchaseAsGift = purchaseGift;
39 | }
40 |
41 | ///
42 | /// 单个购物车项目
43 | ///
44 | internal sealed record CartItem
45 | {
46 | public SteamGameId GameId { get; set; }
47 | public string Name { get; set; }
48 | public int Price { get; set; }
49 | public CartItem(SteamGameId gameId, string name, int price)
50 | {
51 | Name = name;
52 | GameId = gameId;
53 | Price = price;
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/CheckGameResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 |
3 | ///
4 | /// 获取游戏详情
5 | ///
6 | public sealed record CheckGameResponse
7 | {
8 | ///
9 | /// 是否成功
10 | ///
11 | public bool Success { get; set; }
12 | ///
13 | /// 名称
14 | ///
15 | public string Name { get; set; }
16 | ///
17 | /// 是否已拥有
18 | ///
19 | public bool Owned { get; set; }
20 | ///
21 | /// 是否添加愿望单
22 | ///
23 | public bool InWishlist { get; set; }
24 | ///
25 | /// 是否已关注
26 | ///
27 | public bool IsFollow { get; set; }
28 |
29 | ///
30 | /// 构造函数
31 | ///
32 | ///
33 | ///
34 | public CheckGameResponse(bool success, string name)
35 | {
36 | Success = success;
37 | Name = name;
38 | }
39 |
40 | ///
41 | /// 构造函数
42 | ///
43 | ///
44 | ///
45 | ///
46 | ///
47 | ///
48 | public CheckGameResponse(bool success, string name, bool owned, bool inWishlist, bool isFollow)
49 | {
50 | Success = success;
51 | Name = name;
52 | Owned = owned;
53 | InWishlist = inWishlist;
54 | IsFollow = isFollow;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/ClaimItemResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 | internal sealed record ClaimItemResponse
6 | {
7 | [JsonPropertyName("communityitemid")]
8 | public string? CommunityItemId { get; set; }
9 |
10 | [JsonPropertyName("next_claim_time")]
11 | public long NextClaimTime { get; set; }
12 |
13 | [JsonPropertyName("reward_item")]
14 | public RewardItemData? RewardItem { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/CombineItemStacksResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 | internal sealed record CombineItemStacksResponse
5 | {
6 | [JsonPropertyName("response")]
7 | public ResponseData? Response { get; set; }
8 |
9 | public sealed record ResponseData
10 | {
11 | [JsonPropertyName("item_json")]
12 | public string? ItemJson { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/CuratorData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.Common;
4 | ///
5 | ///
6 | ///
7 | public class CuratorData
8 | {
9 | ///
10 | ///
11 | ///
12 | [JsonPropertyName("clanid")]
13 | public string? ClanId { get; set; }
14 | ///
15 | ///
16 | ///
17 | [JsonPropertyName("listid")]
18 | public string? ListId { get; set; }
19 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/FlagsData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.Common;
4 | ///
5 | ///
6 | ///
7 | public class FlagsData
8 | {
9 | ///
10 | ///
11 | ///
12 | [JsonPropertyName("is_gift")]
13 | public bool IsGift { get; set; }
14 | ///
15 | ///
16 | ///
17 | [JsonPropertyName("is_private")]
18 | public bool IsPrivate { get; set; }
19 | }
20 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/GiftInfoData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.Common;
4 | ///
5 | ///
6 | ///
7 | public sealed record GiftInfoData
8 | {
9 | ///
10 | ///
11 | ///
12 | [JsonPropertyName("accountid_giftee")]
13 | public ulong AccountIdGiftee { get; set; }
14 | ///
15 | ///
16 | ///
17 | [JsonPropertyName("gift_message")]
18 | public GiftMessageData? GiftMessage { get; set; }
19 | ///
20 | ///
21 | ///
22 | [JsonPropertyName("time_scheduled_send")]
23 | public ulong TimeScheduledSend { get; set; }
24 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/GiftMessageData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.Common;
4 | ///
5 | ///
6 | ///
7 | public sealed record GiftMessageData
8 | {
9 | ///
10 | ///
11 | ///
12 | [JsonPropertyName("gifteename")]
13 | public string? GifteeName { get; set; }
14 | ///
15 | ///
16 | ///
17 | [JsonPropertyName("message")]
18 | public string? Message { get; set; }
19 | ///
20 | ///
21 | ///
22 | [JsonPropertyName("sentiment")]
23 | public string? Sentiment { get; set; }
24 | ///
25 | ///
26 | ///
27 | [JsonPropertyName("signature")]
28 | public string? Signature { get; set; }
29 | }
30 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/IdData.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Plugin;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.Common;
5 | ///
6 | ///
7 | ///
8 | public sealed record IdData
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("appid")]
14 | public uint? AppId { get; set; }
15 | ///
16 | ///
17 | ///
18 | [JsonPropertyName("packageid")]
19 | public uint? PackageId { get; set; }
20 | ///
21 | ///
22 | ///
23 | [JsonPropertyName("bundleid")]
24 | public uint? BundleId { get; set; }
25 |
26 | ///
27 | ///
28 | ///
29 | public IdData() { }
30 | ///
31 | ///
32 | ///
33 | ///
34 | ///
35 | public IdData(SteamGameId id)
36 | {
37 | switch (id.Type)
38 | {
39 | case ESteamGameIdType.App:
40 | AppId = id.Id;
41 | break;
42 | case ESteamGameIdType.Sub:
43 | PackageId = id.Id;
44 | break;
45 | case ESteamGameIdType.Bundle:
46 | BundleId = id.Id;
47 | break;
48 | default:
49 | throw new KeyNotFoundException();
50 | }
51 | }
52 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/NavdataData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.Common;
4 | ///
5 | ///
6 | ///
7 | public class NavdataData
8 | {
9 | ///
10 | ///
11 | ///
12 | [JsonPropertyName("domain")]
13 | public string? Domain { get; set; }
14 | ///
15 | ///
16 | ///
17 | [JsonPropertyName("controller")]
18 | public string? Controller { get; set; }
19 | ///
20 | ///
21 | ///
22 | [JsonPropertyName("method")]
23 | public string? Method { get; set; }
24 | ///
25 | ///
26 | ///
27 | [JsonPropertyName("submethod")]
28 | public string? SubMethod { get; set; }
29 | ///
30 | ///
31 | ///
32 | [JsonPropertyName("feature")]
33 | public string? Feature { get; set; }
34 | ///
35 | ///
36 | ///
37 | [JsonPropertyName("depth")]
38 | public int Depth { get; set; }
39 | ///
40 | ///
41 | ///
42 | [JsonPropertyName("countrycode")]
43 | public string CountryCode { get; set; } = Langs.CountryCode;
44 | ///
45 | ///
46 | ///
47 | [JsonPropertyName("webkey")]
48 | public int WebKey { get; set; }
49 | ///
50 | ///
51 | ///
52 | [JsonPropertyName("is_client")]
53 | public bool IsClient { get; set; }
54 | ///
55 | ///
56 | ///
57 | [JsonPropertyName("curator_data")]
58 | public CuratorData? CuratorData { get; set; }
59 | ///
60 | ///
61 | ///
62 | [JsonPropertyName("is_likely_bot")]
63 | public bool IsLikelyBot { get; set; }
64 | ///
65 | ///
66 | ///
67 | [JsonPropertyName("is_utm")]
68 | public bool IsUtm { get; set; }
69 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/PendingGiftData.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Common;
2 |
3 | internal sealed record PendingGiftData
4 | {
5 | public PendingGiftData(ulong giftId, string? gameName, string? senderName, ulong senderSteamId)
6 | {
7 | GiftId = giftId;
8 | GameName = gameName;
9 | SenderName = senderName;
10 | SenderSteamId = senderSteamId;
11 | }
12 |
13 | public ulong GiftId { get; set; }
14 | public string? GameName { get; set; }
15 | public string? SenderName { get; set; }
16 | public ulong SenderSteamId { get; set; }
17 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/Common/RewardItemData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.Common;
4 | internal sealed record RewardItemData
5 | {
6 | [JsonPropertyName("appid")]
7 | public uint AppId { get; set; }
8 |
9 | [JsonPropertyName("defid")]
10 | public uint DefId { get; set; }
11 |
12 | [JsonPropertyName("type")]
13 | public int Type { get; set; }
14 |
15 | [JsonPropertyName("community_item_class")]
16 | public int CommunityItemClass { get; set; }
17 |
18 | [JsonPropertyName("community_item_type")]
19 | public int CommunityItemType { get; set; }
20 |
21 | [JsonPropertyName("point_cost")]
22 | public string? PointCost { get; set; }
23 |
24 | [JsonPropertyName("timestamp_created")]
25 | public long TimestampCreated { get; set; }
26 |
27 | [JsonPropertyName("timestamp_updated")]
28 | public long TimestampUpdated { get; set; }
29 |
30 | [JsonPropertyName("timestamp_available")]
31 | public long TimestampAvailable { get; set; }
32 |
33 | [JsonPropertyName("timestamp_available_end")]
34 | public long TimestampAvailableEnd { get; set; }
35 |
36 | [JsonPropertyName("quantity")]
37 | public string? Quantity { get; set; }
38 |
39 | [JsonPropertyName("internal_description")]
40 | public string? InternalDescription { get; set; }
41 |
42 | [JsonPropertyName("active")]
43 | public bool Active { get; set; }
44 |
45 | [JsonPropertyName("community_item_data")]
46 | public CommunityItemData? CommunityItem { get; set; }
47 |
48 | [JsonPropertyName("usable_duration")]
49 | public int UsableDuration { get; set; }
50 |
51 | [JsonPropertyName("bundle_discount")]
52 | public int BundleDiscount { get; set; }
53 |
54 | public sealed record CommunityItemData
55 | {
56 | [JsonPropertyName("item_name")]
57 | public string? ItemName { get; set; }
58 |
59 | [JsonPropertyName("item_title")]
60 | public string? ItemTitle { get; set; }
61 |
62 | [JsonPropertyName("item_description")]
63 | public string? ItemDescription { get; set; }
64 |
65 | [JsonPropertyName("item_image_small")]
66 | public string? ItemImageSmall { get; set; }
67 |
68 | [JsonPropertyName("item_image_large")]
69 | public string? ItemImageLarge { get; set; }
70 |
71 | [JsonPropertyName("animated")]
72 | public bool Animated { get; set; }
73 |
74 | [JsonPropertyName("tiled")]
75 | public bool Tiled { get; set; }
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/CuratorItem.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | ///
6 | /// 鉴赏家信息
7 | ///
8 | public sealed record CuratorItem
9 | {
10 | ///
11 | /// 名称
12 | ///
13 | [JsonPropertyName("name")]
14 | public string? Name { get; set; }
15 |
16 | ///
17 | /// 描述
18 | ///
19 | [JsonPropertyName("curator_description")]
20 | public string? Description { get; set; }
21 |
22 | ///
23 | /// ID
24 | ///
25 | [JsonPropertyName("clanID")]
26 | public string? ClanId { get; set; }
27 |
28 | ///
29 | /// 关注人数
30 | ///
31 | [JsonPropertyName("total_followers")]
32 | public uint TotalFollowers { get; set; }
33 |
34 | ///
35 | /// 评测数量
36 | ///
37 | [JsonPropertyName("total_reviews")]
38 | public uint TotalReviews { get; set; }
39 |
40 | ///
41 | /// 推荐评测数量
42 | ///
43 | [JsonPropertyName("total_recommended")]
44 | public uint TotalRecommanded { get; set; }
45 |
46 | ///
47 | /// 不推荐评测数量
48 | ///
49 | [JsonPropertyName("total_not_recommended")]
50 | public uint TotalNotRecommanded { get; set; }
51 |
52 | ///
53 | /// 情报评测数量
54 | ///
55 | [JsonPropertyName("total_informative")]
56 | public uint TotalInformative { get; set; }
57 | }
58 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/DigitalGiftCardOption.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 | internal sealed record DigitalGiftCardOption
3 | {
4 | public string Name { get; set; } = "";
5 | public uint Balance { get; set; }
6 | }
7 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/ENotificationType.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 |
3 | ///
4 | /// 通知类型
5 | ///
6 | internal enum ENotificationType : byte
7 | {
8 | ///
9 | /// 收到礼物
10 | ///
11 | ReceivedGift = 2,
12 | ///
13 | /// 订阅的讨论回复
14 | ///
15 | SubscribedDissionReplyed,
16 | ///
17 | /// 有新物品
18 | ///
19 | ReceivedNewItem,
20 | ///
21 | /// 收到好友邀请
22 | ///
23 | ReceivedFriendInvitation,
24 | ///
25 | /// 大促开始
26 | ///
27 | MajorSaleStart,
28 | ///
29 | /// 愿望单物品打折
30 | ///
31 | ItemInWishlistOnSale = 8,
32 | ///
33 | /// 收到交易报价
34 | ///
35 | ReceivedTradeOffer,
36 | ///
37 | /// 收到客服回复
38 | ///
39 | ReceivedSteamSupportReply = 11,
40 | ///
41 | /// Steam回合通知
42 | ///
43 | SteamTurnNotification,
44 | ///
45 | /// Steam消息
46 | ///
47 | SteamCommunityMessage = 14,
48 | }
49 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/EditProfilePayload.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | ///
6 | /// 编辑个人资料实体类
7 | ///
8 | public class EditProfilePayload
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("strPersonaName")]
14 | public string? PersonaName { get; set; }
15 | ///
16 | ///
17 | ///
18 |
19 | [JsonPropertyName("strCustomURL")]
20 | public string? CustomURL { get; set; }
21 | ///
22 | ///
23 | ///
24 |
25 | [JsonPropertyName("strRealName")]
26 | public string? RealName { get; set; }
27 | ///
28 | ///
29 | ///
30 |
31 | [JsonPropertyName("strSummary")]
32 | public string? Summary { get; set; }
33 | ///
34 | ///
35 | ///
36 |
37 | [JsonPropertyName("strAvatarHash")]
38 | public string? AvatarHash { get; set; }
39 | ///
40 | ///
41 | ///
42 |
43 | [JsonPropertyName("rtPersonaNameBannedUntil")]
44 | public long PersonaNameBannedUntil { get; set; }
45 | ///
46 | ///
47 | ///
48 |
49 | [JsonPropertyName("rtProfileSummaryBannedUntil")]
50 | public long ProfileSummaryBannedUntil { get; set; }
51 | ///
52 | ///
53 | ///
54 |
55 | [JsonPropertyName("rtAvatarBannedUntil")]
56 | public long AvatarBannedUntil { get; set; }
57 | ///
58 | ///
59 | ///
60 |
61 | [JsonPropertyName("LocationData")]
62 | public LocationData? Location { get; set; }
63 | ///
64 | ///
65 | ///
66 |
67 | [JsonPropertyName("ProfilePreferences")]
68 | public ProfilePreferencesData? ProfilePreferences { get; set; }
69 |
70 | ///
71 | ///
72 | ///
73 | public class LocationData
74 | {
75 | ///
76 | ///
77 | ///
78 | [JsonPropertyName("locCountry")]
79 | public string? Country { get; set; }
80 |
81 | ///
82 | ///
83 | ///
84 | [JsonPropertyName("locCountryCode")]
85 | public string? CountryCode { get; set; }
86 |
87 | ///
88 | ///
89 | ///
90 | [JsonPropertyName("locState")]
91 | public string? State { get; set; }
92 |
93 | ///
94 | ///
95 | ///
96 | [JsonPropertyName("locStateCode")]
97 | public string? StateCode { get; set; }
98 |
99 | ///
100 | ///
101 | ///
102 | [JsonPropertyName("locCity")]
103 | public string? City { get; set; }
104 |
105 | ///
106 | ///
107 | ///
108 | [JsonPropertyName("locCityCode")]
109 | public string? CityCode { get; set; }
110 | }
111 |
112 | ///
113 | ///
114 | ///
115 | public class ProfilePreferencesData
116 | {
117 | ///
118 | ///
119 | ///
120 | [JsonPropertyName("hide_profile_awards")]
121 | public int HideProfileAwards { get; set; }
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/EditProfileResponse.cs:
--------------------------------------------------------------------------------
1 | using SteamKit2;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 |
6 | ///
7 | /// 编辑个人资料响应
8 | ///
9 | public sealed record EditProfileResponse
10 | {
11 | ///
12 | /// 是否成功
13 | ///
14 | [JsonPropertyName("success")]
15 | public EResult Success { get; set; }
16 |
17 | ///
18 | /// 错误消息
19 | ///
20 | [JsonPropertyName("errmsg")]
21 | public string? ErrMsg { get; set; }
22 |
23 | ///
24 | /// 重定向地址
25 | ///
26 | [JsonPropertyName("redirect")]
27 | public string? Redirect { get; set; }
28 | }
29 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/ExchangeAPIResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | internal sealed record ExchangeAPIResponse
6 | {
7 | [JsonPropertyName("base")]
8 | public string Base { get; set; } = "";
9 |
10 | [JsonPropertyName("date")]
11 | public string Date { get; set; } = "";
12 |
13 | [JsonPropertyName("time_last_updated")]
14 | public long UpdateTime { get; set; }
15 |
16 | [JsonPropertyName("rates")]
17 | public Dictionary Rates { get; set; } = [];
18 | }
19 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/ExternalLinkCheckoutPayload.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 |
3 | ///
4 | /// 外部支付请求体
5 | ///
6 | sealed record ExternalLinkCheckoutPayload
7 | {
8 | ///
9 | /// 构造函数
10 | ///
11 | ///
12 | ///
13 | public ExternalLinkCheckoutPayload(Uri formUrl, Dictionary payload)
14 | {
15 | FormUrl = formUrl;
16 | Payload = payload;
17 | }
18 |
19 | public Uri FormUrl { get; init; }
20 | public Dictionary Payload { get; init; }
21 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/FetchProfileSummaryData.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 | ///
3 | /// 个人资料数据
4 | ///
5 | internal sealed record FetchProfileSummaryData
6 | {
7 | ///
8 | /// 用户Id
9 | ///
10 | public ulong SteamId { get; set; }
11 |
12 | ///
13 | /// 昵称名
14 | ///
15 | public string? NickName { get; set; }
16 |
17 | ///
18 | /// 真名
19 | ///
20 | public string? RealName { get; set; }
21 |
22 | ///
23 | /// 国家/地区
24 | ///
25 | public string? Region { get; set; }
26 |
27 | public string? Description { get; set; }
28 |
29 | public bool IsOnline { get; set; }
30 |
31 | ///
32 | /// 等级
33 | ///
34 | public int Level { get; set; } = -1;
35 | ///
36 | /// 徽章数量
37 | ///
38 | public long BadgeCount { get; set; } = -1;
39 | ///
40 | /// 游戏数量
41 | ///
42 | public long GameCount { get; set; } = -1;
43 | ///
44 | /// 愿望单数量
45 | ///
46 | public long WishlistCount { get; set; } = -1;
47 | ///
48 | /// 截图数量
49 | ///
50 | public long ScreenshotCount { get; set; } = -1;
51 | ///
52 | /// 视频数量
53 | ///
54 | public long VideoCount { get; set; } = -1;
55 | ///
56 | /// 创意工坊物品数量
57 | ///
58 | public long WorkshopCount { get; set; } = -1;
59 | ///
60 | /// 评测数量
61 | ///
62 | public long ReviewCount { get; set; } = -1;
63 | ///
64 | /// 指南数量
65 | ///
66 | public long GuideCount { get; set; } = -1;
67 | ///
68 | /// 艺术作品数量
69 | ///
70 | public long ArtworkCount { get; set; } = -1;
71 | ///
72 | /// 好友数量
73 | ///
74 | public long FriendCount { get; set; } = -1;
75 | ///
76 | /// 组数量
77 | ///
78 | public long GroupCount { get; set; } = -1;
79 |
80 | ///
81 | /// 个人资料留言数量
82 | ///
83 | public long CommentCount { get; set; } = -1;
84 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/FinalPriceResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.WebApi;
2 | using SteamKit2;
3 | using System.Text.Json.Serialization;
4 |
5 | namespace ASFEnhance.Data;
6 |
7 | ///
8 | ///
9 | ///
10 | public sealed record FinalPriceResponse : BaseResultResponse
11 | {
12 | ///
13 | ///
14 | ///
15 | [JsonPropertyName("base")]
16 | public string? BasePrice { get; set; }
17 |
18 | ///
19 | ///
20 | ///
21 | [JsonPropertyName("tax")]
22 | public string? Tax { get; set; }
23 |
24 | ///
25 | ///
26 | ///
27 | [JsonPropertyName("discount")]
28 | public string? Discount { get; set; }
29 |
30 | ///
31 | ///
32 | ///
33 | [JsonPropertyName("currencycode")]
34 | public ECurrencyCode CurrencyCode { get; set; }
35 |
36 | ///
37 | ///
38 | ///
39 | [JsonPropertyName("formattedTotal")]
40 | public string? FormattedTotal { get; set; }
41 | }
42 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/FinalizeTransactionResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.WebApi;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 |
6 | internal sealed record FinalizeTransactionResponse : BaseResultResponse
7 | {
8 | [JsonPropertyName("purchaseresultdetail")]
9 | public int PurchaseResultDetail { get; set; }
10 | [JsonPropertyName("bShowBRSpecificCreditCardError")]
11 | public bool BShowBRSpecificCreditCardError { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/GetOwnedGamesResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 | internal sealed record GetOwnedGamesResponse
5 | {
6 | [JsonPropertyName("response")]
7 | public ResponseData? Response { get; set; }
8 |
9 | internal sealed record ResponseData
10 | {
11 | [JsonPropertyName("game_count")]
12 | public uint GameCount { get; set; }
13 |
14 | [JsonPropertyName("games")]
15 | public List? Games { get; set; }
16 | }
17 |
18 | internal sealed record GameData
19 | {
20 | [JsonPropertyName("appid")]
21 | public uint AppId { get; set; }
22 |
23 | [JsonPropertyName("name")]
24 | public string? Name { get; set; }
25 |
26 | [JsonPropertyName("playtime_2weeks")]
27 | public uint PlayTime2Weeks { get; set; }
28 |
29 | [JsonPropertyName("playtime_forever")]
30 | public uint PlayTimeForever { get; set; }
31 |
32 | [JsonPropertyName("has_community_visible_stats")]
33 | public bool HasCommunityVisibleStats { get; set; }
34 |
35 | [JsonPropertyName("playtime_windows_forever")]
36 | public uint PlaytimeWindowsForever { get; set; }
37 |
38 | [JsonPropertyName("playtime_mac_forever")]
39 | public uint PlaytimeMacForever { get; set; }
40 |
41 | [JsonPropertyName("playtime_linux_forever")]
42 | public uint PlaytimeLinuxForever { get; set; }
43 |
44 | [JsonPropertyName("rtime_last_played")]
45 | public ulong RtimeLastPlayed { get; set; }
46 |
47 | [JsonPropertyName("playtime_disconnected")]
48 | public uint PlaytimeDisconnected { get; set; }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/GetPlayerBansResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 | ///
5 | /// 获取封禁状态
6 | ///
7 | internal sealed record GetPlayerBansResponse
8 | {
9 | [JsonPropertyName("players")]
10 | public List? Players { get; set; }
11 |
12 | public sealed record PlayerData
13 | {
14 | public string SteamId { get; set; } = "";
15 | public bool CommunityBanned { get; set; }
16 | public bool VACBanned { get; set; }
17 | public int NumberOfVACBans { get; set; }
18 | public int DaysSinceLastBan { get; set; }
19 | public int NumberOfGameBans { get; set; }
20 | public string EconomyBan { get; set; } = "none";
21 | }
22 | }
23 |
24 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/GroupData.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 | internal sealed record GroupData(bool Valid, string? GroupName, JoinGroupStatus Status);
3 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IAccountCartService/AddItemsToCartRequest.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.IAccountCartService;
5 |
6 | ///
7 | ///
8 | ///
9 | public sealed record AddItemsToCartRequest
10 | {
11 | ///
12 | ///
13 | ///
14 | [JsonPropertyName("user_country")]
15 | public string UserCountry { get; set; } = Langs.CountryCode;
16 | ///
17 | ///
18 | ///
19 | [JsonPropertyName("items")]
20 | public List? Items { get; set; }
21 | ///
22 | ///
23 | ///
24 | [JsonPropertyName("navdata")]
25 | public NavdataData? Navdata { get; set; }
26 |
27 | ///
28 | ///
29 | ///
30 | public sealed record ItemData
31 | {
32 | ///
33 | ///
34 | ///
35 | [JsonPropertyName("packageid")]
36 | public uint? PackageId { get; set; }
37 | ///
38 | ///
39 | ///
40 | [JsonPropertyName("bundleid")]
41 | public uint? BundleId { get; set; }
42 | ///
43 | ///
44 | ///
45 | [JsonPropertyName("gift_info")]
46 | public GiftInfoData? GIftInfo { get; set; }
47 | ///
48 | ///
49 | ///
50 | [JsonPropertyName("flags")]
51 | public FlagsData? Flags { get; set; }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IAccountCartService/AddItemsToCartResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.IAccountCartService;
5 | ///
6 | ///
7 | ///
8 | public sealed record AddItemsToCartResponse
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("line_item_ids")]
14 | public List? LineItemIds { get; set; }
15 | ///
16 | ///
17 | ///
18 | [JsonPropertyName("cart")]
19 | public CartData? Cart { get; set; }
20 | }
21 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IAccountCartService/GetCartResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.IAccountCartService;
5 | ///
6 | /// 获取购物车响应
7 | ///
8 | public sealed record GetCartResponse
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("cart")]
14 | public CartData? Cart { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IAccountCartService/ModifyLineItemRequest.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.IAccountCartService;
5 |
6 | ///
7 | ///
8 | ///
9 | public sealed record ModifyLineItemRequest
10 | {
11 | ///
12 | ///
13 | ///
14 | [JsonPropertyName("line_item_id")]
15 | public ulong LineItemId { get; set; }
16 | ///
17 | ///
18 | ///
19 | [JsonPropertyName("user_country")]
20 | public string UserCountry { get; set; } = Langs.CountryCode;
21 | ///
22 | ///
23 | ///
24 | [JsonPropertyName("gift_info")]
25 | public GiftInfoData? GiftInfo { get; set; }
26 | ///
27 | ///
28 | ///
29 | [JsonPropertyName("flags")]
30 | public FlagsData? Flags { get; set; }
31 | }
32 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IAccountCartService/RemoveLineItemRequest.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.IAccountCartService;
4 |
5 | ///
6 | ///
7 | ///
8 | public sealed record RemoveLineItemRequest
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("line_item_id")]
14 | public ulong LineItemId { get; set; }
15 | ///
16 | ///
17 | ///
18 | [JsonPropertyName("user_country")]
19 | public string UserCountry { get; set; } = Langs.CountryCode;
20 | }
21 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IAccountPrivateAppsService/GetPrivateAppListResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.IAccountPrivateAppsService;
4 | internal sealed record GetPrivateAppListResponse
5 | {
6 | ///
7 | ///
8 | ///
9 | [JsonPropertyName("private_apps")]
10 | public AppListData? PrivateApps { get; set; }
11 |
12 | public sealed record AppListData
13 | {
14 | [JsonPropertyName("appids")]
15 | public List? AppIds { get; set; }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/ILoyaltyRewardsService/QueryRewardItemsResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.ILoyaltyRewardsService;
5 | internal sealed record QueryRewardItemsResponse
6 | {
7 | [JsonPropertyName("definitions")]
8 | public List? Definitions { get; set; }
9 | }
10 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/ISteamNotificationService/GetSteamNotificationsResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.ISteamNotificationService;
4 |
5 | internal sealed record GetSteamNotificationsResponse
6 | {
7 | [JsonPropertyName("response")]
8 | public ResponseData? Response { get; set; }
9 |
10 | public sealed record ResponseData
11 | {
12 | [JsonPropertyName("notifications")]
13 | public List? Notifications { get; set; }
14 |
15 | [JsonPropertyName("confirmation_count")]
16 | public int ConfirmationCount { get; set; }
17 |
18 | [JsonPropertyName("pending_gift_count")]
19 | public int PendingGiftCount { get; set; }
20 |
21 | [JsonPropertyName("pending_friend_count")]
22 | public int PendingFriendCount { get; set; }
23 |
24 | [JsonPropertyName("unread_count")]
25 | public int UnreadCount { get; set; }
26 |
27 | [JsonPropertyName("pending_family_invite_count")]
28 | public int PendingFamilyInviteCount { get; set; }
29 | }
30 |
31 | public sealed record NotificationData
32 | {
33 | [JsonPropertyName("notification_id")]
34 | public string? NotificationId { get; set; }
35 |
36 | [JsonPropertyName("notification_targets")]
37 | public int NotificationTargets { get; set; }
38 |
39 | [JsonPropertyName("notification_type")]
40 | public int NotificationType { get; set; }
41 |
42 | [JsonPropertyName("body_data")]
43 | public string? BodyData { get; set; }
44 |
45 | [JsonPropertyName("read")]
46 | public bool Read { get; set; }
47 |
48 | [JsonPropertyName("timestamp")]
49 | public long Timestamp { get; set; }
50 |
51 | [JsonPropertyName("hidden")]
52 | public bool Hidden { get; set; }
53 |
54 | [JsonPropertyName("expiry")]
55 | public long Expiry { get; set; }
56 |
57 | [JsonPropertyName("viewed")]
58 | public long Viewed { get; set; }
59 | }
60 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/IStoreBrowseService/GetItemsRequest.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Common;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.IStoreBrowseService;
5 | ///
6 | ///
7 | ///
8 | public sealed record GetItemsRequest
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("ids")]
14 | public List? Ids { get; set; }
15 |
16 | ///
17 | ///
18 | ///
19 | [JsonPropertyName("context")]
20 | public ContextData? Context { get; set; }
21 | ///
22 | ///
23 | ///
24 | [JsonPropertyName("data_request")]
25 | public DataRequestData? DataRequest { get; set; }
26 |
27 | ///
28 | ///
29 | ///
30 | public sealed record ContextData
31 | {
32 | ///
33 | ///
34 | ///
35 | [JsonPropertyName("language")]
36 | public string Language { get; set; } = DefaultOrCurrentLanguage;
37 | ///
38 | ///
39 | ///
40 | [JsonPropertyName("country_code")]
41 | public string CountryCode { get; set; } = Langs.CountryCode;
42 | ///
43 | ///
44 | ///
45 | [JsonPropertyName("steam_realm")]
46 | public string SteamRealm { get; set; } = "1";
47 | }
48 |
49 | ///
50 | ///
51 | ///
52 | public sealed record DataRequestData
53 | {
54 | ///
55 | ///
56 | ///
57 | [JsonPropertyName("include_all_purchase_options")]
58 | public bool IncludeAllPurchaseOptions { get; set; }
59 | ///
60 | ///
61 | ///
62 | [JsonPropertyName("include_full_description")]
63 | public bool IncludeFullDescription { get; set; }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IStoreBrowseService/GetItemsResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.IStoreBrowseService;
4 | ///
5 | ///
6 | ///
7 | public sealed record GetItemsResponse
8 | {
9 | ///
10 | ///
11 | ///
12 | [JsonPropertyName("store_items")]
13 | public List? StoreItems { get; set; }
14 |
15 | ///
16 | ///
17 | ///
18 | public sealed record StoreItemData
19 | {
20 | ///
21 | ///
22 | ///
23 | [JsonPropertyName("item_type")]
24 | public int ItemType { get; set; }
25 | ///
26 | ///
27 | ///
28 | [JsonPropertyName("id")]
29 | public uint Id { get; set; }
30 | ///
31 | ///
32 | ///
33 | [JsonPropertyName("success")]
34 | public byte Success { get; set; }
35 | ///
36 | ///
37 | ///
38 | [JsonPropertyName("visible")]
39 | public bool Visiable { get; set; }
40 | ///
41 | ///
42 | ///
43 | [JsonPropertyName("name")]
44 | public string? Name { get; set; }
45 | ///
46 | ///
47 | ///
48 | [JsonPropertyName("store_url_path")]
49 | public string? StoreUrlPath { get; set; }
50 | ///
51 | ///
52 | ///
53 | [JsonPropertyName("appid")]
54 | public uint AppId { get; set; }
55 | ///
56 | ///
57 | ///
58 | [JsonPropertyName("type")]
59 | public byte Type { get; set; }
60 | ///
61 | ///
62 | ///
63 | [JsonPropertyName("is_free")]
64 | public bool IsFree { get; set; }
65 | ///
66 | ///
67 | ///
68 | [JsonPropertyName("purchase_options")]
69 | public List? PurchaseOptions { get; set; }
70 | ///
71 | ///
72 | ///
73 | [JsonPropertyName("full_description")]
74 | public string? FullDescription { get; set; }
75 | }
76 |
77 | ///
78 | ///
79 | ///
80 | public sealed record PurchaseOptionData
81 | {
82 | ///
83 | ///
84 | ///
85 | [JsonPropertyName("packageid")]
86 | public uint? PackageId { get; set; }
87 | ///
88 | ///
89 | ///
90 | [JsonPropertyName("bundleid")]
91 | public uint? BundleId { get; set; }
92 | ///
93 | ///
94 | ///
95 | [JsonPropertyName("purchase_option_name")]
96 | public string? PurchaseOptionName { get; set; }
97 | ///
98 | ///
99 | ///
100 | [JsonPropertyName("final_price_in_cents")]
101 | public string? FinalPriceInCents { get; set; }
102 | ///
103 | ///
104 | ///
105 | [JsonPropertyName("formatted_final_price")]
106 | public string? FormattedFinalPrice { get; set; }
107 | ///
108 | ///
109 | ///
110 | [JsonPropertyName("user_can_purchase_as_gift")]
111 | public bool UserCanPurchaseAsGift { get; set; }
112 | ///
113 | ///
114 | ///
115 | [JsonPropertyName("hide_discount_pct_for_compliance")]
116 | public bool HideDiscountPctForCompliance { get; set; }
117 | ///
118 | ///
119 | ///
120 | [JsonPropertyName("included_game_count")]
121 | public int IncludedGameCount { get; set; }
122 | ///
123 | ///
124 | ///
125 | [JsonPropertyName("requires_shipping")]
126 | public bool RequiresShipping { get; set; }
127 | }
128 | }
129 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IStoreService/GetDiscoveryQueueResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.IStoreService;
4 |
5 | ///
6 | /// 获取探索队列响应
7 | ///
8 | internal sealed record GetDiscoveryQueueResponse
9 | {
10 | [JsonPropertyName("appids")]
11 | public List? AppIds { get; set; }
12 |
13 | [JsonPropertyName("country_code")]
14 | public string? CountryCode { get; set; }
15 |
16 | [JsonPropertyName("settings")]
17 | public SettingsData? Settings { get; set; }
18 |
19 | [JsonPropertyName("skipped")]
20 | public int Skipped { get; set; }
21 |
22 | [JsonPropertyName("exhausted")]
23 | public bool Exhausted { get; set; }
24 |
25 | [JsonPropertyName("experimental_cohort")]
26 | public int ExperimentalCohort { get; set; }
27 |
28 |
29 | internal sealed record SettingsData
30 | {
31 | [JsonPropertyName("include_coming_soon")]
32 | public bool IncludeComingSoon { get; set; }
33 | }
34 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/IWishlistService/GetWishlistCountResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.IWishlistService;
4 | ///
5 | /// 愿望单响应
6 | ///
7 | public sealed record GetWishlistCountResponse
8 | {
9 | ///
10 | /// 愿望单物品
11 | ///
12 | [JsonPropertyName("count")]
13 | public int Count { get; set; }
14 | }
15 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/IWishlistService/GetWishlistResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.IWishlistService;
4 | ///
5 | /// 愿望单响应
6 | ///
7 | public sealed record GetWishlistResponse
8 | {
9 | ///
10 | /// 愿望单物品
11 | ///
12 | [JsonPropertyName("items")]
13 | public List? Items { get; set; }
14 |
15 | ///
16 | /// 愿望单信息
17 | ///
18 | public sealed record ItemData
19 | {
20 | ///
21 | /// appid
22 | ///
23 | [JsonPropertyName("appid")]
24 | public uint AppId { get; set; }
25 | ///
26 | /// 优先级
27 | ///
28 | [JsonPropertyName("priority")]
29 | public uint Priority { get; set; }
30 | ///
31 | /// 添加日期
32 | ///
33 | [JsonPropertyName("date_added")]
34 | public ulong DateAdded { get; set; }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/InitTransactionResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.WebApi;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 |
6 | ///
7 | ///
8 | ///
9 | public sealed record InitTransactionResponse : BaseResultResponse
10 | {
11 | ///
12 | ///
13 | ///
14 | [JsonPropertyName("purchaseresultdetail")]
15 | public int PurchaseResultDetail { get; set; }
16 | ///
17 | ///
18 | ///
19 | [JsonPropertyName("paymentmethod")]
20 | public int PaymentMethod { get; set; }
21 | ///
22 | ///
23 | ///
24 | [JsonPropertyName("transid")]
25 | public string? TransId { get; set; }
26 | ///
27 | ///
28 | ///
29 | [JsonPropertyName("transactionid")]
30 | public string? TransActionId { get; set; }
31 | ///
32 | ///
33 | ///
34 | [JsonPropertyName("transactionprovider")]
35 | public int TransActionProvider { get; set; }
36 | ///
37 | ///
38 | ///
39 | [JsonPropertyName("paymentmethodcountrycode")]
40 | public string? PaymentMethodCountryCode { get; set; }
41 | ///
42 | ///
43 | ///
44 | [JsonPropertyName("paypaltoken")]
45 | public string? PaypalToken { get; set; }
46 | ///
47 | ///
48 | ///
49 | [JsonPropertyName("paypalacct")]
50 | public int PaypalAcct { get; set; }
51 | ///
52 | ///
53 | ///
54 | [JsonPropertyName("packagewitherror")]
55 | public int PackageWithError { get; set; }
56 | ///
57 | ///
58 | ///
59 | [JsonPropertyName("appcausingerror")]
60 | public int AppCausingError { get; set; }
61 | ///
62 | ///
63 | ///
64 | [JsonPropertyName("pendingpurchasepaymentmethod")]
65 | public int PendingPurchasePaymenTmethod { get; set; }
66 | ///
67 | ///
68 | ///
69 | [JsonPropertyName("authorizationurl")]
70 | public string? AuthorizationUrl { get; set; }
71 | }
72 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/JoinGroupStatus.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 |
3 | internal enum JoinGroupStatus
4 | {
5 | /// 出错
6 | Failed = -1,
7 | /// 未加入
8 | NotJoined = 0,
9 | /// 已加入
10 | Joined = 1,
11 | /// 已申请加入
12 | Applied = 2,
13 | }
14 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/LeavelGroupResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 | internal sealed record LeaveGroupResponse
5 | {
6 | [JsonPropertyName("success")]
7 | public bool Success { get; init; }
8 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/LicensesResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 |
3 | ///
4 | /// 许可类型
5 | ///
6 | internal enum LicenseType : byte
7 | {
8 | Unknown = 0,
9 | ///
10 | /// 零售cdk
11 | ///
12 | Retail,
13 | ///
14 | /// 免费
15 | ///
16 | Complimentary,
17 | ///
18 | /// Steam商店购买
19 | ///
20 | SteamStore,
21 | ///
22 | /// 礼物/玩家通行证
23 | ///
24 | GiftOrGuestPass,
25 | }
26 |
27 | internal sealed record LicensesData
28 | {
29 | ///
30 | /// 类型
31 | ///
32 | public LicenseType Type { get; set; }
33 | ///
34 | /// 名称
35 | ///
36 | public string? Name { get; set; }
37 | ///
38 | /// SubId
39 | ///
40 | public uint PackageId { get; set; }
41 | }
42 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/NotificationOptions.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | internal sealed record NotificationOptions
6 | {
7 | public NotificationTarget ReceivedGift { get; set; } = NotificationTarget.OFF;
8 | public NotificationTarget SubscribedDissionReplyed { get; set; } = NotificationTarget.OFF;
9 | public NotificationTarget ReceivedNewItem { get; set; } = NotificationTarget.OFF;
10 | public NotificationTarget ReceivedFriendInvitation { get; set; } = NotificationTarget.OFF;
11 | public NotificationTarget MajorSaleStart { get; set; } = NotificationTarget.OFF;
12 | public NotificationTarget ItemInWishlistOnSale { get; set; } = NotificationTarget.OFF;
13 | public NotificationTarget ReceivedTradeOffer { get; set; } = NotificationTarget.OFF;
14 | public NotificationTarget ReceivedSteamSupportReply { get; set; } = NotificationTarget.OFF;
15 | public NotificationTarget SteamTurnNotification { get; set; } = NotificationTarget.OFF;
16 | }
17 |
18 | [Flags]
19 | internal enum NotificationTarget : byte
20 | {
21 | OFF = 0,
22 | On = 1,
23 | SteamClient = 2,
24 | OnAndSteamClient = On | SteamClient,
25 | MobileApp = 8,
26 | OnAndMobileApp = On | MobileApp,
27 | All = On | SteamClient | MobileApp
28 | }
29 |
30 | internal sealed record NotificationPayload
31 | {
32 | public NotificationPayload(ENotificationType notificationType, NotificationTarget notificationTargets)
33 | {
34 | NotificationType = notificationType;
35 | NotificationTargets = notificationTargets;
36 | }
37 |
38 | [JsonPropertyName("notification_type")]
39 | public ENotificationType NotificationType { get; set; }
40 |
41 | [JsonPropertyName("notification_targets")]
42 | public NotificationTarget NotificationTargets { get; set; }
43 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/AddressConfig.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Plugin;
2 |
3 | ///
4 | /// 地址信息
5 | ///
6 | public sealed record AddressConfig
7 | {
8 | ///
9 | /// 姓
10 | ///
11 | public string? FirstName { get; set; }
12 | ///
13 | /// 名
14 | ///
15 | public string? LastName { get; set; }
16 |
17 | ///
18 | /// 街道地址
19 | ///
20 | public string Address { get; set; } = "";
21 |
22 | ///
23 | /// 城市
24 | ///
25 | public string City { get; set; } = "";
26 |
27 | ///
28 | /// 国家
29 | ///
30 | public string Country { get; set; } = "";
31 |
32 | ///
33 | /// 省/州
34 | ///
35 | public string State { get; set; } = "";
36 |
37 | ///
38 | /// 邮编
39 | ///
40 | public string PostCode { get; set; } = "";
41 | }
42 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/EmailOptions.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Plugin;
2 |
3 | internal sealed record EmailOptions
4 | {
5 | ///
6 | /// 允许发送电子邮件 (总开关)
7 | ///
8 | public bool EnableEmailNotification { get; set; }
9 | ///
10 | /// 愿望单物品打折时
11 | ///
12 | public bool WhenWishlistDiscount { get; set; }
13 | ///
14 | /// 愿望单物品发行或脱离抢先体验时
15 | ///
16 | public bool WhenWishlistRelease { get; set; }
17 | ///
18 | /// 关注的青睐之光物品发行或脱离抢先体验时
19 | ///
20 | public bool WhenGreenLightRelease { get; set; }
21 | ///
22 | /// 关注的发行商发行或者脱离抢险体验时
23 | ///
24 | public bool WhenFollowPublisherRelease { get; set; }
25 | ///
26 | /// 当季节促销开始时
27 | ///
28 | public bool WhenSaleEvent { get; set; }
29 | ///
30 | /// 收到鉴赏家评测副本
31 | ///
32 | public bool WhenReceiveCuratorReview { get; set; }
33 | ///
34 | /// 收到社区奖励
35 | ///
36 | public bool WhenReceiveCommunityReward { get; set; }
37 | ///
38 | /// 收到游戏活动通知
39 | ///
40 | public bool WhenGameEventNotification { get; set; }
41 | }
42 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/GroupItem.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Plugin;
2 |
3 | internal sealed record GroupItem
4 | {
5 | public string Name { get; set; }
6 | public ulong GroupId { get; set; }
7 |
8 | public GroupItem(string name, ulong groupId)
9 | {
10 | Name = name;
11 | GroupId = groupId;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/HistoryParseResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Plugin;
2 |
3 | ///
4 | /// 账户历史数据返回值
5 | ///
6 | internal sealed record HistoryParseResponse
7 | {
8 | /// 未知
9 | public int Unknown;
10 | /// 商店购买
11 | public int StorePurchase;
12 | /// 商店购买[余额]
13 | public int StorePurchaseWallet;
14 | /// 礼物购买
15 | public int GiftPurchase;
16 | /// 礼物购买[余额]
17 | public int GiftPurchaseWallet;
18 | /// 市场购买
19 | public int MarketPurchase;
20 | /// 市场出售
21 | public int MarketSelling;
22 | /// 游戏内购
23 | public int InGamePurchase;
24 | /// 退款
25 | public int RefundPurchase;
26 | /// 退款[余额]
27 | public int RefundPurchaseWallet;
28 | /// 购买余额
29 | public int WalletPurchase;
30 | /// 其他
31 | public int Other;
32 |
33 | public static HistoryParseResponse operator +(HistoryParseResponse a, HistoryParseResponse b)
34 | {
35 | HistoryParseResponse result = new()
36 | {
37 | Unknown = a.Unknown + b.Unknown,
38 | StorePurchase = a.StorePurchase + b.StorePurchase,
39 | StorePurchaseWallet = a.StorePurchaseWallet + b.StorePurchaseWallet,
40 | GiftPurchase = a.GiftPurchase + b.GiftPurchase,
41 | GiftPurchaseWallet = a.GiftPurchaseWallet + b.GiftPurchaseWallet,
42 | MarketPurchase = a.MarketPurchase + b.MarketPurchase,
43 | MarketSelling = a.MarketSelling + b.MarketSelling,
44 | InGamePurchase = a.InGamePurchase + b.InGamePurchase,
45 | RefundPurchase = a.RefundPurchase + b.RefundPurchase,
46 | RefundPurchaseWallet = a.RefundPurchaseWallet + b.RefundPurchaseWallet,
47 | WalletPurchase = a.WalletPurchase + b.WalletPurchase,
48 | Other = a.Other + b.Other,
49 | };
50 | return result;
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/PluginConfig.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Plugin;
2 |
3 | ///
4 | /// 应用配置
5 | ///
6 | public sealed record PluginConfig
7 | {
8 | ///
9 | /// 是否同意使用协议
10 | ///
11 | public bool EULA { get; set; } = true;
12 |
13 | ///
14 | /// 是否启用统计
15 | ///
16 | public bool Statistic { get; set; } = true;
17 |
18 | ///
19 | /// 是否启用开发者特性
20 | ///
21 | public bool DevFeature { get; set; }
22 |
23 | ///
24 | /// 禁用命令表
25 | ///
26 | public HashSet? DisabledCmds { get; set; }
27 |
28 | ///
29 | /// 单条地址信息
30 | ///
31 | public AddressConfig? Address { get; set; }
32 |
33 | ///
34 | /// 多条地址信息
35 | ///
36 | public List? Addresses { get; set; }
37 |
38 | ///
39 | /// Api Key, 用于获取封禁信息, 非必须
40 | ///
41 | public string? ApiKey { get; set; }
42 |
43 | ///
44 | /// 自动领取物品的机器人名, 逗号分隔
45 | ///
46 | public string? AutoClaimItemBotNames { get; set; }
47 | ///
48 | /// 自动领取物品周期, 单位小时
49 | ///
50 | public uint AutoClaimItemPeriod { get; set; } = 23;
51 |
52 | ///
53 | /// 默认语言, 影响 PUBLICRECOMMAND 命令
54 | ///
55 | public string? DefaultLanguage { get; set; }
56 |
57 | ///
58 | /// 自定义送礼消息
59 | ///
60 | public string? CustomGifteeMessage { get; set; }
61 | }
62 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/SteamGameID.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data.Plugin;
2 |
3 | ///
4 | ///
5 | ///
6 | public sealed record SteamGameId
7 | {
8 | ///
9 | ///
10 | ///
11 | public string? Input { get; set; }
12 | ///
13 | ///
14 | ///
15 | public ESteamGameIdType Type { get; set; }
16 | ///
17 | ///
18 | ///
19 | public uint Id { get; set; }
20 |
21 | ///
22 | ///
23 | ///
24 | ///
25 | ///
26 | public SteamGameId(ESteamGameIdType type, uint gameId)
27 | {
28 | Input = "";
29 | Type = type;
30 | Id = gameId;
31 | }
32 | ///
33 | ///
34 | ///
35 | ///
36 | ///
37 | ///
38 | public SteamGameId(string input, ESteamGameIdType type, uint gameId)
39 | {
40 | Input = input;
41 | Type = type;
42 | Id = gameId;
43 | }
44 |
45 | ///
46 | ///
47 | ///
48 | ///
49 | public override string ToString()
50 | {
51 | return $"{Type}/{Id}";
52 | }
53 | }
54 |
55 | ///
56 | /// Id类型
57 | ///
58 | [Flags]
59 | public enum ESteamGameIdType : byte
60 | {
61 | ///
62 | /// 错误
63 | ///
64 | Error = 0,
65 | ///
66 | /// 应用
67 | ///
68 | App = 1,
69 | ///
70 | /// Sub
71 | ///
72 | Sub = 2,
73 | ///
74 | /// 捆绑包
75 | ///
76 | Bundle = 4,
77 | ///
78 | /// 所有
79 | ///
80 | All = App | Sub | Bundle,
81 | }
82 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/Plugin/SubModuleInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace ASFEnhance.Data.Plugin;
4 | internal sealed record SubModuleInfo
5 | {
6 | ///
7 | /// 插件名称
8 | ///
9 | public string PluginName { get; set; } = null!;
10 | ///
11 | /// 插件命令前缀
12 | ///
13 | public string? CmdPrefix { get; set; }
14 | ///
15 | /// 更新仓库名称
16 | ///
17 | public string? RepoName { get; set; }
18 | ///
19 | /// 插件版本
20 | ///
21 | public Version PluginVersion { get; set; } = null!;
22 | ///
23 | /// 命令处理函数
24 | ///
25 | public MethodInfo CommandHandler { get; set; } = null!;
26 | ///
27 | /// 参数列表
28 | ///
29 | public List ParamList { get; set; } = null!;
30 |
31 | ///
32 | /// 判断CmdPrefix是否匹配
33 | ///
34 | ///
35 | ///
36 | public bool MatchCmdPrefix(string text) => !string.IsNullOrEmpty(CmdPrefix) && CmdPrefix == text;
37 | }
38 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/PluginUpdateResponse.cs:
--------------------------------------------------------------------------------
1 | namespace ASFEnhance.Data;
2 |
3 | ///
4 | /// 插件更新信息
5 | ///
6 | internal sealed record PluginUpdateResponse
7 | {
8 | ///
9 | /// 插件名称
10 | ///
11 | public string PluginName { get; set; } = "";
12 | ///
13 | /// 有可用更新
14 | ///
15 | public bool CanUpdate => OnlineVersion != null && OnlineVersion > CurrentVersion;
16 | ///
17 | /// 已经为最新版本
18 | ///
19 | public bool IsLatest => OnlineVersion == CurrentVersion;
20 |
21 | public string Tips => CanUpdate ? Langs.CanUpdate : "";
22 | public Version? CurrentVersion { get; set; }
23 | public Version? OnlineVersion { get; set; }
24 | public string? UpdateLog { get; set; }
25 | public string ReleaseNote { get; set; } = "";
26 | }
27 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/PurchaseResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.WebApi;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 |
6 | internal sealed record PurchaseResponse : BaseResultResponse
7 | {
8 | [JsonPropertyName("transid")]
9 | public string? TransId { get; set; }
10 |
11 | [JsonPropertyName("paymentmethod")]
12 | public byte PaymentMethod { get; set; }
13 |
14 | [JsonPropertyName("transactionid")]
15 | public string? TransActionId { get; set; }
16 |
17 | [JsonPropertyName("transactionprovider")]
18 | public byte TransSctionProvider { get; set; }
19 |
20 | [JsonPropertyName("paymentmethodcountrycode")]
21 | public string? PaymentMethodCountryCode { get; set; }
22 | }
23 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/RecommendGameResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | ///
6 | ///
7 | ///
8 | public sealed record RecommendGameResponse
9 | {
10 | ///
11 | ///
12 | ///
13 | [JsonPropertyName("success")]
14 | public bool Result { get; set; }
15 |
16 | ///
17 | ///
18 | ///
19 | [JsonPropertyName("strError")]
20 | public string? ErrorMsg { get; set; }
21 | }
22 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/SteamReplayResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data;
4 |
5 | internal sealed record SteamReplayResponse
6 | {
7 | [JsonPropertyName("response")]
8 | public ResponseData Response { get; set; } = new();
9 |
10 | public class ResponseData
11 | {
12 | [JsonPropertyName("images")]
13 | public List Imanges { get; set; } = [];
14 | }
15 | public class ImagesData
16 | {
17 | [JsonPropertyName("name")]
18 | public string Name { get; set; } = "";
19 |
20 | [JsonPropertyName("url_path")]
21 | public string Path { get; set; } = "";
22 | }
23 | }
24 |
25 | internal sealed record SteamReplayPermissionsResponse
26 | {
27 | [JsonPropertyName("response")]
28 | public ResponseData Response { get; set; } = new();
29 |
30 | public class ResponseData
31 | {
32 | [JsonPropertyName("privacy_state")]
33 | public int Privacy { get; set; }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/TransactionStatusResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.WebApi;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 |
6 | ///
7 | ///
8 | ///
9 | public sealed record TransactionStatusResponse : BaseResultResponse
10 | {
11 | ///
12 | ///
13 | ///
14 | [JsonPropertyName("purchaseresultdetail")]
15 | public int PurchaseResultDetail { get; set; }
16 | ///
17 | ///
18 | ///
19 | [JsonPropertyName("purchasereceipt")]
20 | public PurchaseReceiptResponse? PurchaseReceipt { get; set; }
21 |
22 | ///
23 | ///
24 | ///
25 | public sealed class PurchaseReceiptResponse
26 | {
27 | ///
28 | ///
29 | ///
30 | [JsonPropertyName("paymentmethod")]
31 | public int PaymentMethod { get; set; }
32 | ///
33 | ///
34 | ///
35 | [JsonPropertyName("purchasestatus")]
36 | public int PurchaseStatus { get; set; }
37 | ///
38 | ///
39 | ///
40 | [JsonPropertyName("resultdetail")]
41 | public int ResultDetail { get; set; }
42 | ///
43 | ///
44 | ///
45 | [JsonPropertyName("baseprice")]
46 | public string? BasePrice { get; set; }
47 |
48 | ///
49 | ///
50 | ///
51 | [JsonPropertyName("formattedTotal")]
52 | public string FormattedTotal { get; set; } = "";
53 | ///
54 | ///
55 | ///
56 | [JsonPropertyName("tax")]
57 | public string? Tax { get; set; }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/UnpackGiftResponse.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.WebApi;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data;
5 | internal sealed record UnpackGiftResponse : BaseResultResponse
6 | {
7 | [JsonPropertyName("gidgiftnew")]
8 | public ulong GidGiftNew { get; set; }
9 |
10 | [JsonPropertyName("accepted")]
11 | public bool Accepted { get; set; }
12 | }
13 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/AJaxFollowResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.WebApi;
4 |
5 | internal sealed record AJaxFollowResponse
6 | {
7 | [JsonPropertyName("success")]
8 | public BaseResultResponse? Success { get; set; }
9 | }
10 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/AddWishlistResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.WebApi;
4 |
5 | ///
6 | /// 添加愿望单响应
7 | ///
8 | public record AddWishlistResponse
9 | {
10 | ///
11 | /// 结果
12 | ///
13 | [JsonPropertyName("success")]
14 | public bool Result { get; set; }
15 |
16 | ///
17 | /// 愿望单数量
18 | ///
19 | [JsonPropertyName("wishlistCount")]
20 | public int WishlistCount { get; set; }
21 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/AjaxCreateWalletAndCheckFundsResponse.cs:
--------------------------------------------------------------------------------
1 | using SteamKit2;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.WebApi;
5 |
6 | ///
7 | ///
8 | ///
9 | public sealed record AjaxCreateWalletAndCheckFundsResponse
10 | {
11 | ///
12 | ///
13 | ///
14 | [JsonPropertyName("success")]
15 | public EResult Success { get; set; }
16 | }
17 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/AjaxGetCuratorsResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.WebApi;
4 |
5 | internal sealed record AjaxGetCuratorsResponse
6 | {
7 | [JsonPropertyName("success")]
8 | public bool Success { get; set; }
9 |
10 | [JsonPropertyName("pagesize")]
11 | public uint PageSize { get; set; }
12 |
13 | [JsonPropertyName("total_count")]
14 | public uint TotalCount { get; set; }
15 |
16 | [JsonPropertyName("start")]
17 | public uint Start { get; set; }
18 |
19 | [JsonPropertyName("results_html")]
20 | public string? Html { get; set; }
21 | }
22 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/AjaxGetInviteTokens.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.WebApi;
4 |
5 | internal sealed record AjaxGetInviteTokens : BaseResultResponse
6 | {
7 | [JsonPropertyName("token")]
8 | public string Token { get; set; } = "";
9 |
10 | [JsonPropertyName("invite")]
11 | public InviteData Invite { get; set; } = new();
12 |
13 | [JsonIgnore]
14 | public string? Prefix { get; set; }
15 | }
16 |
17 | internal sealed record InviteData
18 | {
19 | [JsonPropertyName("invite_token")]
20 | public string? InviteToken { get; set; }
21 |
22 | [JsonPropertyName("invite_limit")]
23 | public string? InviteLimit { get; set; }
24 |
25 | [JsonPropertyName("invite_duration")]
26 | public string? InviteDuration { get; set; }
27 |
28 | [JsonPropertyName("time_created")]
29 | public long TimeCreated { get; set; }
30 |
31 | [JsonPropertyName("valid")]
32 | public byte Valid { get; set; }
33 | }
34 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/AjaxRedeemWalletCodeResponse.cs:
--------------------------------------------------------------------------------
1 | using SteamKit2;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.WebApi;
5 |
6 | ///
7 | ///
8 | ///
9 | public sealed record AjaxRedeemWalletCodeResponse
10 | {
11 | ///
12 | ///
13 | ///
14 | [JsonPropertyName("success")]
15 | public EResult Success { get; set; }
16 |
17 | ///
18 | ///
19 | ///
20 | [JsonPropertyName("detail")]
21 | internal int Detail { get; set; }
22 |
23 | ///
24 | ///
25 | ///
26 | [JsonPropertyName("formattednewwalletbalance")]
27 | internal string WalletBalance { get; set; } = "";
28 | }
29 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/BaseResultResponse.cs:
--------------------------------------------------------------------------------
1 | using SteamKit2;
2 | using System.Text.Json.Serialization;
3 |
4 | namespace ASFEnhance.Data.WebApi;
5 | ///
6 | /// 结果响应
7 | ///
8 | public record BaseResultResponse
9 | {
10 | ///
11 | /// 结果
12 | ///
13 | [JsonPropertyName("success")]
14 | public EResult Result { get; set; }
15 | }
16 |
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/IgnoreGameResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.WebApi;
4 |
5 | ///
6 | /// 添加愿望单响应
7 | ///
8 | public record IgnoreGameResponse
9 | {
10 | ///
11 | /// 结果
12 | ///
13 | [JsonPropertyName("success")]
14 | public bool Result { get; set; }
15 | }
--------------------------------------------------------------------------------
/ASFEnhance/Data/WebApi/TradeOfferAcceptResponse.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Data.WebApi;
4 |
5 | ///
6 | /// 来自ASF
7 | ///
8 | internal sealed record TradeOfferAcceptResponse
9 | {
10 | [JsonPropertyName("strError")]
11 | public string? ErrorText { get; set; }
12 |
13 | [JsonPropertyName("needs_mobile_confirmation")]
14 | public bool RequiresMobileConfirmation { get; set; }
15 | }
--------------------------------------------------------------------------------
/ASFEnhance/DevFeature/Command.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.Localization;
3 | using ArchiSteamFarm.Steam;
4 | using System.Text;
5 |
6 | namespace ASFEnhance.DevFeature;
7 |
8 | internal static class Command
9 | {
10 | ///
11 | /// 获取商店Cookies
12 | ///
13 | ///
14 | ///
15 | internal static string? ResponseGetCookies(Bot bot)
16 | {
17 | if (!bot.IsConnectedAndLoggedOn)
18 | {
19 | return bot.FormatBotResponse(Strings.BotNotConnected);
20 | }
21 |
22 | var response = new StringBuilder();
23 |
24 | response.AppendLine(Langs.MultipleLineResult);
25 | response.AppendLine(Langs.ClientCookies);
26 |
27 | var cc = bot.ArchiWebHandler.WebBrowser.CookieContainer.GetCookies(SteamStoreURL);
28 |
29 | foreach (var c in cc.ToList())
30 | {
31 | response.AppendLineFormat(Langs.CookieItem, c.Name, c.Value);
32 | }
33 |
34 | return bot.FormatBotResponse(response.ToString());
35 | }
36 |
37 | ///
38 | /// 获取商店Cookies (多个 bot)
39 | ///
40 | ///
41 | ///
42 | ///
43 | internal static async Task ResponseGetCookies(string botNames)
44 | {
45 | if (string.IsNullOrEmpty(botNames))
46 | {
47 | throw new ArgumentNullException(nameof(botNames));
48 | }
49 |
50 | var bots = Bot.GetBots(botNames);
51 |
52 | if ((bots == null) || (bots.Count == 0))
53 | {
54 | return FormatStaticResponse(Strings.BotNotFound, botNames);
55 | }
56 |
57 | var results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => ResponseGetCookies(bot)))).ConfigureAwait(false);
58 |
59 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
60 |
61 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
62 | }
63 |
64 | ///
65 | /// 获取Bot AccessToken
66 | ///
67 | ///
68 | ///
69 | ///
70 | internal static Task ResponseGetAccessToken(Bot bot)
71 | {
72 | if (!bot.IsConnectedAndLoggedOn)
73 | {
74 | return Task.FromResult(bot.FormatBotResponse(Strings.BotNotConnected));
75 | }
76 |
77 | var accessToken = bot.AccessToken;
78 |
79 | bool success = !string.IsNullOrEmpty(accessToken);
80 |
81 | return Task.FromResult(bot.FormatBotResponse(success ? accessToken! : string.Format(Langs.FetchDataFailed, nameof(accessToken))));
82 | }
83 |
84 | ///
85 | /// 获取Bot AccessToken (多个 bot)
86 | ///
87 | ///
88 | ///
89 | ///
90 | internal static async Task ResponseGetAccessToken(string botNames)
91 | {
92 | if (string.IsNullOrEmpty(botNames))
93 | {
94 | throw new ArgumentNullException(nameof(botNames));
95 | }
96 |
97 | var bots = Bot.GetBots(botNames);
98 |
99 | if ((bots == null) || (bots.Count == 0))
100 | {
101 | return FormatStaticResponse(Strings.BotNotFound, botNames);
102 | }
103 |
104 | var results = await Utilities.InParallel(bots.Select(bot => Task.Run(() => ResponseGetAccessToken(bot)))).ConfigureAwait(false);
105 |
106 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
107 |
108 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
109 | }
110 | }
111 |
--------------------------------------------------------------------------------
/ASFEnhance/Event/NominatePayload.cs:
--------------------------------------------------------------------------------
1 | using ProtoBuf;
2 |
3 | namespace ASFEnhance.Event;
4 |
5 | ///
6 | /// 提名载荷
7 | ///
8 | [ProtoContract]
9 | internal sealed record NominatePayload
10 | {
11 | [ProtoMember(1)]
12 | public int CategoryId { get; set; }
13 | [ProtoMember(2)]
14 | public int NominatedId { get; set; }
15 | [ProtoMember(3)]
16 | public int Source { get; set; }
17 | }
--------------------------------------------------------------------------------
/ASFEnhance/Event/SteamAwardVoteData.cs:
--------------------------------------------------------------------------------
1 | using System.Text.Json.Serialization;
2 |
3 | namespace ASFEnhance.Event;
4 |
5 | ///
6 | /// SteamAward投票内容
7 | ///
8 | internal sealed record SteamAwardVoteData
9 | {
10 | [JsonPropertyName("definitions")]
11 | public DefinitionData? Definitions { get; init; }
12 | [JsonPropertyName("user_votes")]
13 | public List? UserVotes { get; init; }
14 |
15 | internal sealed record DefinitionData
16 | {
17 | [JsonPropertyName("votes")]
18 | public List? Votes { get; set; }
19 | }
20 |
21 | internal sealed record VoteData
22 | {
23 | [JsonPropertyName("voteid")]
24 | public byte VoteId { get; set; }
25 | [JsonPropertyName("active")]
26 | public byte Active { get; set; }
27 | }
28 |
29 | internal sealed record UserVoteData
30 | {
31 | [JsonPropertyName("voteid")]
32 | public byte VoteId { get; set; }
33 | [JsonPropertyName("appid")]
34 | public uint Appid { get; set; }
35 | [JsonPropertyName("communityitemid")]
36 | public string? Communityitemid { get; set; }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/ASFEnhance/Explorer/Command.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.Localization;
3 | using ArchiSteamFarm.Steam;
4 |
5 | namespace ASFEnhance.Explorer;
6 |
7 | internal static class Command
8 | {
9 | ///
10 | /// 浏览探索队列
11 | ///
12 | ///
13 | ///
14 | internal static async Task ResponseExploreDiscoveryQueue(Bot bot)
15 | {
16 | if (!bot.IsConnectedAndLoggedOn)
17 | {
18 | return bot.FormatBotResponse(Strings.BotNotConnected);
19 | }
20 |
21 | var appids = await WebRequest.GetDiscoveryQueue(bot).ConfigureAwait(false);
22 | if (appids == null || appids.Count == 0)
23 | {
24 | return bot.FormatBotResponse(Langs.NetworkError);
25 | }
26 |
27 | foreach (var appid in appids)
28 | {
29 | await WebRequest.SkipDiscoveryQueueItem(bot, appid).ConfigureAwait(false);
30 | await Task.Delay(200).ConfigureAwait(false);
31 | }
32 |
33 | return bot.FormatBotResponse(Langs.DiscoveryQueueExploreredSuccess);
34 | }
35 |
36 | ///
37 | /// 浏览探索队列 (多个Bot)
38 | ///
39 | ///
40 | ///
41 | ///
42 | internal static async Task ResponseExploreDiscoveryQueue(string botNames)
43 | {
44 | if (string.IsNullOrEmpty(botNames))
45 | {
46 | throw new ArgumentNullException(nameof(botNames));
47 | }
48 |
49 | var bots = Bot.GetBots(botNames);
50 |
51 | if (bots == null || bots.Count == 0)
52 | {
53 | return FormatStaticResponse(Strings.BotNotFound, botNames);
54 | }
55 |
56 | var results = await Utilities.InParallel(bots.Select(ResponseExploreDiscoveryQueue)).ConfigureAwait(false);
57 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
58 |
59 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
60 | }
61 | }
--------------------------------------------------------------------------------
/ASFEnhance/Explorer/ReflectionHelper.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 |
3 | namespace ASFEnhance.Explorer;
4 |
5 | internal static class ReflectionHelper
6 | {
7 | internal static T? GetStaticPrivateProperty(this Type type, string fieldname) where T : notnull
8 | {
9 | var flag = BindingFlags.Static | BindingFlags.NonPublic;
10 | var field = type.GetProperty(fieldname, flag);
11 | return (T?)field?.GetValue(null);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/ASFEnhance/Explorer/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ASFEnhance.Data;
3 | using ASFEnhance.Data.IStoreService;
4 |
5 | namespace ASFEnhance.Explorer;
6 |
7 | internal static class WebRequest
8 | {
9 | internal static async Task?> GetDiscoveryQueue(Bot bot)
10 | {
11 | var token = bot.AccessToken ?? throw new AccessTokenNullException(bot);
12 | var country = bot.GetUserCountryCode();
13 | var request = new Uri(SteamApiURL,
14 | $"/IStoreService/GetDiscoveryQueue/v1/?access_token={token}&country_code={country}&rebuild_queue=1&queue_type=0&ignore_user_preferences=1");
15 | var response = await bot.ArchiWebHandler
16 | .UrlGetToJsonObjectWithSession>(request).ConfigureAwait(false);
17 |
18 | return response?.Content?.Response?.AppIds;
19 | }
20 |
21 | internal static async Task SkipDiscoveryQueueItem(Bot bot, uint appId)
22 | {
23 | var token = bot.AccessToken ?? throw new AccessTokenNullException(bot);
24 | var request = new Uri(SteamApiURL,
25 | $"/IStoreService/SkipDiscoveryQueueItem/v1/?access_token={token}&appid={appId}");
26 | var response = await bot.ArchiWebHandler
27 | .UrlPostToJsonObject(request).ConfigureAwait(false);
28 |
29 | return response?.Content != null;
30 | }
31 | }
--------------------------------------------------------------------------------
/ASFEnhance/Friend/HtmlParser.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using ArchiSteamFarm.Web.Responses;
3 | using System.Text.RegularExpressions;
4 |
5 | namespace ASFEnhance.Friend;
6 |
7 | internal static class HtmlParser
8 | {
9 | ///
10 | /// 解析个人资料页面
11 | ///
12 | ///
13 | ///
14 | internal static ulong? ParseProfilePage(HtmlDocumentResponse? response)
15 | {
16 | if (response?.Content == null)
17 | {
18 | return null;
19 | }
20 |
21 | var scriptNode = response.Content.QuerySelector("#responsive_page_template_content>script");
22 | var text = scriptNode?.Text();
23 |
24 | if (!string.IsNullOrEmpty(text))
25 | {
26 | Regex regex = RegexUtils.MatchSteamId();
27 |
28 | var match = regex.Match(text);
29 | if (match.Success)
30 | {
31 | var strId = match.Groups[1].Value;
32 | return ulong.TryParse(strId, out ulong steamId) ? steamId : null;
33 | }
34 | }
35 |
36 | return null;
37 | }
38 |
39 | ///
40 | /// 解析添加好友链接前缀
41 | ///
42 | ///
43 | ///
44 | internal static string? ParseInviteLinkPrefix(HtmlDocumentResponse? response)
45 | {
46 | if (response?.Content == null)
47 | {
48 | return null;
49 | }
50 |
51 | var configEle = response.Content.QuerySelector("#application_config");
52 |
53 | string? configJson = configEle?.GetAttribute("data-userinfo");
54 |
55 | if (string.IsNullOrEmpty(configJson))
56 | {
57 | return null;
58 | }
59 |
60 | var match = RegexUtils.MatchShortLink().Match(configJson);
61 | return match.Success ? match.Groups[1].Value.Replace("\\/", "/") : null;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/ASFEnhance/Friend/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ASFEnhance.Data.WebApi;
3 | using System.Net;
4 |
5 | namespace ASFEnhance.Friend;
6 |
7 | internal static class WebRequest
8 | {
9 | ///
10 | /// 验证个人资料链接
11 | ///
12 | ///
13 | ///
14 | ///
15 | internal static async Task GetSteamIdByProfileLink(this Bot bot, string path)
16 | {
17 | var request = new Uri(SteamCommunityURL, path);
18 |
19 | var response = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request).ConfigureAwait(false);
20 |
21 | return HtmlParser.ParseProfilePage(response);
22 | }
23 |
24 | ///
25 | /// 读取好友邀请链接
26 | ///
27 | ///
28 | ///
29 | internal static async Task GetAddFriendPage(this Bot bot)
30 | {
31 | var request = new Uri(SteamCommunityURL, $"/profiles/{bot.SteamID}/friends/add");
32 | var response = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request, referer: SteamStoreURL).ConfigureAwait(false);
33 |
34 | var prefix = HtmlParser.ParseInviteLinkPrefix(response);
35 | if (string.IsNullOrEmpty(prefix))
36 | {
37 | return null;
38 | }
39 |
40 | request = new Uri(SteamCommunityURL, $"/invites/ajaxcreate");
41 | var response2 = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: null).ConfigureAwait(false);
42 |
43 | if (response2?.Content != null)
44 | {
45 | var result = response2.Content;
46 | result.Prefix = prefix;
47 | return result;
48 | }
49 | else
50 | {
51 | return null;
52 | }
53 | }
54 |
55 | ///
56 | /// 使用邀请链接添加好友
57 | ///
58 | ///
59 | ///
60 | ///
61 | ///
62 | internal static async Task AddFriendViaInviteLink(this Bot bot, string identity, string token)
63 | {
64 | var request = new Uri(SteamCommunityURL, $"/user/{identity}/{token}");
65 | var response = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request, referer: SteamCommunityURL).ConfigureAwait(false);
66 |
67 | if (response?.Content == null)
68 | {
69 | return Langs.NetworkError;
70 | }
71 |
72 | var previewLink = response.Content?.QuerySelector("#responsive_page_template_content > script:nth-child(1)")?.TextContent;
73 | var match = RegexUtils.MatchSteamId().Match(previewLink ?? "");
74 | if (!match.Success)
75 | {
76 | return Langs.GetProfileFailed;
77 | }
78 |
79 | var steamId = match.Groups[1].Value;
80 | if (steamId == bot.SteamID.ToString())
81 | {
82 | return Langs.CanNotAddMyself;
83 | }
84 |
85 | var request2 = new Uri(SteamCommunityURL, $"/invites/ajaxredeem");
86 | var data = new Dictionary(3)
87 | {
88 | { "steamid_user", steamId },
89 | { "invite_token", token },
90 | };
91 |
92 | response = await bot.ArchiWebHandler.UrlPostToHtmlDocumentWithSession(request2, data: data, referer: request).ConfigureAwait(false);
93 |
94 | return response?.StatusCode == HttpStatusCode.OK ? Langs.AddFriendSuccess : Langs.AddFriendFailed;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/ASFEnhance/Group/HtmlParser.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using ArchiSteamFarm.Web.Responses;
3 | using ASFEnhance.Data;
4 | using ASFEnhance.Data.Plugin;
5 |
6 | namespace ASFEnhance.Group;
7 |
8 | internal static class HtmlParser
9 | {
10 | ///
11 | /// 获取群组名
12 | ///
13 | ///
14 | ///
15 | /// true,string: 群组名
16 | /// false,string: 群组未找到/网络错误
17 | ///
18 | internal static (bool, string) GetGroupName(HtmlDocumentResponse? response)
19 | {
20 | if (response?.Content == null)
21 | {
22 | return (false, Langs.NetworkError);
23 | }
24 |
25 | var groupNameNode = response.Content.QuerySelector("div.grouppage_resp_title ellipsis");
26 |
27 | if (groupNameNode != null)
28 | {
29 | var groupName = groupNameNode.TextContent.Trim().Replace("\t\t\t\t", " ");
30 |
31 | return (true, groupName);
32 | }
33 |
34 | var errorMessage = response.Content.QuerySelector("div.error_ctn h3");
35 |
36 | return (false, errorMessage?.TextContent.Trim() ?? Langs.NetworkError);
37 | }
38 |
39 | ///
40 | /// 判断是否已加入群组
41 | ///
42 | ///
43 | ///
44 | internal static JoinGroupStatus CheckJoinGroup(HtmlDocumentResponse? response)
45 | {
46 | if (response?.Content == null)
47 | {
48 | throw new ArgumentNullException(nameof(response));
49 | }
50 |
51 | var joinAction = response.Content.QuerySelector("div.grouppage_join_area>a");
52 | var link = joinAction?.GetAttribute("href");
53 |
54 | if (link != null)
55 | {
56 | return link.StartsWith("javascript") ? JoinGroupStatus.NotJoined : JoinGroupStatus.Joined;
57 | }
58 |
59 | return JoinGroupStatus.Applied;
60 | }
61 |
62 | ///
63 | /// 解析群组列表
64 | ///
65 | ///
66 | internal static HashSet? ParseGropuList(HtmlDocumentResponse? response)
67 | {
68 | if (response?.Content == null)
69 | {
70 | return null;
71 | }
72 |
73 | var groupNodes = response.Content.QuerySelectorAll("#search_results>div[id][class]");
74 |
75 | HashSet groups = [];
76 |
77 | if (groupNodes.Length != 0)
78 | {
79 | foreach (var groupNode in groupNodes)
80 | {
81 | var eleName = groupNode.QuerySelector("a.linkTitle");
82 | var eleAction = groupNode.QuerySelector("div.actions>a");
83 |
84 | var groupName = eleName?.Text();
85 |
86 | if (string.IsNullOrEmpty(groupName))
87 | {
88 | ASFLogger.LogGenericDebug(string.Format("{0} == NULL", nameof(groupName)));
89 | continue;
90 | }
91 |
92 | var strOnlick = eleAction?.GetAttribute("onclick") ?? "( '0',";
93 |
94 | var match = RegexUtils.MatchStrOnClick().Match(strOnlick);
95 |
96 | if (!match.Success)
97 | {
98 | ASFLogger.LogGenericWarning(string.Format(Langs.SomethingIsNull, nameof(eleName)));
99 | }
100 | else
101 | {
102 | var strGroupId = match.Groups[1].ToString();
103 |
104 | if (!ulong.TryParse(strGroupId, out var groupId))
105 | {
106 | ASFLogger.LogGenericWarning(string.Format("{0} {1} cant parse to uint", nameof(strGroupId),
107 | strGroupId));
108 | }
109 | else
110 | {
111 | groups.Add(new GroupItem(groupName, groupId));
112 | }
113 | }
114 | }
115 | }
116 |
117 | return groups;
118 | }
119 | }
--------------------------------------------------------------------------------
/ASFEnhance/Group/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ArchiSteamFarm.Steam.Integration;
3 | using ASFEnhance.Data;
4 | using ASFEnhance.Data.Plugin;
5 |
6 |
7 | namespace ASFEnhance.Group;
8 |
9 | internal static class WebRequest
10 | {
11 | ///
12 | /// 获取群组信息
13 | ///
14 | ///
15 | ///
16 | ///
17 | public static async Task FetchGroupData(Bot bot, string groupId)
18 | {
19 | var request = new Uri(SteamCommunityURL, $"/groups/{groupId}?l=schinese");
20 | var response = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request, referer: SteamCommunityURL).ConfigureAwait(false);
21 |
22 | if (response?.Content == null)
23 | {
24 | return null;
25 | }
26 |
27 | var eleError = response.Content.QuerySelector("div.error_ctn");
28 |
29 | if (eleError != null)
30 | {
31 | return new GroupData(false, null, JoinGroupStatus.Failed);
32 | }
33 |
34 | var eleSecondName = response.Content.QuerySelector("div.grouppage_header_name>span");
35 | if (eleSecondName != null)
36 | {
37 | eleSecondName.TextContent = "";
38 | }
39 |
40 | var groupName = response.Content.QuerySelector("div.grouppage_header_name")?.TextContent.Trim();
41 | var eleLeave = response.Content.QuerySelector("div.content a[href^='javascript:ConfirmLeaveGroup']");
42 | var eleCancel = response.Content.QuerySelector("div.content a[href^='javascript:ConfirmCancelJoinRequest']");
43 |
44 | JoinGroupStatus status;
45 |
46 | if (eleLeave != null)
47 | {
48 | status = JoinGroupStatus.Joined;
49 | }
50 | else if (eleCancel != null)
51 | {
52 | status = JoinGroupStatus.Applied;
53 | }
54 | else
55 | {
56 | status = JoinGroupStatus.NotJoined;
57 | }
58 |
59 | return new GroupData(true, groupName, status);
60 | }
61 |
62 |
63 | ///
64 | /// 加入指定群组
65 | ///
66 | ///
67 | ///
68 | ///
69 | internal static async Task JoinGroup(Bot bot, string groupName)
70 | {
71 | var request = new Uri(SteamCommunityURL, $"/groups/{groupName}");
72 | Dictionary data = new(2, StringComparer.Ordinal)
73 | {
74 | { "action", "join" },
75 | };
76 | var result = await bot.ArchiWebHandler.UrlPostWithSession(request, data: data, referer: SteamCommunityURL, session: ArchiWebHandler.ESession.CamelCase).ConfigureAwait(false);
77 | return result;
78 | }
79 |
80 | ///
81 | /// 离开群组
82 | ///
83 | ///
84 | ///
85 | ///
86 | internal static async Task LeaveGroup(Bot bot, ulong GroupId)
87 | {
88 | var request = new Uri(SteamCommunityURL, $"/profiles/{bot.SteamID}/friends/action");
89 | var referer = new Uri(SteamCommunityURL, $"/profiles/{bot.SteamID}/groups");
90 |
91 | Dictionary data = new(3, StringComparer.Ordinal)
92 | {
93 | { "steamid", bot.SteamID.ToString() },
94 | { "ajax", "1" },
95 | { "action", "leave_group" },
96 | { "steamids[]", GroupId.ToString() }
97 | };
98 |
99 | var response = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: data, referer: referer, session: ArchiWebHandler.ESession.Lowercase).ConfigureAwait(false);
100 |
101 | return response?.Content?.Success ?? false;
102 | }
103 |
104 | ///
105 | /// 获取群组列表
106 | ///
107 | ///
108 | ///
109 | internal static async Task?> GetGroupList(Bot bot)
110 | {
111 | Uri request = new(SteamCommunityURL, $"/profiles/{bot.SteamID}/groups/");
112 |
113 | var response = await bot.ArchiWebHandler.UrlGetToHtmlDocumentWithSession(request, referer: SteamStoreURL).ConfigureAwait(false);
114 |
115 | return HtmlParser.ParseGropuList(response);
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/ASFEnhance/Localization/Static.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | █████╗ ███████╗███████╗███████╗███╗ ██╗██╗ ██╗ █████╗ ███╗ ██╗ ██████╗███████╗
122 | ██╔══██╗██╔════╝██╔════╝██╔════╝████╗ ██║██║ ██║██╔══██╗████╗ ██║██╔════╝██╔════╝
123 | ███████║███████╗█████╗ █████╗ ██╔██╗ ██║███████║███████║██╔██╗ ██║██║ █████╗
124 | ██╔══██║╚════██║██╔══╝ ██╔══╝ ██║╚██╗██║██╔══██║██╔══██║██║╚██╗██║██║ ██╔══╝
125 | ██║ ██║███████║██║ ███████╗██║ ╚████║██║ ██║██║ ██║██║ ╚████║╚██████╗███████╗
126 | ╚═╝ ╚═╝╚══════╝╚═╝ ╚══════╝╚═╝ ╚═══╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═══╝ ╚═════╝╚══════╝
127 |
128 |
129 | ==========================================================================
130 |
131 |
--------------------------------------------------------------------------------
/ASFEnhance/Profile/ElementExtensions.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 |
3 | namespace ASFEnhance.Profile;
4 | internal static class ElementExtensions
5 | {
6 | ///
7 | /// 读取TextContent为int
8 | ///
9 | ///
10 | ///
11 | ///
12 | public static int ReadTextContentAsInt(this IElement? element, int defValue)
13 | {
14 | var text = element?.TextContent.Replace(",", "").Replace(".", "");
15 | if (string.IsNullOrEmpty(text))
16 | {
17 | return defValue;
18 | }
19 | else
20 | {
21 | return int.TryParse(text, out var value) ? value : defValue;
22 | }
23 | }
24 |
25 | ///
26 | /// 读取TextContent为long
27 | ///
28 | ///
29 | ///
30 | ///
31 | public static long ReadTextContentAsLong(this IElement? element, long defValue)
32 | {
33 | var text = element?.TextContent.Replace(",", "").Replace(".", "");
34 | if (string.IsNullOrEmpty(text))
35 | {
36 | return defValue;
37 | }
38 | else
39 | {
40 | return long.TryParse(text, out var value) ? value : defValue;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ASFEnhance/RegexUtils.cs:
--------------------------------------------------------------------------------
1 | using System.Text.RegularExpressions;
2 |
3 | namespace ASFEnhance;
4 | internal static partial class RegexUtils
5 | {
6 | [GeneratedRegex(@"\d+([.,]\d+)?")]
7 | public static partial Regex MatchTotalPrice();
8 |
9 | [GeneratedRegex("[0-9,.]+")]
10 | public static partial Regex MatchPrice();
11 |
12 | [GeneratedRegex(@"([.,])\d\d?$")]
13 | public static partial Regex MatchPriceValue();
14 |
15 | [GeneratedRegex(@"(\w+)\/(\d+)")]
16 | public static partial Regex MatchGameLink();
17 |
18 | [GeneratedRegex(@"[,.\d]+")]
19 | public static partial Regex MatchStrPrice();
20 |
21 | [GeneratedRegex("g_historyCursor = ([^;]+)")]
22 | public static partial Regex MatchHistoryCursor();
23 |
24 | [GeneratedRegex(@"^([-+])?([^\d,.]*)([\d,. ]+)\s*([^\d,.]*|[руб6.]*)$")]
25 | public static partial Regex MatchHistoryItem();
26 |
27 | [GeneratedRegex(@"\( (\d+),")]
28 | public static partial Regex MatchSubId();
29 |
30 | [GeneratedRegex("g_rgTopCurators = ([^;]+);")]
31 | public static partial Regex MatchCuratorPayload();
32 |
33 | [GeneratedRegex("\"CLANACCOUNTID\":(\\d+),")]
34 | public static partial Regex MatchClanaCCountId();
35 |
36 | [GeneratedRegex("\"steamid\":\"(\\d+)\"")]
37 | public static partial Regex MatchSteamId();
38 |
39 | [GeneratedRegex(@"\( '(\d+)',")]
40 | public static partial Regex MatchStrOnClick();
41 |
42 | [GeneratedRegex("[A-Z0-9]{5}-?[A-Z0-9]{5}-?[A-Z0-9]{5}", RegexOptions.IgnoreCase, "zh-CN")]
43 | public static partial Regex MatchGameKey();
44 |
45 | [GeneratedRegex(@"%(?:(l|u|d|bot)(\d*))%")]
46 | public static partial Regex MatchVariables();
47 |
48 | [GeneratedRegex(@"ogg\/(\d+)")]
49 | public static partial Regex MatchGameId();
50 |
51 | [GeneratedRegex(@"(\d)[^,]*,")]
52 | public static partial Regex MatchLevel();
53 |
54 | [GeneratedRegex(@"gamecards\/(\d+)")]
55 | public static partial Regex MatchBadgeAppId();
56 |
57 | [GeneratedRegex("\"short_url\":\"([^\"]+)\"")]
58 | public static partial Regex MatchShortLink();
59 |
60 | [GeneratedRegex(@"((app|sub|bundle)\/\d+)")]
61 | public static partial Regex MatchGameIds();
62 |
63 | [GeneratedRegex(@"\s+|\(\?\)")]
64 | public static partial Regex MatchSubNames();
65 |
66 | [GeneratedRegex(@"\d+$")]
67 | public static partial Regex MatchSubPrice();
68 |
69 | [GeneratedRegex(@"\( (\d+) \)")]
70 | public static partial Regex MatchCardBalance();
71 |
72 | [GeneratedRegex(@"(?:(?:s.team\/p\/)|(?:steamcommunity.com\/user\/))([^-]+-[^/]+)\/([A-Z]+)")]
73 | public static partial Regex MatchFriendInviteLink();
74 |
75 | [GeneratedRegex(@"gift(\d+)_step_init")]
76 | public static partial Regex MatchGiftId();
77 |
78 | [GeneratedRegex(@"""webapi_token"":""([^""]+)""")]
79 | public static partial Regex MatchWebApiToken();
80 |
81 | [GeneratedRegex(@"(?:\(|()([^))]+)(?:\)|))")]
82 | public static partial Regex MatchWalletTooltips();
83 |
84 | [GeneratedRegex(@"pending_gift_(\d+)")]
85 | public static partial Regex MatchPendingGift();
86 |
87 | [GeneratedRegex(@"ShowDeclineGiftOptions\( '\d+', '(\d+)' \);")]
88 | public static partial Regex MatchPendingGiftAndSender();
89 |
90 | [GeneratedRegex(@"cart=(\d+)")]
91 | public static partial Regex MatchCartIdFromUri();
92 | [GeneratedRegex(@"countryflags\/([^.]+)\.gif")]
93 | public static partial Regex MatchCountryFlag();
94 |
95 | [GeneratedRegex(@"groups\/([^/]+)")]
96 | public static partial Regex MatchGroupName();
97 | }
98 |
--------------------------------------------------------------------------------
/ASFEnhance/Store/HtmlParser.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Web.Responses;
2 | using System.Text;
3 |
4 | namespace ASFEnhance.Store;
5 |
6 | internal static class HtmlParser
7 | {
8 | ///
9 | /// 解析搜索页
10 | ///
11 | ///
12 | ///
13 | internal static string? ParseSearchPage(HtmlDocumentResponse? response)
14 | {
15 | if (response?.Content == null)
16 | {
17 | return null;
18 | }
19 |
20 | var gameNodes = response.Content.QuerySelectorAll("#search_resultsRows>a.search_result_row");
21 |
22 | if (gameNodes.Length == 0)
23 | {
24 | return Langs.SearchResultEmpty;
25 | }
26 |
27 | var result = new StringBuilder();
28 |
29 | result.AppendLine(Langs.MultipleLineResult);
30 |
31 | result.AppendLine(Langs.SearchResultTitle);
32 |
33 | var matchGameId = RegexUtils.MatchGameIds();
34 |
35 | foreach (var gameNode in gameNodes)
36 | {
37 | var eleTitle = gameNode.QuerySelector("span.title");
38 |
39 | if (eleTitle == null)
40 | {
41 | ASFLogger.LogGenericDebug(string.Format(Langs.SomethingIsNull, nameof(eleTitle)));
42 | continue;
43 | }
44 |
45 | var gameTitle = eleTitle.TextContent.Trim();
46 | var gameHref = gameNode?.GetAttribute("href") ?? "";
47 |
48 | var match = matchGameId.Match(gameHref);
49 |
50 | if (match.Success)
51 | {
52 | result.AppendLineFormat(Langs.AreaItem, match.Value, gameTitle);
53 | }
54 | }
55 |
56 | return result.ToString();
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ASFEnhance/Update/Command.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.Plugins;
3 | using ArchiSteamFarm.Plugins.Interfaces;
4 | using ArchiSteamFarm.Steam;
5 | using ASFEnhance.Data;
6 | using ASFEnhance.Data.Plugin;
7 | using ASFEnhance.Explorer;
8 | using System.Collections.Frozen;
9 | using System.Text;
10 | using static ArchiSteamFarm.Storage.GlobalConfig;
11 |
12 | namespace ASFEnhance.Update;
13 |
14 | internal static class Command
15 | {
16 | ///
17 | /// 获取已安装的插件列表
18 | ///
19 | ///
20 | internal static string? ResponsePluginList()
21 | {
22 | FrozenSet? activePlugins;
23 | try
24 | {
25 | activePlugins = typeof(PluginsCore).GetStaticPrivateProperty>("ActivePlugins");
26 | }
27 | catch (Exception ex)
28 | {
29 | ASFLogger.LogGenericException(ex);
30 | return FormatStaticResponse(Langs.SubModuleLoadFailed);
31 | }
32 |
33 | if (activePlugins != null)
34 | {
35 | var sb = new StringBuilder();
36 | sb.AppendLine(FormatStaticResponse(Langs.MultipleLineResult));
37 | sb.AppendLineFormat(Langs.PluginListTips, activePlugins.Count);
38 |
39 | var subModules = new Dictionary();
40 | foreach (var subModule in _Adapter_.ExtensionCore.SubModules.Values)
41 | {
42 | subModules.TryAdd(subModule.PluginName, subModule);
43 | }
44 |
45 | var index = 1;
46 | foreach (var plugin in activePlugins)
47 | {
48 | if (plugin.Name == "ASFEnhance")
49 | {
50 | sb.AppendLineFormat(Langs.PluginListItem, index++, plugin.Name, plugin.Version, "ASFE");
51 | }
52 | else if (subModules.TryGetValue(plugin.Name, out var subModule))
53 | {
54 | sb.AppendLineFormat(Langs.PluginListItem, index++, subModule.PluginName, subModule.PluginVersion, subModule.CmdPrefix ?? "---");
55 | }
56 | else
57 | {
58 | sb.AppendLineFormat(Langs.PluginListItem2, index++, plugin.Name, plugin.Version);
59 | }
60 | }
61 |
62 | return sb.ToString();
63 | }
64 | else
65 | {
66 | //大概不可能会执行到这里
67 | return FormatStaticResponse(Langs.SubModuleNoModule);
68 | }
69 | }
70 |
71 | ///
72 | /// 获取插件最新版本
73 | ///
74 | ///
75 | ///
76 | internal static async Task ResponseGetPluginLatestVersion(string? pluginNames = null)
77 | {
78 | var entries = pluginNames?.ToUpperInvariant().Split(',', StringSplitOptions.RemoveEmptyEntries);
79 |
80 | var tasks = new List>();
81 |
82 | if (entries?.Length > 0)
83 | {
84 | foreach (var entry in entries)
85 | {
86 | if (entry == "ASFE" || entry == "ASFENHANCE")
87 | {
88 | tasks.Add(WebRequest.GetPluginReleaseNote("ASFEnhance", MyVersion, "ASFEnhance"));
89 | }
90 | else if (_Adapter_.ExtensionCore.SubModules.TryGetValue(entry, out var subModule))
91 | {
92 | tasks.Add(WebRequest.GetPluginReleaseNote(subModule.PluginName, subModule.PluginVersion, subModule.RepoName));
93 | }
94 | }
95 | }
96 | else
97 | {
98 | tasks.Add(WebRequest.GetPluginReleaseNote("ASFEnhance", MyVersion, "ASFEnhance"));
99 | foreach (var subModule in _Adapter_.ExtensionCore.SubModules.Values)
100 | {
101 | tasks.Add(WebRequest.GetPluginReleaseNote(subModule.PluginName, subModule.PluginVersion, subModule.RepoName));
102 | }
103 | }
104 |
105 | if (tasks.Count == 0)
106 | {
107 | return FormatStaticResponse(Langs.UpdateFailedPluginNotFound, pluginNames);
108 | }
109 |
110 | var results = await Utilities.InParallel(tasks).ConfigureAwait(false);
111 |
112 | var sb = new StringBuilder();
113 | sb.AppendLine(FormatStaticResponse(Langs.UpdatePluginUpdateInfo));
114 |
115 | foreach (var info in results)
116 | {
117 | sb.AppendLine(Static.Line);
118 | sb.AppendLineFormat(Langs.UpdatePluginListItemName, info.PluginName, info.Tips);
119 | sb.AppendLineFormat(Langs.UpdatePluginListItemVersion, info.CurrentVersion, info.OnlineVersion?.ToString() ?? "-.-.-.-");
120 | if (!string.IsNullOrEmpty(info.ReleaseNote))
121 | {
122 | sb.AppendLineFormat(Langs.UpdatePluginListItemReleaseNote, info.ReleaseNote);
123 | }
124 | }
125 |
126 | sb.AppendLine(Static.Line);
127 | sb.AppendLine(Langs.UpdatePluginVersionTips);
128 |
129 | return sb.ToString();
130 | }
131 |
132 | ///
133 | /// 更新插件版本
134 | ///
135 | ///
136 | ///
137 | ///
138 | ///
139 | internal static async Task ResponsePluginUpdate(Bot bot, EAccess access, string? pluginNames = null)
140 | {
141 | var entries = pluginNames?.ToUpperInvariant().Split(',', StringSplitOptions.RemoveEmptyEntries);
142 |
143 | #if DEBUG
144 | var channel = nameof(EUpdateChannel.PreRelease);
145 | #else
146 | var channel = nameof(EUpdateChannel.Stable);
147 | #endif
148 |
149 | List plugins = [];
150 |
151 | if (entries?.Length > 0)
152 | {
153 | foreach (var entry in entries)
154 | {
155 | if (entry == "ASFE" || entry == "ASFENHANCE")
156 | {
157 | plugins.Add("ASFEnhance");
158 | }
159 |
160 | foreach (var (pluginId, subModule) in _Adapter_.ExtensionCore.SubModules)
161 | {
162 | if (pluginId == entry || subModule.MatchCmdPrefix(entry))
163 | {
164 | plugins.Add(subModule.PluginName);
165 | }
166 | }
167 | }
168 | }
169 | else
170 | {
171 | plugins.Add("ASFEnhance");
172 | foreach (var subModule in _Adapter_.ExtensionCore.SubModules.Values)
173 | {
174 | plugins.Add(subModule.PluginName);
175 | }
176 | }
177 |
178 | if (plugins.Count == 0)
179 | {
180 | return FormatStaticResponse(Langs.UpdateFailedPluginNotFound, pluginNames);
181 | }
182 |
183 | var cmd = string.Format("UPDATEPLUGINS {0} {1}", channel, string.Join(',', plugins));
184 | return await bot.Commands.Response(access, cmd, 0).ConfigureAwait(false);
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/ASFEnhance/Update/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Web.GitHub;
2 | using ASFEnhance.Data;
3 |
4 | namespace ASFEnhance.Update;
5 |
6 | internal static class WebRequest
7 | {
8 | ///
9 | /// 获取发行版信息
10 | ///
11 | ///
12 | ///
13 | ///
14 | ///
15 | internal static async Task GetPluginReleaseNote(string pluginName, Version pluginVersion, string? pluginRepo)
16 | {
17 | var response = new PluginUpdateResponse
18 | {
19 | PluginName = pluginName,
20 | CurrentVersion = pluginVersion,
21 | OnlineVersion = null,
22 | ReleaseNote = "",
23 | };
24 |
25 | if (string.IsNullOrEmpty(pluginRepo))
26 | {
27 | response.ReleaseNote = Langs.PluginUpdateNotSupport;
28 | }
29 | else
30 | {
31 | if (!pluginRepo.Contains('/'))
32 | {
33 | pluginRepo = "chr233/" + pluginRepo;
34 | }
35 |
36 | var relesaeData = await GitHubService.GetLatestRelease(pluginRepo, true, default).ConfigureAwait(false);
37 | if (relesaeData == null)
38 | {
39 | response.ReleaseNote = Langs.GetReleaseInfoFailedNetworkError;
40 | }
41 | else
42 | {
43 | response.ReleaseNote = relesaeData.MarkdownBody;
44 | response.OnlineVersion = Version.TryParse(relesaeData.Tag, out var version) ? version : null;
45 | }
46 | }
47 |
48 | return response;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/ASFEnhance/Wallet/Command.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Core;
2 | using ArchiSteamFarm.Localization;
3 | using ArchiSteamFarm.Steam;
4 | using SteamKit2;
5 | using System.Text;
6 |
7 | namespace ASFEnhance.Wallet;
8 |
9 | internal static class Command
10 | {
11 | ///
12 | /// 激活钱包充值码
13 | ///
14 | ///
15 | ///
16 | ///
17 | ///
18 | internal static async Task ResponseRedeemWallet(Bot bot, string targetCode)
19 | {
20 | if (!bot.IsConnectedAndLoggedOn)
21 | {
22 | return bot.FormatBotResponse(Strings.BotNotConnected);
23 | }
24 |
25 | if (string.IsNullOrEmpty(targetCode))
26 | {
27 | throw new ArgumentNullException(nameof(targetCode));
28 | }
29 |
30 | var sb = new StringBuilder();
31 |
32 | string[] codes = targetCode.Split(SeparatorDot, StringSplitOptions.RemoveEmptyEntries);
33 |
34 | if (codes.Length > 1)
35 | {
36 | sb.AppendLine(Langs.MultipleLineResult);
37 | }
38 |
39 | foreach (string code in codes)
40 | {
41 | var result = await WebRequest.RedeemWalletCode(bot, code).ConfigureAwait(false);
42 |
43 | if (result != null)
44 | {
45 | if (result.Success == EResult.OK)
46 | {
47 | sb.AppendLineFormat(Langs.CookieItem, code, Langs.Success);
48 | }
49 | else if (result.Success == EResult.InvalidState)
50 | {
51 | if (Config.Addresses?.Count > 0)
52 | {
53 | var address = Config.Addresses[Random.Shared.Next(0, Config.Addresses.Count)];
54 | var result2 = await WebRequest.RedeemWalletCode(bot, code, address).ConfigureAwait(false);
55 |
56 | if (result2 != null)
57 | {
58 | sb.AppendLineFormat(Langs.RedeemWalletError, code, result2.Success == EResult.OK ? Langs.Success : Langs.Failure, result.Detail.ToString());
59 | }
60 | else
61 | {
62 | sb.AppendLineFormat(Langs.CookieItem, code, Langs.NetworkError);
63 | }
64 | }
65 | else
66 | {
67 | sb.AppendLineFormat(Langs.CookieItem, code, Langs.NoAvilableAddressError);
68 | }
69 | }
70 | else
71 | {
72 | sb.AppendLineFormat(Langs.RedeemWalletError, code, Langs.Failure, result.Detail);
73 | }
74 | }
75 | else
76 | {
77 | sb.AppendLineFormat(Langs.CookieItem, code, Langs.NetworkError);
78 | }
79 |
80 |
81 | }
82 |
83 | return bot.FormatBotResponse(sb.ToString());
84 | }
85 |
86 | ///
87 | /// 激活钱包充值码 (多个Bot)
88 | ///
89 | ///
90 | ///
91 | ///
92 | ///
93 | internal static async Task ResponseRedeemWallet(string botNames, string targetCode)
94 | {
95 | if (string.IsNullOrEmpty(botNames))
96 | {
97 | throw new ArgumentNullException(nameof(botNames));
98 | }
99 |
100 | var bots = Bot.GetBots(botNames);
101 |
102 | if ((bots == null) || (bots.Count == 0))
103 | {
104 | return FormatStaticResponse(Strings.BotNotFound, botNames);
105 | }
106 |
107 | var results = await Utilities.InParallel(bots.Select(bot => ResponseRedeemWallet(bot, targetCode))).ConfigureAwait(false);
108 |
109 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
110 |
111 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
112 | }
113 |
114 | ///
115 | /// 激活钱包充值码 (多个Bot)
116 | ///
117 | ///
118 | ///
119 | ///
120 | internal static async Task ResponseRedeemWalletMult(string targetCode)
121 | {
122 | var bots = Bot.GetBots("ASF")?.Where(x => x.IsConnectedAndLoggedOn).ToList();
123 |
124 | if ((bots == null) || (bots.Count == 0))
125 | {
126 | return FormatStaticResponse(Strings.BotNotFound, "ASF");
127 | }
128 |
129 | var codes = targetCode.Split(',', StringSplitOptions.RemoveEmptyEntries);
130 |
131 | var tasks = new List>();
132 | int index = 0;
133 | foreach (string code in codes)
134 | {
135 | if (index >= bots.Count)
136 | {
137 | index = 0;
138 | }
139 | var bot = bots[index++];
140 | tasks.Add(ResponseRedeemWallet(bot, code));
141 | }
142 |
143 | var results = await Utilities.InParallel(tasks).ConfigureAwait(false);
144 |
145 | var responses = new List(results.Where(result => !string.IsNullOrEmpty(result)));
146 |
147 | return responses.Count > 0 ? string.Join(Environment.NewLine, responses) : null;
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/ASFEnhance/Wallet/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ASFEnhance.Data.Plugin;
3 | using ASFEnhance.Data.WebApi;
4 |
5 | namespace ASFEnhance.Wallet;
6 |
7 | ///
8 | /// 网络请求
9 | ///
10 | public static class WebRequest
11 | {
12 | ///
13 | /// 激活钱包代码
14 | ///
15 | ///
16 | ///
17 | ///
18 | public static async Task RedeemWalletCode(Bot bot, string code)
19 | {
20 | Uri request = new(SteamStoreURL, "/account/ajaxredeemwalletcode");
21 | Uri referer = new(SteamStoreURL, "/account/redeemwalletcode");
22 |
23 | Dictionary data = new(2, StringComparer.Ordinal) {
24 | { "wallet_code", code }
25 | };
26 |
27 | var response = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: data, referer: referer).ConfigureAwait(false);
28 |
29 | return response?.Content;
30 | }
31 |
32 | ///
33 | /// 激活钱包代码
34 | ///
35 | ///
36 | ///
37 | ///
38 | ///
39 | public static async Task RedeemWalletCode(Bot bot, string code, AddressConfig address)
40 | {
41 | Uri request = new(SteamStoreURL, "/account/ajaxcreatewalletandcheckfunds/");
42 | Uri referer = new(SteamStoreURL, "/account/redeemwalletcode");
43 |
44 | Dictionary data = new(8, StringComparer.Ordinal) {
45 | { "wallet_code", code },
46 | { "CreateFromAddress", "1" },
47 | { "Address", address.Address },
48 | { "City", address.City },
49 | { "Country", address.Country },
50 | { "State", address.State },
51 | { "PostCode", address.PostCode },
52 | };
53 |
54 | var response = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: data, referer: referer).ConfigureAwait(false);
55 |
56 | return response?.Content;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/ASFEnhance/WishList/WebRequest.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ASFEnhance.Data;
3 | using ASFEnhance.Data.IWishlistService;
4 | using ASFEnhance.Data.WebApi;
5 |
6 | namespace ASFEnhance.WishList;
7 |
8 | ///
9 | /// 网络请求
10 | ///
11 | public static class WebRequest
12 | {
13 | ///
14 | /// 添加愿望单
15 | ///
16 | ///
17 | ///
18 | ///
19 | ///
20 | public static async Task AddWishlist(this Bot bot, uint gameId, bool isAdd)
21 | {
22 | var request = new Uri(SteamStoreURL, isAdd ? "/api/addtowishlist" : "/api/removefromwishlist");
23 | var referer = new Uri(SteamStoreURL, "/app/" + gameId);
24 |
25 | var data = new Dictionary(2, StringComparer.Ordinal)
26 | {
27 | { "appid", gameId.ToString() },
28 | };
29 |
30 | var response = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: data, referer: referer).ConfigureAwait(false);
31 | return response?.Content;
32 | }
33 |
34 | ///
35 | /// 关注游戏
36 | ///
37 | ///
38 | ///
39 | ///
40 | ///
41 | public static async Task FollowGame(this Bot bot, uint gameId, bool isFollow)
42 | {
43 | var request = new Uri(SteamStoreURL, "/explore/followgame/");
44 | var referer = new Uri(SteamStoreURL, $"/app/{gameId}");
45 |
46 | var data = new Dictionary(3, StringComparer.Ordinal)
47 | {
48 | { "appid", gameId.ToString() },
49 | };
50 |
51 | if (!isFollow)
52 | {
53 | data.Add("unfollow", "1");
54 | }
55 |
56 | var response = await bot.ArchiWebHandler.UrlPostToHtmlDocumentWithSession(request, data: data, referer: referer).ConfigureAwait(false);
57 |
58 | if (response == null)
59 | {
60 | return false;
61 | }
62 |
63 | return response?.Content?.Body?.InnerHtml.ToLowerInvariant() == "true";
64 | }
65 |
66 | ///
67 | /// 检查关注/愿望单情况
68 | ///
69 | ///
70 | ///
71 | ///
72 | public static async Task CheckGame(this Bot bot, uint gameId)
73 | {
74 | var request = new Uri(SteamStoreURL, $"/app/{gameId}");
75 |
76 | var response = await bot.ArchiWebHandler.UrlPostToHtmlDocumentWithSession(request).ConfigureAwait(false);
77 |
78 | if (response == null)
79 | {
80 | return new(false, "网络错误");
81 | }
82 |
83 | if (response.FinalUri.LocalPath.Equals("/"))
84 | {
85 | return new(false, "商店页未找到");
86 | }
87 |
88 | return HtmlParser.ParseStorePage(response);
89 | }
90 |
91 | ///
92 | /// 忽略指定游戏
93 | ///
94 | ///
95 | ///
96 | ///
97 | ///
98 | internal static async Task IgnoreGame(this Bot bot, uint gameId, bool isIgnore)
99 | {
100 | var request = new Uri(SteamStoreURL, "/recommended/ignorerecommendation/");
101 | var referer = new Uri(SteamStoreURL, $"/app/{gameId}");
102 |
103 | var data = new Dictionary(3, StringComparer.Ordinal)
104 | {
105 | { "appid", gameId.ToString() },
106 | };
107 |
108 | if (isIgnore)
109 | {
110 | data.Add("ignore_reason", "0");
111 | }
112 | else
113 | {
114 | data.Add("remove", "1");
115 | }
116 |
117 | var response = await bot.ArchiWebHandler.UrlPostToJsonObjectWithSession(request, data: data, referer: referer).ConfigureAwait(false);
118 |
119 | if (response == null)
120 | {
121 | return false;
122 | }
123 |
124 | return response?.Content?.Result == true;
125 | }
126 |
127 | ///
128 | /// 获取愿望单
129 | ///
130 | ///
131 | ///
132 | ///
133 | public static async Task GetWishlistGames(Bot bot)
134 | {
135 | if (!bot.IsConnectedAndLoggedOn)
136 | {
137 | return null;
138 | }
139 |
140 | var token = bot.AccessToken ?? throw new AccessTokenNullException(bot);
141 | var request = new Uri(SteamApiURL, $"/IWishlistService/GetWishlist/v1/?access_token={token}&steamid={bot.SteamID}");
142 |
143 | var response = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession>(request).ConfigureAwait(false);
144 |
145 | return response?.Content?.Response;
146 | }
147 |
148 | ///
149 | /// 获取愿望单数量
150 | ///
151 | ///
152 | ///
153 | ///
154 | public static async Task GetWishlistGamesCount(Bot bot)
155 | {
156 | if (!bot.IsConnectedAndLoggedOn)
157 | {
158 | return -1;
159 | }
160 |
161 | var token = bot.AccessToken ?? throw new AccessTokenNullException(bot);
162 | var request = new Uri(SteamApiURL, $"/IWishlistService/GetWishlistItemCount/v1/?access_token={token}&steamid={bot.SteamID}");
163 |
164 | var response = await bot.ArchiWebHandler.UrlGetToJsonObjectWithSession>(request).ConfigureAwait(false);
165 |
166 | return response?.Content?.Response?.Count ?? -1;
167 | }
168 | }
169 |
--------------------------------------------------------------------------------
/ASFEnhance/Wishlist/HtmlParser.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using ArchiSteamFarm.Web.Responses;
3 | using ASFEnhance.Data;
4 |
5 | namespace ASFEnhance.WishList;
6 |
7 | internal static class HtmlParser
8 | {
9 | ///
10 | /// 解析商店页面
11 | ///
12 | ///
13 | ///
14 | internal static CheckGameResponse ParseStorePage(HtmlDocumentResponse? response)
15 | {
16 | if (response?.Content == null)
17 | {
18 | return new(false, "网络错误");
19 | }
20 |
21 | var eleGameName = response.Content.QuerySelector("#appHubAppName");
22 | string gameName = eleGameName?.Text() ?? string.Format(Langs.GetStoreNameFailed);
23 |
24 | bool owned = response.Content.QuerySelector("div.already_in_library") != null;
25 |
26 | var eleWishlist = response.Content.QuerySelector("#add_to_wishlist_area_success");
27 | bool inWishlist = eleWishlist != null && string.IsNullOrEmpty(eleGameName?.GetAttribute("style") ?? null);
28 |
29 | var eleFollow = response.Content.QuerySelector("#queueBtnFollow>div:first-child");
30 | bool isFollow = eleFollow != null && string.IsNullOrEmpty(eleFollow.GetAttribute("style"));
31 |
32 | return new CheckGameResponse(true, gameName, owned, inWishlist, isFollow);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/ASFEnhance/_Adapter_/Endpoint.cs:
--------------------------------------------------------------------------------
1 | using ASFEnhance.Data.Plugin;
2 | using System.Composition;
3 | using System.Reflection;
4 |
5 | namespace ASFEnhance._Adapter_;
6 |
7 | ///
8 | /// 子模块接入点
9 | ///
10 | [Export]
11 | public static class Endpoint
12 | {
13 | ///
14 | /// 注册子模块
15 | ///
16 | /// 插件名称
17 | /// 插件唯一标识符
18 | /// 命令前缀
19 | /// 自动更新仓库
20 | /// 版本
21 | /// 命令处理函数
22 | ///
23 | ///
24 | public static string RegisterModule(string pluginName, string pluginId, string? cmdPrefix, string? repoName, Version version, MethodInfo cmdHandler)
25 | {
26 | if (string.IsNullOrEmpty(pluginName))
27 | {
28 | throw new ArgumentNullException(nameof(pluginName));
29 | }
30 |
31 | if (string.IsNullOrEmpty(pluginId))
32 | {
33 | throw new ArgumentNullException(nameof(pluginId));
34 | }
35 |
36 | ArgumentNullException.ThrowIfNull(version);
37 |
38 | ArgumentNullException.ThrowIfNull(cmdHandler);
39 |
40 | var paramList = new List();
41 | foreach (var paramInfo in cmdHandler.GetParameters())
42 | {
43 | paramList.Add(paramInfo.Name?.ToUpperInvariant());
44 | }
45 |
46 | var subModule = new SubModuleInfo
47 | {
48 | PluginName = pluginName,
49 | CmdPrefix = cmdPrefix?.ToUpperInvariant(),
50 | RepoName = repoName,
51 | PluginVersion = version,
52 | CommandHandler = cmdHandler,
53 | ParamList = paramList,
54 | };
55 |
56 | pluginId = pluginId.ToUpperInvariant();
57 | var success = ExtensionCore.SubModules.TryAdd(pluginId, subModule);
58 |
59 | if (!success)
60 | {
61 | ASFLogger.LogGenericWarning(string.Format(Langs.SubModuleRegisterFailedLog, pluginName, pluginId));
62 | }
63 |
64 | return success ? pluginName : Langs.SubModuleRegisterFailed;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/ASFEnhance/_Adapter_/ExtensionCore.cs:
--------------------------------------------------------------------------------
1 | using ArchiSteamFarm.Steam;
2 | using ASFEnhance.Data.Plugin;
3 |
4 | namespace ASFEnhance._Adapter_;
5 |
6 | ///
7 | /// 调用外部模块命令
8 | ///
9 | public static class ExtensionCore
10 | {
11 | ///
12 | /// 子模块字典
13 | ///
14 | internal static Dictionary SubModules { get; } = [];
15 |
16 | internal static bool HasSubModule => SubModules.Count != 0;
17 |
18 | ///
19 | /// 调用子模块命令处理函数
20 | ///
21 | ///
22 | ///
23 | ///
24 | ///
25 | ///
26 | ///
27 | ///
28 | ///
29 | static Task? Invoke(this SubModuleInfo subModule, Bot bot, EAccess access, string cmd, string message,
30 | string[] args, ulong steamId)
31 | {
32 | // 根据参数名称填入参数
33 | var objList = new List