pdh-android-collector · v1.0.0 · 2026-06-11 · sha256 0569989720056580
pdh-android-collector v1.0.0A
Immutable. This exact content is served forever at /api/v1/blob/0569989720056580.
---
name: pdh-android-collector
display-name: PDH 安卓本地采集器构建
description: Build or debug a Personal-Data-Hub collector that pulls a Chinese Android app's data off a rooted phone over ADB. Encodes the real-device rule — prefer on-device plaintext SQLite over fragile web APIs (cookies/signing/endpoints drift; local DBs are stable + sign-free).
version: 1.0.0
category: integration
user-invocable: true
tags:
[
pdh,
personal-data-hub,
android,
adb,
root,
sqlite,
collector,
douyin,
toutiao,
weibo,
kuaishou,
xiaohongshu,
data-collection,
]
capabilities:
[
device-mapping,
local-db-extraction,
web-api-fallback,
signbridge-verification,
collector-scaffolding,
]
instructions: |
Use this skill when adding or debugging on-device data collection for a Chinese
Android app (抖音/头条/微博/快手/小红书/淘宝/拼多多/12306/高德/携程/games), or when a
`cc hub <platform>-adb-sync` returns 0 rows. The durable strategy: read the app's
on-device PLAINTEXT SQLite DBs over ADB+root rather than its web API. Pipeline:
map device → try existing collector + read reason code → inspect local DBs (magic
header + node:sqlite) → build a reader (su base64 pull + dual-load sqlite read) →
normalize into the existing adapter's snapshot kinds → wire BOTH cli+desktop bridges
→ verify on the real device via an Electron harness. Reach for a web fetcher only
when the data isn't on-device; signed endpoints need the desktop SignBridge.
Usage: /pdh-android-collector "<app or package>" — or follow the body as a runbook.
examples:
- input: '/pdh-android-collector "抖音 观看历史"'
output: "video_record.db record_<uid> 明文 → history(BROWSE) 事件采集器,900 行真机验证"
- input: '/pdh-android-collector "为什么 toutiao-adb-sync 返回 0 行"'
output: "诊断: profile error_code 16 + cookie 无 uid → 改读本地 account_db 恢复 uid"
os: [win32, darwin, linux]
author: ChainlessChain
---
# PDH 安卓本地采集器构建 (pdh-android-collector)
The durable way to collect a Chinese app's data is **reading its on-device plaintext
SQLite DBs over ADB+root**, NOT calling its web API. Web APIs drift constantly (cookie
schemas, signing salts, endpoint paths, permission codes); the local DBs are stable and
need no signing. Reach for a web fetcher only when the data genuinely isn't on-device.
Real-device provenance (device 5lhyaqu8lbwstc6x / Xiaomi chopin, 2026-06-11): this
recipe cracked Toutiao (uid from `account_db.login_info` when the web profile returned
`error_code 16`) and Douyin (`video_record.db` `record_<uid>` = 900 plaintext watch
records, sidestepping the SQLCipher IM db). Reference memory `pdh_real_device_findings_2026_06_11`
and `pdh_local_db_over_web_api_strategy`.
## Phase 0 — Map the device (always first)
```bash
adb devices -l # device + transport
adb shell "su -c id" # root (uid=0) — release APKs aren't debuggable
adb shell pm list packages -3 # which target apps are installed
```
Map installed packages → adapters in `packages/personal-data-hub/lib/adapters/`. Only
installed apps verify. The app must be **logged in AND have generated the data** — most
"0 rows" results are login/usage state, not collector bugs.
## Phase 1 — Try the existing collector, read the reason code
`cc hub <platform>-adb-sync --json` (weibo/douyin/kuaishou/toutiao/xhs/bilibili exist):
- `*_COOKIES_INCOMPLETE` / `*_NO_WEBVIEW_COOKIES` → not logged into the in-app WebView.
- `*_IM_DB_ENCRYPTED` → SQLCipher; needs frida key-hook (hard — separate infra).
- web `error_code` / HTTP 404 → endpoint/permission drift (Phase 4).
`cc hub stats --json` runs under the **global `cc`** (host-ABI native sqlite); the
workspace CLI under plain `node` usually can't load `bs3mc` (Phase 5).
## Phase 2 — Inspect the app's local DBs (highest ROI)
```bash
D=/data/data/<pkg>/databases
adb shell "su -c 'ls -S $D/'" # by size; ignore -wal/-shm/-journal
adb shell "su -c 'head -c 16 $D/<file>'" | od -A n -t x1
# 53514c697465... = "SQLite format 3" = PLAINTEXT; else SQLCipher/other
```
Pull plaintext DBs (base64 stream — MIUI-safe) and inspect with Node's built-in
`node:sqlite` (no native dep, no device):
```bash
adb shell "su -c 'base64 $D/<file>'" | tr -d '\r\n' | base64 -d > "$TEMP/x.db"
node --experimental-sqlite -e '…' # ⚠️ Node-on-Windows /tmp = C:\tmp; use process.env.TEMP
```
Dump `sqlite_master` tables + counts + `PRAGMA table_info`, then sample rows. **The uid is
usually in a filename or table name** (`<uid>_im.db`, `record_<uid>`) and/or a plaintext
`account_db.login_info` row — that's how you recover the uid the web API won't give.
## Phase 3 — Build the collector (mirror the established pattern)
References: `social-toutiao-adb/account-reader.js`, `social-douyin-adb/watch-history-reader.js`.
A reader module exports:
- `pull<Db>ViaSu(adb, serial, opts)` — `ls … || echo NOT_FOUND` (on NOT_FOUND run
`pm list packages <pkg>` to distinguish "not installed" from "installed-but-no-data"),
`id -u` root check, `base64 <path> | tr -d '\n\r'` stream, validate `SQLite format 3`
magic, write tmp file.
- `read<Data>(dbPath, opts)` — open via
`require("../social-bilibili-adb/chromium-cookies-reader")._internals.loadDatabaseClass()`
(dual-load: bs3mc under Electron / better-sqlite3 under test), `{readonly:true}`.
- `create<X>Extension(factoryOpts)` — bridge handler `(params, ctx)=>{…}`, requiring
`ctx.adb` + `ctx.pickDevice`.
Normalize into the **existing** adapter's snapshot kinds (no second adapter) — e.g. douyin
watch history reuses `social-douyin`'s `KIND_HISTORY`. Then wire the extension in **BOTH**
`packages/cli/src/lib/personal-data-hub-wiring.js` AND
`desktop-app-vue/src/main/personal-data-hub/wiring.js` (the `extensions:{…}` object), add a
`hub.<x>Sync` facade method, and a `cc hub <x>-sync` command in `packages/cli/src/commands/hub.js`.
## Phase 4 — Only if the data isn't local (web API)
Pull the real cookie/credential off the device first (local DBs give the uid). Capture the
**actual** response before coding — endpoints drift. ByteDance passport uses `{message,data}`
envelopes with `err_no`/`error_code` (NOT `code`/`status_code`) and returns HTTP 200 +
`data:[]` on error — surface `err_no != 0` or it masks as "empty". Signed endpoints
(X-Bogus / `_signature` / X-S / WBI / DS / weapi) only work via the desktop SignBridge — the
CLI short-circuits them.
## Phase 5 — Verify on the real device (Electron harness)
The native sqlite reader (`bs3mc`) and the SignBridge (`WebContentsView`) load only under
**Electron**, not plain `node`. Write a throwaway `_harness.cjs` and run
`desktop-app-vue/node_modules/.bin/electron _harness.cjs`:
```js
const { app } = require("electron");
app.disableHardwareAcceleration();
app.commandLine.appendSwitch("no-sandbox");
app.whenReady().then(async () => {
// require reader+collector by ABSOLUTE path; build a bridge whose invoke()
// routes to createXExtension({})(params,{adb,pickDevice}); call collect…(bridge,{…});
// console.log the result; app.exit(0);
});
```
Verifies the whole pipeline (pull → read → normalize, + signing) without driving the GUI.
**Delete the harness + any pulled DBs after; never commit a harness or device data.**
## Testing + landing
Inject `adb` (fake canned `su` output) and `_databaseClass` (fake Database with
`prepare().all()/.get()`) — no device, no native driver. Cover: pull diagnosis branches
(NOT_FOUND→installed/not, NO_ROOT, NOT_SQLITE, EMPTY), reader row parsing, and a
collect→normalize round-trip asserting a valid `partitionBatch`. Run the full pdh suite for
regressions.
**Release gate:** any `pdh/lib` change needs pdh version bump + `npm publish` + Android
`USR_VERSION` bump before it reaches the published CLI / Android (traps #27/#28). In the
desktop dev app (`npm run dev`, symlinks workspace pdh) it's live immediately.
## Git hygiene
Parallel Claude sessions share one `.git`. Commit with `git commit --only -- <paths>` (stage
new files with `git add -- <path>` first). The post-commit hook auto-pushes to github + gitee.