# Changelog of WeChat Official Account Scraper — Articles & Monitoring (`korado_labs/wechat-official-account-scraper`) Actor

- **URL**: https://apify.com/korado\_labs/wechat-official-account-scraper/changelog.md
- **Full Actor documentation**: https://apify.com/korado\_labs/wechat-official-account-scraper.md

## Changelog

### 0.4.1 — 2026-08-19

- **Proxy support.** New optional `proxyConfiguration` (standard Apify Proxy
  editor) and `proxyUrls` (bring-your-own HTTP(S) proxies, rotated per request).
  Routing goes through undici `ProxyAgent` dispatchers, one cached per proxy URL.
  A China residential exit restores the source index's region-gated account
  vertical; proxies also add anti-bot resilience for heavy runs.
- **Anti-bot session reset.** When the source index serves its interstitial, the
  adapter now drops the blocked session cookies, re-warms a fresh session from
  the landing page, backs off, and retries (twice by default) instead of failing
  the stream immediately. A sticky wall still ends in a typed `blocked` outcome,
  which stays free for the caller.
- Verified identity stability beyond one session: the same article kept its
  source document id across a 6.5-hour gap and different datacenters.

### 0.4.0 — 2026-08-19

- **Stable article identity across runs.** `articleId` now comes from the source's
  own document id (Sogou `li[d]`, WeChat `biz`/`mid`/`idx`) instead of hashing the
  URL. Sogou re-encrypts its `/link` redirect blobs per session, so URL-derived ids
  changed every run and change tracking would have re-billed every article as new.
- **`resolveLinks` input (default off).** Replaces session-bound Sogou redirect
  links with the real `mp.weixin.qq.com` article URL using Sogou's own `k`/`h`
  anti-bot handshake and session cookies. One extra request per row.
- **Account search geo-gate fallback.** Sogou serves the account vertical (type=1)
  only to some regions; elsewhere every query returns a soft "no results" page.
  When that happens, accounts are now derived from public article pages
  (name + gh\_ id) instead of returning nothing.
- Article detail pages now expose a permanent `wx-{biz}-{mid}-{idx}` identity even
  for signed, expiring `src=11` URLs.
- `RUN_STATS.outputRows` counts delivered rows again (owner/dev runs charge 0).
- Dockerfile: install devDependencies explicitly (base image sets
  NODE\_ENV=production) and wipe the base image's preinstalled node\_modules
  (its bundled crawlee conflicted with the SDK's @crawlee/\* at Actor.init).
- Dataset schema: removed the `$schema` key from `fields` (Apify's schema
  compiler cannot resolve the 2020-12 meta-schema and fails the build).

### 0.3.1-rc.1

- Empty / default `articleSearch` input now uses the demo keyword `科技` so
  Apify's daily quality check can return real rows. A succeed-with-zero-rows
  run is treated as a failure by the Store and can flag the Actor under
  maintenance. Provided-but-unusable input still fails.
- Dataset schema `fields` is now descriptive, not restrictive: removed `enum`,
  `required`, and numeric bounds that would kill a customer's run on upstream
  HTML drift.
- Output schema now includes `actorSpecification` (required to publish) and no
  longer uses the old "OA Monitor" title.
- `actor.json` title shortened to the 63-character Store limit. `categories`
  and `defaultMemoryMbytes` removed because they are not accepted actor.json
  fields and can fail `apify push`.
- Optional source arrays (`articleUrls`, `watchlist`) no longer have schema
  defaults, so they cannot be injected into unrelated operations.
- Charging an undefined pay-per-event name no longer aborts the run: the row is
  delivered unbilled instead.
- `account` / `watchlist` accept wxid and `gh_` ids. Identifiers are resolved
  through public account search; unknown ids fail instead of silently returning
  an empty history.
- Dockerfile prefers `npm ci` when a lockfile is present.
- **Live Sogou HTML drift (2026-08-19):** article publisher names moved from
  `<a class="account">` to `<span class="all-time-y2">`. The article parser now
  accepts both and HTML-unescapes result hrefs. Account-directory search
  (`type=1`) currently returns empty official-account pages for every public
  query tested; `accountSearch` / `monitorAccounts` cannot pass a live kill
  gate until that index returns cards again.

### 0.3.0-rc.1

- **Monitoring now works with no setup.** Added an Apify key-value state backend
  (`src/store/kvStore.ts`) so `monitorAccounts` retains history for every Store
  user. Previously it required `DATABASE_URL`, which only the Actor owner could
  set, making change tracking unusable for Store customers.
- PostgreSQL remains supported and is now the optional upgrade for concurrent
  monitor runs; the key-value backend guards against overlap with a lease and
  reports `StateStoreBusyError` as an unbilled row rather than failing the run.
- Key-value state is sharded across 256 records, bounded per shard, pruned by
  last-seen time, and sanitized on read so a corrupt record degrades to
  "unseen" instead of crashing a run.
- `RUN_STATS` now records which `stateBackend` was used.
- Renamed the Actor to `wechat-official-account-scraper` and rewrote the Store
  title, description, categories and README for search visibility.
- Documented pay-per-event pricing, free-plan limits and out-of-scope data
  directly in the README and input schema.

### 0.2 — release candidate

- Replaced separate dataset writes and PPE calls with budget-aware `Actor.pushData(item, eventName)` delivery.
- Added a durable Postgres delivery outbox and per-article advisory locking for overlapping runs.
- Removed unsupported metrics and translation claims from the public Actor contract.
- Added publicly indexed recent-account and monitoring operations based on exact display names.
- Added runtime input bounds, response byte limits, timeouts, deduplication, SSRF-safe webhooks, output schema, and dataset schema.
- Added Postgres, fault-injection, fuzz, performance, and stress release gates.
- Fixed the failure path in `src/main.ts`. `Actor.exit(1)` is invalid because `Actor.exit()` accepts `string | ExitOptions`; it failed to compile against the real SDK types and would not have marked a crashed run as failed. Now uses `Actor.fail({ exitCode: 1, statusMessage })`.
- Fixed `.actor/input_schema.json` for `apify push` validation: added the required `description` on `operation`, removed the contradictory `required` + `default` pairing, and added a `keyword` prefill so the Actor runs from the Store UI with no typing.
- Added a regression test locking the Apify input-schema publish rules (title, description, editor on string/array, enum on select).
- Added QA-only ambient type stubs (`qa/typestubs/modules.d.ts`) so the Apify SDK and `pg` seams stay typechecked in offline environments.
- Closed a webhook SSRF gap: production delivery now validates every DNS answer, rejects private/special-use IPv4 and IPv6, pins the selected public IP into the TLS connection, preserves the original hostname for SNI/certificate validation, refuses redirects, and bounds retry schedules and response handling.
- Disabled redirects for user-supplied WeChat article-detail URLs to prevent an upstream open redirect from becoming an SSRF path.
- Invalid input/configuration now emits one free diagnostic row and marks the Apify run `FAILED`; upstream blocking remains intentionally fail-soft for scheduled monitoring.
- Expanded the security regression suite; correctness/E2E is now 63/63 with 6/6 hyper-stress gates.

### 0.1

- Initial architecture scaffold.
