個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證A single-page gaming dashboard, in the spirit of lastfm-dash: what you're playing right now, your most-played and recently played games, a genre breakdown, lifetime stats and a set of fun data-driven insight widgets, all themed live from the current game's art.
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗v1.0.2 · A self-hosted, single-page gaming dashboard, in the spirit of lastfm-dash.
It shows what you're playing right now across every platform you connect, your most-played and recently played games, a genre breakdown, lifetime stats and a set of fun insight widgets. The whole page is styled as a game HUD and recolours itself from the current game's art.
| Platform | Required? | Now playing | Playtime | Achievements | Connects via |
|---|---|---|---|---|---|
| Steam | Yes | Live | Lifetime, per-OS, plus the ledger | Yes, with unlock dates | Official Web API key |
| Xbox / PC Game Pass | Optional | Live, with rich presence | Per game, where reported | Gamerscore and progress | OpenXBL (unofficial) |
| PlayStation (PS4 / PS5) | Optional | Live | Per game (PS4 / PS5) | Trophies, by tier | NPSSO token (unofficial) |
| Nintendo (Switch / Switch 2) | Optional | Last played only | Per game, plus the last 7 days | None (Nintendo has none) | Nintendo Account sign-in (unofficial) |
| RetroAchievements | Optional | Live, with rich presence | None | Yes | Official API key |
| Twitch | Optional | Live banner when you're streaming | n/a | n/a | Official Helix API |
Steam is the backbone. The playtime ledger, Most Played periods, Genre Breakdown and most widgets are built from Steam data. The other platforms add their own panels and join the Now Playing card. See each platform's section below for setup and limits.
Whenever a number is derived rather than read straight from an API, the dashboard says so, and it labels partial data as partial.
zlib (bundled almost everywhere). The
curl extension is used when available, with an allow_url_fopen
fallback.cache/.cp config.sample.php config.php
steam_api_key and steam_id in config.php. steam_id accepts
your 64-bit SteamID, your custom profile name, or your full profile URL.php -S localhost:8000
cron.php every 15 minutes (see
Background job), then set
'cron_enabled' => true. The dashboard works without it, but period
totals, achievements and genres fill in far more completely with it.Any setting you leave out of config.php falls back to its default, so an
older config.php keeps working when new options are added. Copy the
relevant lines from config.sample.php when you want a new feature.
All settings live in config.php. The comments in config.sample.php
explain each one in more detail.
| Setting | Default | What it does |
|---|---|---|
steam_api_key, steam_id | none | Required. Your Steam Web API key and account |
openxbl_api_key | '' | Xbox via OpenXBL; blank turns Xbox off |
xbox_presence_ttl | 120 | Seconds between Xbox "now playing" checks (min 60) |
psn_npsso | '' | PlayStation sign-in token; blank turns PlayStation off |
psn_presence_ttl | 120 | Seconds between PlayStation "now playing" checks (min 60) |
nintendo_session_token | '' | Nintendo sign-in token from nintendo-link.php; blank turns Nintendo off |
nintendo_locale | 'en-US' | Language for Nintendo game names |
retroachievements_username, retroachievements_api_key | '' | RetroAchievements; blank turns it off |
twitch_channel, twitch_client_id, twitch_client_secret | '' | Twitch live banner; any blank turns it off |
twitch_embed_player | true | false shows a preview image instead of the video player |
site_domain | '' | Public domain for the footer's DNSSEC / Security Headers checks; blank uses the request's hostname |
app_name | 'XP Ledger' | Page title and header |
poll_interval_ms | 30000 | How often the page refreshes Now Playing and Twitch |
cache_ttl | 120 | General Steam API cache, in seconds |
top_limit, recent_limit | 8 | Rows in Most Played / Recently Played |
Steam doesn't keep a dated play history. It only exposes each game's
lifetime playtime and a rolling "last two weeks" figure. XP Ledger builds
the history itself: each cron.php run (and, at most every 10 minutes,
the dashboard's own polling) snapshots every game's playtime counter and
records how much each one moved. That ledger, saved as
cache/ledger_*.json.gz, is what makes This Week / Today, Weekly Rhythm
and the MCP playtime_log tool possible.
Limits worth knowing, which the UI also states where they apply:
cache/ if
you care about it.cron.php)Run it every 15 minutes:
# Real system cron (preferred):
0,15,30,45 * * * * php /full/path/to/XP-Ledger/cron.php >/dev/null 2>&1
# URL-based cron on shared hosting:
0,15,30,45 * * * * curl -s "https://yourdomain.com/path/cron.php?token=YOUR_CRON_SECRET" >/dev/null
Each run:
achievement_backfill_per_run), plus their
global unlock rates,store_backfill_per_run; the Store API allows
about 200 requests per 5 minutes),Steam has no bulk endpoint for achievements or store details, so a large library fills in over several runs instead of one rate-limited burst. If a service fails to answer, the run stops that step early rather than waiting out a timeout per game. The run's one-line summary also reports when a PlayStation or Nintendo sign-in needs renewing.
Once it's scheduled, set 'cron_enabled' => true to show a footer note. If
you set cron_secret, web-triggered runs need a matching ?token=.
Command-line runs are always allowed.
Microsoft only opens its Xbox APIs to licensed game developers, so XP
Ledger uses OpenXBL, an unofficial third-party gateway.
Sign in there with your Microsoft account, copy your personal API key from
the dashboard into openxbl_api_key, and you get:
xbox_summary MCP tool.This covers anything played on an Xbox console and PC games played through the Xbox app, PC Game Pass or the Microsoft Store, since they share one Xbox account. Steam games on PC are already covered by the Steam side.
Things to know:
xbox_presence_ttl seconds, caches the
title list for 15 minutes and the profile and playtime for an hour
(playtime for every game is one batched request). That comes to roughly
40-50 requests an hour with the dashboard open all day.Sony has no public API, so XP Ledger uses the same unofficial PlayStation App endpoints as the psn-api library. To connect:
npsso value.psn_npsso in config.php.Treat the NPSSO like a password: it signs in as you. XP Ledger exchanges it
for an access token and refresh token, stored in cache/ and refreshed
automatically. The NPSSO lasts about two months. When it expires, the
dashboard shows a banner asking for a new one.
You get PlayStation games in the Now Playing card, a PlayStation panel
(trophy level, platinum/gold/silver/bronze counts, hours, recent games) and
a playstation_summary MCP tool. Things to know:
psn_presence_ttl seconds). Failures show the last good
data and pause PlayStation requests for 5 minutes.Nintendo has no public API, so XP Ledger reads the play history behind the "Play Activity" screen in Nintendo's own Nintendo Store app, signing in the same way that app does. To connect:
nintendo-link.php in your browser (or run php nintendo-link.php).nintendo_session_token line it gives you into config.php.The session token lasts about two years. Treat it like a password: it signs in as you. The helper stores nothing on the server, so it's safe to leave in place, but you can delete it once you're connected. If the token stops working, the dashboard shows a banner and you run the helper again.
You get Nintendo games in the Now Playing card (as last played), a
Nintendo panel (hours, games played, a 7-day bar chart, recent games) and a
nintendo_summary MCP tool. Things to know:
Set retroachievements_username and retroachievements_api_key (from
retroachievements.org/settings,
"Keys" section). This adds the RetroAchievements panel, includes retro games
in the Now Playing card (detected through rich presence), adds your points
to Lifetime Stats, and adds a retroachievements_summary MCP tool.
RetroAchievements doesn't track playtime, so it doesn't feed the ledger or
the period totals.
When your Twitch channel is live, a banner appears at the top of the dashboard with the stream embedded (muted), its title, game, viewer count and how long you've been live. It updates on the dashboard's normal refresh, so it appears and disappears without a reload. When you're not live, nothing shows.
This uses Twitch's official API. To set it up:
XP Ledger <yourname>http://localhost (required by the form, not used)config.php:
'twitch_channel' => 'yourchannel', // or the full twitch.tv URL
'twitch_client_id' => 'paste-client-id-here',
'twitch_client_secret' => 'paste-client-secret-here',
'twitch_embed_player' => true, // false = preview image instead of the player
Things to know:
localhost or your site's domain, not a bare IP address. On an IP the
banner text still works but the player won't load.mcp.php exposes your data as read-only tools for any MCP client (Claude,
ChatGPT/OpenAI, and others). Set mcp_api_key in config.php to a long
random secret. It's blank by default, which disables the endpoint. Then
connect with an Authorization: Bearer <your mcp_api_key> header, e.g.:
claude mcp add --transport http xp-ledger https://yourdomain.com/mcp.php \
--header "Authorization: Bearer YOUR_MCP_API_KEY"
| Tool | What it returns |
|---|---|
get_now_playing | Current/last played game across platforms, the one before it, and whether you're live on Twitch |
list_games | The Steam library, filterable (played/unplayed) and sortable |
top_games | Most played for a period, with how complete that period's data is |
genre_breakdown | Playtime-weighted genre percentages for a period |
game_details | One game (by appid or name): playtime per OS, store info, unlocked achievements with dates |
list_achievements | Unlocks with exact timestamps, filterable by date |
playtime_log | Raw ledger entries and how far back tracking goes |
lifetime_stats | The Lifetime Stats panel's totals |
xbox_summary | Only when Xbox is configured: profile, now playing, every title with gamerscore and playtime |
playstation_summary | Only when PlayStation is configured: trophy level and counts, now playing, every game with playtime and trophy progress |
nintendo_summary | Only when Nintendo is configured: every game with minutes, days played, first/last played, and the last 7 days |
retroachievements_summary | Only when RetroAchievements is configured |
widget_* | One tool per insight widget |
Client setup (Claude app connectors, OpenAI's Responses API, etc.) is the same as for lastfm-dash. See its README for step-by-step instructions.
Every response sends these headers, from lib/Security.php:
| Header | Value |
|---|---|
Strict-Transport-Security | max-age=31536000; includeSubDomains |
Content-Security-Policy | Strict: scripts and styles only from this site, plus a per-request nonce for the one inline script; no 'unsafe-inline' anywhere. Images from any https: origin (game art comes from many CDNs), Google Fonts, and the Twitch player in a frame. |
X-Frame-Options | SAMEORIGIN |
X-Content-Type-Options | nosniff |
Referrer-Policy | strict-origin-when-cross-origin |
Permissions-Policy | Everything off, except autoplay and fullscreen for the Twitch player |
Cross-Origin-Opener-Policy / Cross-Origin-Resource-Policy | same-origin |
Cross-Origin-Embedder-Policy | credentialless: cross-origin images still load, but without cookies. Not require-corp, which would block game art from CDNs that don't send CORP headers |
X-Powered-By (which reveals the PHP version) is removed. JSON and text
endpoints get a lock-down policy (default-src 'none'), and the included
.htaccess adds the basics to static files on Apache.
Because the policy blocks inline styles and scripts, the page has no
style="…" or onclick="…" attributes. Sizes and colours are rendered as
data-w / data-h / data-bg attributes and applied by assets/app.js.
Keep it that way when changing the templates, or the browser will block the
change (it shows up as a "Refused to…" message in the console).
Under "Powered by", the footer shows two live checks, each linking to a public tool so you can verify it:
headers-check.php), so it also catches a proxy or
CDN that strips them. It needs HTTPS: over plain HTTP it shows ✗. Links to
securityheaders.com.Both use the hostname the page was opened on, or site_domain if you set
it. On localhost or a bare IP address, DNSSEC shows ✗ because there's no
public domain to check.
config.php holds your API keys and the PlayStation, Nintendo and
Twitch credentials. It's gitignored; keep it private.cache/ holds access tokens (PlayStation, Nintendo, Twitch) and your
playtime history. The included .htaccess blocks web access to it on
Apache. On nginx or other servers, block that folder in your server config.mcp.php is off until you set mcp_api_key. Anyone with that key can
read everything the dashboard can, so treat it like a password.nintendo-link.php stores nothing and only shows a token to whoever
signs in with their own Nintendo account. Delete it after connecting if
you'd rather it wasn't there.cron_secret if cron.php is reachable on the web, so random
visitors can't trigger runs.Pull the new version (or copy the files over), keeping your config.php
and cache/ folder. New settings fall back to their defaults until you add
them, so nothing breaks. Check config.sample.php for new options. The
footer tells you when a newer release is available.
Each platform is its own small client in lib/: Steam.php, Xbox.php,
PlayStation.php, Nintendo.php, RetroAchievements.php and Twitch.php.
Library::nowPlaying() merges the game platforms into one shared card shape
(see steamCard(), xboxCard(), psnCard() and retroCards()), so
another source with a usable API can be added by writing a client and a
...Cards() method that returns the same shape.
GPL-3.0. See LICENSE.
'unsafe-inline' (aimed at an A+ on
securityheaders.com). The footer shows a
live ✓/✗ for DNSSEC and Security Headers.most_played_default_period, genre_default_period | 'all_time' | Initial period for each picker |
timezone | server default | IANA timezone, e.g. 'Europe/London' |
ledger_snapshot_interval | 600 | How often page polling may snapshot playtime, in seconds |
achievement_backfill_per_run | 25 | Games' achievements checked per cron run |
store_backfill_per_run | 40 | Games' store details fetched per cron run |
store_live_budget | 10 | Live store lookups a single page request may make |
loot_game_limit, review_sample | 20, 60 | Widget tuning |
cron_enabled, cron_secret | false, '' | Footer note, and a ?token= required for web-triggered cron runs |
mcp_api_key | '' | Enables mcp.php; blank turns it off |
github_repo, update_check_ttl | repo, 3600 | Footer update check; '' turns it off |