house worker

This commit is contained in:
2026-10-11 22:05:05 +00:00
parent ecd49fc410
commit b52e9188f4
6 changed files with 247 additions and 24 deletions

View File

@@ -6,6 +6,7 @@
* HOUSE_API_URL=https://stanrunge.dev HOUSE_WORKER_TOKEN=... bun scripts/house-worker.ts
* bun scripts/house-worker.ts --once # one fast-lane poll, then exit
* bun scripts/house-worker.ts --dry-run # fetch + parse, print, send nothing
* bun scripts/house-worker.ts --check-api # check website auth only, no Pararius requests
*
* Politeness rules, in order of importance:
* - one request at a time, at least HOUSE_MIN_GAP_SECONDS apart (with jitter)
@@ -20,6 +21,7 @@ import {
searchPageUrl
} from '../src/lib/house-tracker/pararius';
import { backoffMs, nextDelayMs } from '../src/lib/house-tracker/schedule';
import { createWorkerApi } from '../src/lib/house-tracker/worker-api';
import type {
Lane,
ListingDetail,
@@ -31,6 +33,7 @@ import type {
const args = new Set(process.argv.slice(2));
const once = args.has('--once');
const checkApi = args.has('--check-api');
const dryRun = args.has('--dry-run') || process.env.HOUSE_DRY_RUN === '1';
const list = (value: string | undefined, fallback: string[]) =>
@@ -66,8 +69,18 @@ const config = {
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36'
};
if (!dryRun && (!config.api || config.token.length < 32))
throw new Error('HOUSE_API_URL and HOUSE_WORKER_TOKEN (32+ chars) are required');
if (checkApi && dryRun) throw new Error('--check-api cannot be combined with dry-run mode');
const requestApi = dryRun
? null
: createWorkerApi(
{
url: config.api,
token: config.token,
accessClientId: process.env.HOUSE_CF_ACCESS_CLIENT_ID,
accessClientSecret: process.env.HOUSE_CF_ACCESS_CLIENT_SECRET
},
checkApi ? 1 : 4
);
const SOURCE = 'pararius';
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -173,28 +186,8 @@ async function getHtml(url: string, referer?: string): Promise<{ html: string; s
// ---------------------------------------------------------------------------
// Website API
async function api<T>(path: string, body: unknown): Promise<T | null> {
if (dryRun) return null;
for (let attempt = 0; attempt < 4; attempt++) {
try {
const response = await fetch(`${config.api}/projects/house-tracker/api/worker/${path}`, {
method: 'POST',
headers: { 'content-type': 'application/json', authorization: `Bearer ${config.token}` },
body: JSON.stringify(body),
signal: AbortSignal.timeout(30_000)
});
if (response.ok) return (await response.json()) as T;
const text = await response.text();
if (response.status < 500)
throw new Error(`API ${path} ${response.status}: ${text.slice(0, 300)}`);
log(`API ${path} ${response.status}, retrying`);
} catch (error) {
if (attempt === 3) throw error;
log(`API ${path} failed: ${(error as Error).message}, retrying`);
}
await sleep(5000 * 2 ** attempt);
}
return null;
async function api<T>(path: 'sync' | 'details' | 'report', body: unknown): Promise<T | null> {
return requestApi ? requestApi<T>(path, body) : null;
}
async function report(
@@ -340,6 +333,12 @@ async function guarded(
}
async function main() {
if (checkApi) {
const result = await requestApi!<{ ok: boolean }>('health');
if (result?.ok !== true) throw new Error('API health: unexpected response');
log('API connection OK. Worker authentication passed; no Pararius requests were made.');
return;
}
log(
`house worker: ${config.fastSearches.length} fast, ${config.slowSearches.length} slow searches, mode=${config.fetchMode}${dryRun ? ', dry run' : ''}`
);