├── .eslintrc.cjs
├── .github
└── workflows
│ └── version.yml
├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── apps
├── QRLogin.js
├── SplitFiction.js
├── achievement.js
├── bind.js
├── cart.js
├── charts.js
├── client.js
├── dev.js
├── discounts.js
├── expend.js
├── help.js
├── index.js
├── info.js
├── inventory.js
├── online.js
├── push.js
├── review.js
├── rollGame.js
├── search.js
├── setting.js
├── stats.js
├── wishlist.js
└── yearReview.js
├── components
├── App.js
├── Config.js
├── Render.js
├── Version.js
├── YamlReader.js
└── index.js
├── config
└── default_config
│ ├── gif.yaml
│ ├── other.yaml
│ ├── push.yaml
│ ├── steam.yaml
│ └── tips.yaml
├── guoba.support.js
├── index.js
├── jsconfig.json
├── lib
├── Bot.js
├── common.js
├── index.js
├── logger.js
├── plugin.js
├── puppeteer.js
├── redis.js
└── segment.js
├── models
├── api
│ ├── IAccountCartService.js
│ ├── IAccountPrivateAppsService.js
│ ├── IAuthenticationService.js
│ ├── ICheckoutService.js
│ ├── IClientCommService.js
│ ├── ICommunityService.js
│ ├── IFamilyGroupsService.js
│ ├── IFriendsListService.js
│ ├── IMobileAppService.js
│ ├── IPlayerService.js
│ ├── ISaleFeatureService.js
│ ├── ISteamChartsService.js
│ ├── ISteamUser.js
│ ├── ISteamUserOAuth.js
│ ├── ISteamUserStats.js
│ ├── ISteamWebAPIUtil.js
│ ├── IStoreBrowseService.js
│ ├── IStoreQueryService.js
│ ├── IStoreService.js
│ ├── IStoreTopSellersService.js
│ ├── IUserAccountService.js
│ ├── IUserReviewsService.js
│ ├── IWishlistService.js
│ ├── community.js
│ ├── index.js
│ └── store.js
├── bind
│ └── index.js
├── canvas
│ ├── canvas.js
│ ├── game.js
│ ├── index.js
│ ├── info.js
│ └── inventory.js
├── db
│ ├── base.js
│ ├── familyInventoryPush.js
│ ├── game.js
│ ├── index.js
│ ├── kv.js
│ ├── priceChangePush.js
│ ├── push.js
│ ├── stats.js
│ ├── token.js
│ ├── user.js
│ └── userInventory.js
├── help
│ ├── config.js
│ ├── help.js
│ ├── index.js
│ └── theme.js
├── index.js
├── info
│ ├── gif.js
│ └── index.js
├── setting
│ └── index.js
├── task
│ ├── familyInventory.js
│ ├── index.js
│ ├── play.js
│ ├── priceChange.js
│ ├── userInventory.js
│ └── userWishlist.js
└── utils
│ ├── bot.js
│ ├── index.js
│ ├── request.js
│ └── steam.js
├── package.json
└── resources
├── SplitFiction
├── 三人探戈.png
├── 不详的迎接.png
├── 与神抗争.png
├── 世界分隔.png
├── 九头蛇.png
├── 你好,锤子先生.png
├── 停车场.png
├── 全新视角.png
├── 农场生活.png
├── 冰封之王.png
├── 冰封殿堂.png
├── 分头行动.png
├── 列车劫案.png
├── 勇武骑士.png
├── 囚犯.png
├── 地下世界.png
├── 坠入奇境.png
├── 塌缩之星.png
├── 处决场.png
├── 大地之母.png
├── 大逃亡.png
├── 大都市生活.png
├── 太空逃生.png
├── 宝藏之庙.png
├── 宝藏叛徒.png
├── 实用无人机.png
├── 实验室.png
├── 屠龙者.png
├── 工厂入口.png
├── 工厂外围.png
├── 工艺之庙.png
├── 巨石之怒.png
├── 废物处理.png
├── 弹珠锁.png
├── 战争坡道.png
├── 打破常规.png
├── 攀登摩天楼.png
├── 暗夜之光.png
├── 最高安全级别.png
├── 月亮市集.png
├── 机动战术.png
├── 枪械升级.png
├── 森林之心.png
├── 横截面.png
├── 毁灭性的移动树干.png
├── 毒液滚筒.png
├── 水之庙.png
├── 沙鱼传奇.png
├── 深入风暴.png
├── 温暖的问候.png
├── 潜入行动.png
├── 灵魂向导.png
├── 牢房片区.png
├── 犯罪集团首领.png
├── 生日蛋糕.png
├── 电音律动.png
├── 登山远足.png
├── 皇宫.png
├── 监狱大院.png
├── 监狱飞船.png
├── 监督者.png
├── 神威之龙.png
├── 空中亡命.png
├── 笔记本.png
├── 系统安全模式.png
├── 终极决战.png
├── 翻转都市.png
├── 自由斗士.png
├── 节目游戏.png
├── 蜿蜒小路.png
├── 蠢猴子.png
├── 补水设施.png
├── 记忆碎片.png
├── 运输船.png
├── 重力摩托.png
├── 长青领主.png
├── 霓虹街道.png
├── 面对面.png
├── 风筝.png
├── 驾车逃离.png
├── 高峰时间.png
├── 鬼镇.png
├── 龙骑士大团结.png
└── 龙魂.png
├── common
├── common.css
├── font
│ ├── MiSans-Bold.ttf
│ ├── MiSans-Light.ttf
│ └── MiSans-Normal.ttf
└── layout
│ └── default.html
├── game
├── friend_add.png
├── friend_bg.png
├── game.css
├── game.html
└── game.png
├── help
├── help.jpg
├── icon.png
├── index.css
├── index.html
├── theme
│ ├── 1.png
│ ├── 2.png
│ ├── 3.png
│ ├── 4.png
│ └── 5.png
├── version-info.css
└── version-info.html
├── info
├── index.css
├── index.html
├── shared_global.css
└── steam.html
├── inventory
├── index.css
└── index.html
├── review
├── index.html
└── recommended.css
├── setting
├── imgs
│ ├── bg.png
│ ├── cfg-right.jpg
│ └── cfg-right.png
├── index.css
└── index.html
└── user
├── check.webp
├── index.css
└── index.html
/.eslintrc.cjs:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es2021: true
5 | },
6 | extends: 'eslint:recommended',
7 | overrides: [
8 | {
9 | env: {
10 | node: true
11 | },
12 | files: [
13 | '.eslintrc.{js,cjs}'
14 | ],
15 | parserOptions: {
16 | sourceType: 'script'
17 | }
18 | }
19 | ],
20 | parserOptions: {
21 | ecmaVersion: 'latest',
22 | sourceType: 'module'
23 | },
24 | ignorePatterns: [
25 | 'test/**'
26 | ],
27 | rules: {
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/.github/workflows/version.yml:
--------------------------------------------------------------------------------
1 | name: 更新版本号
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 |
8 | jobs:
9 | build:
10 | runs-on: ubuntu-latest
11 |
12 | steps:
13 | - name: 更新版本号
14 | uses: googleapis/release-please-action@v4
15 | id: release_please
16 | with:
17 | release-type: node
18 | token: ${{ secrets.GITHUB_TOKEN }}
19 | bump-minor-pre-major: true
20 | version-file: package.json
21 | fork: false
22 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | test
2 | **/test.js
3 | **/test.ts
4 | node_modules
5 | config/config
6 | data
7 | data.db
8 | temp
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 小叶
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Steam Plugin
2 |
3 |
4 |
5 | **提供 steam 相关功能**
6 |
7 |
8 |
9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
15 | 
16 |
17 |
18 |

19 |
20 |
21 |
22 | 
23 |
24 | ## **注意**
25 |
26 | 1. 一定要填**Steam Web API Key**,否则无法使用绝大部分功能,通常会返回 401 或 403 错误,请前往[Steam API](https://steamcommunity.com/dev/apikey)申请API Key, 域名随意填写
27 |
28 | 相关链接:
29 |
30 | - [Steam Web API 说明](https://partner.steamgames.com/doc/webapi_overview/auth)
31 | - [申请API Key](https://steamcommunity.com/dev/apikey)
32 | - [Steam API 条款](https://steamcommunity.com/dev/apiterms)
33 |
34 | 2. Steam 是国外网站, 所以通常需要配置代理或反代链接, 否则可能会出现连接超时, 通常会返回: `timeout of 5000ms exceeded`
35 |
36 | ## 介绍
37 |
38 | 这是一个基于 [Miao-Yunzai](https://github.com/yoimiya-kokomi/Miao-Yunzai)&[Trss-Yunzai](https://github.com/TimeRainStarSky/Yunzai)&[Karin](https://github.com/KarinJS/Karin)的扩展插件, 提供 steam 群友状态播报, steam 库存, steam 愿望单 等功能
39 |
40 | ## 安装
41 |
42 | ### Yunzai使用
43 |
44 | #### 使用github
45 |
46 | ```bash
47 | git clone --depth=1 https://github.com/XasYer/steam-plugin.git ./plugins/steam-plugin
48 | ```
49 |
50 | #### 使用gitee
51 |
52 | ```bash
53 | git clone --depth=1 https://gitee.com/xiaoye12123/steam-plugin.git ./plugins/steam-plugin
54 | ```
55 |
56 | ### Karin使用
57 |
58 | #### 使用github
59 |
60 | ```bash
61 | git clone --depth=1 https://github.com/XasYer/steam-plugin.git ./plugins/karin-plugin-steam
62 | ```
63 |
64 | #### 使用gitee
65 |
66 | ```bash
67 | git clone --depth=1 https://gitee.com/xiaoye12123/steam-plugin.git ./plugins/karin-plugin-steam
68 | ```
69 |
70 | ### 安装依赖
71 |
72 | ```bash
73 | pnpm install --filter=steam-plugin
74 | ```
75 |
76 | ## 功能
77 |
78 | 
79 |
80 | ## 联系方式
81 |
82 | - QQ 群: [741577559](http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=IvPaOVo_p-6n--FaLm1v39ML9EZaBRCm&authKey=YPs0p%2FRh8MGPQrWZgn99fk4kGB5PtRAoOYIUqK71FBsBYCDdekxCEHFFHnznpYA1&noverify=0&group_code=741577559)
83 |
84 | ## 使用cloudflare搭建反代 (连接不上steam情况下的备选)
85 |
86 | 1. 需要`cloudflare账号`, 以及在`cf托管的域名`, 自行查看对应教程
87 | 2. 打开cf主页左侧的`Workers 和 Pages`, 点击`创建`, 然后点击`创建 Worker`
88 | 3. 名字随意, 可参考`steam` 然后点击`部署` 再点击`编辑代码`
89 | 4. 复制以下代码到编辑器, `覆盖`原内容, 然后点击`部署`, 出现`版本已保存`即可
90 | ```js
91 | export default {
92 | async fetch(request) {
93 | const url = new URL(request.url);
94 | const path = decodeURIComponent(url.pathname.replace("/", ""));
95 | if (!path || !path.startsWith("http")) {
96 | return new Response("Ciallo~(∠・ω< )⌒☆");
97 | }
98 | const target = new URL(path);
99 | url.hostname = path.replace(/https?:\/\//, "");
100 | url.protocol = target.protocol;
101 | url.pathname = target.pathname;
102 | return await fetch(new Request(url, request));
103 | },
104 | };
105 | ```
106 | 5. 依次点击`左上角第3步填写的名字`, `设置`, `域和路由`右边的`添加`, `自定义域`, 然后填入你想设置的二级或多级域名, 比如`steam.example.com`, 然后点`添加域`, 要改成自己的在cf托管的域名
107 | 6. 测试(可选): 浏览器访问`https://steam.example.com/https://api.steampowered.com/ISteamWebAPIUtil/GetServerInfo/v1/`, `steam.example.com`替换成第5步设置的域名, 如果能看到`servertime`字段, 说明配置成功
108 | 7. 对你的Bot发送`#steam设置通用反代https://steam.example.com/{{url}}`, 域名替换成第5步设置的域名
109 |
110 | ### 注意事项
111 |
112 | 1. cloudflare的workers免费账户的每天请求数量限制10w次(一个账号所有的workers请求总量)
113 | 2. 2024年12月03日 cloudflare 更新[服务条款 2.2.1](https://www.cloudflare.com/zh-cn/terms/) **禁止使用服务提供虚拟专用网络或其他类似的代理服务** 若任使用请知晓可能出现的风险, 包括但不限于: **暂停或终止您对cloudflare服务的使用或访问** 等等。
114 |
115 | ## 贡献者
116 |
117 | > 🌟 星光闪烁,你们的智慧如同璀璨的夜空。感谢所有为 **steam-plugin** 做出贡献的人!
118 |
119 |
120 |
121 |
122 |
123 | 
124 |
125 | ## 其他
126 |
127 | 如果觉得此插件对你有帮助的话,可以点一个 star,你的支持就是不断更新的动力~
128 |
--------------------------------------------------------------------------------
/apps/QRLogin.js:
--------------------------------------------------------------------------------
1 | import { App } from '#components'
2 | import { segment } from '#lib'
3 | import { api, db, utils } from '#models'
4 | import moment from 'moment'
5 | import QRCode from 'qrcode'
6 |
7 | const appInfo = {
8 | id: 'QRLogin',
9 | name: '扫码登录'
10 | }
11 |
12 | const baseReg = '扫码(?:登[录陆]|绑定)'
13 |
14 | const rule = {
15 | QRLoginTips: {
16 | reg: App.getReg(`${baseReg}(帮助)?`),
17 | fnc: async (e) => {
18 | const msg = [
19 | '将使用steamApp扫码二维码进行登录, 登录完成后机器人可获得对应账号的access_token并保存, 拥有access_token后可执行各种隐私操作, 请在**特别信任**的机器人上进行扫码登录, 如果确认需要扫码登录, 请先打开steamApp进入扫码界面并使用以下方法(不支持扫描相册二维码)',
20 | '使用方法:',
21 | '1. 发送#steam确认扫码登录',
22 | '可能会出现地区不一致导致Steam阻止登录的情况, 可以先尝试切换到和Bot反代地区一致后再次扫码登录',
23 | '---------------',
24 | '2. 本地请求二维码',
25 | '2.1: 打开Chrome浏览器访问 https://store.steampowered.com/',
26 | '2.2: 按f12点击控制台/Console,输入以下内容并回车',
27 | 'copy("#steam辅助扫码登录"+await fetch(\'https://api.steampowered.com/IAuthenticationService/BeginAuthSessionViaQR/v1?input_json={"device_details":{"device_friendly_name":"Xiaomi 15 Pro","platform_type":3,"os_type":-500,"gaming_device_type":528},"website_id":"Mobile"}\',{method: \'POST\'}).then(res=>res.json()).then(res=>res.response).then(JSON.stringify).then(res=>{console.log(res);return res}));alert("复制成功")',
28 | '2.3 弹窗复制成功后发送复制后的内容到机器人',
29 | 'ps: 需自行处理代理保证请求和扫码的地址一致'
30 | ]
31 | await utils.bot.makeForwardMsg(e, msg)
32 | return true
33 | }
34 | },
35 | QRLogin: {
36 | reg: App.getReg(`(?:确认|辅助)${baseReg}(.*)`),
37 | fnc: async (e) => {
38 | const input = rule.QRLogin.reg.exec(e.msg)[1].trim()
39 | let session = input ? JSON.parse(input) : await api.IAuthenticationService.BeginAuthSessionViaQR()
40 | if (session.response) {
41 | session = session.response
42 | }
43 | const qrcode = (await QRCode.toDataURL(session.challenge_url)).replace('data:image/png;base64,', 'base64://')
44 | await App.reply(e, ['请在30秒内使用steamApp扫描二维码进行登录', segment.image(qrcode)], {
45 | recallMsg: 30,
46 | quote: true
47 | })
48 | for (let i = 0; i < 6; i++) {
49 | await new Promise(resolve => setTimeout(resolve, 1000 * 5))
50 | const qrcodeRes = await api.IAuthenticationService.PollAuthSessionStatus(session.client_id, session.request_id).catch(() => ({}))
51 | if (qrcodeRes.access_token) {
52 | const jwt = utils.steam.decodeAccessTokenJwt(qrcodeRes.access_token)
53 | const cookie = utils.steam.getCookie(jwt.sub, qrcodeRes.access_token)
54 | const dbRes = await db.token.set(e.user_id, qrcodeRes.access_token, cookie, qrcodeRes.refresh_token)
55 | const user = await db.user.getBySteamId(dbRes.steamId)
56 | if (user?.userId) {
57 | if (user.userId != e.user_id) {
58 | await db.user.del(user.userId, dbRes.steamId)
59 | await db.user.add(e.user_id, dbRes.steamId)
60 | } else {
61 | await db.user.set(e.user_id, dbRes.steamId, true)
62 | }
63 | } else {
64 | await db.user.add(e.user_id, dbRes.steamId)
65 | }
66 | return `登录成功\nsteamId: ${dbRes.steamId}\n登录名: ${qrcodeRes.account_name.replace(/^(.)(.*)(.)$/, '$1***$3')}\n需要切换到对应的steamId才会生效`
67 | }
68 | }
69 | return '登录超时~请重新触发指令'
70 | }
71 | },
72 | refreshToken: {
73 | reg: App.getReg('刷新(access_token|token|ak|ck|accesstoken|cookie)'),
74 | cfg: {
75 | accessToken: true
76 | },
77 | fnc: async (e) => {
78 | await utils.steam.refreshAccessToken(e.user_id, true)
79 | return 'access_token已刷新~'
80 | }
81 | },
82 | showToken: {
83 | reg: App.getReg('我的(access_token|token|ak|ck|accesstoken|cookie)'),
84 | cfg: {
85 | accessToken: true,
86 | private: true
87 | },
88 | fnc: async (e, { accessToken, cookie }) => {
89 | const jwt = utils.steam.decodeAccessTokenJwt(accessToken)
90 | await App.reply(e, accessToken, { at: false })
91 | await App.reply(e, cookie, { at: false })
92 | return [
93 | `${accessToken.slice(0, 5)}...: access_token`,
94 | `${cookie.slice(0, 5)}...: cookie`,
95 | `过期时间: ${moment.unix(jwt.exp).format('YYYY-MM-DD HH:mm:ss')}`
96 | ].join('\n')
97 | }
98 | },
99 | deleteToken: {
100 | reg: App.getReg('删除(access_token|token|ak|ck|accesstoken|cookie)'),
101 | cfg: {
102 | accessToken: true
103 | },
104 | fnc: async (e, { steamId }) => {
105 | await db.token.del(e.user_id, steamId)
106 | return 'access_token已删除~'
107 | }
108 | }
109 | }
110 |
111 | export const app = new App(appInfo, rule).create()
112 |
--------------------------------------------------------------------------------
/apps/achievement.js:
--------------------------------------------------------------------------------
1 | import { App, Render } from '#components'
2 | import { utils, api } from '#models'
3 | import _ from 'lodash'
4 |
5 | const appInfo = {
6 | id: 'achievement',
7 | name: '成就统计'
8 | }
9 |
10 | const rule = {
11 | achievements: {
12 | reg: App.getReg('(成就|统计)\\s*(\\d+|\\d+[-:\\s]\\d+)?'),
13 | cfg: {
14 | tips: true,
15 | steamId: true,
16 | appid: true
17 | },
18 | fnc: async (e, { appid, steamId, uid }) => {
19 | const type = e.msg.includes('成就') ? '成就' : '统计'
20 | // 先获取游戏的成就列表
21 | const achievementsByGame = await api.ISteamUserStats.GetSchemaForGame(appid).catch(() => {})
22 | if (!achievementsByGame || !achievementsByGame.availableGameStats) {
23 | return `没有找到${appid}的成就信息`
24 | }
25 | const achievementsByUser = await api.ISteamUserStats.GetUserStatsForGame(appid, steamId).catch(() => {})
26 | if (!achievementsByUser || !achievementsByUser.achievements) {
27 | return `没有找到${steamId}在${appid}的成就信息`
28 | }
29 | const nickname = await utils.bot.getUserName(e.self_id, uid, e.group_id)
30 | const data = [
31 | {
32 | title: `${nickname}的${achievementsByGame.gameName} ${type}统计`,
33 | games: [{
34 | name: achievementsByGame.gameName,
35 | appid
36 | }]
37 | }
38 | ]
39 | if (type === '成就') {
40 | if (!achievementsByGame.availableGameStats.achievements?.length) {
41 | return `${achievementsByGame.gameName}好像没有成就呢`
42 | }
43 | const completeAchievements = []
44 | const unCompleteAchievements = []
45 | achievementsByGame.availableGameStats.achievements.forEach(all => {
46 | const user = achievementsByUser.achievements.find(i => i.name === all.name)
47 | const info = {
48 | name: all.displayName,
49 | desc: all.hidden ? '已隐藏' : all.description,
50 | image: user ? all.icon : all.icongray,
51 | isAvatar: true
52 | }
53 | if (user) {
54 | completeAchievements.push(info)
55 | } else {
56 | unCompleteAchievements.push(info)
57 | }
58 | })
59 | data.push(
60 | {
61 | title: '已完成成就',
62 | desc: `共${completeAchievements.length}个`,
63 | games: completeAchievements
64 | },
65 | {
66 | title: '未完成成就',
67 | desc: `共${unCompleteAchievements.length}个`,
68 | games: unCompleteAchievements
69 | }
70 | )
71 | } else {
72 | if (!achievementsByGame.availableGameStats.stats?.length) {
73 | return `${achievementsByGame.gameName}好像没有统计呢`
74 | }
75 | const completeStats = achievementsByUser.stats.map(i => {
76 | const item = achievementsByGame.availableGameStats.stats.find(j => j.name === i.name)
77 | if (item) {
78 | return {
79 | name: item.name,
80 | detail: i.value,
81 | desc: item.displayName || '',
82 | noImg: true
83 | }
84 | } else {
85 | return false
86 | }
87 | }).filter(Boolean)
88 | data.push(
89 | {
90 | title: '已完成统计',
91 | desc: `共${completeStats.length}个`,
92 | games: completeStats
93 | }
94 | )
95 | }
96 | return await Render.render('inventory/index', { data })
97 | }
98 | },
99 | achievementStats: {
100 | reg: App.getReg('成就统计\\s*(\\d*)'),
101 | cfg: {
102 | tips: true,
103 | appid: true
104 | },
105 | fnc: async (e, { appid }) => {
106 | // 先获取游戏的成就列表
107 | const achievementsByGame = await api.ISteamUserStats.GetSchemaForGame(appid)
108 | if (!achievementsByGame || !achievementsByGame.availableGameStats) {
109 | return `没有找到${appid}的成就信息`
110 | }
111 | // 全球统计
112 | const achievements = await api.ISteamUserStats.GetGlobalAchievementPercentagesForApp(appid)
113 | const data = [
114 | {
115 | title: `${achievementsByGame.gameName} 全球成就统计`,
116 | games: [{
117 | name: achievementsByGame.gameName,
118 | appid
119 | }]
120 | }
121 | ]
122 | const games = []
123 | achievementsByGame.availableGameStats.achievements.forEach(all => {
124 | const i = achievements.find(i => i.name === all.name)
125 | if (!i) return
126 | const percent = parseInt(i.percent)
127 | const info = {
128 | name: all.displayName,
129 | desc: all.hidden ? '已隐藏' : all.description,
130 | image: i ? all.icon : all.icongray,
131 | isAvatar: true,
132 | detail: `${percent}%`,
133 | detailPercent: percent
134 | }
135 | games.push(info)
136 | })
137 | data.push({
138 | title: '全球成就统计',
139 | desc: `共${games.length}个`,
140 | games: _.orderBy(games, 'detailPercent', 'desc')
141 | })
142 | return await Render.render('inventory/index', { data })
143 | }
144 | }
145 | }
146 |
147 | export const app = new App(appInfo, rule).create()
148 |
--------------------------------------------------------------------------------
/apps/bind.js:
--------------------------------------------------------------------------------
1 | import { utils, db, bind } from '#models'
2 | import { App, Config } from '#components'
3 | import { logger } from '#lib'
4 |
5 | const appInfo = {
6 | id: 'bind',
7 | name: '绑定Steam'
8 | }
9 |
10 | const rule = {
11 | getBindImg: {
12 | // 这个指令必须# 不然可能会误触发
13 | reg: /^#steam$/i,
14 | fnc: async e => await bind.getBindSteamIdsImg(e.self_id, utils.bot.getAtUid(e.at, e.user_id), e.group_id)
15 | },
16 | bind: {
17 | reg: App.getReg('(?:[切更]换)?(?:绑定|bind)\\s*(\\d+)?'),
18 | fnc: async e => {
19 | // 如果是主人可以at其他用户进行绑定
20 | const uid = utils.bot.getAtUid(e.isMaster ? e.at : '', e.user_id)
21 | const textId = rule.bind.reg.exec(e.msg)[1]
22 | const userBindAll = await db.user.getAllByUserId(uid)
23 | if (!textId) {
24 | return await bind.getBindSteamIdsImg(e.self_id, uid, e.group_id, userBindAll)
25 | }
26 | const index = Number(textId) <= userBindAll.length ? Number(textId) - 1 : -1
27 | const steamId = index >= 0 ? userBindAll[index].steamId : utils.steam.getSteamId(textId)
28 | // 检查steamId是否被绑定
29 | const bindInfo = await db.user.getBySteamId(steamId)
30 | if (bindInfo) {
31 | if (bindInfo.userId == uid) {
32 | await db.user.set(uid, steamId)
33 | } else {
34 | return Config.tips.repeatBindTips
35 | }
36 | } else {
37 | await db.user.add(uid, steamId)
38 | // 群聊绑定才添加
39 | if (e.group_id) {
40 | await db.push.setNA(uid, steamId)
41 | await db.push.set(uid, steamId, e.self_id, e.group_id, {
42 | play: Config.push.defaultPush,
43 | state: Config.push.defaultPush,
44 | inventory: false,
45 | wishlist: false
46 | })
47 | }
48 | }
49 | return await bind.getBindSteamIdsImg(e.self_id, uid, e.group_id)
50 | }
51 | },
52 | unbind: {
53 | reg: App.getReg('(?:强制)?(?:解除?绑定?|unbind|取消绑定)\\s*(\\d+)?'),
54 | fnc: async e => {
55 | const textId = rule.unbind.reg.exec(e.msg)[1]
56 | if (!textId) {
57 | return '要和SteamID或好友码一起发送哦'
58 | }
59 | const isForce = e.msg.includes('强制') && e.isMaster
60 | // 如果是主人可以at其他用户进行绑定
61 | const uid = utils.bot.getAtUid(e.isMaster ? e.at : '', e.user_id)
62 | const userBindAll = await db.user.getAllByUserId(uid)
63 | const index = Number(textId) <= userBindAll.length ? Number(textId) - 1 : -1
64 | const steamId = index >= 0 ? userBindAll[index].steamId : utils.steam.getSteamId(textId)
65 | // 检查steamId是否被绑定
66 | const bindInfo = await db.user.getBySteamId(steamId)
67 | if (bindInfo) {
68 | if (bindInfo.userId == uid || isForce) {
69 | const id = isForce ? bindInfo.userId : uid
70 | try {
71 | await db.user.del(id, steamId)
72 | return `已解除绑定${steamId}`
73 | } catch (error) {
74 | logger.error(error)
75 | return `解绑失败了, 请稍后再试\n${error.message}`
76 | }
77 | } else {
78 | return '只能解绑自己绑定的steamId哦'
79 | }
80 | }
81 | return '还没有人绑定这个steamId呢'
82 | }
83 | }
84 | }
85 |
86 | export const app = new App(appInfo, rule).create()
87 |
--------------------------------------------------------------------------------
/apps/cart.js:
--------------------------------------------------------------------------------
1 | import { App, Render } from '#components'
2 | import { api, utils } from '#models'
3 |
4 | const appInfo = {
5 | id: 'cart',
6 | name: '购物车操作'
7 | }
8 |
9 | const rule = {
10 | modify: {
11 | reg: App.getReg('([添增]加|[删移][除出])购物车\\s*(\\d*)'),
12 | cfg: {
13 | tips: true,
14 | accessToken: true,
15 | appid: true
16 | },
17 | fnc: async (e, { accessToken, steamId, appid }) => {
18 | // 获取用户的地区代码
19 | const country = await api.IUserAccountService.GetUserCountry(accessToken, steamId)
20 | if (!country) {
21 | return '获取地区代码失败...'
22 | }
23 | // 获取packageid
24 | const infos = await api.IStoreBrowseService.GetItems([{ bundleid: appid }, { appid }])
25 | const info = infos[appid]
26 | if (!info) {
27 | return `没有获取到${appid}的信息`
28 | }
29 | const modifyType = e.msg.includes('加') ? 'add' : 'del'
30 | if (info.is_free && modifyType === 'add') {
31 | return `${info.name}是免费游戏哦, 直接入库吧`
32 | }
33 | const appType = info.best_purchase_option.packageid ? 'packageid' : 'bundleid'
34 | const packageid = appType === 'packageid' ? info.best_purchase_option.packageid : info.best_purchase_option.bundleid
35 | // 先检查有没有在购物车中
36 | const cart = await api.IAccountCartService.GetCart(accessToken, country)
37 | const item = cart.line_items?.find(i => i[appType] === packageid)
38 | if (modifyType === 'del') {
39 | if (item) {
40 | const res = await api.IAccountCartService.RemoveItemFromCart(accessToken, item.line_item_id, country)
41 | if (!res.line_items?.some(i => i[appType] === packageid)) {
42 | return `删除${info.name}成功~`
43 | } else {
44 | return `删除${info.name}失败...`
45 | }
46 | } else {
47 | return `${info.name}没有在购物车中~`
48 | }
49 | } else {
50 | if (!item) {
51 | // 加入购物车
52 | const res = await api.IAccountCartService.AddItemsToCart(accessToken, { [appType]: packageid }, country)
53 | if (res.cart.line_items?.some(i => i.packageid === packageid)) {
54 | return `已添加${info.name}到购物车~`
55 | } else {
56 | return `添加${info.name}到购物车失败...`
57 | }
58 | } else {
59 | return `${info.name}已经在购物车中~`
60 | }
61 | }
62 | }
63 | },
64 | look: {
65 | reg: App.getReg('(查看|清空)购物车'),
66 | cfg: {
67 | accessToken: true
68 | },
69 | fnc: async (e, { accessToken, steamId }) => {
70 | const country = await api.IUserAccountService.GetUserCountry(accessToken, steamId)
71 | if (!country) {
72 | return '获取地区代码失败...'
73 | }
74 | if (e.msg.includes('清空')) {
75 | await api.IAccountCartService.DeleteCart(accessToken)
76 | return '已清空购物车~'
77 | } else {
78 | const cart = await api.IAccountCartService.GetCart(accessToken, country)
79 | if (!cart.line_items) {
80 | return '购物车为空~'
81 | }
82 | const ids = cart.line_items.map(i => {
83 | if (i.packageid) {
84 | return { packageid: i.packageid }
85 | } else if (i.bundleid) {
86 | return { bundleid: i.bundleid }
87 | } else {
88 | return {}
89 | }
90 | })
91 | const infos = await api.IStoreBrowseService.GetItems(ids, { include_assets: true })
92 | const games = cart.line_items.map(i => {
93 | const info = infos[i.packageid || i.bundleid]
94 | if (!info) {
95 | return {
96 | name: '未知项目',
97 | appid: i.packageid || i.bundleid,
98 | image: '',
99 | price: {
100 | original: i.price_when_added?.formatted_amount
101 | }
102 | }
103 | }
104 | const image = info.assets
105 | // eslint-disable-next-line no-template-curly-in-string
106 | ? utils.steam.getStaticUrl(info.assets.asset_url_format.replace('${FILENAME}', info.assets.header))
107 | : utils.steam.getHeaderImgUrlByAppid(info.appid)
108 | return {
109 | name: info.name,
110 | appid: info.appid || info.id,
111 | image,
112 | desc: i.packageid ? '游戏或DLC' : '捆绑包',
113 | price: {
114 | original: i.price_when_added.formatted_amount
115 | }
116 | }
117 | })
118 | const data = [{
119 | title: `${await utils.bot.getUserName(e.self_id, e.user_id, e.group_id)}购物车一共有${games.length}件商品`,
120 | desc: `一共 ${cart.subtotal.formatted_amount}`,
121 | games
122 | }]
123 | return await Render.render('inventory/index', { data })
124 | }
125 | }
126 | }
127 | }
128 |
129 | export const app = new App(appInfo, rule).create()
130 |
--------------------------------------------------------------------------------
/apps/client.js:
--------------------------------------------------------------------------------
1 | import { App, Render } from '#components'
2 | import { api } from '#models'
3 |
4 | const appInfo = {
5 | id: 'client',
6 | name: '客户端操作'
7 | }
8 |
9 | const baseReg = '(?:客户端|clinet|c)'
10 |
11 | const rule = {
12 | info: {
13 | reg: App.getReg(`${baseReg}信息`),
14 | cfg: {
15 | tips: true,
16 | accessToken: true
17 | },
18 | fnc: async (e, { accessToken }) => {
19 | const clients = await api.IClientCommService.GetAllClientLogonInfo(accessToken)
20 | if (!clients.sessions) {
21 | return '没有登录Steam客户端'
22 | }
23 | const text = clients.sessions.map(i => [
24 | `instanceid: ${i.client_instanceid}`,
25 | `os: ${i.os_name}`,
26 | `name: ${i.machine_name}`
27 | ].join('\n')).join('\n----------\n')
28 | return `可用instanceid指定客户端默认为第一个\n${text}`
29 | }
30 | },
31 | appList: {
32 | reg: App.getReg(`${baseReg}游戏列表(\\d*)`),
33 | cfg: {
34 | tips: true,
35 | accessToken: true
36 | },
37 | fnc: async (e, { accessToken }) => {
38 | const instanceid = rule.appList.reg.exec(e.msg)[1]
39 | const res = await api.IClientCommService.GetClientAppList(accessToken, instanceid)
40 | if (!res) {
41 | return `没有获取到客户端游戏列表${instanceid ? ', 请检查instanceid是否正确' : ''}`
42 | }
43 | // 需要更新的游戏
44 | const updateList = []
45 | // 已安装的游戏
46 | const installList = []
47 | // 未安装的游戏
48 | const uninstallList = []
49 | res.apps.forEach(i => {
50 | if (i.app_type !== 'game') {
51 | return
52 | }
53 | const info = {
54 | appid: i.appid,
55 | name: i.app
56 | }
57 | if (i.installed) {
58 | if (i.target_buildid && i.target_buildid !== i.source_buildid) {
59 | // 还可以细分
60 | // queue_position -1: 未安排下载 0: 已安排
61 | // rt_time_scheduled: 计划下载时间
62 | // download_paused true: 暂停下载 false: 正在下载
63 | updateList.push({
64 | ...info,
65 | desc: `${formatBytes(i.bytes_downloaded)} / ${formatBytes(i.bytes_to_download)}`
66 | })
67 | } else {
68 | installList.push(info)
69 | }
70 | } else {
71 | uninstallList.push(info)
72 | }
73 | })
74 | const data = []
75 | if (updateList.length) {
76 | data.push({
77 | title: '有更新的游戏',
78 | games: updateList
79 | })
80 | }
81 | if (installList.length) {
82 | data.push({
83 | title: '已安装的游戏',
84 | games: installList
85 | })
86 | }
87 | if (uninstallList.length) {
88 | data.push({
89 | title: '未安装的游戏',
90 | games: uninstallList
91 | })
92 | }
93 | if (!data.length) {
94 | return '游戏列表为空'
95 | }
96 | return await Render.render('inventory/index', {
97 | data
98 | })
99 | }
100 | },
101 | install: {
102 | reg: App.getReg(`${baseReg}(?:安装|下载)(?:游戏)?\\s*(\\d*)\\s*(\\d*)`),
103 | cfg: {
104 | accessToken: true,
105 | appid: true
106 | },
107 | fnc: async (e, { accessToken, appid }) => {
108 | const instanceid = rule.install.reg.exec(e.msg)[2]
109 | await api.IClientCommService.InstallClientApp(accessToken, appid, instanceid)
110 | return '已发送安装请求'
111 | }
112 | },
113 | launch: {
114 | reg: App.getReg(`${baseReg}(?:启动|打开)(?:游戏)?\\s*(\\d*)\\s*(\\d*)`),
115 | cfg: {
116 | accessToken: true,
117 | appid: true
118 | },
119 | fnc: async (e, { accessToken, appid }) => {
120 | const instanceid = rule.launch.reg.exec(e.msg)[2]
121 | await api.IClientCommService.LaunchClientApp(accessToken, appid, instanceid)
122 | return '已发送启动请求'
123 | }
124 | },
125 | uninstall: {
126 | reg: App.getReg(`${baseReg}(?:卸载|删除)(?:游戏)?\\s*(\\d*)\\s*(\\d*)`),
127 | cfg: {
128 | accessToken: true,
129 | appid: true
130 | },
131 | fnc: async (e, { accessToken, appid }) => {
132 | const instanceid = rule.uninstall.reg.exec(e.msg)[2]
133 | await api.IClientCommService.UninstallClientApp(accessToken, appid, instanceid)
134 | return '已发送卸载请求'
135 | }
136 | },
137 | download: {
138 | reg: App.getReg(`${baseReg}(?:恢复|暂停|停止|继续)(?:下载|更新)?(?:游戏)?\\s*(\\d*)\\s*(\\d*)`),
139 | cfg: {
140 | accessToken: true,
141 | appid: true
142 | },
143 | fnc: async (e, { accessToken, appid }) => {
144 | const instanceid = rule.download.reg.exec(e.msg)[2]
145 | const download = /(恢复|继续)/.test(e.msg)
146 | await api.IClientCommService.SetClientAppUpdateState(accessToken, appid, download, instanceid)
147 | return `已发送${download ? '继续' : '暂停'}下载请求`
148 | }
149 | }
150 | }
151 |
152 | function formatBytes (bytes, fractionDigits = 2) {
153 | if (!bytes) return '0 B'
154 |
155 | const k = 1024
156 | const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
157 | const i = Math.floor(Math.log(bytes) / Math.log(k))
158 |
159 | return `${parseFloat((bytes / Math.pow(k, i)).toFixed(fractionDigits))} ${sizes[i]}`
160 | }
161 |
162 | export const app = new App(appInfo, rule).create()
163 |
--------------------------------------------------------------------------------
/apps/dev.js:
--------------------------------------------------------------------------------
1 | import { App, Config } from '#components'
2 | import { api, db, utils } from '#models'
3 | import { segment } from '#lib'
4 |
5 | const appInfo = {
6 | id: 'dev',
7 | name: '接口测试'
8 | }
9 |
10 | const rule = {
11 | dev: {
12 | reg: App.getReg('dev\\s*(.*)'),
13 | cfg: {
14 | tips: true
15 | },
16 | fnc: async e => {
17 | const keys = Object.keys(api)
18 | const text = rule.dev.reg.exec(e.msg)[1]
19 | if (!text) {
20 | const methods = keys.map((interfaceName, interfaceIndex) => {
21 | return Object.keys(api[interfaceName]).map((methodName, methodIndex) => {
22 | return `${interfaceIndex}.${methodIndex} ${interfaceName}.${methodName}(${getParams(api[interfaceName][methodName]).join(', ')})`
23 | }).join('\n\n')
24 | })
25 | const msg = [
26 | '使用方法: ',
27 | '#steamdev 接口名.方法名 参数1 参数2...',
28 | '带s的参数为数组',
29 | '参数可使用{steamid}和{accesstoken}占位符,表示当前绑定的SteamID和AccessToken',
30 | '接口名和方法名可使用数字索引,例如: 0.0 1.1 2.2',
31 | '可使用的接口名和方法名如下:',
32 | '部分api未经测试,可能存在bug',
33 | ...methods
34 | ]
35 | await utils.bot.makeForwardMsg(e, msg)
36 | return true
37 | }
38 | const [cmd, ...args] = split(text)
39 | const [interfaceKey, methodKey] = cmd.split('.')
40 | const interfaceName = keys[interfaceKey] || interfaceKey
41 | const methods = Object.keys(api[interfaceName])
42 | const methodName = methods[methodKey] || methodKey
43 | const method = api[interfaceName][methodName]
44 | const methodParams = getParams(method)
45 | const uid = utils.bot.getAtUid(e.at, e.user_id)
46 | const steamId = await db.user.getBind(uid)
47 | if (!steamId) {
48 | await e.reply([segment.at(uid), '\n', Config.tips.noSteamIdTips])
49 | return true
50 | }
51 | const hasAccessToken = /{access_?token}/i.test(e.msg)
52 | if (hasAccessToken && uid != e.user_id) {
53 | return '只能操作自己的accessToken'
54 | }
55 | const token = hasAccessToken && await utils.steam.getAccessToken(uid, steamId)
56 | if (hasAccessToken && !token.success) {
57 | return '没有绑定accessToken'
58 | }
59 | const replaceParams = (text) => {
60 | if (Array.isArray(text)) {
61 | return text.map(replaceParams)
62 | } else {
63 | return text.replace(/{steamid}/ig, steamId).replace(/{access_?token}/ig, token.accessToken)
64 | }
65 | }
66 | const params = args.map(replaceParams)
67 | const start = Date.now()
68 | const result = await method(...params)
69 | const end = Date.now()
70 | const time = end - start
71 | const msg = [
72 | `接口: ${interfaceName}.${methodName}(${methodParams.join(', ')})`,
73 | `参数: ${params.map(i => i.length > 17 ? i.replace(/^(.{5})(.*)(.{5})$/, '$1...$3') : i).join(' ')}`,
74 | `耗时: ${time}ms`,
75 | '结果: ',
76 | JSON.stringify(result, null, 2) ?? 'undefined'
77 | ]
78 | await utils.bot.makeForwardMsg(e, msg)
79 | return true
80 | }
81 | }
82 | }
83 |
84 | function getParams (fn) {
85 | const fnStr = fn.toString().split('\n')[0]
86 | const params = fnStr.match(/\((.*)\)/)[1]
87 | return params.split(',').map(param => param.trim()).filter(Boolean)
88 | }
89 |
90 | function split (text) {
91 | const reg = /\[.*?\]|\S+/g
92 | const matches = text.match(reg)
93 |
94 | return matches.map(match => {
95 | if (match.startsWith('[') && match.endsWith(']')) {
96 | return match.slice(1, -1).split(' ')
97 | }
98 | return match
99 | })
100 | }
101 |
102 | export const app = new App(appInfo, rule).create()
103 |
--------------------------------------------------------------------------------
/apps/discounts.js:
--------------------------------------------------------------------------------
1 | import { App, Render } from '#components'
2 | import { api, utils } from '#models'
3 | import moment from 'moment'
4 |
5 | const appInfo = {
6 | id: 'discounts',
7 | name: '优惠'
8 | }
9 |
10 | const rule = {
11 | discounts: {
12 | reg: App.getReg('(优惠|特惠|热销|新品|即将推出)'),
13 | cfg: {
14 | tips: true
15 | },
16 | fnc: async e => {
17 | const res = await api.store.featuredcategories()
18 | const items = [
19 | {
20 | title: '优惠',
21 | key: 'specials'
22 | },
23 | {
24 | title: '即将推出',
25 | key: 'coming_soon'
26 | },
27 | {
28 | title: '热销',
29 | key: 'top_sellers'
30 | },
31 | {
32 | title: '新品',
33 | key: 'new_releases'
34 | }
35 | ]
36 | const data = []
37 | for (const item of items) {
38 | const key = {
39 | title: item.title,
40 | games: []
41 | }
42 | for (const i of res[item.key]?.items || []) {
43 | key.games.push({
44 | appid: i.id,
45 | name: i.name,
46 | desc: i.discount_expiration ? moment.unix(i.discount_expiration).format('YYYY-MM-DD HH:mm:ss') : '',
47 | image: i.image,
48 | price: i.discounted
49 | ? {
50 | original: `¥ ${i.original_price / 100}`,
51 | discount: i.discount_percent,
52 | current: `¥ ${i.final_price / 100}`
53 | }
54 | : {
55 | original: i.original_price ? `¥ ${i.original_price / 100}` : ''
56 | }
57 | })
58 | }
59 | data.push(key)
60 | }
61 | return await Render.render('inventory/index', { data })
62 | }
63 | },
64 | queue: {
65 | reg: App.getReg('探索队列'),
66 | cfg: {
67 | tips: true,
68 | accessToken: true
69 | },
70 | fnc: async (e, { accessToken, steamId }) => {
71 | const country = await api.IUserAccountService.GetUserCountry(accessToken, steamId)
72 | if (!country) {
73 | return '获取地区代码失败...'
74 | }
75 | const { appids, skipped } = await api.IStoreService.GetDiscoveryQueue(accessToken, country)
76 | const infoList = await api.IStoreBrowseService.GetItems(appids, { include_assets: true })
77 | const games = appids.map(appid => {
78 | const info = infoList[appid]
79 | if (!info) {
80 | return {
81 | appid
82 | }
83 | }
84 | return {
85 | appid,
86 | name: info.name,
87 | image: utils.steam.getHeaderImgUrlByAppid(appid, 'apps', info.assets.header),
88 | price: utils.steam.generatePrice(info.best_purchase_option, info.is_free)
89 | }
90 | })
91 | const data = [{
92 | title: `${await utils.bot.getUserName(e.self_id, e.user_id, e.group_id)}的探索队列`,
93 | desc: [`已跳过${skipped}个游戏`, '#steam探索队列跳过+appid', '#steam探索队列全部跳过'],
94 | games
95 | }]
96 | return await Render.render('inventory/index', { data })
97 | }
98 | },
99 | queueSkip: {
100 | reg: App.getReg('探索队列跳过\\s*(\\d*)'),
101 | cfg: {
102 | accessToken: true,
103 | appid: true
104 | },
105 | fnc: async (e, { accessToken, appid }) => {
106 | await api.IStoreService.SkipDiscoveryQueueItem(accessToken, appid)
107 | return `已跳过游戏${appid}~`
108 | }
109 | },
110 | queueSkipAll: {
111 | reg: App.getReg('探索队列全部跳过'),
112 | cfg: {
113 | accessToken: true
114 | },
115 | fnc: async (e, { accessToken, steamId }) => {
116 | const country = await api.IUserAccountService.GetUserCountry(accessToken, steamId)
117 | if (!country) {
118 | return '获取地区代码失败...'
119 | }
120 | const appids = (await api.IStoreService.GetDiscoveryQueue(accessToken, country)).appids
121 | await Promise.all(appids.map(async appid => await api.IStoreService.SkipDiscoveryQueueItem(accessToken, appid)))
122 | return '已跳过所有游戏~'
123 | }
124 | }
125 | }
126 |
127 | export const app = new App(appInfo, rule).create()
128 |
--------------------------------------------------------------------------------
/apps/expend.js:
--------------------------------------------------------------------------------
1 | import { App } from '#components'
2 | import { api } from '#models'
3 |
4 | const appInfo = {
5 | id: 'expend',
6 | name: '支出'
7 | }
8 |
9 | const rule = {
10 | total: {
11 | reg: App.getReg('总?(支出|消费|花费)'),
12 | cfg: {
13 | accessToken: true,
14 | tips: true
15 | },
16 | fnc: async (e, { cookie }) => {
17 | const html = await api.store.AjaxLoadMoreHistory(cookie)
18 | if (!html) {
19 | return '获取失败或消费为空'
20 | }
21 | const use = {}
22 | html.replace(/[\n\t]/g, '').split('').filter(i => !/(退款|wht_refunded|钱包<)/.test(i)).forEach(i => {
23 | const reg = /([\s\S]+?)<\/td>/
24 | const regRet = reg.exec(i)
25 | if (regRet) {
26 | const [currency, value] = regRet[1].split(' ')
27 | use[currency] = (use[currency] || 0) + parseFloat(value)
28 | }
29 | })
30 | const text = Object.keys(use).map(currency => `${currency} ${use[currency].toFixed(2)}`).join(' + ')
31 | return `在steam消费了${text}\n数据来源: 由 客服 -> 购买消费 -> 查看完整的购买记录 计算而来 仅供参考\n也可以前往 客服 -> 我的账户 -> 您 Steam 帐户的相关数据 -> 外部资金消费记录 查看 TotalSpend 的值`
32 | }
33 | }
34 | }
35 |
36 | export const app = new App(appInfo, rule).create()
37 |
--------------------------------------------------------------------------------
/apps/help.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { help as helpUtil, utils } from '#models'
3 | import { App, Render, Version } from '#components'
4 |
5 | const appInfo = {
6 | id: 'help',
7 | name: '帮助'
8 | }
9 |
10 | const rule = {
11 | help: {
12 | reg: App.getReg('(插件|plugin)?(帮助|菜单|help)'),
13 | fnc: async e => {
14 | const helpGroup = []
15 |
16 | const token = await utils.steam.getAccessToken(e.user_id)
17 | _.forEach(helpUtil.helpList, (group) => {
18 | switch (group.auth) {
19 | case 'master':
20 | if (!e.isMaster) {
21 | return true
22 | }
23 | break
24 | case 'accessToken':
25 | if (!token.success) {
26 | return true
27 | }
28 | }
29 |
30 | _.forEach(group.list, (help) => {
31 | const icon = _.random(1, 350)
32 | const x = (icon - 1) % 10
33 | const y = (icon - x - 1) / 10
34 | help.css = `background-position:-${x * 50}px -${y * 50}px`
35 | })
36 |
37 | helpGroup.push(group)
38 | })
39 | const themeData = await helpUtil.helpTheme.getThemeData({
40 | colCount: token.success ? 4 : 3,
41 | colWidth: 275
42 | })
43 | return await Render.render('help/index', {
44 | helpGroup,
45 | ...themeData,
46 | scale: 1.4
47 | })
48 | }
49 | }
50 | // version: {
51 | // reg: /^#?steam(插件|plugin)?(版本|version)$/i,
52 | // fnc: version
53 | // }
54 | }
55 |
56 | // eslint-disable-next-line no-unused-vars
57 | async function version (e) {
58 | const img = await Render.render('help/version-info', {
59 | currentVersion: Version.version,
60 | changelogs: Version.changelogs,
61 | scale: 1.2
62 | })
63 | return await e.reply(img)
64 | }
65 |
66 | export const app = new App(appInfo, rule).create()
67 |
--------------------------------------------------------------------------------
/apps/index.js:
--------------------------------------------------------------------------------
1 | import fs from 'node:fs'
2 | import { logger } from '#lib'
3 | import { join } from 'node:path'
4 | import { Version } from '#components'
5 |
6 | const startTime = Date.now()
7 |
8 | const path = join(Version.pluginPath, 'apps')
9 |
10 | const apps = {}
11 |
12 | fs.readdirSync(path).forEach(file => {
13 | if (file.endsWith('.js') && file !== 'index.js') {
14 | apps[file.replace('.js', '')] = import(`file://${join(path, file)}`)
15 | }
16 | })
17 |
18 | await Promise.all(Object.keys(apps).map(async id => {
19 | try {
20 | const startTime = Date.now()
21 | const exp = await apps[id]
22 | apps[id] = exp.app
23 | logger.debug(`加载js: apps/${id}.js成功 耗时: ${Date.now() - startTime}ms`)
24 | } catch (error) {
25 | delete apps[id]
26 | logger.log('error', `加载js: apps/${id}.js错误\n`, error)
27 | }
28 | }))
29 |
30 | export { apps }
31 |
32 | logger.log('info', '-----------------')
33 | logger.log('info', `${Version.pluginName} v${Version.pluginVersion} 加载成功~ 耗时: ${Date.now() - startTime}ms`)
34 | logger.log('info', '-------^_^-------')
35 |
--------------------------------------------------------------------------------
/apps/online.js:
--------------------------------------------------------------------------------
1 | import { App } from '#components'
2 | import { segment } from '#lib'
3 | import { api, utils } from '#models'
4 |
5 | const appInfo = {
6 | id: 'online',
7 | name: 'Online'
8 | }
9 |
10 | const rule = {
11 | online: {
12 | reg: App.getReg('在线(?:统计|数据|人数)?\\s*(\\d*)'),
13 | cfg: {
14 | appid: true
15 | },
16 | fnc: async (e, { appid }) => {
17 | const players = await api.ISteamUserStats.GetNumberOfCurrentPlayers(appid)
18 | if (players === false) {
19 | return '查询失败,可能没有这个游戏?'
20 | }
21 | const icon = utils.steam.getHeaderImgUrlByAppid(appid)
22 | const iconBuffer = await utils.getImgUrlBuffer(icon)
23 | const msg = []
24 | if (iconBuffer) {
25 | msg.push(segment.image(iconBuffer))
26 | }
27 | msg.push(`当前在线人数: ${players}`)
28 | return msg
29 | }
30 | }
31 | }
32 |
33 | export const app = new App(appInfo, rule).create()
34 |
--------------------------------------------------------------------------------
/apps/review.js:
--------------------------------------------------------------------------------
1 | import { App, Render } from '#components'
2 | import { api } from '#models'
3 |
4 | const appInfo = {
5 | id: 'review',
6 | name: '评论'
7 | }
8 |
9 | const rule = {
10 | review: {
11 | reg: App.getReg('(?:最新|热门)?评论\\s*(\\d*)'),
12 | cfg: {
13 | tips: true,
14 | appid: true
15 | },
16 | fnc: async (e, { appid }) => {
17 | const data = await api.store.appreviews(appid, 20, e.msg.includes('最新'))
18 | const [, state, honor] = /data-tooltip-html="(.+?)">(.+?)<\//.exec(data.review_score) || []
19 | return await Render.render('review/index', {
20 | review: data.html,
21 | state,
22 | honor,
23 | appid
24 | })
25 | }
26 | }
27 | }
28 |
29 | export const app = new App(appInfo, rule).create()
30 |
--------------------------------------------------------------------------------
/apps/rollGame.js:
--------------------------------------------------------------------------------
1 | import { utils, api } from '#models'
2 | import { Render, App, Config } from '#components'
3 | import _ from 'lodash'
4 |
5 | const appInfo = {
6 | id: 'rollGame',
7 | name: 'roll游戏'
8 | }
9 |
10 | const rule = {
11 | rollGame: {
12 | reg: App.getReg('(玩什么|玩啥|roll)(游戏)?\\s*(\\d*)'),
13 | cfg: {
14 | tips: true,
15 | steamId: true
16 | },
17 | fnc: async (e, { steamId, uid }) => {
18 | const nickname = await utils.bot.getUserName(e.self_id, uid, e.group_id) || steamId
19 | const screenshotOptions = {
20 | title: '',
21 | games: [],
22 | desc: ''
23 | }
24 |
25 | const games = await api.IPlayerService.GetOwnedGames(steamId)
26 | if (!games.length) {
27 | return Config.tips.inventoryEmptyTips
28 | }
29 |
30 | const configCount = Config.other.rollGameCount
31 | const recomendCount = games.length >= configCount ? configCount : games.length
32 | screenshotOptions.games = _.sampleSize(games, recomendCount)
33 | screenshotOptions.title = `${nickname} 随机给您推荐了 ${recomendCount} 个游戏`
34 |
35 | screenshotOptions.games.map(i => {
36 | i.desc = `${getTime(i.playtime_forever)} ${i.playtime_2weeks ? `/ ${getTime(i.playtime_2weeks)}` : ''}`
37 | return i
38 | })
39 | screenshotOptions.desc = '以下游戏从您的游戏库中通过完全随机的方式选出,不代表任何个人或团体的观点'
40 | return await Render.render('inventory/index', {
41 | data: [screenshotOptions]
42 | })
43 | }
44 | }
45 | }
46 |
47 | /**
48 | * 将游戏时长(单位:分)转换小时
49 | * @param {number} time
50 | * @returns {string}
51 | */
52 | function getTime (time) {
53 | return (time / 60).toFixed(1) + 'h'
54 | }
55 |
56 | export const app = new App(appInfo, rule).create()
57 |
--------------------------------------------------------------------------------
/apps/search.js:
--------------------------------------------------------------------------------
1 | import { api } from '#models'
2 | import { App, Render } from '#components'
3 |
4 | const appInfo = {
5 | id: 'search',
6 | name: '搜索'
7 | }
8 |
9 | const rule = {
10 | search: {
11 | reg: App.getReg('(?:搜索|search|查找|find)\\s*(.*)'),
12 | cfg: {
13 | tips: true
14 | },
15 | fnc: async e => {
16 | const name = rule.search.reg.exec(e.msg)[1].trim()
17 | if (!name) {
18 | return '要搜什么?'
19 | }
20 | const result = await api.store.search(name)
21 | const games = result.split('').map(i => {
22 | if (!i.includes('appid')) {
23 | return null
24 | }
25 | const appid = i.match(/data-ds-appid="(\d+)"/)?.[1]
26 | const name = i.match(/class="match_name">(.*?)<\/div>/)?.[1]
27 | const price = i.match(/class="match_price">(.*?)<\/div>/)?.[1]
28 | const image = i.match(/ /)?.[1]
29 | return {
30 | appid,
31 | name,
32 | image,
33 | price: price
34 | ? {
35 | discount: 0,
36 | original: price
37 | }
38 | : null
39 | }
40 | }).filter(Boolean)
41 | if (!games.length) {
42 | return '没有搜索到相关的游戏, 换个关键词试试?'
43 | }
44 | const screenshotOptions = {
45 | title: `${name} 搜索结果`,
46 | games
47 |
48 | }
49 | return await Render.render('inventory/index', { data: [screenshotOptions] })
50 | }
51 | }
52 | }
53 |
54 | export const app = new App(appInfo, rule).create()
55 |
--------------------------------------------------------------------------------
/apps/wishlist.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import moment from 'moment'
3 | import { utils, api } from '#models'
4 | import { App, Config, Render } from '#components'
5 |
6 | const appInfo = {
7 | id: 'wishlist',
8 | name: '愿望单'
9 | }
10 |
11 | const rule = {
12 | list: {
13 | reg: App.getReg('愿望单\\s*(\\d*)'),
14 | cfg: {
15 | tips: true,
16 | steamId: true
17 | },
18 | fnc: async (e, { steamId, uid }) => {
19 | const nickname = await utils.bot.getUserName(e.self_id, uid, e.group_id) || steamId
20 | const wishlist = await api.IWishlistService.GetWishlist(steamId)
21 | if (!wishlist.length) {
22 | return Config.tips.wishListEmptyTips
23 | }
24 | if (wishlist.length > Config.other.hiddenLength) {
25 | wishlist.length = Config.other.hiddenLength
26 | }
27 | // 愿望单没有给name, 尝试获取一下, 顺便也可以获取一下价格 获取失败超过3次就不再获取了
28 | // 2024年11月27日 已更新 有个api可以获取多个appid 仅url长度限制
29 | const appidsInfo = await api.IStoreBrowseService.GetItems(wishlist.map(i => i.appid), {
30 | include_assets: true
31 | })
32 | const total = {
33 | price: 0,
34 | currency: ''
35 | }
36 | const games = wishlist.map(i => {
37 | const info = appidsInfo[i.appid] || {
38 | best_purchase_option: {
39 | formatted_original_price: '获取失败'
40 | }
41 | }
42 | const price = info.best_purchase_option?.formatted_final_price ? /[\d.]+/.exec(info.best_purchase_option?.formatted_final_price)?.[0] : ''
43 | if (price) {
44 | total.price += parseFloat(price)
45 | total.currency = info.best_purchase_option.formatted_final_price.replace(price, '')
46 | }
47 | return {
48 | ...i,
49 | name: info.name || i.appid,
50 | image: utils.steam.getHeaderImgUrlByAppid(i.appid, 'apps', info.assets?.header),
51 | desc: moment.unix(i.date_added).format('YYYY-MM-DD HH:mm:ss'),
52 | price: info.is_free
53 | ? {
54 | discount: 0,
55 | original: '免费'
56 | }
57 | : {
58 | discount: info.best_purchase_option?.discount_pct || 0,
59 | original: info.best_purchase_option?.formatted_original_price || info.best_purchase_option?.formatted_final_price || '即将推出',
60 | current: info.best_purchase_option?.formatted_final_price || ''
61 | }
62 | }
63 | })
64 | const data = [{
65 | title: `${nickname} 愿望单共有 ${wishlist.length} 个游戏`,
66 | desc: `清空愿望单需要: ${total.currency}${total.price.toFixed(2)}`,
67 | games: _.orderBy(games, 'date_added', 'desc')
68 | }]
69 | return await Render.render('inventory/index', {
70 | data
71 | })
72 | }
73 | },
74 | add: {
75 | reg: App.getReg('[添增]加愿望单\\s*(\\d*)'),
76 | cfg: {
77 | accessToken: true,
78 | appid: true
79 | },
80 | fnc: async (e, { appid, accessToken }) => {
81 | await api.IWishlistService.AddToWishlist(accessToken, appid)
82 | return '已添加愿望单~'
83 | }
84 | },
85 | remove: {
86 | reg: App.getReg('[删移][除出]愿望单\\s*(\\d*)'),
87 | cfg: {
88 | accessToken: true,
89 | appid: true
90 | },
91 | fnc: async (e, { appid, accessToken }) => {
92 | await api.IWishlistService.RemoveFromWishlist(accessToken, appid)
93 | return '已移出愿望单~'
94 | }
95 | }
96 | }
97 |
98 | export const app = new App(appInfo, rule).create()
99 |
--------------------------------------------------------------------------------
/apps/yearReview.js:
--------------------------------------------------------------------------------
1 | import { App } from '#components'
2 | import { segment } from '#lib'
3 | import { api, utils } from '#models'
4 | import _ from 'lodash'
5 | import moment from 'moment'
6 |
7 | const appInfo = {
8 | id: 'yearReview',
9 | name: '年度回顾'
10 | }
11 |
12 | const rule = {
13 | shareImage: {
14 | reg: App.getReg('年度回顾分享图片\\s*(\\d+|\\d+[-:\\s]\\d+)?'),
15 | cfg: {
16 | steamId: true
17 | },
18 | fnc: async (e, { steamId }) => {
19 | const year = e.msg.match(/\d+/g)?.shift() || getYear()
20 | const images = await api.ISaleFeatureService.GetUserYearInReviewShareImage(steamId, year)
21 | const i = _.sample(images)
22 | if (!i) {
23 | return `年度回顾可见性未公开, 获取失败, 可前往\nhttps://store.steampowered.com/replay/${steamId}/${year}\n进行查看`
24 | }
25 | const path = utils.steam.getStaticUrl(i.url_path)
26 | const buffer = await utils.getImgUrlBuffer(path)
27 | if (buffer) {
28 | return segment.image(buffer)
29 | } else {
30 | return '图片获取失败,请稍后再试'
31 | }
32 | }
33 | }
34 | }
35 |
36 | function getYear () {
37 | const m = moment().month()
38 | const y = moment().year()
39 | return m < 11 ? y - 1 : y
40 | }
41 |
42 | export const app = new App(appInfo, rule).create()
43 |
--------------------------------------------------------------------------------
/components/Render.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import _ from 'lodash'
3 | import { join } from 'path'
4 | import { logger, puppeteer } from '#lib'
5 | import template from 'art-template'
6 | import { canvas, info, utils } from '#models'
7 | import { Version, Config } from '#components'
8 |
9 | function scale (pct = 1) {
10 | const scale = Math.min(2, Math.max(0.5, Config.other.renderScale / 100))
11 | pct = pct * scale
12 | return `style=transform:scale(${pct})`
13 | }
14 |
15 | const Render = {
16 | async render (path, params) {
17 | path = path.replace(/.html$/, '')
18 | const layoutPath = join(Version.pluginPath, 'resources', 'common', 'layout')
19 | const data = {
20 | tplFile: `${Version.pluginPath}/resources/${path}.html`,
21 | pluResPath: `${Version.pluginPath}/resources/`,
22 | saveId: path.split('/').pop(),
23 | imgType: 'jpeg',
24 | defaultLayout: join(layoutPath, 'default.html'),
25 | sys: {
26 | scale: scale(params.scale || 1),
27 | copyright: params.copyright || `Created By ${Version.BotName} v${Version.BotVersion} & ${Version.pluginName} v${Version.pluginVersion} `
28 | },
29 | pageGotoParams: {
30 | // waitUntil: 'networkidle0' // +0.5s
31 | waitUntil: 'load'
32 | },
33 | ...params
34 | }
35 | if (path === 'inventory/index') {
36 | const hiddenLength = Config.other.hiddenLength
37 | const minLength = Math.min(
38 | Math.max(...params.data.map(i => i.games?.length || 0)),
39 | Math.max(1, Number(Config.other.itemLength) || 1)
40 | )
41 | params.data = await Promise.all(params.data.map(async i => {
42 | if (!Array.isArray(i.desc)) i.desc = [i.desc].filter(Boolean)
43 | if (!i.games) i.games = []
44 | if (i.games.length > hiddenLength) {
45 | const length = i.games.length - hiddenLength
46 | i.desc.push(`太多辣 ! 已隐藏${length}个项目`)
47 | i.games.length = hiddenLength
48 | }
49 | const infos = params.schinese ? await utils.steam.getGameSchineseInfo(i.games.map(g => g.appid)) : {}
50 | i.games = i.games.map(g => {
51 | const info = infos[g.appid] || {}
52 | if (!g.image && !g.noImg) {
53 | g.image = utils.steam.getHeaderImgUrlByAppid(g.appid)
54 | }
55 | if (info.name) {
56 | g.name = info.name
57 | if (g.image) {
58 | g.image = utils.steam.getHeaderImgUrlByAppid(info.appid, 'apps', info.header)
59 | }
60 | }
61 | return g
62 | })
63 | return i
64 | }))
65 | const len = minLength === 1 ? 1.4 : minLength
66 | data.style = ``
67 | // 暂时只支持inventory/index
68 | if (Config.other.renderType == 2) {
69 | return canvas.inventory.render(params.data, minLength)
70 | }
71 | } else if (path === 'game/game') {
72 | params.data = params.data.map(i => _.sortBy(i.games, 'name')).flat()
73 | if (Config.other.renderType == 2) {
74 | return canvas.game.render(params.data)
75 | } else {
76 | return this.simpleRender(path, params)
77 | }
78 | } else if (path === 'info/index') {
79 | if (data.toGif) {
80 | data.tempPath = join(Version.pluginPath, 'temp', String(data.tempName || Date.now())).replace(/\\/g, '/')
81 | try {
82 | return await info.gif.render(data)
83 | } catch (error) {
84 | if (fs.existsSync(data.tempPath)) {
85 | fs.rmdirSync(data.tempPath, { recursive: true })
86 | }
87 | data.toGif = false
88 | logger.error(error)
89 | // throw error
90 | }
91 | }
92 | if (Config.other.renderType == 2) {
93 | return await canvas.info.render(data)
94 | }
95 | }
96 | const img = await puppeteer.screenshot(`${Version.pluginName}/${path}`, data)
97 | if (img) {
98 | return img
99 | } else {
100 | return Config.tips.makeImageFailedTips
101 | }
102 | },
103 | async simpleRender (path, params) {
104 | path = path.replace(/.html$/, '')
105 | const data = {
106 | tplFile: `${Version.pluginPath}/resources/${path}.html`,
107 | pluResPath: `${Version.pluginPath}/resources/`,
108 | saveId: path.split('/').pop(),
109 | imgType: 'jpeg',
110 | pageGotoParams: {
111 | waitUntil: 'load'
112 | },
113 | ...params
114 | }
115 | const img = await puppeteer.screenshot(`${Version.pluginName}/${path}`, data)
116 | if (img) {
117 | return img
118 | } else {
119 | return '制作图片出错辣!再试一次吧'
120 | }
121 | },
122 | tplFile (path, params, tempPath) {
123 | const name = path.split('/').pop()
124 | const tplPath = join(tempPath, name + '.html')
125 | const tmp = template.render(fs.readFileSync(params.tplFile, 'utf-8'), params)
126 | fs.writeFileSync(tplPath, tmp)
127 | return tplPath.replace(/\\/g, '/')
128 | }
129 | }
130 |
131 | export default Render
132 |
--------------------------------------------------------------------------------
/components/Version.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import { fileURLToPath } from 'url'
3 | import { join, dirname, basename } from 'path'
4 |
5 | const __filename = fileURLToPath(import.meta.url)
6 |
7 | const __dirname = dirname(__filename)
8 |
9 | const BotPackage = JSON.parse(fs.readFileSync('package.json', 'utf8'))
10 |
11 | const pluginPath = join(__dirname, '..').replace(/\\/g, '/')
12 |
13 | const pluginName = basename(pluginPath)
14 |
15 | const pluginPackage = JSON.parse(fs.readFileSync(join(pluginPath, 'package.json'), 'utf8'))
16 |
17 | const pluginVersion = pluginPackage.version
18 |
19 | /**
20 | * @type {'Karin'|'Miao-Yunzai'|'Trss-Yunzai'|'Yunzai-Next'}
21 | */
22 | const BotName = (() => {
23 | if (/^karin/i.test(pluginName)) {
24 | return 'Karin'
25 | } else if (BotPackage.dependencies.react) {
26 | fs.rmSync(pluginPath, { recursive: true })
27 | return 'Yunzai-Next'
28 | } else if (Array.isArray(global.Bot?.uin)) {
29 | return 'Trss-Yunzai'
30 | } else if (BotPackage.dependencies.sequelize) {
31 | return 'Miao-Yunzai'
32 | } else {
33 | throw new Error('还有人玩Yunzai-Bot??')
34 | }
35 | })()
36 |
37 | const BotVersion = BotPackage.version
38 |
39 | const BotPath = join(pluginPath, '../..')
40 |
41 | export default {
42 | BotName,
43 | BotPath,
44 | BotVersion,
45 | pluginName,
46 | pluginPath,
47 | pluginVersion
48 | }
49 |
--------------------------------------------------------------------------------
/components/YamlReader.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import YAML from 'yaml'
3 |
4 | /**
5 | * YamlReader类提供了对YAML文件的动态读写功能
6 | */
7 | export default class YamlReader {
8 | /**
9 | * 创建一个YamlReader实例。
10 | * @param {string} filePath - 文件路径
11 | */
12 | constructor (filePath) {
13 | this.filePath = filePath
14 | this.document = this.parseDocument()
15 | }
16 |
17 | /**
18 | * 解析YAML文件并返回Document对象,保留注释。
19 | * @returns {Document} 包含YAML数据和注释的Document对象
20 | */
21 | parseDocument () {
22 | const fileContent = fs.readFileSync(this.filePath, 'utf8')
23 | return YAML.parseDocument(fileContent)
24 | }
25 |
26 | /**
27 | * 修改指定参数的值。
28 | * @param {string} key - 参数键名
29 | * @param {any} value - 新的参数值
30 | */
31 | set (key, value) {
32 | const keys = key.split('.')
33 | const lastKey = keys.pop()
34 | let current = this.document
35 | // 遍历嵌套键名,直到找到最后一个键
36 | for (const key of keys) {
37 | if (!current.has(key)) {
38 | current.set(key, new YAML.YAMLMap())
39 | }
40 | current = current.get(key)
41 | }
42 | // 设置最后一个键的值
43 | current.set(lastKey, value)
44 | this.write()
45 | }
46 |
47 | /**
48 | * 从YAML文件中删除指定参数。
49 | * @param {string} key - 要删除的参数键名
50 | */
51 | rm (key) {
52 | const keys = key.split('.')
53 | const lastKey = keys.pop()
54 | let current = this.document
55 | // 遍历嵌套键名,直到找到最后一个键
56 | for (const key of keys) {
57 | if (current.has(key)) {
58 | current = current.get(key)
59 | } else {
60 | return // 如果键不存在,直接返回
61 | }
62 | }
63 | // 删除最后一个键
64 | current.delete(lastKey)
65 | this.write()
66 | }
67 |
68 | /**
69 | * 将更新后的Document对象写入YAML文件中。
70 | */
71 | write () {
72 | fs.writeFileSync(this.filePath, this.document.toString(), 'utf8')
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/components/index.js:
--------------------------------------------------------------------------------
1 | import App from './App.js'
2 | import Render from './Render.js'
3 | import Config from './Config.js'
4 | import Version from './Version.js'
5 | import YamlReader from './YamlReader.js'
6 |
7 | export {
8 | Version,
9 | YamlReader,
10 | Render,
11 | Config,
12 | App
13 | }
14 |
--------------------------------------------------------------------------------
/config/default_config/gif.yaml:
--------------------------------------------------------------------------------
1 | # 渲染gif图片 !谨慎开启! 会短时间内截图多次, 可能导致服务器压力过大
2 | # 由puppeteer截图多张图片通过ffmpeg生成gif
3 | # 需要有全局安装ffmpeg
4 |
5 | # 转换成gif的模式
6 | # 1: 使用puppeteer多次截图
7 | # 2: 使用puppeteer-screen-recorder录制视频
8 | # 3: 使用canvas多次生成图片 不会使用下面的配置项
9 | gifMode: 1
10 |
11 | # 帧率
12 | frameRate: 20
13 |
14 | # 截图的数量 由多少张图片合成一张gif
15 | frameCount: 30
16 | # 每张截图之间的延迟 单位毫秒
17 | frameSleep: 50
18 |
19 | # 视频长度 单位秒
20 | videoLimit: 3
21 |
22 | # steam状态 迷你背景图 头像框 头像
23 | infoGif: false
24 |
25 | # 仅用于更新注释捏
26 | gifDoc: 1
27 |
--------------------------------------------------------------------------------
/config/default_config/other.yaml:
--------------------------------------------------------------------------------
1 | # 图片渲染精度
2 | renderScale: 130
3 |
4 | # 生成图片方式
5 | # 1: puppeteer
6 | # 2: canvas (仅支持部分功能)
7 | renderType: 1
8 |
9 | # 截图时数量达到多少时隐藏剩余项目
10 | # 比如库存有几千个游戏,截图时只截取游玩最多的前99个,那么后面的游戏就隐藏了
11 | hiddenLength: 99
12 |
13 | # 截图时每行显示的最大数量
14 | itemLength: 3
15 |
16 | # 是否显示Steam头像 可能会有18+的头像
17 | steamAvatar: true
18 |
19 | # steam状态 发送消息模式
20 | # 1: 文字
21 | # 2: 仿steam风格的迷你卡片
22 | # 3: 原汁原味的steam风格的迷你卡片(需要社区反代或通用反代)
23 | infoMode: 2
24 |
25 | # 是否开启日志
26 | log: true
27 |
28 | # 游戏推荐数量
29 | rollGameCount: 3
30 |
31 | # 统计数据的显示数量 比如群统计
32 | statsCount: 10
33 |
34 | # 插件优先级 数值越小优先级越高
35 | priority: 5
36 |
37 | # 是否必须携带#号开头才会触发命令 重启后生效
38 | requireHashTag: false
39 |
40 | # 是否监听文件变化 如果不监听则每次更改文件需要重启
41 | # 改动此配置需重启后生效
42 | watchFile: true
43 |
44 | # 默认地区代码
45 | # 部分游戏有锁区中国地区会不可见
46 | # 排行榜,搜索等会使用这个地区的数据
47 | # 以及游戏金额等
48 | # 可通过下面这个接口查看部分地区代码
49 | # https://api.steampowered.com/IStoreTopSellersService/GetCountryList/v1/?language=schinese
50 | countryCode: CN
51 |
--------------------------------------------------------------------------------
/config/default_config/push.yaml:
--------------------------------------------------------------------------------
1 | # 游玩推送总开关
2 | enable: true
3 |
4 | # 开始游戏后是否推送
5 | playStart: true
6 |
7 | # 游玩结束后是否推送
8 | playEnd: true
9 |
10 | # 状态推送总开关
11 | stateChange: true
12 |
13 | # 上线是否推送
14 | stateOnline: true
15 |
16 | # 下线是否推送
17 | stateOffline: true
18 |
19 | # 设置每次检查时请求的api
20 |
21 | # 1: ISteamUserOAuth/GetUserSummaries/v2
22 | # 此接口需要access_token 429情况未知
23 | # 和2接口参数返回值一样, 但是使用access_token鉴权
24 | # 需要有人扫码登录获取access_token后才可以调用
25 |
26 | # 2: ISteamUser/GetPlayerSummaries/v2
27 | # 此接口会有429限制, 经测试 40+steamid 3min 会出现 steamid越多越容易出现
28 | # 429 是根据apiKey进行限制, 可配置多个apiKey
29 |
30 | # 3: IPlayerService/GetPlayerLinkDetails/v1
31 | # 429情况暂时未知, 但是这个接口只会返回正在玩的appid不会返回name, 所以需要再请求一个接口获得游戏名
32 |
33 | # 4: 随机
34 |
35 | # tips: 依次进行获取 如果选择1出现429则尝试使用2 2出现429则尝试使用3 3出现429则停止尝试
36 | # 1如果没有access_token则跳过
37 |
38 | # more api please wait or issue/pr...
39 | pushApi: 2
40 |
41 | # 推送模式
42 | # 1: 文字推送 一条消息就是一个群友 xxx正在玩xxx
43 | # 2: 图片推送 一张图片展示所有群友 会展示游戏的header图片
44 | # 3: 仿steam风格的播报图片 只会展示头像和游戏名 不会展示游戏的header图片和时间
45 | pushMode: 1
46 |
47 | # Steam Web API 使用条款
48 | # https://steamcommunity.com/dev/apiterms
49 | # 其中说明: 每天对 Steam Web API 的调用次数限制为十万 (100,000) 次
50 | # 可以是cron表达式 也可以是数字 单位: 分钟
51 | time: 5
52 |
53 | # 是否开启家庭库存增加推送
54 | # 请注意:
55 | # 1. 不能批量查询
56 | # 2. 需要绑定先扫码登录获取access_token
57 | familyInventotyAdd: false
58 |
59 | # 家庭库存检查间隔
60 | # 可以是cron表达式 也可以是数字 单位: 分钟
61 | familyInventotyTime: 0 0 12 * * ?
62 |
63 | # 是否开启降价推送
64 | # 可选 0: 关闭降价推送 1: 扫码绑定后可开启降价推送 2: 所有用户都可开启降价推送
65 | priceChange: 0
66 |
67 | # 降价推送方式
68 | # 1: 仅降价期间第一次查询推送 2: 每次检查都推送
69 | priceChangeType: 1
70 |
71 | # 降价检查间隔
72 | # 可以是cron表达式 也可以是数字 单位: 分钟
73 | priceChangeTime: 0 5 12 * * ?
74 |
75 | # 是否开启用户库存增加推送
76 | # 可选 0: 关闭用户库存推送 1: 扫码绑定后可开启库存推送 2: 所有用户都可开启库存推送
77 | # 请注意:
78 | # 1. 不能批量查询
79 | # 2. 因为接口没有返回入库时间, 所以会缓存用户的库存
80 | userInventoryChange: 0
81 |
82 | # 用户库存检查间隔
83 | # 可以是cron表达式 也可以是数字 单位: 分钟
84 | userInventoryTime: 0 0 12 * * ?
85 |
86 | # 是否开启用户愿望单增加推送
87 | # 可选 0: 关闭用户愿望单推送 1: 扫码绑定后可开启愿望单推送 2: 所有用户都可开启愿望单推送
88 | # 请注意:
89 | # 1. 不能批量查询
90 | userWishlistChange: 0
91 |
92 | # 用户愿望单检查间隔
93 | # 可以是cron表达式 也可以是数字 单位: 分钟
94 | userWishlistTime: 0 0 12 * * ?
95 |
96 | # 是否默认开启游玩推送和状态推送 即绑定steamId之后不需要发 #steam开启游玩推送 和 #steam开启状态推送 指令
97 | defaultPush: true
98 |
99 | # 是否随机Bot进行推送, 有多个Bot在同一群群时随机选择一个在线的Bot推送状态 (仅限TRSS)
100 | randomBot: false
101 |
102 | # 是否缓存游戏的中文名
103 | # 需要单独请求一个接口获取游戏的中文名并缓存在数据库中
104 | cacheName: true
105 |
106 | # 群统计是否过滤掉黑名单群和白名单群
107 | # 如果关闭则每次会获取忽略黑白名单的所有群 但是不会推送 仅统计
108 | statusFilterGroup: true
109 |
110 | # 推送的Bot黑名单, 不开启推送的Bot将不会被推送, 比如腾讯QQBot限制主动消息
111 | blackBotList:
112 | - 3889000138
113 |
114 | # 推送的Bot白名单, 只推送白名单中的Bot
115 | whiteBotList: []
116 |
117 | # 推送黑名单群
118 | blackGroupList:
119 | - 741577559
120 |
121 | # 推送白名单群
122 | whiteGroupList: []
123 |
--------------------------------------------------------------------------------
/config/default_config/steam.yaml:
--------------------------------------------------------------------------------
1 | # Steam Web API的apiKey 大部分功能都需要
2 | # https://partner.steamgames.com/doc/webapi_overview/auth
3 | apiKey: []
4 |
5 | # proxy代理
6 | proxy: ""
7 |
8 | # api请求超时时间 单位: 秒
9 | timeout: 5
10 |
11 | # 优先使用通用, 再使用指定, 如果填了proxy, 则都会带上proxy请求
12 | # 通用反代 比如填写: https://example.com/{{url}} 则会替换 {{url}} 为实际请求的url
13 | commonProxy: ""
14 |
15 | # 主要用的接口
16 | # https://api.steampowered.com 的反代 会替换成对应地址
17 | apiProxy: ""
18 |
19 | # steam商城相关的接口
20 | # https://store.steampowered.com 的反代 会替换成对应地址
21 | storeProxy: ""
22 |
23 | # steam社区相关的接口
24 | # https://steamcommunity.com/ 的反代 会替换成对应地址
25 | communityProxy: ""
26 |
--------------------------------------------------------------------------------
/config/default_config/tips.yaml:
--------------------------------------------------------------------------------
1 | # 若未配置则使用默认配置
2 |
3 | # 重复触发指令时的提示语
4 | repeatTips: "太快辣! 要受不了了🥵"
5 |
6 | # 正在查询的提示语
7 | loadingTips: "在查了...在查了..."
8 |
9 | # 未绑定steamId时的提示语
10 | noSteamIdTips: "还没有绑定steamId哦, 先绑定steamId吧"
11 |
12 | # 未绑定accessToken时的提示语
13 | noAccessTokenTips: "没有绑定accessToken哦, 先#steam扫码登录吧"
14 |
15 | # 重复绑定steamId时的提示语
16 | repeatBindTips: "这个steamId已经被绑定了, 要不要换一个?"
17 |
18 | # 未输入游戏appid时的提示语
19 | noAppidTips: "需要带上游戏的appid哦~"
20 |
21 | # 库存为空或未公开时的提示语
22 | inventoryEmptyTips: "获得成就: 没有给G胖一分钱"
23 |
24 | # 最近游玩为空或未公开时的提示语
25 | recentPlayEmptyTips: "最近电子阳痿了"
26 |
27 | # 愿望单为空或未公开时的提示语
28 | wishListEmptyTips: "愿望当场就实现了, 羡慕"
29 |
30 | # 在私聊中使用群聊限定的指令时的提示语
31 | privateUseTips: "请在群聊中使用此功能~"
32 |
33 | # 在群聊中使用私聊限定的指令时的提示语
34 | groupUseTips: "请在私聊中使用此功能~"
35 |
36 | # 没有开启对应推送功能时的提示语 {{type}} 对应的推送类型
37 | pushDisableTips: "主人没有开启{{type}}推送功能哦"
38 |
39 | # 用户尝试开关其他人的推送时提示语 {{type}} 对应的推送类型
40 | pushPermissionTips: "只能开启或关闭自己的{{type}}推送哦"
41 |
42 | # 开启或关闭推送的提示语
43 | # 可用的模版字符串
44 | # {{target}} 开启/关闭
45 | # {{type}} 推送类型
46 | # {{groupId}} 群号
47 | # {{userId}} 用户id
48 | # {{steamId}} 用户绑定的steamId
49 | pushChangeTips: "已{{target}}{{type}}推送{{steamId}}到{{groupId}}~"
50 |
51 | # 在推送黑名单中的提示语
52 | blackGroupTips: "本群在推送黑名单中, 请联系主人解除~"
53 |
54 | # 不在推送白名单中的提示语
55 | noWhiteGroupTips: "本群没有在推送白名单中, 请联系主人添加~"
56 |
57 | # 制作图片失败时的提示语
58 | makeImageFailedTips: "制作图片出错辣!再试一次吧"
59 |
60 | # 没有开启家庭库存推送时的提示语
61 | familyInventoryDisabledTips: "主人没有开启家庭库存推送功能哦"
62 |
--------------------------------------------------------------------------------
/guoba.support.js:
--------------------------------------------------------------------------------
1 | import lodash from 'lodash'
2 | import { Config } from '#components'
3 | import { setting } from '#models'
4 |
5 | export function supportGuoba () {
6 | return {
7 | pluginInfo: {
8 | name: 'steam-plugin',
9 | title: 'steam-plugin',
10 | author: '@小叶',
11 | authorLink: 'https://github.com/XasYer',
12 | link: 'https://github.com/XasYer/steam-plugin',
13 | isV3: true,
14 | isV2: false,
15 | description: '提供 steam 相关功能',
16 | icon: 'mdi:steam'
17 | },
18 | configInfo: {
19 | schemas: setting.getGuobasChemas(),
20 | getConfigData () {
21 | const data = {}
22 | for (const file of Config.files) {
23 | const name = file.replace('.yaml', '')
24 | data[name] = Config.getDefOrConfig(name)
25 | }
26 | return data
27 | },
28 | setConfigData (data, { Result }) {
29 | const config = Config.getCfg()
30 |
31 | for (const key in data) {
32 | const split = key.split('.')
33 | if (lodash.isEqual(config[split[1]], data[key])) continue
34 | Config.modify(split[0], split[1], data[key])
35 | }
36 | return Result.ok({}, '𝑪𝒊𝒂𝒍𝒍𝒐~(∠・ω< )⌒★')
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | export * from './apps/index.js'
2 |
--------------------------------------------------------------------------------
/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "baseUrl": "./",
4 | "paths": {
5 | "#components": [
6 | "components/index.js"
7 | ],
8 | "#models": [
9 | "models/index.js"
10 | ],
11 | "#lib": [
12 | "lib/index.js"
13 | ]
14 | }
15 | },
16 | "exclude": [
17 | "**/node_modules/*",
18 | "**/resources/*",
19 | "**/temp/*",
20 | "**/data/*"
21 | ],
22 | "include": [
23 | "./**/*"
24 | ]
25 | }
26 |
--------------------------------------------------------------------------------
/lib/Bot.js:
--------------------------------------------------------------------------------
1 | import { Version } from '#components'
2 |
3 | const Bot = await (async () => {
4 | switch (Version.BotName) {
5 | case 'Karin':
6 | return (await import('node-karin')).default
7 | default:
8 | return global.Bot
9 | }
10 | })()
11 |
12 | export default Bot
13 |
--------------------------------------------------------------------------------
/lib/common.js:
--------------------------------------------------------------------------------
1 | import { Version } from '#components'
2 |
3 | const common = await (async () => {
4 | switch (Version.BotName) {
5 | case 'Karin':
6 | return (await import('node-karin')).common
7 | default:
8 | return (await import('../../../lib/common/common.js')).default
9 | }
10 | })()
11 |
12 | export default common
13 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | import Bot from './Bot.js'
2 | import redis from './redis.js'
3 | import logger from './logger.js'
4 | import plugin from './plugin.js'
5 | import common from './common.js'
6 | import segment from './segment.js'
7 | import puppeteer from './puppeteer.js'
8 |
9 | export {
10 | Bot,
11 | redis,
12 | logger,
13 | plugin,
14 | common,
15 | segment,
16 | puppeteer
17 | }
18 |
--------------------------------------------------------------------------------
/lib/logger.js:
--------------------------------------------------------------------------------
1 | import { Config, Version } from '#components'
2 | import chalk from 'chalk'
3 |
4 | const logger = await (async () => {
5 | switch (Version.BotName) {
6 | case 'Karin':
7 | return (await import('node-karin')).logger
8 | default:
9 | return global.logger
10 | }
11 | })()
12 |
13 | const getRandomHexColor = () => {
14 | const randomColor = Math.floor(Math.random() * 16777215).toString(16)
15 | return `#${randomColor.padStart(6, '0')}`
16 | }
17 |
18 | export default {
19 | ...logger,
20 | log: (level, ...logs) => logger[level](chalk.hex(getRandomHexColor())(`[${Version.pluginName}]`, ...logs)),
21 | info: (...logs) => logger[Config.other.log ? 'info' : 'debug'](chalk.hex(getRandomHexColor())(`[${Version.pluginName}]`, ...logs)),
22 | error: (...logs) => Config.other.log && logger.error(`[${Version.pluginName}]`, ...logs),
23 | debug: (...logs) => logger.debug(`[${Version.pluginName}]`, ...logs),
24 | warn: (...logs) => logger.warn(`[${Version.pluginName}]`, ...logs)
25 | }
26 |
--------------------------------------------------------------------------------
/lib/plugin.js:
--------------------------------------------------------------------------------
1 | import { Version } from '#components'
2 |
3 | const plugin = await (async () => {
4 | switch (Version.BotName) {
5 | case 'Karin':
6 | return (await import('node-karin')).Plugin
7 | default:
8 | return global.plugin
9 | }
10 | })()
11 |
12 | export default plugin
13 |
--------------------------------------------------------------------------------
/lib/puppeteer.js:
--------------------------------------------------------------------------------
1 | import { segment } from '#lib'
2 | import { Version } from '#components'
3 |
4 | const puppeteer = await (async () => {
5 | switch (Version.BotName) {
6 | case 'Karin': {
7 | const Renderer = (await import('node-karin')).Renderer
8 | return {
9 | screenshot: async (path, options) => {
10 | options.data = { ...options }
11 | options.name = Version.pluginName + path
12 | options.file = options.tplFile
13 | options.type = options.imgType || 'jpeg'
14 | options.fileID = options.saveId
15 | options.screensEval = '#container'
16 | const img = await Renderer.render(options)
17 | return segment.image(img)
18 | }
19 | }
20 | }
21 | default:
22 | return (await import('../../../lib/puppeteer/puppeteer.js')).default
23 | }
24 | })()
25 |
26 | export default puppeteer
27 |
--------------------------------------------------------------------------------
/lib/redis.js:
--------------------------------------------------------------------------------
1 | import { Version } from '#components'
2 |
3 | const redis = await (async () => {
4 | switch (Version.BotName) {
5 | case 'Karin':
6 | return (await import('node-karin')).redis
7 | default:
8 | return global.redis
9 | }
10 | })()
11 |
12 | export default redis
13 |
--------------------------------------------------------------------------------
/lib/segment.js:
--------------------------------------------------------------------------------
1 | import { Version } from '#components'
2 |
3 | const segment = await (async () => {
4 | switch (Version.BotName) {
5 | case 'Karin':
6 | return (await import('node-karin')).segment
7 | default:
8 | return global.segment
9 | }
10 | })()
11 |
12 | export default segment
13 |
--------------------------------------------------------------------------------
/models/api/IAccountCartService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 添加游戏到购物车
5 | * @param {string} accessToken
6 | * @param {number|{packageid?: number,bundleid?: number}} packageid
7 | * @param {string} country 用户地区代码
8 | * @returns {Promise<{
9 | * line_item_ids: string[]
10 | * cart: {
11 | * line_items: {
12 | * line_item_id: string
13 | * type: number
14 | * packageid: number
15 | * is_valid: boolean
16 | * time_added: number
17 | * price_when_added: {
18 | * amount_in_cents: string
19 | * currency_code: number
20 | * formatted_amount: string
21 | * }
22 | * flags: {
23 | * is_gift: boolean
24 | * is_private: boolean
25 | * }
26 | * }[]
27 | * subtotal: {
28 | * amount_in_cents: string
29 | * currency_code: number
30 | * formatted_amount: string
31 | * }
32 | * is_valid: boolean
33 | * }
34 | * }>}
35 | */
36 | export async function AddItemsToCart (accessToken, packageid, country = 'CN') {
37 | const input = {
38 | items: [typeof packageid !== 'object' ? { packageid } : packageid],
39 | user_country: country,
40 | navdata: {
41 | domain: 'store.steampowered.com',
42 | controller: 'search',
43 | method: 'search',
44 | submethod: 'query',
45 | feature: '',
46 | depth: 0,
47 | countrycode: country,
48 | is_client: false,
49 | curator_data: {},
50 | is_likely_bot: false,
51 | is_utm: false
52 | }
53 | }
54 | return utils.request.post('IAccountCartService/AddItemsToCart/v1', {
55 | params: {
56 | access_token: accessToken,
57 | input_json: JSON.stringify(input)
58 | }
59 | }).then(res => res.response)
60 | }
61 |
62 | /**
63 | * 清空购物车
64 | * @param {string} accessToken
65 | * @returns {Promise}
66 | */
67 | export async function DeleteCart (accessToken) {
68 | return utils.request.post('IAccountCartService/DeleteCart/v1', {
69 | params: {
70 | access_token: accessToken
71 | }
72 | }).then(res => undefined)
73 | }
74 |
75 | /**
76 | * 查看购物车
77 | * @param {string} accessToken
78 | * @param {string} country 用户地区代码
79 | * @returns {Promise<{
80 | * line_items?: {
81 | * line_item_id: string
82 | * type: number
83 | * packageid?: number
84 | * bundleid?: number
85 | * is_valid: boolean
86 | * time_added: number
87 | * price_when_added: {
88 | * amount_in_cents: string
89 | * currency_code: number
90 | * formatted_amount: string
91 | * }
92 | * flags: {
93 | * is_gift: boolean
94 | * is_private: boolean
95 | * }
96 | * }[]
97 | * subtotal: {
98 | * amount_in_cents: string
99 | * currency_code: number
100 | * formatted_amount: string
101 | * }
102 | * is_valid: boolean
103 | * }>}
104 | */
105 | export async function GetCart (accessToken, country = 'CN') {
106 | return utils.request.get('IAccountCartService/GetCart/v1', {
107 | params: {
108 | access_token: accessToken,
109 | user_country: country
110 | }
111 | }).then(res => res.response.cart)
112 | }
113 |
114 | /**
115 | * 删除购物车某一项
116 | * @param {string} accessToken
117 | * @param {string} lineItemId
118 | * @param {string} country 用户地区代码
119 | * @returns {Promise<{
120 | * line_items: {
121 | * line_item_id: string
122 | * type: number
123 | * packageid?: number
124 | * bundleid?: number
125 | * is_valid: boolean
126 | * time_added: number
127 | * price_when_added: {
128 | * amount_in_cents: string
129 | * currency_code: number
130 | * formatted_amount: string
131 | * }
132 | * flags: {
133 | * is_gift: boolean
134 | * is_private: boolean
135 | * }
136 | * }[]
137 | * subtotal: {
138 | * amount_in_cents: string
139 | * currency_code: number
140 | * formatted_amount: string
141 | * }
142 | * is_valid: boolean
143 | * }>}
144 | */
145 | export async function RemoveItemFromCart (accessToken, lineItemId, country = 'CN') {
146 | return utils.request.post('IAccountCartService/RemoveItemFromCart/v1', {
147 | params: {
148 | access_token: accessToken,
149 | line_item_id: lineItemId,
150 | user_country: country
151 | }
152 | }).then(res => res.response.cart)
153 | }
154 |
--------------------------------------------------------------------------------
/models/api/IAccountPrivateAppsService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 获得私密应用列表
5 | * @param {string} accessToken
6 | * @returns {Promise}
7 | */
8 | export async function GetPrivateAppList (accessToken) {
9 | return utils.request.get('IAccountPrivateAppsService/GetPrivateAppList/v1', {
10 | params: {
11 | access_token: accessToken
12 | }
13 | }).then(res => res.response.private_apps?.appids || [])
14 | }
15 |
16 | /**
17 | * 添加或删除私密应用
18 | * @param {string} accessToken
19 | * @param {number[]} appids
20 | * @param {boolean} flag
21 | * @returns {Promise}
22 | */
23 | export async function ToggleAppPrivacy (accessToken, appids, flag = true) {
24 | !Array.isArray(appids) && (appids = [appids])
25 | const input = {
26 | private: flag,
27 | appids
28 | }
29 | await utils.request.post('IAccountPrivateAppsService/ToggleAppPrivacy/v1', {
30 | params: {
31 | access_token: accessToken,
32 | input_json: JSON.stringify(input)
33 | }
34 | })
35 | }
36 |
--------------------------------------------------------------------------------
/models/api/IAuthenticationService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 开始扫码登录
5 | * @returns {Promise<{
6 | * client_id: string,
7 | * challenge_url: string,
8 | * request_id: string,
9 | * interval: number,
10 | * allowed_confirmations: {
11 | * confirmation_type: number
12 | * }[],
13 | * version: number
14 | * }>}
15 | */
16 | export async function BeginAuthSessionViaQR () {
17 | const input = {
18 | device_details: {
19 | device_friendly_name: 'Xiaomi 15 Pro',
20 | platform_type: 3,
21 | os_type: -500,
22 | gaming_device_type: 528
23 | },
24 | website_id: 'Mobile'
25 | }
26 | return utils.request.post('IAuthenticationService/BeginAuthSessionViaQR/v1', {
27 | params: {
28 | input_json: JSON.stringify(input),
29 | key: undefined
30 | }
31 | }).then(res => res.response)
32 | }
33 |
34 | /**
35 | * 列举已登录的账号
36 | * @param {string} accessToken
37 | * @returns {Promise<{
38 | * refresh_tokens: {
39 | * token_id: string,
40 | * token_description: string,
41 | * time_updated: number,
42 | * platform_type: number,
43 | * logged_in: boolean,
44 | * os_platform: number,
45 | * auth_type: number,
46 | * gaming_device_type: number,
47 | * first_seen: {
48 | * time: number,
49 | * },
50 | * last_seen: {
51 | * time: number,
52 | * ip: {
53 | * v4: number
54 | * }
55 | * country: string,
56 | * state: string,
57 | * city: string,
58 | * },
59 | * os_type: number,
60 | * authentication_type: number,
61 | * }[]
62 | * requesting_token: string
63 | * }>}
64 | */
65 | export async function EnumerateTokens (accessToken) {
66 | return utils.request.post('IAuthenticationService/EnumerateTokens/v1', {
67 | params: {
68 | access_token: accessToken,
69 | key: undefined
70 | }
71 | }).then(res => res.response)
72 | }
73 |
74 | /**
75 | * 刷新access_token
76 | * @param {string} refreshToken
77 | * @param {string} steamId
78 | * @returns {Promise<{
79 | * access_token?: string,
80 | * }>}
81 | */
82 | export async function GenerateAccessTokenForApp (refreshToken, steamId) {
83 | return utils.request.post('IAuthenticationService/GenerateAccessTokenForApp/v1', {
84 | params: {
85 | refresh_token: refreshToken,
86 | steamid: steamId,
87 | renewal_type: 0,
88 | key: undefined
89 | }
90 | }).then(res => res.response)
91 | }
92 |
93 | /**
94 | * 查询扫码登录结果
95 | * @param {string} clientId
96 | * @param {string} requestId
97 | * @returns {Promise<{
98 | * had_remote_interaction: boolean,
99 | * refresh_token?: string,
100 | * access_token?: string,
101 | * account_name?: string,
102 | * }>}
103 | */
104 | export async function PollAuthSessionStatus (clientId, requestId) {
105 | return utils.request.post('IAuthenticationService/PollAuthSessionStatus/v1', {
106 | params: {
107 | client_id: clientId,
108 | request_id: requestId,
109 | token_to_revoke: 0,
110 | key: undefined
111 | }
112 | }).then(res => res.response)
113 | }
114 |
--------------------------------------------------------------------------------
/models/api/ICheckoutService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 添加免费游戏入库
5 | * @param {string} accessToken
6 | * @param {number} appid
7 | * @returns {Promise<{
8 | * appids_added?: number[],
9 | * packageids_added?: number[],
10 | * purchase_result_detail?: number
11 | * }>}
12 | */
13 | export async function AddFreeLicense (accessToken, appid) {
14 | const input = {
15 | item_id: {
16 | appid
17 | }
18 | }
19 | return utils.request.post('ICheckoutService/AddFreeLicense/v1', {
20 | params: {
21 | access_token: accessToken,
22 | input_json: JSON.stringify(input)
23 | }
24 | }).then(res => res.response)
25 | }
26 |
27 | /**
28 | * GetFriendOwnershipForGifting
29 | * @param {string} accessToken
30 | * @param {number[]} appids
31 | * @returns {Promise<{
32 | * item_id: {
33 | * appid: number
34 | * }[],
35 | * friend_ownership: {
36 | * accountid: number,
37 | * already_owns: boolean,
38 | * }[]
39 | * }[]>}
40 | */
41 | export async function GetFriendOwnershipForGifting (accessToken, appids) {
42 | !Array.isArray(appids) && (appids = [appids])
43 | const input = {
44 | item_ids: appids.map(appid => ({ appid }))
45 | }
46 | return utils.request.get('ICheckoutService/GetFriendOwnershipForGifting/v1', {
47 | params: {
48 | access_token: accessToken,
49 | input_json: JSON.stringify(input)
50 | }
51 | }).then(res => res.response?.ownership_info || [])
52 | }
53 |
54 | /**
55 | * 验证购物车
56 | * @param {string} accessToken
57 | * @param {string} country 用户地区代码
58 | * @returns {Promise}
59 | */
60 | export async function ValidateCart (accessToken, country = 'CN') {
61 | const input = {
62 | gidshoppingcart: '0',
63 | context: {
64 | language: 'schinese',
65 | elanguage: 0,
66 | country_code: country,
67 | steam_realm: 1
68 | },
69 | data_request: {
70 | include_assets: true,
71 | include_release: true,
72 | include_platforms: true,
73 | include_all_purchase_options: false,
74 | include_screenshots: false,
75 | include_trailers: false,
76 | include_ratings: false,
77 | include_tag_count: 0,
78 | include_reviews: false,
79 | include_basic_info: true,
80 | include_supported_languages: false,
81 | include_full_description: false,
82 | include_included_items: true,
83 | included_item_data_request: {
84 | include_assets: true,
85 | include_release: true,
86 | include_platforms: true,
87 | include_all_purchase_options: false,
88 | include_screenshots: false,
89 | include_trailers: false,
90 | include_ratings: false,
91 | include_tag_count: 0,
92 | include_reviews: false,
93 | include_basic_info: true,
94 | include_supported_languages: false,
95 | include_full_description: false,
96 | include_included_items: false,
97 | included_item_data_request: null,
98 | include_assets_without_overrides: false,
99 | apply_user_filters: false,
100 | include_links: false
101 | },
102 | include_assets_without_overrides: false,
103 | apply_user_filters: false,
104 | include_links: false
105 | },
106 | gift_info: null,
107 | gidreplayoftransid: '0',
108 | for_init_purchase: false
109 | }
110 | return utils.request.get('ICheckoutService/ValidateCart/v1', {
111 | params: {
112 | access_token: accessToken,
113 | input_json: JSON.stringify(input)
114 | }
115 | }).then(res => res.response)
116 | }
117 |
--------------------------------------------------------------------------------
/models/api/ICommunityService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 获取社区信息
5 | * @param {number} appid
6 | * @returns {Promise<{
7 | * appid: number
8 | * name: string
9 | * icon: string
10 | * community_visible_stats: boolean
11 | * propagation: string
12 | * app_type: number,
13 | * content_descriptorids?: number[]
14 | * }[]>}
15 | */
16 | export async function GetApps (appids) {
17 | !Array.isArray(appids) && (appids = [appids])
18 | const input = {
19 | language: 6,
20 | appids
21 | }
22 | return await utils.request.get('ICommunityService/GetApps/v1', {
23 | params: {
24 | input_json: JSON.stringify(input)
25 | }
26 | }).then(res => res.response.apps || [])
27 | }
28 |
29 | /**
30 | * 获取历史头像
31 | * @param {string} accessToken
32 | * @param {string} steamid
33 | * @returns {Promise}
34 | */
35 | export async function GetAvatarHistory (accessToken, steamid) {
36 | return await utils.request.post('ICommunityService/GetAvatarHistory/v1', {
37 | params: {
38 | access_token: accessToken,
39 | steamid
40 | }
41 | })
42 | }
43 |
--------------------------------------------------------------------------------
/models/api/IFriendsListService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * GetFavorites
5 | * @param {string} accessToken
6 | * @returns {Promise}
7 | */
8 | export async function GetFavorites (accessToken) {
9 | return utils.request.get('IFriendsListService/GetFavorites/v1', {
10 | params: {
11 | access_token: accessToken
12 | }
13 | })
14 | }
15 |
16 | /**
17 | * 获得好友列表
18 | * @param {string} accessToken
19 | * @returns {Promise<{
20 | * bincremental: boolean,
21 | * friends: {
22 | * ulfriendid: string,
23 | * efriendrelationship: number,
24 | * }
25 | * }>}
26 | */
27 | export async function GetFriendsList (accessToken) {
28 | return utils.request.get('IFriendsListService/GetFriendsList/v1', {
29 | params: {
30 | access_token: accessToken
31 | }
32 | })
33 | }
34 |
--------------------------------------------------------------------------------
/models/api/IMobileAppService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * GetMobileSummary
5 | * @param {string} accessToken
6 | * @param {number} authenticatorGid
7 | * @returns {Promise}
8 | */
9 | export async function GetMobileSummary (accessToken, authenticatorGid) {
10 | return utils.request.post('IMobileAppService/GetMobileSummary/v1', {
11 | params: {
12 | access_token: accessToken,
13 | authenticator_gid: authenticatorGid
14 | }
15 | })
16 | }
17 |
--------------------------------------------------------------------------------
/models/api/ISteamChartsService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 | import { Config } from '#components'
3 |
4 | /**
5 | * GetBestOfYearPages
6 | * @returns {Promise<{
7 | * name: string,
8 | * url_path: string,
9 | * banner_url: string[]
10 | * }[]>}
11 | */
12 | export async function GetBestOfYearPages () {
13 | return utils.request.get('ISteamChartsService/GetBestOfYearPages/v1')
14 | .then(res => res.response.pages)
15 | }
16 |
17 | /**
18 | * 获取steam当前热玩排行榜
19 | * @returns {Promise<{
20 | * last_update: number,
21 | * ranks: {
22 | * rank: number,
23 | * appid: number,
24 | * concurrent_in_game: number,
25 | * peak_in_game: number,
26 | * item: {
27 | * name: string,
28 | * is_free: boolean,
29 | * best_purchase_option?: {
30 | * formatted_final_price: string,
31 | * formatted_original_price: string,
32 | * discount_pct?: number,
33 | * }
34 | * }
35 | * }[]
36 | * }>}
37 | */
38 | export async function GetGamesByConcurrentPlayers () {
39 | const input = {
40 | country_code: Config.other.countryCode,
41 | context: {
42 | language: 'schinese',
43 | country_code: Config.other.countryCode
44 | },
45 | data_request: {
46 | include_basic_info: true
47 | }
48 | }
49 | return utils.request.get('ISteamChartsService/GetGamesByConcurrentPlayers/v1', {
50 | params: {
51 | input_json: JSON.stringify(input)
52 | }
53 | }).then(res => res.response)
54 | }
55 |
56 | /**
57 | * 获取steam每日热玩排行榜
58 | * @returns {Promise<{
59 | * rollup_date: number,
60 | * ranks: {
61 | * rank: number,
62 | * appid: number,
63 | * last_week_rank: number,
64 | * peak_in_game: number,
65 | * item: {
66 | * name: string,
67 | * is_free: boolean,
68 | * best_purchase_option?: {
69 | * formatted_final_price: string,
70 | * formatted_original_price: string,
71 | * discount_pct?: number,
72 | * }
73 | * }
74 | * }[]
75 | * }>}
76 | */
77 | export async function GetMostPlayedGames () {
78 | const input = {
79 | context: {
80 | language: 'schinese',
81 | country_code: Config.other.countryCode
82 | },
83 | data_request: {
84 | include_basic_info: true
85 | }
86 | }
87 | return utils.request.get('ISteamChartsService/GetMostPlayedGames/v1', {
88 | params: {
89 | input_json: JSON.stringify(input)
90 | }
91 | }).then(res => res.response)
92 | }
93 |
94 | /**
95 | * 获取steam最热新品 (按月统计)
96 | * @returns {Promise<{
97 | * name: string,
98 | * start_of_month: number,
99 | * url_path: string,
100 | * item_ids: {
101 | * appid: number,
102 | * }[]
103 | * }[]>}
104 | */
105 | export async function GetTopReleasesPages () {
106 | return utils.request.get('ISteamChartsService/GetTopReleasesPages/v1')
107 | .then(res => res.response.pages)
108 | }
109 |
--------------------------------------------------------------------------------
/models/api/ISteamUser.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { utils } from '#models'
3 |
4 | /**
5 | * 获取用户相关信息
6 | * @param {string|string[]} steamIds
7 | * @returns {Promise<{
8 | * steamid: string,
9 | * communityvisibilitystate: number,
10 | * profilestate: number,
11 | * personaname: string,
12 | * profileurl: string,
13 | * avatar: string,
14 | * avatarmedium: string,
15 | * avatarfull: string,
16 | * lastlogoff?: number,
17 | * personastate: number,
18 | * timecreated: string,
19 | * gameid?: string,
20 | * gameextrainfo?: string,
21 | * }[]>}
22 | */
23 | export async function GetPlayerSummaries (steamIds) {
24 | !Array.isArray(steamIds) && (steamIds = [steamIds])
25 | const result = []
26 | // 一次只能获取100个用户信息
27 | for (const items of _.chunk(steamIds, 100)) {
28 | const res = await utils.request.get('ISteamUser/GetPlayerSummaries/v2', {
29 | params: {
30 | steamIds: items.join(',')
31 | }
32 | })
33 | if (res.response?.players?.length) {
34 | result.push(...res.response.players)
35 | }
36 | }
37 | return result
38 | }
39 |
40 | /**
41 | * 获取好友列表
42 | * @param {string} steamid
43 | * @returns {Promise<{
44 | * steamid: string,
45 | * relationship: string,
46 | * friend_since: number
47 | * }[]>}
48 | */
49 | export async function GetFriendList (steamid) {
50 | return await utils.request.get('ISteamUser/GetFriendList/v1', {
51 | params: {
52 | steamid
53 | }
54 | }).then(res => res.friendslist.friends)
55 | }
56 |
57 | /**
58 | * 获取群组列表
59 | * @param {string} steamid
60 | * @returns {Promise<{
61 | * gid: string,
62 | * }[]>}
63 | */
64 | export async function GetUserGroupList (steamid) {
65 | return await utils.request.get('ISteamUser/GetUserGroupList/v1', {
66 | params: {
67 | steamid
68 | }
69 | }).then(res => res.response?.groups || [])
70 | }
71 |
72 | /**
73 | * 获取用户封禁信息
74 | * @param {string|string[]} steamIds
75 | * @returns {Promise<{
76 | * SteamId: string,
77 | * CommunityBanned: boolean,
78 | * VACBanned: boolean,
79 | * NumberOfVACBans: number,
80 | * DaysSinceLastBan: number,
81 | * NumberOfGameBans: number,
82 | * EconomyBan: string,
83 | * }[]>}
84 | */
85 | export async function GetPlayerBans (steamIds) {
86 | !Array.isArray(steamIds) && (steamIds = [steamIds])
87 | const result = []
88 | for (const items of _.chunk(steamIds, 100)) {
89 | const res = await utils.request.get('ISteamUser/GetPlayerBans/v1', {
90 | params: {
91 | steamIds: items.join(',')
92 | }
93 | })
94 | result.push(...res.players)
95 | }
96 | return result
97 | }
98 |
--------------------------------------------------------------------------------
/models/api/ISteamUserOAuth.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 | import { utils } from '#models'
3 |
4 | /**
5 | * 获取用户相关信息
6 | * @param {string} accessToken
7 | * @param {string|string[]} steamIds
8 | * @returns {Promise<{
9 | * steamid: string,
10 | * communityvisibilitystate: number,
11 | * profilestate: number,
12 | * personaname: string,
13 | * profileurl: string,
14 | * avatar: string,
15 | * avatarmedium: string,
16 | * avatarfull: string,
17 | * lastlogoff?: number,
18 | * personastate: number,
19 | * timecreated: string,
20 | * gameid?: string,
21 | * gameextrainfo?: string,
22 | * }[]>}
23 | */
24 | export async function GetUserSummaries (accessToken, steamIds) {
25 | !Array.isArray(steamIds) && (steamIds = [steamIds])
26 | const result = []
27 | // 一次只能获取100个用户信息
28 | for (const items of _.chunk(steamIds, 100)) {
29 | const res = await utils.request.get('ISteamUserOAuth/GetUserSummaries/v2', {
30 | params: {
31 | access_token: accessToken,
32 | steamIds: items.join(',')
33 | }
34 | })
35 | if (res.players?.length) {
36 | result.push(...res.players)
37 | }
38 | }
39 | return result
40 | }
41 |
--------------------------------------------------------------------------------
/models/api/ISteamUserStats.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 获取指定游戏的全局成就百分比
5 | * @param {string} appid
6 | * @returns {Promise<{
7 | * name: string,
8 | * percent: number
9 | * }[]>}
10 | */
11 | export async function GetGlobalAchievementPercentagesForApp (appid) {
12 | return utils.request.get('ISteamUserStats/GetGlobalAchievementPercentagesForApp/v2', {
13 | params: {
14 | gameid: appid
15 | }
16 | }).then(res => res.achievementpercentages?.achievements || [])
17 | }
18 |
19 | /**
20 | * 获取指定游戏当前在 Steam 上的活跃玩家的总数量
21 | * @param {string} appid
22 | * @returns {Promise}
23 | */
24 | export async function GetNumberOfCurrentPlayers (appid) {
25 | return utils.request.get('ISteamUserStats/GetNumberOfCurrentPlayers/v1', {
26 | params: {
27 | appid
28 | }
29 | }).then(res => res.response.player_count ?? false)
30 | }
31 |
32 | /**
33 | * 获取游戏成就总览
34 | * @param {string} appid
35 | * @returns {Promise<{
36 | * gameName: string,
37 | * gameVersion: string,
38 | * availableGameStats?: {
39 | * achievements?: {
40 | * name: string,
41 | * defaultvalue: number,
42 | * displayName: string,
43 | * hidden: number,
44 | * description: string,
45 | * icon: string,
46 | * icongray: string
47 | * }[],
48 | * stats?: {
49 | * name: string,
50 | * defaultvalue: number,
51 | * displayName: string,
52 | * }[]
53 | * }
54 | * }>}
55 | */
56 | export async function GetSchemaForGame (appid) {
57 | return utils.request.get('ISteamUserStats/GetSchemaForGame/v2', {
58 | params: {
59 | appid
60 | }
61 | }).then(res => res.game || {})
62 | }
63 |
64 | /**
65 | * 获取用户游戏成就数据
66 | * @param {string} appid
67 | * @param {string} steamid
68 | * @returns {Promise<{
69 | * steamID: string,
70 | * gameName: string,
71 | * achievements?: {
72 | * name: string,
73 | * achieved: number,
74 | * }[],
75 | * stats?: {
76 | * name: string,
77 | * value: number,
78 | * }[]
79 | * }>}
80 | */
81 | export async function GetUserStatsForGame (appid, steamid) {
82 | return utils.request.get('ISteamUserStats/GetUserStatsForGame/v2', {
83 | params: {
84 | appid,
85 | steamid
86 | }
87 | }).then(res => res.playerstats || {})
88 | }
89 |
--------------------------------------------------------------------------------
/models/api/ISteamWebAPIUtil.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * GetServerInfo
5 | * @returns {Promise<{
6 | * servertime: number,
7 | * servertimestring: string,
8 | * }>}
9 | */
10 | export async function GetServerInfo () {
11 | return utils.request.get('ISteamWebAPIUtil/GetServerInfo/v1')
12 | }
13 |
--------------------------------------------------------------------------------
/models/api/IStoreQueryService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 | import { Config } from '#components'
3 |
4 | /**
5 | * 查询热销排行
6 | * @returns {Promise<{
7 | * appid: number,
8 | * name: string,
9 | * is_free: boolean,
10 | * best_purchase_option?: {
11 | * formatted_final_price: string,
12 | * formatted_original_price: string,
13 | * discount_pct?: number,
14 | * }
15 | * }[]>}
16 | */
17 | export async function Query () {
18 | const input = {
19 | query_name: 'SteamCharts Live Top Sellers',
20 | context: {
21 | language: 'schinese',
22 | country_code: Config.other.countryCode
23 | },
24 | query: {
25 | start: 0,
26 | count: 100,
27 | sort: 10,
28 | filters: {
29 | type_filters: {
30 | include_apps: true
31 | }
32 | }
33 | },
34 | data_request: {
35 | include_basic_info: true
36 | },
37 | overrideCountryCode: Config.other.countryCode
38 | }
39 | return utils.request.get('IStoreQueryService/Query/v1', {
40 | params: {
41 | input_json: JSON.stringify(input)
42 | }
43 | }).then(res => res.response.store_items || [])
44 | }
45 |
--------------------------------------------------------------------------------
/models/api/IStoreService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 获得探索队列
5 | * @param {string} accessToken
6 | * @param {string} country 用户地区代码
7 | * @returns {Promise<{
8 | * appids: number[],
9 | * country_code: string,
10 | * settings: any,
11 | * skipped: number,
12 | * exhausted: boolean
13 | * experimental_cohort: number,
14 | * }>}
15 | */
16 | export async function GetDiscoveryQueue (accessToken, country = 'CN') {
17 | const input = {
18 | queue_type: 0,
19 | country_code: country,
20 | rebuild_queue: true,
21 | rebuild_queue_if_stale: true
22 | }
23 | return utils.request.get('IStoreService/GetDiscoveryQueue/v1', {
24 | params: {
25 | access_token: accessToken,
26 | input_json: JSON.stringify(input)
27 | }
28 | }).then(res => res.response)
29 | }
30 |
31 | /**
32 | * 获得探索队列的设置
33 | * @param {string} accessToken
34 | * @returns {Promise}
35 | */
36 | export async function GetDiscoveryQueueSettings (accessToken) {
37 | const input = {
38 | queue_type: 0
39 | }
40 | return utils.request.get('IStoreService/GetDiscoveryQueueSettings/v1', {
41 | params: {
42 | access_token: accessToken,
43 | input_json: JSON.stringify(input)
44 | }
45 | }).then(res => res.response)
46 | }
47 |
48 | /**
49 | * 获得探索队列中已跳过的游戏
50 | * @param {string} accessToken
51 | * @param {number} steamid
52 | * @returns {Promise}
53 | */
54 | export async function GetDiscoveryQueueSkippedApps (accessToken, steamid) {
55 | const input = {
56 | queue_type: 0,
57 | steamid
58 | }
59 | return utils.request.get('IStoreService/GetDiscoveryQueueSkippedApps/v1', {
60 | params: {
61 | access_token: accessToken,
62 | input_json: JSON.stringify(input)
63 | }
64 | }).then(res => res.response.appids || [])
65 | }
66 |
67 | /**
68 | * 获取不同语言的tag名称
69 | * @param {number[]} tagids
70 | * @returns {Promise<{
71 | * tagid: number,
72 | * english_name: string,
73 | * name: string,
74 | * normalized_name: string,
75 | * }[]>}
76 | */
77 | export async function GetLocalizedNameForTags (tagids) {
78 | !Array.isArray(tagids) && (tagids = [tagids])
79 | const input = {
80 | tagids,
81 | language: 'schinese'
82 | }
83 | return utils.request.get('IStoreService/GetLocalizedNameForTags/v1', {
84 | params: {
85 | input_json: JSON.stringify(input)
86 | }
87 | }).then(res => res.response.tags || [])
88 | }
89 |
90 | /**
91 | * 获取所有带有本地化名称的白名单标签。
92 | * @returns {Promise<{
93 | * tagid: number,
94 | * name: string,
95 | * }[]>}
96 | */
97 | export async function GetMostPopularTags () {
98 | return utils.request.get('IStoreService/GetMostPopularTags/v1').then(res => res.response.tags || [])
99 | }
100 |
101 | /**
102 | * 获得tag列表
103 | * @returns {Promise<{
104 | * version_hash: string,
105 | * tags: {
106 | * tagid: number,
107 | * name: string,
108 | * }[]
109 | * }>}
110 | */
111 | export async function GetTagList () {
112 | return utils.request.get('IStoreService/GetTagList/v1')
113 | .then(res => res.response)
114 | }
115 |
116 | /**
117 | * 跳过探索队列中的某一项
118 | * @param {string} accessToken
119 | * @param {number} appid
120 | * @returns {Promise}
121 | */
122 | export async function SkipDiscoveryQueueItem (accessToken, appid) {
123 | const input = {
124 | queue_type: 0,
125 | appid
126 | }
127 | return utils.request.post('IStoreService/SkipDiscoveryQueueItem/v1', {
128 | params: {
129 | access_token: accessToken,
130 | input_json: JSON.stringify(input)
131 | }
132 | })
133 | }
134 |
--------------------------------------------------------------------------------
/models/api/IStoreTopSellersService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 | import { Config } from '#components'
3 |
4 | /**
5 | * GetCountryList
6 | * @returns {Promise<{
7 | * country_code: string,
8 | * name: string,
9 | * }[]>}
10 | */
11 | export async function GetCountryList () {
12 | return utils.request.get('IStoreTopSellersService/GetCountryList/v1')
13 | .then(res => res.response.countries)
14 | }
15 |
16 | /**
17 | * 获取每周热销榜
18 | * @param {number?} startDate 开始日期
19 | * @returns {Promise<{
20 | * start_date: number,
21 | * ranks: {
22 | * rank: number,
23 | * appid: number,
24 | * last_week_rank: number,
25 | * consecutive_weeks: number,
26 | * item: {
27 | * name: string,
28 | * appid: number,
29 | * is_free: boolean,
30 | * best_purchase_option?: {
31 | * formatted_final_price: string,
32 | * formatted_original_price: string,
33 | * discount_pct?: number,
34 | * }
35 | * },
36 | * first_top100?: boolean,
37 | * consecutive_weeks: number,
38 | * last_week_rank?: number,
39 | * }[]
40 | * }>}
41 | */
42 | export async function GetWeeklyTopSellers (startDate) {
43 | const input = {
44 | country_code: Config.other.countryCode,
45 | page_count: 100,
46 | context: {
47 | language: 'schinese',
48 | country_code: Config.other.countryCode
49 | },
50 | data_request: {
51 | include_basic_info: true
52 | }
53 | }
54 | return utils.request.get('IStoreTopSellersService/GetWeeklyTopSellers/v1', {
55 | params: {
56 | input_json: JSON.stringify(input),
57 | start_date: startDate
58 | }
59 | }).then(res => res.response)
60 | }
61 |
--------------------------------------------------------------------------------
/models/api/IUserAccountService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 获取用户的地区代码
5 | * @param {string} accessToken
6 | * @param {string} steamId
7 | * @returns {Promise}
8 | */
9 | export async function GetUserCountry (accessToken, steamId) {
10 | return utils.request.post('IUserAccountService/GetUserCountry/v1', {
11 | params: {
12 | access_token: accessToken,
13 | steamid: steamId
14 | }
15 | }).then(res => res.response.country)
16 | }
17 |
--------------------------------------------------------------------------------
/models/api/IUserReviewsService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * GetFriendsRecommendedApp
5 | * @returns {Promise}
6 | */
7 | export async function GetFriendsRecommendedApp () {
8 | return utils.request.get('IUserReviewsService/GetFriendsRecommendedApp/v1')
9 | }
10 |
--------------------------------------------------------------------------------
/models/api/IWishlistService.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 |
3 | /**
4 | * 添加到愿望单
5 | * @param {string} accessToken
6 | * @param {string} appid
7 | * @returns {Promise}
8 | */
9 | export async function AddToWishlist (accessToken, appid) {
10 | return utils.request.post('IWishlistService/AddToWishlist/v1', {
11 | params: {
12 | access_token: accessToken,
13 | appid
14 | }
15 | }).then(res => res.response)
16 | }
17 |
18 | /**
19 | * 获取用户的愿望单 (居然不给name)
20 | * @param {string} steamid
21 | * @returns {Promise<{
22 | * appid: number,
23 | * priority: number,
24 | * date_added: number
25 | * }[]>}
26 | */
27 | export async function GetWishlist (steamid) {
28 | return utils.request.get('IWishlistService/GetWishlist/v1', {
29 | params: {
30 | steamid
31 | }
32 | }).then(res => res.response.items || [])
33 | }
34 |
35 | /**
36 | * 获取用户的愿望单数量
37 | * @param {string} steamid
38 | * @returns {Promise}
39 | */
40 | export async function GetWishlistItemCount (steamid) {
41 | return utils.request.get('IWishlistService/GetWishlistItemCount/v1', {
42 | params: {
43 | steamid
44 | }
45 | }).then(res => res.response.count)
46 | }
47 |
48 | /**
49 | * 移除愿望单
50 | * @param {string} accessToken
51 | * @param {string} appid
52 | * @returns {Promise} 愿望单数量
53 | */
54 | export async function RemoveFromWishlist (accessToken, appid) {
55 | return utils.request.post('IWishlistService/RemoveFromWishlist/v1', {
56 | params: {
57 | access_token: accessToken,
58 | appid
59 | }
60 | }).then(res => res.response.wishlist_count)
61 | }
62 |
--------------------------------------------------------------------------------
/models/api/community.js:
--------------------------------------------------------------------------------
1 | import { utils } from '#models'
2 | import { Config } from '#components'
3 |
4 | export function getBaseURL () {
5 | const url = 'https://steamcommunity.com/'
6 | if (Config.steam.commonProxy) {
7 | return Config.steam.commonProxy.replace('{{url}}', url)
8 | } else if (Config.steam.communityProxy) {
9 | return Config.steam.communityProxy.replace(/\/$/, '')
10 | } else {
11 | return url
12 | }
13 | }
14 |
15 | /**
16 | * 获取迷你信息
17 | * @param {number} friendCode
18 | * @param {boolean} json
19 | * @returns {Promise<{
20 | * level: number,
21 | * level_class: string,
22 | * avatar_url: string,
23 | * persona_name: string,
24 | * favorite_badge?: {
25 | * name: string,
26 | * xp: string
27 | * level: number
28 | * description: string
29 | * icon: string
30 | * },
31 | * in_game?: {
32 | * name: string,
33 | * is_non_steam: boolean,
34 | * logo: string,
35 | * rich_presence: string
36 | * },
37 | * profile_background?: {
38 | * 'video/webm': string,
39 | * 'video/mp4': string,
40 | * },
41 | * avatar_frame?: string,
42 | * }|string>}
43 | */
44 | export async function miniprofile (friendCode, json = false) {
45 | return utils.request.get(`miniprofile/${friendCode}${json ? '/json' : ''}`, {
46 | baseURL: getBaseURL(),
47 | params: {
48 | t: Date.now()
49 | }
50 | })
51 | }
52 |
--------------------------------------------------------------------------------
/models/api/index.js:
--------------------------------------------------------------------------------
1 | export * as store from './store.js'
2 | export * as community from './community.js'
3 | export * as ISteamUser from './ISteamUser.js'
4 | export * as IStoreService from './IStoreService.js'
5 | export * as IPlayerService from './IPlayerService.js'
6 | export * as ISteamUserOAuth from './ISteamUserOAuth.js'
7 | export * as ISteamUserStats from './ISteamUserStats.js'
8 | export * as IWishlistService from './IWishlistService.js'
9 | export * as ICheckoutService from './ICheckoutService.js'
10 | export * as ISteamWebAPIUtil from './ISteamWebAPIUtil.js'
11 | export * as ICommunityService from './ICommunityService.js'
12 | export * as IMobileAppService from './IMobileAppService.js'
13 | export * as IClientCommService from './IClientCommService.js'
14 | export * as IStoreQueryService from './IStoreQueryService.js'
15 | export * as IStoreBrowseService from './IStoreBrowseService.js'
16 | export * as ISaleFeatureService from './ISaleFeatureService.js'
17 | export * as ISteamChartsService from './ISteamChartsService.js'
18 | export * as IFriendsListService from './IFriendsListService.js'
19 | export * as IUserReviewsService from './IUserReviewsService.js'
20 | export * as IAccountCartService from './IAccountCartService.js'
21 | export * as IUserAccountService from './IUserAccountService.js'
22 | export * as IFamilyGroupsService from './IFamilyGroupsService.js'
23 | export * as IAuthenticationService from './IAuthenticationService.js'
24 | export * as IStoreTopSellersService from './IStoreTopSellersService.js'
25 | export * as IAccountPrivateAppsService from './IAccountPrivateAppsService.js'
26 |
--------------------------------------------------------------------------------
/models/bind/index.js:
--------------------------------------------------------------------------------
1 | import { db, utils, api } from '#models'
2 | import { Config, Render } from '#components'
3 |
4 | /**
5 | * 获得已绑定的steamId的图片
6 | * @param {string} bid
7 | * @param {string} uid
8 | * @param {string} gid
9 | * @param {import('models/db').UserColumns[]?} userBindSteamIdList
10 | * @returns {Promise |