2024-10-09 update
补充离线游戏时长2025-06-19 update
新增图表绘制
背景说明
终于还是购买了 Steam Deck,在金钱的督促下最近电子阳痿缓解了很多,每天也多多少少会玩一会儿游戏。然后在折腾 Steam Deck 的过程中发现有个插件能够记录 Steam Deck 上的游戏时长,因此启发了我:既然我大多数时候游戏都是 Steam 在线游玩的,那是否能够通过 Steam 本身的一些 API 来实现记录每天的游戏时长呢?然后将每天的信息存下来,就能通过简单的减法来计算出每日各个游戏的时长了。
具体实现
Steam API 寻找
Steam 的 API 其实比较难找,因为官方的、成体系的、面向普通开发者 API 文档找起来很困难也不够新,看起来最正式、有良好维护的其实是面向一些游戏相关开发工作的 Steamworks API 文档:Steamworks API 概览。
面向普通开发者的 API 文档:Steam Web API 文档、Steam Web API,看上去就是一副缺少维护的样子,接口也都还是 http 的,虽然确实能调通。
最后还是找到了一个第三方提供的 API 文档:Steam Web API Documentation(GitHub 地址:SteamWebAPIDocumentation),看说明来源是通过 Steam 官方提供的 API 来获取所有支持的 API。
This is a static page that is automatically generated from GetSupportedAPIList using public and publisher keys. Additionally service methods are parsed from Steam client’s protobuf files.
但是实际测下来,即使是 Steam 官方接口返回的接口调用信息也是不准确的,如:GetOwnedGames 接口显示可以通过 appids_filter 来筛选需要查询的游戏,但是实际不生效,然后看到 Steam Web API 里又写到:
appids_filter
You can optionally filter the list to a set of appids. Note that these cannot be passed as a URL parameter, instead you must use the JSON format described in Steam_Web_API#Calling_Service_interfaces. The expected input is an array of integers (in JSON: “appids_filter: [ 440, 500, 550 ]” )
和
Calling Service interfaces
There is a new style of WebAPI which we refer to as “Services”. They function in many ways like the WebAPIs you are used to, the main difference being that all service APIs will accept their arguments as a single JSON blob in addition to taking them as GET or POST parameters. To pass in data as JSON, invoke the webapi with a parameter set like:
?key=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX&format=json&input_json={steamid: 76561197972495328}
Note that the JSON will need to be URL-encoded. The “key” and “format” fields should still be passed as separate parameters, as before. POST requests are supported as well.
You can identify if a WebAPI is a “Service” by the name of the interface; if it ends in “Service” like “IPlayerService”, then it supports this additional method of passing parameter data. Some Service methods have parameters that are more complex structures and require this different input format.
但是实际上都是在扯淡……按照文档说明的规则去写同样调不通。
查下来有个已经归档的项目里的说法是,参考 TF2 wiki 内的说法,需要使用以下格式:
appids_filter (Optional) (uint32)
Restricts results to the appids passed here. This is an array and should be passed likeappids_filter[0]=440&appids_filter[1]=570
经过验证以上方式调用是有效的。
同时,文档里说可以使用 language
参数来修改返回的 appinfo 的语言,这个实测下来也无效——或者这个参数非常偏门,不是 zh-CN、Chinese 之类正常逻辑能想到的。好在也不太有这个需求&有中文信息的游戏也不多。
游戏时长获取方案
一开始打算使用 GetRecentlyPlayedGames
直接获取两周内各游戏的游戏时长然后通过每日定时执行脚本计算来获取每日的具体时长信息。但实际尝试下来发现“两周内时长”是一个滑窗动值,要根据这个计算出每日的时长信息是不足的,比如:游戏 A 昨天的两周内游戏时长是 14 小时,今天的两周内游戏时长是 15 小时,这只能够计算出游戏 A 今天的游戏时长比两周前的那天多 1 小时,并不能获取具体当天游戏时长。也许通过某些数学方式能够获取,但是总归太过麻烦。
选择使用其他接口,最终决定使用 GetOwnedGames
接口来获取,该接口会返回所有拥有的游戏,并返回该游戏的历史总游戏时长,使用该接口每天记录一次数据,并与前一天的数据做差值计算,即可获取到当天的各游戏时长。
验证过程中发现该方案的问题:GetOwnedGames
如同字面意思,只能返回“Owned”的游戏,而 Steam 最近的新家庭共享功能可以让我玩到我没有购买但是家庭组内其他成员购买的游戏。在这种情况下,通过 GetOwnedGames
获取到的信息中是缺少这些游戏的。
最后思考下来的解决方案是仍然需要使用 GetRecentlyPlayedGames
来进行补充,该接口能够正常显示两周内玩过的家庭共享游戏信息,包括总游戏时长。因此,在使用 GetOwnedGames
获取数据后,再通过 GetRecentlyPlayedGames
获取数据并对其中缺失的游戏进行补充,即可有效获取到所有游戏时长信息。
随后的事情就比较简单了,用今天的数据和前一天的数据做差值计算,即可得到当天的各游戏时长;如果存在昨天不存在的游戏数据说明是今天第一次玩的,直接用现有的游戏总时长即可。
2025/05/27 update
由于
GetRecentlyPlayedGames
仅获取两周内游戏时长,因此若家庭组内游戏超过两周未游玩,下次游玩时则会错误将总时长作为当天游戏时长,该情况下处理逻辑暂未实现,需要手动校准。
定时脚本执行及数据保存方案
一开始其实考虑过在自己买的云服务器上定时运行脚本并,但是想了想后面云服务器到期大概率不太会继续续用了,即使续用大概率也会购买另一家云厂商的,到时候再进行数据迁移会比较麻烦。
最后考虑了多种方案还是决定白嫖 GitHub 的资源,使用 GitHub Actions 来实现定时的脚本执行,并使用 GitHub 仓库本身来保存数据。
大致方案如下:
- 新建私有仓库,将上述游戏时长获取脚本保存到该仓库中;
- 新建一个 GitHub Action,配置定时执行,自动拉取该仓库并执行脚本,最后将数据通过 commit 和 push 保存到该仓库中。
图表绘制
(20250619 update) 长期是另外写一个 jupyter 脚本来拉数据画图表,今天想着还是写一个画图的脚本每天执行,将结果放到 README.md 中。
代码
游戏时长获取
以下为游戏时长获取脚本,使用 Python 实现。
|
|
保存图表
(20250619 update)
|
|
GitHub Actions yaml 配置
以下为 GitHub Actions 的 yaml 配置文件。
基本都是 GitHub Actions 的八股文,可以参考官方文档或者网络上其他文档。
|
|
(20250619 update)
新增执行绘图脚本的 job
|
|