mirror of
https://github.com/seanbetts/steam-hardware-watch.git
synced 2026-10-06 05:02:52 +02:00
Capture Steam Machine launch watch baseline
This commit is contained in:
1 parent
dab6f8b6d0
commit
ed9f4500a2
23 files changed
+1926
-82
No files matched your search
@@ -33,11 +33,14 @@ looks_like_importinfo_shipment_table() {
|
||||
|
||||
looks_like_importgenius_shipment_page() {
|
||||
path=$1
|
||||
for marker in "Bill of Lading" "GAME CONSOLE" "VALVE CORPORATION"; do
|
||||
for marker in "Bill of Lading" "VALVE CORPORATION"; do
|
||||
if ! grep -qi "$marker" "$path"; then
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
if ! grep -Eqi "GAME CONSOLE|VIRTUAL REALITY|WIRELESS PC CONTROLLER|VR CONTROLLER|HEADSET" "$path"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -123,6 +126,18 @@ fetch_importgenius \
|
||||
"importgenius-ingram-valve" \
|
||||
"importgenius-ingram-valve" \
|
||||
"https://www.importgenius.com/importers/ingram-micro-c-o-valve-corporation" || true
|
||||
fetch_importgenius \
|
||||
"importgenius-ceva-valve" \
|
||||
"importgenius-ceva-valve" \
|
||||
"https://www.importgenius.com/importers/ceva-c-o-valve-corporation" || true
|
||||
fetch_importgenius \
|
||||
"importgenius-ceva-nl-valve" \
|
||||
"importgenius-ceva-nl-valve" \
|
||||
"https://www.importgenius.com/importers/ceva-nl-c-o-valve-corporation" || true
|
||||
fetch_importgenius \
|
||||
"importgenius-valve-corp" \
|
||||
"importgenius-valve-corp" \
|
||||
"https://www.importgenius.com/importers/valve-corp" || true
|
||||
|
||||
python3 "$SCRIPT_DIR/parse_importinfo_shipments.py" \
|
||||
--manifest "$MANIFEST" \
|
||||
|
||||
+105
-7
@@ -33,8 +33,71 @@ load_local_env() {
|
||||
fi
|
||||
}
|
||||
|
||||
configure_playwright_runtime() {
|
||||
NODE_BIN="${NODE_BIN:-}"
|
||||
if [ -z "$NODE_BIN" ]; then
|
||||
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
||||
fi
|
||||
bundled_node="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
|
||||
if [ -z "$NODE_BIN" ] && [ -x "$bundled_node" ]; then
|
||||
NODE_BIN="$bundled_node"
|
||||
fi
|
||||
export NODE_BIN
|
||||
|
||||
if [ -z "${NODE_PATH:-}" ]; then
|
||||
for node_modules in \
|
||||
"$REPO_DIR/node_modules" \
|
||||
"$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules"
|
||||
do
|
||||
if [ -d "$node_modules" ]; then
|
||||
NODE_PATH="$node_modules"
|
||||
export NODE_PATH
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
cdp_endpoint_alive() {
|
||||
endpoint="${PLAYWRIGHT_CDP_ENDPOINT:-}"
|
||||
if [ -z "$endpoint" ]; then
|
||||
return 1
|
||||
fi
|
||||
curl --max-time 2 -fsS "$endpoint/json/version" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
KOMODO_BOOTSTRAP_ATTEMPTED=0
|
||||
|
||||
bootstrap_dedicated_browser() {
|
||||
if [ "${KOMODO_PLAYWRIGHT_FALLBACK:-0}" != "1" ] || [ "${KOMODO_AUTO_BOOTSTRAP:-1}" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$KOMODO_BOOTSTRAP_ATTEMPTED" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
KOMODO_BOOTSTRAP_ATTEMPTED=1
|
||||
bootstrap_cmd="${KOMODO_BOOTSTRAP_CMD:-$SCRIPT_DIR/bootstrap_komodo.sh}"
|
||||
if [ ! -x "$bootstrap_cmd" ]; then
|
||||
printf '%s\n' "Komodo bootstrap command not executable: $bootstrap_cmd" >> "$ERROR_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if "$bootstrap_cmd" >/dev/null 2>>"$ERROR_FILE"; then
|
||||
load_local_env
|
||||
fi
|
||||
}
|
||||
|
||||
maybe_set_cdp_endpoint() {
|
||||
if [ -n "${PLAYWRIGHT_CDP_ENDPOINT:-}" ] || [ -z "${PLAYWRIGHT_PROFILE_DIR:-}" ]; then
|
||||
if [ -n "${PLAYWRIGHT_CDP_ENDPOINT:-}" ]; then
|
||||
if cdp_endpoint_alive; then
|
||||
return 0
|
||||
fi
|
||||
unset PLAYWRIGHT_CDP_ENDPOINT
|
||||
fi
|
||||
|
||||
if [ -z "${PLAYWRIGHT_PROFILE_DIR:-}" ]; then
|
||||
bootstrap_dedicated_browser
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -50,7 +113,13 @@ maybe_set_cdp_endpoint() {
|
||||
if [ -n "$port" ]; then
|
||||
PLAYWRIGHT_CDP_ENDPOINT="http://127.0.0.1:$port"
|
||||
export PLAYWRIGHT_CDP_ENDPOINT
|
||||
if cdp_endpoint_alive; then
|
||||
return 0
|
||||
fi
|
||||
unset PLAYWRIGHT_CDP_ENDPOINT
|
||||
fi
|
||||
|
||||
bootstrap_dedicated_browser
|
||||
}
|
||||
|
||||
load_local_env
|
||||
@@ -71,14 +140,21 @@ fetch_json() {
|
||||
|
||||
rm -f "$tmp"
|
||||
|
||||
if [ "$allow_fallback" = "1" ] && [ "${KOMODO_PLAYWRIGHT_FALLBACK:-0}" = "1" ] && command -v node >/dev/null 2>&1; then
|
||||
if [ "$allow_fallback" = "1" ] && [ "${KOMODO_PLAYWRIGHT_FALLBACK:-0}" = "1" ]; then
|
||||
configure_playwright_runtime
|
||||
if [ -z "${NODE_BIN:-}" ]; then
|
||||
printf '%s\n' "Playwright fallback unavailable: node executable not found" >> "$ERROR_FILE"
|
||||
rm -f "$tmp"
|
||||
printf '%s\n' "failed to fetch JSON from $url" >> "$ERROR_FILE"
|
||||
return 1
|
||||
fi
|
||||
maybe_set_cdp_endpoint
|
||||
PLAYWRIGHT_CORE_PATH="${PLAYWRIGHT_CORE_PATH:-}"
|
||||
if command -v playwright-cli >/dev/null 2>&1; then
|
||||
PLAYWRIGHT_CLI_BIN="$(command -v playwright-cli)"
|
||||
PLAYWRIGHT_CORE_PATH="$(CDPATH= cd -- "$(dirname "$PLAYWRIGHT_CLI_BIN")/../lib/node_modules/@playwright/cli/node_modules/playwright-core" 2>/dev/null && pwd || true)"
|
||||
fi
|
||||
PLAYWRIGHT_CORE_PATH="$PLAYWRIGHT_CORE_PATH" node "$SCRIPT_DIR/fetch_with_playwright.js" "$url" "$tmp"
|
||||
PLAYWRIGHT_CORE_PATH="$PLAYWRIGHT_CORE_PATH" "$NODE_BIN" "$SCRIPT_DIR/fetch_with_playwright.js" "$url" "$tmp"
|
||||
if jq empty "$tmp" >/dev/null 2>&1; then
|
||||
mv "$tmp" "$OUT_DIR/$name.json"
|
||||
return 0
|
||||
@@ -124,8 +200,14 @@ do
|
||||
fi
|
||||
done
|
||||
|
||||
for id in $(jq -r '.[].id' "$OUT_DIR/sections-controller-search.json" 2>/dev/null || true); do
|
||||
fetch_json "media-parent-section-$id" "https://komodostation.com/wp-json/wp/v2/media?parent=$id&per_page=100" || true
|
||||
for section_report in \
|
||||
sections-controller-search \
|
||||
sections-machine-search \
|
||||
sections-frame-search
|
||||
do
|
||||
for id in $(jq -r '.[].id' "$OUT_DIR/$section_report.json" 2>/dev/null || true); do
|
||||
fetch_json "media-parent-section-$id" "https://komodostation.com/wp-json/wp/v2/media?parent=$id&per_page=100" || true
|
||||
done
|
||||
done
|
||||
|
||||
{
|
||||
@@ -153,8 +235,24 @@ jq -r '
|
||||
| @tsv
|
||||
' "$OUT_DIR/media-controller-search.json" > "$REPORT_DIR/komodo-controller-media-interesting.tsv"
|
||||
|
||||
: > "$REPORT_DIR/komodo-machine-media.tsv"
|
||||
: > "$REPORT_DIR/komodo-frame-media.tsv"
|
||||
write_section_media_report() {
|
||||
sections_file="$1"
|
||||
output_file="$2"
|
||||
: > "$output_file"
|
||||
for id in $(jq -r '.[].id' "$sections_file" 2>/dev/null || true); do
|
||||
media_file="$OUT_DIR/media-parent-section-$id.json"
|
||||
if [ -f "$media_file" ]; then
|
||||
jq -r '
|
||||
.[]?
|
||||
| [.id,.date,.modified,.slug,.mime_type,.post,.source_url]
|
||||
| @tsv
|
||||
' "$media_file" >> "$output_file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
write_section_media_report "$OUT_DIR/sections-machine-search.json" "$REPORT_DIR/komodo-machine-media.tsv"
|
||||
write_section_media_report "$OUT_DIR/sections-frame-search.json" "$REPORT_DIR/komodo-frame-media.tsv"
|
||||
|
||||
jq -r '
|
||||
.[]?
|
||||
|
||||
@@ -26,8 +26,71 @@ load_local_env() {
|
||||
fi
|
||||
}
|
||||
|
||||
configure_playwright_runtime() {
|
||||
NODE_BIN="${NODE_BIN:-}"
|
||||
if [ -z "$NODE_BIN" ]; then
|
||||
NODE_BIN="$(command -v node 2>/dev/null || true)"
|
||||
fi
|
||||
bundled_node="$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/bin/node"
|
||||
if [ -z "$NODE_BIN" ] && [ -x "$bundled_node" ]; then
|
||||
NODE_BIN="$bundled_node"
|
||||
fi
|
||||
export NODE_BIN
|
||||
|
||||
if [ -z "${NODE_PATH:-}" ]; then
|
||||
for node_modules in \
|
||||
"$REPO_DIR/node_modules" \
|
||||
"$HOME/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules"
|
||||
do
|
||||
if [ -d "$node_modules" ]; then
|
||||
NODE_PATH="$node_modules"
|
||||
export NODE_PATH
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
cdp_endpoint_alive() {
|
||||
endpoint="${STEAMDB_CDP_ENDPOINT:-}"
|
||||
if [ -z "$endpoint" ]; then
|
||||
return 1
|
||||
fi
|
||||
curl --max-time 2 -fsS "$endpoint/json/version" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
STEAMDB_BOOTSTRAP_ATTEMPTED=0
|
||||
|
||||
bootstrap_dedicated_browser() {
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" != "1" ] || [ "${STEAMDB_AUTO_BOOTSTRAP:-1}" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$STEAMDB_BOOTSTRAP_ATTEMPTED" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
STEAMDB_BOOTSTRAP_ATTEMPTED=1
|
||||
bootstrap_cmd="${STEAMDB_BOOTSTRAP_CMD:-$SCRIPT_DIR/bootstrap_steamdb.sh}"
|
||||
if [ ! -x "$bootstrap_cmd" ]; then
|
||||
printf '%s\n' "SteamDB bootstrap command not executable: $bootstrap_cmd" >> "$ERROR_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if "$bootstrap_cmd" >/dev/null 2>>"$ERROR_FILE"; then
|
||||
load_local_env
|
||||
fi
|
||||
}
|
||||
|
||||
maybe_set_cdp_endpoint() {
|
||||
if [ -n "${STEAMDB_CDP_ENDPOINT:-}" ] || [ -z "${STEAMDB_PROFILE_DIR:-}" ]; then
|
||||
if [ -n "${STEAMDB_CDP_ENDPOINT:-}" ]; then
|
||||
if cdp_endpoint_alive; then
|
||||
return 0
|
||||
fi
|
||||
unset STEAMDB_CDP_ENDPOINT
|
||||
fi
|
||||
|
||||
if [ -z "${STEAMDB_PROFILE_DIR:-}" ]; then
|
||||
bootstrap_dedicated_browser
|
||||
return 0
|
||||
fi
|
||||
|
||||
@@ -43,7 +106,13 @@ maybe_set_cdp_endpoint() {
|
||||
if [ -n "$port" ]; then
|
||||
STEAMDB_CDP_ENDPOINT="http://127.0.0.1:$port"
|
||||
export STEAMDB_CDP_ENDPOINT
|
||||
if cdp_endpoint_alive; then
|
||||
return 0
|
||||
fi
|
||||
unset STEAMDB_CDP_ENDPOINT
|
||||
fi
|
||||
|
||||
bootstrap_dedicated_browser
|
||||
}
|
||||
|
||||
is_challenge_page() {
|
||||
@@ -74,9 +143,11 @@ fetch_html() {
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" = "1" ] && command -v node >/dev/null 2>&1; then
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" = "1" ]; then
|
||||
configure_playwright_runtime
|
||||
maybe_set_cdp_endpoint
|
||||
if node "$SCRIPT_DIR/fetch_steamdb_with_playwright.js" "$url" "$tmp" >/dev/null 2>&1 \
|
||||
if [ -n "${NODE_BIN:-}" ] \
|
||||
&& "$NODE_BIN" "$SCRIPT_DIR/fetch_steamdb_with_playwright.js" "$url" "$tmp" >/dev/null 2>&1 \
|
||||
&& [ -s "$tmp" ] \
|
||||
&& ! is_challenge_page "$tmp"; then
|
||||
mv "$tmp" "$out"
|
||||
|
||||
@@ -9,6 +9,8 @@ fi
|
||||
RUN_DIR="$1"
|
||||
OUT_DIR="$RUN_DIR/api/steamvr-depots"
|
||||
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/steamvr-depots-errors.txt"
|
||||
@@ -28,6 +30,101 @@ is_challenge_page() {
|
||||
rg -qi "Checking your browser|Just a moment|Cloudflare|cf-chl|cf-browser-verification" "$path"
|
||||
}
|
||||
|
||||
load_steamdb_env() {
|
||||
env_file="${STEAMDB_ENV_FILE:-$REPO_DIR/.local/steamdb-env.sh}"
|
||||
if [ -f "$env_file" ]; then
|
||||
# shellcheck disable=SC1090
|
||||
. "$env_file"
|
||||
fi
|
||||
}
|
||||
|
||||
steamdb_cdp_endpoint_alive() {
|
||||
endpoint="${STEAMDB_CDP_ENDPOINT:-}"
|
||||
if [ -z "$endpoint" ]; then
|
||||
return 1
|
||||
fi
|
||||
curl --max-time 2 -fsS "$endpoint/json/version" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
STEAMDB_BOOTSTRAP_ATTEMPTED=0
|
||||
|
||||
bootstrap_steamdb_browser() {
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" != "1" ] || [ "${STEAMDB_AUTO_BOOTSTRAP:-1}" = "0" ]; then
|
||||
return 0
|
||||
fi
|
||||
if [ "$STEAMDB_BOOTSTRAP_ATTEMPTED" = "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
STEAMDB_BOOTSTRAP_ATTEMPTED=1
|
||||
bootstrap_cmd="${STEAMDB_BOOTSTRAP_CMD:-$SCRIPT_DIR/bootstrap_steamdb.sh}"
|
||||
if [ ! -x "$bootstrap_cmd" ]; then
|
||||
printf '%s\n' "SteamDB bootstrap command not executable: $bootstrap_cmd" >> "$ERROR_FILE"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if "$bootstrap_cmd" >/dev/null 2>>"$ERROR_FILE"; then
|
||||
load_steamdb_env
|
||||
fi
|
||||
}
|
||||
|
||||
ensure_steamdb_browser() {
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" != "1" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${STEAMDB_CDP_ENDPOINT:-}" ]; then
|
||||
if steamdb_cdp_endpoint_alive; then
|
||||
return 0
|
||||
fi
|
||||
unset STEAMDB_CDP_ENDPOINT
|
||||
fi
|
||||
|
||||
if [ -n "${STEAMDB_PROFILE_DIR:-}" ]; then
|
||||
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
|
||||
if steamdb_cdp_endpoint_alive; then
|
||||
return 0
|
||||
fi
|
||||
unset STEAMDB_CDP_ENDPOINT
|
||||
fi
|
||||
fi
|
||||
|
||||
bootstrap_steamdb_browser
|
||||
}
|
||||
|
||||
fetch_html_with_steamdb_playwright() {
|
||||
url="$1"
|
||||
output="$2"
|
||||
tmp="$output.tmp.playwright"
|
||||
|
||||
if [ "${STEAMDB_PLAYWRIGHT_FALLBACK:-0}" != "1" ] || ! command -v node >/dev/null 2>&1; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
ensure_steamdb_browser
|
||||
|
||||
if node "$SCRIPT_DIR/fetch_steamdb_with_playwright.js" "$url" "$tmp" >/dev/null 2>&1 \
|
||||
&& [ -s "$tmp" ] \
|
||||
&& ! is_challenge_page "$tmp"; then
|
||||
mv "$tmp" "$output"
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$tmp"
|
||||
return 1
|
||||
}
|
||||
|
||||
fetch_html() {
|
||||
name="$1"
|
||||
url="$2"
|
||||
@@ -40,6 +137,11 @@ fetch_html() {
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$tmp"
|
||||
if fetch_html_with_steamdb_playwright "$url" "$out"; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
rm -f "$tmp" "$out"
|
||||
printf '%s\n' "failed to fetch $url: blocked or challenge page" >> "$ERROR_FILE"
|
||||
return 1
|
||||
@@ -57,6 +159,8 @@ fetch_json() {
|
||||
fi
|
||||
}
|
||||
|
||||
load_steamdb_env
|
||||
|
||||
fetch_html "steamdb-depots.html" "https://steamdb.info/app/250820/depots/" || true
|
||||
fetch_json "steamvr-news.json" "https://api.steampowered.com/ISteamNews/GetNewsForApp/v2/?appid=250820&count=40&maxlength=12000&format=json" || true
|
||||
|
||||
|
||||
@@ -6,13 +6,14 @@ function loadPlaywright() {
|
||||
const candidates = [];
|
||||
if (process.env.PLAYWRIGHT_CORE_PATH) candidates.push(process.env.PLAYWRIGHT_CORE_PATH);
|
||||
candidates.push("playwright-core");
|
||||
candidates.push("playwright");
|
||||
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.");
|
||||
throw new Error("Unable to load Playwright. Set PLAYWRIGHT_CORE_PATH, set NODE_PATH to a node_modules directory, or install playwright/playwright-core.");
|
||||
}
|
||||
|
||||
function launchOptions(headless) {
|
||||
|
||||
@@ -6,6 +6,7 @@ function loadPlaywright() {
|
||||
const candidates = [];
|
||||
if (process.env.PLAYWRIGHT_CORE_PATH) candidates.push(process.env.PLAYWRIGHT_CORE_PATH);
|
||||
candidates.push("playwright-core");
|
||||
candidates.push("playwright");
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
return require(candidate);
|
||||
@@ -13,7 +14,7 @@ function loadPlaywright() {
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
"Unable to load playwright-core. Set PLAYWRIGHT_CORE_PATH or install playwright-core."
|
||||
"Unable to load Playwright. Set PLAYWRIGHT_CORE_PATH, set NODE_PATH to a node_modules directory, or install playwright/playwright-core."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ function loadPlaywright() {
|
||||
const candidates = [];
|
||||
if (process.env.PLAYWRIGHT_CORE_PATH) candidates.push(process.env.PLAYWRIGHT_CORE_PATH);
|
||||
candidates.push("playwright-core");
|
||||
candidates.push("playwright");
|
||||
for (const candidate of candidates) {
|
||||
try {
|
||||
return require(candidate);
|
||||
@@ -13,7 +14,7 @@ function loadPlaywright() {
|
||||
}
|
||||
}
|
||||
throw new Error(
|
||||
"Unable to load playwright-core. Set PLAYWRIGHT_CORE_PATH or install playwright-core."
|
||||
"Unable to load Playwright. Set PLAYWRIGHT_CORE_PATH, set NODE_PATH to a node_modules directory, or install playwright/playwright-core."
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -56,6 +56,9 @@ OUTPUT_FIELDS = [
|
||||
PARTY_TERMS = ("VALVE", "CEVA", "INGRAM MICRO", "TECH-FRONT", "CHENG UEI")
|
||||
PRODUCT_TERMS = (
|
||||
"GAME CONSOLE",
|
||||
"VIRTUAL REALITY",
|
||||
"VIRTUAL REALITY DEVICES",
|
||||
"WIRELESS PC CONTROLLER",
|
||||
"VR CONTROLLER",
|
||||
"CONTROLLER",
|
||||
"STEAM",
|
||||
@@ -70,6 +73,9 @@ SOURCE_URLS = {
|
||||
"tech-front-game-console": "https://www.importinfo.com/search?s=TECH-FRONT%20GAME%20CONSOLE%20VALVE",
|
||||
"valve-corporation-game-console": "https://www.importinfo.com/search?s=VALVE%20CORPORATION%20GAME%20CONSOLE",
|
||||
"importgenius-ingram-valve": "https://www.importgenius.com/importers/ingram-micro-c-o-valve-corporation",
|
||||
"importgenius-ceva-valve": "https://www.importgenius.com/importers/ceva-c-o-valve-corporation",
|
||||
"importgenius-ceva-nl-valve": "https://www.importgenius.com/importers/ceva-nl-c-o-valve-corporation",
|
||||
"importgenius-valve-corp": "https://www.importgenius.com/importers/valve-corp",
|
||||
}
|
||||
|
||||
|
||||
@@ -135,11 +141,11 @@ class TextParser(HTMLParser):
|
||||
self.parts = []
|
||||
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag in ("br", "p", "tr", "li", "h1", "h2", "h3", "section"):
|
||||
if tag in ("br", "p", "tr", "li", "h1", "h2", "h3", "section", "div"):
|
||||
self.parts.append("\n")
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in ("p", "tr", "li", "h1", "h2", "h3", "section"):
|
||||
if tag in ("p", "tr", "li", "h1", "h2", "h3", "section", "div"):
|
||||
self.parts.append("\n")
|
||||
|
||||
def handle_data(self, data):
|
||||
@@ -148,6 +154,10 @@ class TextParser(HTMLParser):
|
||||
def text(self):
|
||||
return html.unescape(" ".join("".join(self.parts).split()))
|
||||
|
||||
def lines(self):
|
||||
raw_text = html.unescape("".join(self.parts))
|
||||
return [" ".join(line.split()) for line in raw_text.splitlines() if line.strip()]
|
||||
|
||||
|
||||
def normalize_header(value):
|
||||
return FIELD_MAP.get(value.strip().lower(), "")
|
||||
@@ -177,34 +187,83 @@ def parse_rows(spec):
|
||||
def parse_importgenius_rows(spec):
|
||||
parser = TextParser()
|
||||
parser.feed(spec.path.read_text(encoding="utf-8", errors="ignore"))
|
||||
text = parser.text()
|
||||
text_lines = parser.lines() or [parser.text()]
|
||||
importer_pattern = (
|
||||
r"(?:INGRAM\s+MICRO|CEVA(?:\s+NL)?)\s+C/O\s+VALVE\s+CORPORATION"
|
||||
r"|VALVE\s+CORPORATION"
|
||||
)
|
||||
supplier_pattern = (
|
||||
r"TECH-?FRONT\s+\(CHONGQING\)\s+COMPUTER\s+CO"
|
||||
r"|CHENG\s+UEI\s+PRECISION\s+IND\.\s+CO\s+LTD"
|
||||
)
|
||||
pattern = re.compile(
|
||||
r"(?:^|\s)(?P<rank>\d+)\s+"
|
||||
r"(?P<bol>[A-Z0-9]+)\s+"
|
||||
r"(?P<product>GAME\s+CONSOLE\s*\.?)\s+"
|
||||
r"(?P<importer>(?:INGRAM\s+MICRO|CEVA)\s+C/O\s+VALVE\s+CORPORATION)\s+"
|
||||
r"(?P<supplier>TECH-?FRONT\s+\(CHONGQING\)\s+COMPUTER\s+CO)\s+"
|
||||
r"(?P<product>[A-Z0-9][A-Z0-9 ./&()'-]+?)\s+"
|
||||
r"(?P<importer>" + importer_pattern + r")\s+"
|
||||
r"(?P<supplier>" + supplier_pattern + r")\s+"
|
||||
r"(?P<arrival>\d{4}-\d{2}-\d{2})\s+"
|
||||
r"(?P<country>[A-Za-z ]+?)\s+"
|
||||
r"(?P<weight>[\d,]+\s+Kgs)\s+"
|
||||
r"(?P<quantity>\d+\s+PKG)",
|
||||
re.IGNORECASE,
|
||||
)
|
||||
rows = []
|
||||
for match in pattern.finditer(text):
|
||||
importer_re = re.compile(importer_pattern, re.IGNORECASE)
|
||||
supplier_re = re.compile(supplier_pattern, re.IGNORECASE)
|
||||
rank_re = re.compile(r"\d+")
|
||||
bol_re = re.compile(r"[A-Z0-9]+")
|
||||
date_re = re.compile(r"\d{4}-\d{2}-\d{2}")
|
||||
country_re = re.compile(r"[A-Za-z ]+")
|
||||
weight_re = re.compile(r"[\d,]+\s+Kgs", re.IGNORECASE)
|
||||
quantity_re = re.compile(r"\d+\s+PKG", re.IGNORECASE)
|
||||
|
||||
def build_record(match):
|
||||
record = {field: "" for field in OUTPUT_FIELDS}
|
||||
record["source"] = "importgenius"
|
||||
record["query"] = spec.query
|
||||
record["house_bol"] = match.group("bol").upper()
|
||||
record["arrival_date"] = match.group("arrival")
|
||||
record["foreign_port"] = match.group("country").strip()
|
||||
record["quantity"] = " ".join(match.group("quantity").split())
|
||||
record["weight"] = " ".join(match.group("weight").split())
|
||||
record["shipper"] = normalize_company(match.group("supplier"))
|
||||
record["consignee"] = normalize_company(match.group("importer"))
|
||||
record["house_bol"] = match["bol"].upper()
|
||||
record["arrival_date"] = match["arrival"]
|
||||
record["foreign_port"] = match["country"].strip()
|
||||
record["quantity"] = " ".join(match["quantity"].split())
|
||||
record["weight"] = " ".join(match["weight"].split())
|
||||
record["shipper"] = normalize_company(match["supplier"])
|
||||
record["consignee"] = normalize_company(match["importer"])
|
||||
record["notify_party"] = record["consignee"]
|
||||
record["commodity"] = " ".join(match.group("product").upper().split())
|
||||
record["commodity"] = " ".join(match["product"].upper().split()).strip(" .")
|
||||
record["source_url"] = spec.source_url
|
||||
return record
|
||||
|
||||
rows = []
|
||||
for line in text_lines:
|
||||
for match in pattern.finditer(line):
|
||||
record = build_record(match.groupdict())
|
||||
if is_relevant(record):
|
||||
rows.append(record)
|
||||
for index in range(0, max(0, len(text_lines) - 8)):
|
||||
values = text_lines[index : index + 9]
|
||||
if not (
|
||||
rank_re.fullmatch(values[0])
|
||||
and bol_re.fullmatch(values[1])
|
||||
and importer_re.fullmatch(values[3])
|
||||
and supplier_re.fullmatch(values[4])
|
||||
and date_re.fullmatch(values[5])
|
||||
and country_re.fullmatch(values[6])
|
||||
and weight_re.fullmatch(values[7])
|
||||
and quantity_re.fullmatch(values[8])
|
||||
):
|
||||
continue
|
||||
record = build_record(
|
||||
{
|
||||
"bol": values[1],
|
||||
"product": values[2],
|
||||
"importer": values[3],
|
||||
"supplier": values[4],
|
||||
"arrival": values[5],
|
||||
"country": values[6],
|
||||
"weight": values[7],
|
||||
"quantity": values[8],
|
||||
}
|
||||
)
|
||||
if is_relevant(record):
|
||||
rows.append(record)
|
||||
return rows
|
||||
@@ -350,7 +409,9 @@ def write_report(rows, output):
|
||||
"## Limitations",
|
||||
"",
|
||||
"Customs data is corroborating evidence, not a standalone confirmation. "
|
||||
"GAME CONSOLE descriptions are medium confidence without other identifiers.",
|
||||
"Product descriptions such as GAME CONSOLE, VIRTUAL REALITY DEVICES, "
|
||||
"and WIRELESS PC CONTROLLER do not prove the final Valve retail SKU without "
|
||||
"other identifiers.",
|
||||
"",
|
||||
]
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user