mirror of
https://github.com/seanbetts/steam-hardware-watch.git
synced 2026-10-06 00:00:21 +02:00
Add SteamDB browser fallback
This commit is contained in:
1 parent
5a049365c1
commit
b43753f4df
10 files changed
+505
-17
No files matched your search
@@ -107,6 +107,16 @@ scripts/close_komodo.sh
|
||||
|
||||
That keeps Komodo access isolated from your normal Chrome profile and gives the agent a reusable trusted browser session when Komodo blocks normal automation.
|
||||
|
||||
SteamDB can require the same live-browser treatment:
|
||||
|
||||
```sh
|
||||
scripts/bootstrap_steamdb.sh
|
||||
scripts/run_watch.sh 2026-04-25 ~/steam_hardware_watch /tmp/SteamTracking-master
|
||||
scripts/close_steamdb.sh
|
||||
```
|
||||
|
||||
If both sources are blocked, bootstrap both before the run.
|
||||
|
||||
## Output
|
||||
|
||||
Each run writes:
|
||||
@@ -292,12 +302,60 @@ The helper scripts now support that same live-session route with:
|
||||
- `PLAYWRIGHT_CDP_ENDPOINT`
|
||||
- automatic local env loading from `.local/komodo-env.sh`
|
||||
|
||||
## Optional SteamDB Browser Bootstrap
|
||||
|
||||
SteamDB may return a Cloudflare browser challenge to `curl` and to fresh automated browser contexts. The SteamDB helper uses a realistic Chrome user agent by default and rejects challenge HTML instead of saving it as a successful page. When that is not enough, use a dedicated live Chrome session.
|
||||
|
||||
Use the bundled helper:
|
||||
|
||||
```sh
|
||||
scripts/bootstrap_steamdb.sh
|
||||
```
|
||||
|
||||
This:
|
||||
|
||||
- uses a dedicated profile under `.local/`
|
||||
- launches a separate Chrome instance in the background
|
||||
- exposes a stable CDP endpoint
|
||||
- writes `.local/steamdb-env.sh`
|
||||
|
||||
If SteamDB needs manual interaction, switch to that dedicated Chrome window, complete the challenge, and leave the browser running.
|
||||
|
||||
Then run:
|
||||
|
||||
```sh
|
||||
scripts/run_watch.sh 2026-04-25 ~/steam_hardware_watch /tmp/SteamTracking-master
|
||||
```
|
||||
|
||||
`check_steamdb.sh` automatically picks up `.local/steamdb-env.sh` when present. `run_watch.sh` auto-closes the dedicated SteamDB browser at the end unless you set:
|
||||
|
||||
```sh
|
||||
STEAMDB_KEEP_BROWSER_OPEN=1
|
||||
```
|
||||
|
||||
When finished:
|
||||
|
||||
```sh
|
||||
scripts/close_steamdb.sh
|
||||
```
|
||||
|
||||
Manual equivalent:
|
||||
|
||||
```sh
|
||||
STEAMDB_PLAYWRIGHT_FALLBACK=1
|
||||
STEAMDB_PROFILE_DIR=~/.steam-hardware-watch/playwright-steamdb-profile
|
||||
STEAMDB_CDP_ENDPOINT=http://127.0.0.1:PORT
|
||||
STEAMDB_BROWSER_CHANNEL=chrome
|
||||
```
|
||||
|
||||
## Current Known Limits
|
||||
|
||||
- `Komodo` can block both `curl` and fresh browser automation.
|
||||
- `SteamDB` can return a Cloudflare browser challenge to curl and fresh automated browser contexts.
|
||||
- We can currently discover and report blocked media URLs even when we cannot download them automatically.
|
||||
- Exported storage state may still be insufficient for Cloudflare-protected Komodo assets.
|
||||
- Live trusted browser attach is currently the strongest repeatable fallback for protected Komodo media.
|
||||
- Live trusted browser attach is also the strongest SteamDB fallback when browser-style curl is blocked.
|
||||
- The fully non-visual path is not reliable yet; after bootstrap, the working fallback is still a live dedicated browser running in the background.
|
||||
|
||||
## Recommended Workflow
|
||||
@@ -313,4 +371,5 @@ Escalation:
|
||||
|
||||
1. if Komodo blocks, continue the run
|
||||
2. use `manual-asset-urls.txt` for manual follow-up
|
||||
3. only use the Playwright bootstrap if protected Komodo media is important to the run
|
||||
3. if SteamDB blocks, use `scripts/bootstrap_steamdb.sh` and rerun when SteamDB verification is important
|
||||
4. only use the Playwright bootstrap if protected Komodo media or SteamDB verification is important to the run
|
||||
@@ -137,6 +137,15 @@ Known IDs and starting points are in [references/sources.md](references/sources.
|
||||
|
||||
The helper script saves the raw HTML pages. Summarize only material changes.
|
||||
|
||||
If SteamDB returns a Cloudflare challenge, `check_steamdb.sh` should record the source as blocked instead of saving the challenge HTML as a successful page. To retry with a trusted local browser session:
|
||||
|
||||
1. run `scripts/bootstrap_steamdb.sh`
|
||||
2. complete any manual challenge in the dedicated Chrome window
|
||||
3. leave the browser running
|
||||
4. rerun the watch, or run `check_steamdb.sh` directly
|
||||
|
||||
The helper loads `.local/steamdb-env.sh` automatically and reuses the live browser over CDP when `STEAMDB_PLAYWRIGHT_FALLBACK=1`.
|
||||
|
||||
### 3. SteamTracking / GameTracking
|
||||
|
||||
Look for:
|
||||
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
REPO_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCAL_DIR="$REPO_DIR/.local"
|
||||
|
||||
PROFILE_DIR="${1:-${STEAMDB_PROFILE_DIR:-$LOCAL_DIR/playwright-steamdb-profile}}"
|
||||
PORT="${2:-${STEAMDB_CDP_PORT:-55685}}"
|
||||
URL="${STEAMDB_BOOTSTRAP_URL:-https://steamdb.info/app/4165870/}"
|
||||
ENV_FILE="${STEAMDB_ENV_FILE:-$LOCAL_DIR/steamdb-env.sh}"
|
||||
|
||||
mkdir -p "$PROFILE_DIR" "$LOCAL_DIR"
|
||||
|
||||
existing_pid="$(ps aux | awk -v profile="$PROFILE_DIR" -v port="$PORT" '
|
||||
index($0, "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome") &&
|
||||
index($0, profile) &&
|
||||
index($0, "--remote-debugging-port=" port) {
|
||||
print $2
|
||||
exit
|
||||
}
|
||||
')"
|
||||
|
||||
if [ -z "$existing_pid" ]; then
|
||||
open -g -na "Google Chrome" --args \
|
||||
"--user-data-dir=$PROFILE_DIR" \
|
||||
"--remote-debugging-port=$PORT" \
|
||||
--no-first-run \
|
||||
--disable-sync \
|
||||
--new-window \
|
||||
"$URL"
|
||||
|
||||
attempts=0
|
||||
until curl -fsS "http://127.0.0.1:$PORT/json/version" >/dev/null 2>&1; do
|
||||
attempts=$((attempts + 1))
|
||||
if [ "$attempts" -ge 30 ]; then
|
||||
echo "failed to observe a Chrome CDP endpoint on port $PORT" >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
else
|
||||
echo "SteamDB bootstrap browser already running on port $PORT (pid $existing_pid)."
|
||||
fi
|
||||
|
||||
cat > "$ENV_FILE" <<EOF
|
||||
export STEAMDB_PLAYWRIGHT_FALLBACK=1
|
||||
export STEAMDB_PROFILE_DIR="$PROFILE_DIR"
|
||||
export STEAMDB_CDP_ENDPOINT="http://127.0.0.1:$PORT"
|
||||
export STEAMDB_BROWSER_CHANNEL="chrome"
|
||||
export STEAMDB_REFERER="https://steamdb.info/app/4165870/"
|
||||
EOF
|
||||
|
||||
printf '%s\n' "SteamDB browser bootstrap ready."
|
||||
printf '%s\n' "Profile: $PROFILE_DIR"
|
||||
printf '%s\n' "CDP endpoint: http://127.0.0.1:$PORT"
|
||||
printf '%s\n' "Env file: $ENV_FILE"
|
||||
printf '%s\n' ""
|
||||
printf '%s\n' "Next steps:"
|
||||
printf '%s\n' "1. If SteamDB needs manual interaction, switch to the dedicated Chrome window when convenient."
|
||||
printf '%s\n' "2. Leave that dedicated browser running in the background."
|
||||
printf '%s\n' "3. Source the env file before a run, or let check_steamdb.sh pick it up automatically."
|
||||
@@ -9,20 +9,83 @@ fi
|
||||
RUN_DIR="$1"
|
||||
OUT_DIR="$RUN_DIR/api/steamdb"
|
||||
REPORT_DIR="$RUN_DIR/reports"
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
REPO_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
mkdir -p "$OUT_DIR" "$REPORT_DIR"
|
||||
|
||||
ERROR_FILE="$REPORT_DIR/steamdb-errors.txt"
|
||||
: > "$ERROR_FILE"
|
||||
: > "$REPORT_DIR/steamdb-key-lines.txt"
|
||||
|
||||
load_local_env() {
|
||||
env_file="${STEAMDB_ENV_FILE:-$REPO_DIR/.local/steamdb-env.sh}"
|
||||
if [ -f "$env_file" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$env_file"
|
||||
fi
|
||||
}
|
||||
|
||||
maybe_set_cdp_endpoint() {
|
||||
if [ -n "${STEAMDB_CDP_ENDPOINT:-}" ] || [ -z "${STEAMDB_PROFILE_DIR:-}" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
port="$(ps aux | awk -v profile="$STEAMDB_PROFILE_DIR" '
|
||||
index($0, profile) && match($0, /--remote-debugging-port=([0-9]+)/) {
|
||||
value = substr($0, RSTART, RLENGTH)
|
||||
sub(/^--remote-debugging-port=/, "", value)
|
||||
print value
|
||||
exit
|
||||
}
|
||||
' 2>/dev/null || true)"
|
||||
|
||||
if [ -n "$port" ]; then
|
||||
STEAMDB_CDP_ENDPOINT="http://127.0.0.1:$port"
|
||||
export STEAMDB_CDP_ENDPOINT
|
||||
fi
|
||||
}
|
||||
|
||||
is_challenge_page() {
|
||||
path="$1"
|
||||
rg -qi "Checking your browser|Just a moment|Cloudflare|cf-chl|cf-browser-verification" "$path"
|
||||
}
|
||||
|
||||
load_local_env
|
||||
|
||||
STEAMDB_USER_AGENT="${STEAMDB_USER_AGENT:-Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36}"
|
||||
export STEAMDB_USER_AGENT
|
||||
|
||||
fetch_html() {
|
||||
name="$1"
|
||||
url="$2"
|
||||
if ! curl -A 'Mozilla/5.0' --retry 2 --retry-delay 2 --max-time 45 -fsSL "$url" > "$OUT_DIR/$name.html" 2>/dev/null; then
|
||||
printf '%s\n' "failed to fetch $url" >> "$ERROR_FILE"
|
||||
rm -f "$OUT_DIR/$name.html"
|
||||
return 1
|
||||
tmp="$OUT_DIR/$name.html.tmp"
|
||||
out="$OUT_DIR/$name.html"
|
||||
|
||||
if curl -A "$STEAMDB_USER_AGENT" \
|
||||
-H 'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8' \
|
||||
-H 'Accept-Language: en-US,en;q=0.9' \
|
||||
-H 'Upgrade-Insecure-Requests: 1' \
|
||||
--compressed \
|
||||
--retry 2 --retry-delay 2 --max-time 45 -fsSL "$url" > "$tmp" 2>/dev/null; then
|
||||
if ! is_challenge_page "$tmp"; then
|
||||
mv "$tmp" "$out"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" = "1" ] && command -v node >/dev/null 2>&1; then
|
||||
maybe_set_cdp_endpoint
|
||||
if node "$SCRIPT_DIR/fetch_steamdb_with_playwright.js" "$url" "$tmp" >/dev/null 2>&1 \
|
||||
&& [ -s "$tmp" ] \
|
||||
&& ! is_challenge_page "$tmp"; then
|
||||
mv "$tmp" "$out"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
rm -f "$tmp" "$out"
|
||||
printf '%s\n' "failed to fetch $url: blocked or challenge page" >> "$ERROR_FILE"
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_html "controller-app" "https://steamdb.info/app/4165870/" || true
|
||||
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/bin/sh
|
||||
set -eu
|
||||
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
REPO_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
LOCAL_DIR="$REPO_DIR/.local"
|
||||
|
||||
PROFILE_DIR="${1:-${STEAMDB_PROFILE_DIR:-$LOCAL_DIR/playwright-steamdb-profile}}"
|
||||
ENV_FILE="${STEAMDB_ENV_FILE:-$LOCAL_DIR/steamdb-env.sh}"
|
||||
|
||||
pids="$(ps aux | awk -v profile="$PROFILE_DIR" '
|
||||
index($0, profile) && !index($0, "awk -v profile=") {
|
||||
print $2
|
||||
}
|
||||
')"
|
||||
|
||||
if [ -n "$pids" ]; then
|
||||
for pid in $pids; do
|
||||
kill "$pid" 2>/dev/null || true
|
||||
done
|
||||
printf '%s\n' "Closed dedicated SteamDB browser/profile processes for $PROFILE_DIR."
|
||||
else
|
||||
printf '%s\n' "No dedicated SteamDB browser/profile processes found for $PROFILE_DIR."
|
||||
fi
|
||||
|
||||
rm -f \
|
||||
"$PROFILE_DIR/SingletonLock" \
|
||||
"$PROFILE_DIR/SingletonSocket" \
|
||||
"$PROFILE_DIR/SingletonCookie"
|
||||
|
||||
if [ -f "$ENV_FILE" ]; then
|
||||
rm -f "$ENV_FILE"
|
||||
printf '%s\n' "Removed $ENV_FILE"
|
||||
fi
|
||||
Executable
+116
@@ -0,0 +1,116 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require("fs");
|
||||
const { URL } = require("url");
|
||||
|
||||
function loadPlaywright() {
|
||||
const candidates = [];
|
||||
if (process.env.PLAYWRIGHT_CORE_PATH) candidates.push(process.env.PLAYWRIGHT_CORE_PATH);
|
||||
candidates.push("playwright-core");
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
return require(candidate);
|
||||
} catch (err) {
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
"Unable to load playwright-core. Set PLAYWRIGHT_CORE_PATH or install playwright-core."
|
||||
);
|
||||
}
|
||||
|
||||
function launchOptions(headless) {
|
||||
const options = { headless };
|
||||
const channel = process.env.STEAMDB_BROWSER_CHANNEL || process.env.PLAYWRIGHT_BROWSER_CHANNEL;
|
||||
if (channel) {
|
||||
options.channel = channel;
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
function userAgent() {
|
||||
return process.env.STEAMDB_USER_AGENT ||
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36";
|
||||
}
|
||||
|
||||
function findPage(context, origin) {
|
||||
const pages = context.pages();
|
||||
return pages.find((candidate) => candidate.url().startsWith(origin)) || pages[0] || null;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const [, , targetUrl, outputPath] = process.argv;
|
||||
if (!targetUrl || !outputPath) {
|
||||
console.error("usage: fetch_steamdb_with_playwright.js URL OUTPUT_PATH");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { chromium } = loadPlaywright();
|
||||
const headless = process.env.STEAMDB_PLAYWRIGHT_HEADLESS !== "0" &&
|
||||
process.env.PLAYWRIGHT_HEADLESS !== "0";
|
||||
const profileDir = process.env.STEAMDB_PROFILE_DIR || process.env.PLAYWRIGHT_PROFILE_DIR;
|
||||
const storageState = process.env.STEAMDB_STORAGE_STATE || process.env.PLAYWRIGHT_STORAGE_STATE;
|
||||
const referer = process.env.STEAMDB_REFERER || process.env.PLAYWRIGHT_REFERER;
|
||||
const cdpEndpoint = process.env.STEAMDB_CDP_ENDPOINT || process.env.PLAYWRIGHT_CDP_ENDPOINT;
|
||||
const settleMs = Number(process.env.STEAMDB_PLAYWRIGHT_SETTLE_MS || 8000);
|
||||
|
||||
let browser = null;
|
||||
let context = null;
|
||||
let page = null;
|
||||
let shouldCloseBrowser = false;
|
||||
let shouldCloseContext = false;
|
||||
let shouldClosePage = false;
|
||||
|
||||
if (cdpEndpoint) {
|
||||
browser = await chromium.connectOverCDP(cdpEndpoint);
|
||||
shouldCloseBrowser = true;
|
||||
context = browser.contexts()[0];
|
||||
if (!context) {
|
||||
throw new Error(`No browser context available via CDP endpoint ${cdpEndpoint}`);
|
||||
}
|
||||
const origin = new URL(targetUrl).origin + "/";
|
||||
page = findPage(context, origin);
|
||||
if (!page) {
|
||||
page = await context.newPage();
|
||||
shouldClosePage = true;
|
||||
}
|
||||
} else if (profileDir) {
|
||||
context = await chromium.launchPersistentContext(profileDir, {
|
||||
...launchOptions(headless),
|
||||
userAgent: userAgent(),
|
||||
});
|
||||
shouldCloseContext = true;
|
||||
page = context.pages()[0] || await context.newPage();
|
||||
} else {
|
||||
browser = await chromium.launch(launchOptions(headless));
|
||||
shouldCloseBrowser = true;
|
||||
context = await browser.newContext({
|
||||
userAgent: userAgent(),
|
||||
storageState: storageState || undefined,
|
||||
extraHTTPHeaders: referer ? { Referer: referer } : undefined,
|
||||
});
|
||||
shouldCloseContext = true;
|
||||
page = await context.newPage();
|
||||
}
|
||||
|
||||
try {
|
||||
await page.goto(targetUrl, { waitUntil: "domcontentloaded", timeout: 45000 });
|
||||
if (settleMs > 0) {
|
||||
await page.waitForTimeout(settleMs);
|
||||
}
|
||||
fs.writeFileSync(outputPath, await page.content());
|
||||
} finally {
|
||||
if (shouldClosePage && page && !page.isClosed()) {
|
||||
await page.close();
|
||||
}
|
||||
if (shouldCloseContext && context) {
|
||||
await context.close();
|
||||
}
|
||||
if (shouldCloseBrowser && browser) {
|
||||
await browser.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err && err.stack ? err.stack : String(err));
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -13,14 +13,20 @@ TRACKING_DIR="${3:-/tmp/SteamTracking-master}"
|
||||
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname "$0")" && pwd)"
|
||||
REPO_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
|
||||
KOMODO_ENV_FILE="${KOMODO_ENV_FILE:-$REPO_DIR/.local/komodo-env.sh}"
|
||||
STEAMDB_ENV_FILE="${STEAMDB_ENV_FILE:-$REPO_DIR/.local/steamdb-env.sh}"
|
||||
|
||||
cleanup() {
|
||||
if [ "${KOMODO_KEEP_BROWSER_OPEN:-0}" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ -f "$KOMODO_ENV_FILE" ] && [ -x "$SCRIPT_DIR/close_komodo.sh" ]; then
|
||||
:
|
||||
elif [ -f "$KOMODO_ENV_FILE" ] && [ -x "$SCRIPT_DIR/close_komodo.sh" ]; then
|
||||
"$SCRIPT_DIR/close_komodo.sh" >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
if [ "${STEAMDB_KEEP_BROWSER_OPEN:-0}" = "1" ]; then
|
||||
:
|
||||
elif [ -f "$STEAMDB_ENV_FILE" ] && [ -x "$SCRIPT_DIR/close_steamdb.sh" ]; then
|
||||
"$SCRIPT_DIR/close_steamdb.sh" >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
+13
-9
@@ -1,12 +1,12 @@
|
||||
# Steam Hardware Watch Status
|
||||
|
||||
Last updated: `2026-04-26`
|
||||
Last updated: `2026-04-28`
|
||||
|
||||
## Best Current Answer
|
||||
|
||||
- `Price`: no confirmed public price leak for the new Steam Controller, Steam Machine, or Steam Frame.
|
||||
- `Release date`: official Valve store pages now support a broad `early 2026` / `coming in 2026` window, but no exact preorder, ship, or launch date was found.
|
||||
- `Same-time launch`: stronger than the seeded baseline for a shared public rollout window because Valve's official Steam Hardware page groups Controller, Machine, and Frame together. Exact same-day availability is still unconfirmed, and the controller remains furthest along.
|
||||
- `Release date`: official Valve store pages still support a broad `early 2026` / `coming in 2026` window, but no exact preorder, ship, or launch date was found.
|
||||
- `Same-time launch`: stronger than the seeded baseline for a shared public rollout window because Valve's official Steam Hardware page groups Controller, Machine, and Frame together. Exact same-day availability is still unconfirmed, and the controller remains furthest along because the fresh Komodo movement is still controller-only.
|
||||
|
||||
## Source Snapshot
|
||||
|
||||
@@ -20,26 +20,30 @@ Last updated: `2026-04-26`
|
||||
- `wishlist-only`
|
||||
- On `2026-04-24`, Komodo bulk-updated product records for all three hardware lines across currencies.
|
||||
- Also on `2026-04-24`, Komodo exposed a fresh controller-only section and media rollout with banner, videos, feature art, controls imagery, and a spec image.
|
||||
- Rechecked on `2026-04-26`: product modified timestamps remain `2026-04-24T15:12:12` for Controller, `2026-04-24T14:32:54` for Machine, and `2026-04-24T14:33:36` for Frame.
|
||||
- Rechecked on `2026-04-28`: the Controller product modified timestamp moved to `2026-04-28T18:32:11`; Machine and Frame remain at `2026-04-24T14:32:54` and `2026-04-24T14:33:36`.
|
||||
- No comparable fresh Machine or Frame section and media rollout was found. Machine and Frame still only show older wishlist sections in the current Komodo search output.
|
||||
- Komodo added a new Controller section, `Steam Controller - 10 Specs`, modified `2026-04-27T09:41:01`.
|
||||
- Komodo updated the Controller `09 Spec image` section on `2026-04-28T19:05:51`, adding two new AVIF media records plus their generated sizes.
|
||||
- Visual asset retrieval is now complete for the current Komodo set: `130` discovered and `130` retrieved. The browsable library contains `11` videos, `11` images, `3` logos, `3` poster frames, `42` thumbnails, and `60` variants.
|
||||
|
||||
### SteamDB
|
||||
|
||||
- Browser-verified SteamDB state on `2026-04-26`:
|
||||
- `Steam Controller` app `4165870` remains `prerelease` with store release date `Coming soon`.
|
||||
- `steam_controller_unboxing_2026` app `4653940` remains a released free video app, first seen on `2026-04-20`, with package `1620489` available as free on demand and showing `No Price`.
|
||||
- The standard curl helper was blocked by SteamDB, so browser-style verification is the current usable path.
|
||||
- The standard curl helper was still blocked by SteamDB on `2026-04-28`, so browser-style verification remains the current usable path.
|
||||
|
||||
### SteamTracking / GameTracking
|
||||
|
||||
- A fresh SteamTracking checkout on `2026-04-26` still includes controller onboarding, firmware update flow, puck handling, and pairing logic for the new controller ecosystem.
|
||||
- A fresh SteamTracking checkout on `2026-04-28` still includes controller onboarding, firmware update flow, puck handling, and pairing logic for the new controller ecosystem.
|
||||
- Pairing-focused hits include `ibex_internal`, `ibex_external`, `PairDongleTritonConnected`, `PairDongleTritonDocked`, and `ShouldTritonPairInOobe`.
|
||||
|
||||
### Valve Support / CDN
|
||||
|
||||
- Official Valve store pages were reachable and saved:
|
||||
- `/sale/hardware` says the Steam Hardware family expands in early 2026, lists Steam Controller, Steam Machine, and Steam Frame, and was modified at `2026-04-26T06:06:01Z` in the embedded event metadata.
|
||||
- `/sale/steamcontroller` has a dedicated Steam Controller page and was modified at `2026-04-26T06:05:47Z` in the embedded event metadata.
|
||||
- Official Valve store pages were reachable and saved on `2026-04-28`:
|
||||
- `/sale/hardware` still says the Steam Hardware family expands in early 2026, lists Steam Controller, Steam Machine, and Steam Frame, and its embedded event metadata now shows `rtime32_last_modified` `2026-04-28T10:09:22Z`.
|
||||
- `/sale/steamcontroller` has a dedicated Steam Controller page and its embedded event metadata now shows `rtime32_last_modified` `2026-04-28T18:31:12Z`.
|
||||
- The embedded Steam Hardware submenu now points to cleaner `/hardware`, `/hardware/steamcontroller`, `/hardware/steammachine`, and `/hardware/steamframe` paths instead of the older `/sale/...` paths.
|
||||
- No official Valve price, exact release date, or preorder date was found.
|
||||
|
||||
### Customs / Regulatory
|
||||
|
||||
@@ -12,3 +12,7 @@
|
||||
{"date_checked": "2026-04-26", "source": "steamdb", "entity": "steam-controller", "claim": "SteamDB still shows steam_controller_unboxing_2026 as a released free video app with an available free-on-demand package.", "evidence_type": "hidden_media_app", "confidence": "medium", "ref": "https://steamdb.info/app/4653940/", "notes": "Browser fetch succeeded; app first seen 2026-04-20 and last record update 2026-04-21T22:16:14Z; package 1620489 lists No Price."}
|
||||
{"date_checked": "2026-04-26", "source": "steamtracking", "entity": "steam-controller", "claim": "Fresh SteamTracking checkout still contains Ibex dongles, Triton pairing, and controller OOBE strings.", "evidence_type": "client_strings", "confidence": "medium", "ref": "/Users/sean/steam_hardware_watch/2026-04-26/reports/steamtracking-pairing-focus.txt", "notes": "Includes ibex_internal, ibex_external, PairDongleTritonConnected, PairDongleTritonDocked, and ShouldTritonPairInOobe in service_steaminputmanager.proto."}
|
||||
{"date_checked": "2026-04-26", "source": "komodo", "entity": "steam-controller,steam-machine,steam-frame", "claim": "Komodo API access was restored after using a realistic browser user agent; product records remain last modified on 2026-04-24 and no newer Machine or Frame rollout was found.", "evidence_type": "api_recheck", "confidence": "high", "ref": "/Users/sean/steam_hardware_watch/2026-04-26/reports/komodo-product-modified.tsv", "notes": "Root cause of earlier block was helper user-agent Mozilla/5.0 receiving HTTP 403; default curl and realistic Chrome/Safari UAs returned JSON."}
|
||||
{"date_checked":"2026-04-28","source":"komodo","entity":"steam-controller","claim":"Komodo moved the Steam Controller product record forward again while Machine and Frame remained on their 2026-04-24 modified timestamps.","evidence_type":"product_metadata_delta","confidence":"high","ref":"/Users/sean/steam_hardware_watch/2026-04-28/reports/komodo-product-modified.tsv","notes":"Controller modified 2026-04-28T18:32:11; Machine 2026-04-24T14:32:54; Frame 2026-04-24T14:33:36."}
|
||||
{"date_checked":"2026-04-28","source":"komodo","entity":"steam-controller","claim":"Komodo added a new Controller specs section and updated the Controller spec-image section with two new AVIF media records.","evidence_type":"sections_media_rollout","confidence":"high","ref":"/Users/sean/steam_hardware_watch/2026-04-28/reports/komodo-controller-sections.tsv","notes":"New section 433617 / 433487 modified 2026-04-27T09:41:01; section 433484 / 433473 modified 2026-04-28T19:05:51 with new media 433664 and 433679."}
|
||||
{"date_checked":"2026-04-28","source":"komodo","entity":"steam-controller","claim":"The current Komodo visual asset set is fully retrieved and organized into the media-style library.","evidence_type":"asset_retrieval","confidence":"high","ref":"/Users/sean/steam_hardware_watch/2026-04-28/assets/manifests/asset-library.json","notes":"130 discovered and 130 retrieved; library has 11 videos, 11 images, 3 logos, 3 poster frames, 42 thumbnails, and 60 variants, all classified as steam-controller."}
|
||||
{"date_checked":"2026-04-28","source":"valve-store","entity":"steam-controller,steam-machine,steam-frame","claim":"Official Valve store metadata changed again, including cleaner Steam Hardware submenu URLs under /hardware while still showing only broad 2026 timing.","evidence_type":"official_store_metadata","confidence":"high","ref":"/Users/sean/steam_hardware_watch/2026-04-28/api/valve/steam-hardware-root.html","notes":"Steam Hardware event modified 2026-04-28T10:09:22Z; Controller event modified 2026-04-28T18:31:12Z; no price, preorder date, or exact launch date found."}
|
||||
@@ -0,0 +1,131 @@
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import textwrap
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
|
||||
|
||||
def write_executable(path, content):
|
||||
path.write_text(textwrap.dedent(content).lstrip(), encoding="utf-8")
|
||||
path.chmod(path.stat().st_mode | stat.S_IXUSR)
|
||||
|
||||
|
||||
class CheckSteamDBTests(unittest.TestCase):
|
||||
def run_check(self, curl_mode, node_mode="success"):
|
||||
tmp = TemporaryDirectory()
|
||||
self.addCleanup(tmp.cleanup)
|
||||
tmp_path = Path(tmp.name)
|
||||
run_dir = tmp_path / "run"
|
||||
fake_bin = tmp_path / "bin"
|
||||
fake_bin.mkdir()
|
||||
log_dir = tmp_path / "logs"
|
||||
log_dir.mkdir()
|
||||
|
||||
write_executable(
|
||||
fake_bin / "curl",
|
||||
"""
|
||||
#!/bin/sh
|
||||
printf '%s\\n' "$*" >> "$FAKE_LOG_DIR/curl-args.log"
|
||||
case "$FAKE_CURL_MODE" in
|
||||
success)
|
||||
printf '%s\\n' '<html><title>Steam Controller AppID: 4165870</title><body>Coming soon prerelease</body></html>'
|
||||
;;
|
||||
challenge)
|
||||
printf '%s\\n' '<html><title>Just a moment...</title><body><h1>Checking your browser...</h1> Cloudflare</body></html>'
|
||||
;;
|
||||
fail)
|
||||
exit 22
|
||||
;;
|
||||
*)
|
||||
echo "unknown FAKE_CURL_MODE: $FAKE_CURL_MODE" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
""",
|
||||
)
|
||||
write_executable(
|
||||
fake_bin / "node",
|
||||
"""
|
||||
#!/bin/sh
|
||||
printf '%s\\n' "$*" >> "$FAKE_LOG_DIR/node-args.log"
|
||||
output=""
|
||||
for arg in "$@"; do
|
||||
output="$arg"
|
||||
done
|
||||
case "$FAKE_NODE_MODE" in
|
||||
success)
|
||||
printf '%s\\n' '<html><title>Steam Controller AppID: 4165870</title><body>Coming soon prerelease</body></html>' > "$output"
|
||||
;;
|
||||
challenge)
|
||||
printf '%s\\n' '<html><title>Just a moment...</title><body>Checking your browser... Cloudflare</body></html>' > "$output"
|
||||
;;
|
||||
fail)
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
echo "unknown FAKE_NODE_MODE: $FAKE_NODE_MODE" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
""",
|
||||
)
|
||||
|
||||
env = os.environ.copy()
|
||||
env.update(
|
||||
{
|
||||
"PATH": f"{fake_bin}:{env['PATH']}",
|
||||
"FAKE_CURL_MODE": curl_mode,
|
||||
"FAKE_NODE_MODE": node_mode,
|
||||
"FAKE_LOG_DIR": str(log_dir),
|
||||
"STEAMDB_PLAYWRIGHT_FALLBACK": "1",
|
||||
}
|
||||
)
|
||||
|
||||
result = subprocess.run(
|
||||
[str(ROOT / "scripts" / "check_steamdb.sh"), str(run_dir)],
|
||||
cwd=ROOT,
|
||||
env=env,
|
||||
text=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
check=False,
|
||||
)
|
||||
return result, run_dir, log_dir
|
||||
|
||||
def test_uses_realistic_chrome_user_agent_by_default(self):
|
||||
result, run_dir, log_dir = self.run_check("success")
|
||||
|
||||
self.assertEqual("", result.stderr)
|
||||
self.assertEqual(0, result.returncode)
|
||||
curl_args = (log_dir / "curl-args.log").read_text(encoding="utf-8")
|
||||
self.assertIn("Chrome/123.0.0.0", curl_args)
|
||||
self.assertNotIn("-A Mozilla/5.0 --retry", curl_args)
|
||||
self.assertTrue((run_dir / "api" / "steamdb" / "controller-app.html").exists())
|
||||
|
||||
def test_treats_cloudflare_challenge_html_as_blocked(self):
|
||||
result, run_dir, _ = self.run_check("challenge", node_mode="fail")
|
||||
|
||||
self.assertEqual("", result.stderr)
|
||||
self.assertEqual(0, result.returncode)
|
||||
self.assertFalse((run_dir / "api" / "steamdb" / "controller-app.html").exists())
|
||||
errors = (run_dir / "reports" / "steamdb-errors.txt").read_text(encoding="utf-8")
|
||||
self.assertIn("blocked or challenge page", errors)
|
||||
|
||||
def test_uses_playwright_fallback_after_challenge_page(self):
|
||||
result, run_dir, log_dir = self.run_check("challenge", node_mode="success")
|
||||
|
||||
self.assertEqual("", result.stderr)
|
||||
self.assertEqual(0, result.returncode)
|
||||
self.assertTrue((log_dir / "node-args.log").exists())
|
||||
html = (run_dir / "api" / "steamdb" / "controller-app.html").read_text(
|
||||
encoding="utf-8"
|
||||
)
|
||||
self.assertIn("Steam Controller", html)
|
||||
self.assertIn("Coming soon", html)
|
||||
errors = (run_dir / "reports" / "steamdb-errors.txt").read_text(encoding="utf-8")
|
||||
self.assertEqual("", errors)
|
||||
Reference in new issue
Block a user