├── .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 | ![Release](https://img.shields.io/badge/${{ env.REPO_NAME }}-${{ github.ref_name }}-brightgreen) ![Downloads](https://img.shields.io/github/downloads/chr233/${{ env.REPO_NAME }}/${{ github.ref_name }}/total?label=Downloads) 145 | 146 | Help improve translation: [![Crowdin](https://badges.crowdin.net/asfenhance/localized.svg)](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(); 34 | foreach (var paramName in subModule.ParamList) 35 | { 36 | object? obj = paramName switch 37 | { 38 | "BOT" => bot, 39 | "ACCESS" => access, 40 | "CMD" => cmd, 41 | "MESSAGE" => message, 42 | "ARGS" => args, 43 | "STEAMID" => steamId, 44 | _ => null 45 | }; 46 | objList.Add(obj); 47 | } 48 | 49 | var response = subModule.CommandHandler.Invoke(null, [.. objList]); 50 | if (response != null) 51 | { 52 | if (response is Task task) 53 | { 54 | return task; 55 | } 56 | 57 | if (response is string str) 58 | { 59 | return Task.FromResult(str); 60 | } 61 | } 62 | 63 | return null; 64 | } 65 | 66 | /// 67 | /// 调用外部模块命令 68 | /// 69 | /// 70 | /// 71 | /// 72 | /// 73 | /// 74 | /// 75 | /// 76 | internal static (string? moduleName, Task? handler) ExecuteCommand(string cmd, Bot bot, EAccess access, 77 | string message, string[] args, 78 | ulong steamId) 79 | { 80 | foreach (var (pluginId, subModule) in SubModules) 81 | { 82 | if (cmd == pluginId || subModule.MatchCmdPrefix(cmd)) 83 | { 84 | // 响应 Plugin Info 命令 85 | var pluginInfo = string.Format("{0} {1} [ASFEnhance]", subModule.PluginName, subModule.PluginVersion); 86 | return (subModule.PluginName, Task.FromResult(pluginInfo)); 87 | } 88 | 89 | // 响应命令 90 | var response = subModule.Invoke(bot, access, cmd, message, args, steamId); 91 | if (response != null) 92 | { 93 | return (subModule.PluginName, response); 94 | } 95 | } 96 | 97 | return (null, null); 98 | } 99 | 100 | /// 101 | /// 调用外部模块命令 (限定插件名) 102 | /// 103 | /// 104 | /// 105 | /// 106 | /// 107 | /// 108 | /// 109 | /// 110 | /// 111 | internal static (string? moduleName, Task? handler) ExecuteCommand(string pluginName, string cmd, Bot bot, 112 | EAccess access, string message, string[] args, ulong steamId) 113 | { 114 | foreach (var (pluginId, subModule) in SubModules) 115 | { 116 | if (cmd == pluginId || subModule.MatchCmdPrefix(cmd)) 117 | { 118 | // 响应 Plugin Info 命令 119 | var pluginInfo = string.Format("{0} {1} [ASFEnhance]", subModule.PluginName, subModule.PluginVersion); 120 | return (subModule.PluginName, Task.FromResult(pluginInfo)); 121 | } 122 | 123 | if (pluginId == pluginName || subModule.MatchCmdPrefix(pluginName)) 124 | { 125 | // PluginIdenty 和 CmdPrefix 匹配时响应命令 126 | var response = subModule.Invoke(bot, access, cmd, message, args, steamId); 127 | if (response != null) 128 | { 129 | return (subModule.PluginName, response); 130 | } 131 | } 132 | } 133 | 134 | return (null, null); 135 | } 136 | } -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 2.3.9.0 4 | 5 | 6 | 7 | Chr_ 8 | true 9 | chrxw.com 10 | Copyright © 2021-$([System.DateTime]::UtcNow.Year) $(Company) 11 | An ArchiSteamFarm plugin developed by Chr_. 12 | true 13 | none 14 | enable 15 | latest 16 | true 17 | zh-cn 18 | enable 19 | AGPL-3.0 20 | https://github.com/chr233/ASFEnhance 21 | README.md 22 | $(PackageProjectUrl)/releases 23 | main 24 | Git 25 | $(PackageProjectUrl).git 26 | LatestMajor 27 | linux-arm;linux-arm64;linux-x64;osx-arm64;osx-x64;win-arm64;win-x64 28 | true 29 | net9.0 30 | 31 | 32 | 33 | portable 34 | true 35 | true 36 | 37 | 38 | 39 | none 40 | false 41 | false 42 | false 43 | false 44 | false 45 | false 46 | false 47 | false 48 | true 49 | partial 50 | false 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /Directory.Packages.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | $PROJECT_NAME = "ASFEnhance" 2 | $PLUGIN_NAME = "ASFEnhance.dll" 3 | 4 | dotnet publish $PROJECT_NAME -o ./publish/ -c Release 5 | 6 | if (-Not (Test-Path -Path ./tmp)) { 7 | New-Item -ItemType Directory -Path ./tmp 8 | } 9 | else { 10 | Remove-Item -Path ./tmp/* -Recurse -Force 11 | } 12 | 13 | Copy-Item -Path .\publish\$PLUGIN_NAME -Destination .\tmp\ 14 | 15 | $dirs = Get-ChildItem -Path ./publish -Directory 16 | foreach ($dir in $dirs) { 17 | $subFiles = Get-ChildItem -Path $dir.FullName -File -Filter *.resources.dll 18 | 19 | foreach ($file in $subFiles) { 20 | $resourceName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) 21 | $opDir = "./tmp/$resourceName" 22 | if (-Not (Test-Path -Path $opDir)) { 23 | New-Item -ItemType Directory -Path $opDir 24 | } 25 | 26 | $destinationPath = ".\tmp\$resourceName\$($dir.Name).dll" 27 | Copy-Item -Path $file -Destination $destinationPath 28 | 29 | Write-Output "Copy resource DLL $($file.FullName) -> $destinationPath" 30 | } 31 | } -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /ASFEnhance/Localization/Langs.resx 3 | translation: /ASFEnhance/Localization/%file_name%.%locale%.%file_extension% 4 | -------------------------------------------------------------------------------- /desktop.ini: -------------------------------------------------------------------------------- 1 | [ViewState] 2 | Mode= 3 | Vid= 4 | FolderType=Generic 5 | -------------------------------------------------------------------------------- /img/blacklist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chr233/ASFEnhance/13904cfcc29c28f43790115da1f53d29cfa51cc5/img/blacklist.png -------------------------------------------------------------------------------- /img/whitelist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chr233/ASFEnhance/13904cfcc29c28f43790115da1f53d29cfa51cc5/img/whitelist.png --------------------------------------------------------------------------------