Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 0 additions & 25 deletions CHANGELOG.md

This file was deleted.

27 changes: 18 additions & 9 deletions README.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,9 +35,8 @@

## 丨命令列表

### steam帮助
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQV9pdEVIS2NsaFRKVUtBT1R2Z2JGT0FSX2xJbnB4MEVtRkgtamNLdEc3ZUc0P2U9ZEVHZWxC.jpg" width="480" alt="Steam帮助菜单"></a>

### steam帮助(图片更新可能不及时)
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2FlcEk0R3k1TFRMR2tzLWZmUUJHNkFXRFJCRXZobV9JbHh2MWZ5Rlplb2RrP2U9ejJzWGx5.jpg" width="480" alt="Steam帮助菜单">
### 命令说明

#### 绑定账号
Expand DownExpand Up@@ -68,6 +67,7 @@
| `steam游戏成就123456` | 查看appid123456的成就详情 |
| `steam开启成就推送` | 开启自己的成就推送功能 |
| `steam关闭成就推送` | 关闭自己的成就推送功能 |
| `steam玩什么` | 从自己的游戏库随机挑3个游戏 |

#### 商店相关
| 命令 | 说明 |
Expand All@@ -76,11 +76,16 @@
| `steam取消订阅降价123456` | 取消订阅商店游戏降价信息 |
| `steam订阅降价查看` | 查看订阅的商店游戏降价信息 |

#### 社交相关
| 命令 | 说明 |
|------|:------:|
| `@xx steam加好友123456` | 获取被 @ 用户的好友码 |
| `steam群排行` | 查看群游玩时长排行榜 |

#### 其他服务
| 命令 | 说明 |
|------|:------:|
| `steam帮助` | 呼出本插件帮助菜单 |
Comment on lines +79 to 88

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# 确认“加好友”命令实际是否需要数字参数
rg -n '加好友' -g '*.py' -A 15

Repository: sssysy/SteamUID

Length of output: 153


steam加好友 示例去掉多余参数。
这里应写成 @xx steam加好友123456 与“获取被 @ 用户的好友码”的语义不符,也会误导用户。

🧰 Tools
🪛 markdownlint-cli2 (0.22.1)

[warning] 80-80: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)


[warning] 86-86: Tables should be surrounded by blank lines

(MD058, blanks-around-tables)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@README.md` around lines 79 - 88, Update the `steam加好友` example in the README
command table to remove the `123456` argument, using the `@xx steam加好友` form
while preserving the existing description.

| `@xx steam加好友` | 获取被 @ 用户的好友码 |
| `清除全部缓存` | 清除全部缓存 |

## 效果图
Expand All@@ -89,25 +94,29 @@
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRHR5UVVwYVVENFJhX2UxakdHblNIeUFRVVoxcDVZMGpxRkpBWmYtRGhQRENjP2U9RTdMMlJk.png" width="160"/>

### steam成就推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQWVnNm85WjBHc1NyZWhvWHJJaVFLdUFiZEJqb3ZlQ1JHMXlNdUNLenk3TlY0P2U9cXlpTWdS.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRRDVmeDFrYTF2RlI2aXo2RklpNFlSQkFkUHFGQ1FwaloteW8wZllfbkpVQXhFP2U9Ynd5bXBh.png" width="160"/>

### steam开始游戏 / 结束游戏推送
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQ2lrOERKZ1VIZFE1bkl1S2dOTzRDeUFjQlB1SmRyYlM0SlhjcGdLSTU0bGFvP2U9bm1ja0gy.png" width="160"/>
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQUEtZEVQOXV3blRwMjJaN2R0MVFXNEFaZUMtVTVsOWdQZjFRSmZGSUd5UlIwP2U9elRLQmdu.png" width="160"/>

### steam游戏墙
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQnhKUDRNUFhhX1NKdUROakNNYzJ1T0FTeVRKNGlqam5wMG95YzZRUFF2QXo4P2U9aHNRV2dw.jpg" width="160"/>

## steam玩什么
<img src="https://dlink.host/1drv/aHR0cHM6Ly8xZHJ2Lm1zL2kvYy8xYmIyNTkxODI4ZDcyZTIzL0lRQjcxemRIckRxTlI0ZGYwVmhpQm5YUkFkR0lzZjAzVktic2x2ZTQ3VUNSYnNRP2U9WXhMVUhP.jpg" width="160">



## 计划功能

- [x] 支持设置所有状态推送推送默认值
- [ ] 玩家上下线状态推送
- [ ] 带游玩时长的游戏库存图片
- [x] 游戏降价 / 打折信息订阅推送
- [] steam玩什么 (随机抽游戏)
- [x] steam玩什么 (随机抽游戏)
- [x] 游戏名 / 成就名本地化名字获取
- [ ] 总结推送功能 (定时总结周期内游戏情况而不每次状态变化都推送)
- [] 群游玩时长排行榜
- [x] 群游玩时长排行榜
- [x] 隐藏 steamid / 好友码


Expand All@@ -117,7 +126,7 @@

- 本项目仅供学习使用,请勿用于商业用途
- [GPL-3.0 License](LICENSE)

- 部分功能没有写绘图,以后再说

## 致谢

Expand Down
37 changes: 29 additions & 8 deletions SteamUID/SteamHelp/help.json
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,6 +115,14 @@
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "玩什么",
"desc": "从自己的游戏库随机挑3个游戏",
"eg": "玩什么",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
Expand DownExpand Up@@ -147,21 +155,34 @@
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"社交相关": {
"desc": "加好友和排行榜功能",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"name": "加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"need_ck": false,
"need_sk": false,
"need_admin": false
},
{
"name": "@xx 加好友",
"desc": "获取被 @ 用户的好友码",
"eg": "加好友",
"name": "群排行",
"desc": "查看群游玩时长排行榜",
"eg": "群排行",
"need_ck": false,
"need_sk": false,
"need_admin": false
}
]
},
"其他服务": {
"desc": "一些杂项服务",
"data": [
{
"name": "帮助",
"desc": "呼出本插件帮助菜单",
"eg": "帮助",
"need_ck": false,
"need_sk": false,
"need_admin": false
Expand Down
18 changes: 17 additions & 1 deletion SteamUID/SteamLibarary/__init__.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -6,7 +6,7 @@

from ..utils.exceptions import SteamError, SteamValidationError
from ..utils.target import resolve_target_steamid64
from .library_service import build_library_wall
from .library_service import build_library_wall, build_random_pick

library_SV = SV("steam库存相关")

Expand All@@ -26,3 +26,19 @@ async def get_steamlibrary_image(bot: Bot, ev: Event):
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")

@library_SV.on_command(("玩什么",))
async def get_my_steamlibrary_image(bot: Bot, ev: Event):
try:
steamid64 = await resolve_target_steamid64(ev, ev.text.strip())
if not steamid64:
raise SteamValidationError("请先绑定 steam 账号")

await bot.send("正在从游戏库中随机挑选......")
img_bytes = await build_random_pick(steamid64)
await bot.send(MessageSegment.image(img_bytes))
except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[steam库存] 未知错误: {e!r}")
await bot.send(f"发生未知错误: {e}")
43 changes: 43 additions & 0 deletions SteamUID/SteamLibarary/library_service.py
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
import random
from io import BytesIO

from gsuid_core.data_store import get_res_path
Expand All@@ -7,6 +8,7 @@
from ..SteamConfig.interface import SteamAPI
from ..utils.api import get_steamlibrary_by_steamid64
from ..utils.exceptions import SteamConfigError, SteamValidationError
from ..utils.PIL.draw import draw_what_to_play
from ..utils.PIL.steam_wall import build_wall
from ..utils.utils import batch_download_images

Expand DownExpand Up@@ -50,3 +52,44 @@ async def build_library_wall(steamid64: str) -> bytes:
buf = BytesIO()
wall.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()


async def build_random_pick(steamid64: str) -> bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key = SteamConfig.get_config("SteamWebAPIKey").data
if not api_key:
raise SteamConfigError("请先配置 steam web api key")

library = await get_steamlibrary_by_steamid64(api_key, steamid64)
games = library.get("games")
if games is None:
raise SteamValidationError("获取 steam 游戏库列表失败")
Comment on lines +57 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): build_random_pick 包含语法问题和未定义变量,并会在运行时失败

build_random_pick 中,game_data 被追加但从未定义,结尾的 }) }) 会导致语法错误,同时 pic_quality 在该函数中使用时没有被加载(与 build_library_wall 不同)。请在使用前在本地定义 game_data,移除重复的闭合语法,并在这里加载 pic_quality(或通过共享的辅助函数),以避免在生成图片之前就抛出异常。

Original comment in English

issue (bug_risk): build_random_pick contains syntax issues, undefined variables, and will fail at runtime

In build_random_pick, game_data is appended to but never defined, the trailing }) }) will cause a syntax error, and pic_quality is used without being loaded in this function (unlike build_library_wall). Please define game_data locally before use, remove the duplicate closing syntax, and load pic_quality here (or via a shared helper) so this path doesn’t throw before generating the image.

if not games:
raise SteamValidationError("该 steam 账号暂无游戏库存")
pick_count = min(3, len(games))
picks = random.sample(games, pick_count)

for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })

img = await draw_what_to_play(game_data)
img = img.convert("RGB")

buf = BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
return buf.getvalue()
Comment on lines +55 to +95

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

阻断性问题:语法错误 + game_data 未定义,将导致整个模块无法导入。

第 88 行存在多余的右括号 }) })(Ruff 已报 invalid-syntax),会直接导致该文件解析失败;即便修正括号,函数体内也从未初始化 game_data,第 83 行 game_data.append(...) 会抛出 NameError

由于 SteamLibarary/__init__.py 在模块级 from .library_service import build_library_wall, build_random_pick,此错误会使整个 SteamLibarary 模块加载失败,波及模块内所有已注册命令(包括原有 游戏墙 命令),而不只是新增的“玩什么”。

🐛 修复语法错误与变量初始化
 pick_count = min(3, len(games))
picks = random.sample(games, pick_count)
+ game_data = []
for game in picks:
appid = str(game.get("appid", ""))
name = game.get("name", "未知游戏")
playtime = (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url = SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
- }) })+ })
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
}) })
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
asyncdefbuild_random_pick(steamid64: str) ->bytes:
"""从用户 Steam 游戏库中随机选取 3 款游戏,生成推荐图片。"""
api_key=SteamConfig.get_config("SteamWebAPIKey").data
ifnotapi_key:
raiseSteamConfigError("请先配置 steam web api key")
library=awaitget_steamlibrary_by_steamid64(api_key, steamid64)
games=library.get("games")
ifgamesisNone:
raiseSteamValidationError("获取 steam 游戏库列表失败")
ifnotgames:
raiseSteamValidationError("该 steam 账号暂无游戏库存")
pick_count=min(3, len(games))
picks=random.sample(games, pick_count)
game_data= []
forgameinpicks:
appid=str(game.get("appid", ""))
name=game.get("name", "未知游戏")
playtime= (
game.get("playtime_forever", 0) or
game.get("playtime_windows_forever", 0) +
game.get("playtime_mac_forever", 0) +
game.get("playtime_linux_forever", 0) +
game.get("playtime_deck_forever", 0)
)
cover_url=SteamAPI.GetGameCoverImageURL(appid, variant='library_600x900')
game_data.append({
"appid": appid,
"name": name,
"playtime": playtime,
"cover_url": cover_url,
})
img=awaitdraw_what_to_play(game_data)
img=img.convert("RGB")
buf=BytesIO()
img.save(buf, format="JPEG", quality=pic_quality, subsampling=0)
returnbuf.getvalue()
🧰 Tools
🪛 ast-grep (0.44.1)

[info] 69-69: use secrets package over random package
Context: random.sample(games, pick_count)
Note: [CWE-330] Use of Insufficiently Random Values.

(avoid-random-python)

🪛 Ruff (0.15.20)

[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-88: Expected a statement

(invalid-syntax)


[warning] 88-89: Expected a statement

(invalid-syntax)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamLibarary/library_service.py` around lines 55 - 95, 修复
build_random_pick 中导致模块无法解析的多余右括号,并在遍历 picks 前初始化 game_data 为空列表,确保后续
game_data.append 调用可正常执行;保持现有游戏选择、数据组装和图片生成流程不变。

Source: Linters/SAST tools

27 changes: 25 additions & 2 deletions SteamUID/SteamPoll/poll_service.py
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
import json
import time

from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment
Expand All@@ -18,6 +19,7 @@
SteamBind,
SteamArchivementInfo,
SteamPriceInfo,
SteamPlayRecord,
)
from ..utils.PIL.draw import draw_game_status_photo, draw_archivements_photo
from ..utils.steam_status import (
Expand DownExpand Up@@ -173,13 +175,32 @@ async def _dispatch_to_subs(subs, send_msg, push_column, steamid64) -> None:


async def flush_status_updates(update_list) -> None:
"""将有变化的状态数据写回数据库"""
"""将有变化的状态数据写回状态轮询数据库"""
for steamid64, info in update_list:
await SteamIDInfo.upsert_steamuserinfo(
steamid64, json.dumps(info, ensure_ascii=False)
)


async def update_game_record(push_list) -> None:
"""写入游戏记录数据库"""
now = int(time.time())
for info, old_info in push_list:
steamid64 = info.get("steamid")
if not steamid64:
continue
old_appid = old_info.get("gameid", "")
new_appid = info.get("gameid", "")
# 结束旧游戏(覆盖"切换游戏"与"退出游戏"两种场景)
if old_appid:
ret = await SteamPlayRecord.upsert_record(steamid64, old_appid, end_ts=now)
if ret != 0:
logger.warning(
f"[SteamPoll] 结束游玩记录失败: steamid={steamid64} appid={old_appid}"
)
# 开始新游戏
if new_appid:
await SteamPlayRecord.upsert_record(steamid64, new_appid, now)

async def poll_and_push_game_status() -> None:
"""游戏状态轮询主入口:拉取状态、检测变化、推送、更新基线、落盘。"""
try:
Expand All@@ -198,6 +219,8 @@ async def poll_and_push_game_status() -> None:
await process_game_status_push(push_list, game_info_map)
await update_achievement_baselines(push_list)
await flush_status_updates(update_list)
await update_game_record(push_list)

except Exception as error:
logger.warning(f"[SteamPoll] 游戏状态轮询失败: {error!r}")

Expand Down
54 changes: 54 additions & 0 deletions SteamUID/SteamRanking/__init__.py
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
from gsuid_core.sv import SV
from gsuid_core.bot import Bot
from gsuid_core.models import Event
from gsuid_core.logger import logger
from gsuid_core.segment import MessageSegment

from ..utils.exceptions import SteamError, SteamValidationError
# from ..utils.target import resolve_target_steamid64
from ..utils.utils import time_convert_s
from .ranking_service import get_group_ranking_list

ranking_sv = SV("steam排名服务")

@ranking_sv.on_command(("群排行", "群排名"))
async def group_ranking(bot: Bot, ev: Event):
"""按用户游戏时长从高到低取5位返回"""
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")

ranking_list = await get_group_ranking_list(ev.group_id)
top5 = ranking_list[:5]

if not top5:
await bot.send("本群暂无游戏时长排行数据")
return

text = f"本群游戏时长排行:\n"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

移除多余的 f-string 前缀。

text = f"本群游戏时长排行:\n" 不含任何占位符,Ruff 已将其标记为 F541(error 级别),建议去掉 f 前缀以通过 lint 检查。

🧹 建议修复
- text = f"本群游戏时长排行:\n"+ text = "本群游戏时长排行:\n"
As per static analysis hints, Ruff flags `[error] 28-28: f-string without any placeholders` (F541).
📝 Committable suggestion

‼️IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
text=f"本群游戏时长排行:\n"
text="本群游戏时长排行:\n"
🧰 Tools
🪛 Ruff (0.15.20)

[error] 28-28: f-string without any placeholders

Remove extraneous f prefix

(F541)


[warning] 28-28: String contains ambiguous (FULLWIDTH COLON). Did you mean : (COLON)?

(RUF001)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@SteamUID/SteamRanking/__init__.py` at line 28, Update the text assignment in
the ranking initialization flow to remove the unnecessary f-string prefix from
the literal, preserving the existing string content and newline while resolving
Ruff F541.

Source: Linters/SAST tools

for i, item in enumerate(top5, 1):
text += f"{i}. {item['user_id']} ({time_convert_s(item['total_duration'])})\n"

await bot.send(text)

except SteamError as e:
await bot.send(str(e))
except Exception as e:
logger.exception(f"[SteamRanking - 群排行] 未知错误: {e!r}")
await bot.send("发生未知错误,请联系管理员查看控制台")

"""
============鸽了=============

@ranking_sv.on_command(("我的排行", "我的排名"))
async def my_ranking(bot: Bot, ev: Event):
获取用户在群中的排名,应为总排行 + ... + 用户所在排行位置

@ranking_sv.on_command(("游戏排行", "游戏排行"))
async def game_ranking(bot: Bot, ev: Event):
获取用户在某appid在某群中的用户游玩时长排行
try:
if not ev.group_id:
raise SteamValidationError("请在群聊中使用此功能")
steamid64 = await resolve_target_steamid64(ev)
"""
Loading