/** * 首页长按卡片→多选→批量删除(快),保留指定设备(woan 房间:Lock 6D / Lock Pro DE / Blind Tilt 42)。 * 每轮:长按第一个"非保留"卡进多选 → 勾选同屏其它非保留卡(跨房间会让 Delete 消失,取消该卡)→ Delete → 确认。循环到只剩保留项。 * 安全开关:RESET_EXCEPT=1。 用法:RESET_EXCEPT=1 PLATFORM=android npx ts-node scripts/reset-except.ts */ import * as dotenv from 'dotenv'; import * as path from 'path'; dotenv.config({ path: path.resolve(__dirname, '../.env') }); import { createDriver } from '../drivers/factory'; const KEEP = (process.env.KEEP_DEVICES || 'Lock 6D,Lock Pro DE,Blind Tilt 42').split(',').map(s => s.trim()); const sleep = (ms: number) => new Promise(r => setTimeout(r, ms)); // 首页设备卡名 + 中心坐标(nameText / nameTextMeter) function devices(src: string): { name: string; cx: number; cy: number }[] { const out: { name: string; cx: number; cy: number }[] = []; for (const n of src.split('<')) { if (!/resource-id="[^"]*\/(nameText|nameTextMeter)"/.test(n)) continue; const t = (n.match(/text="([^"]*)"/) || [])[1]; const b = n.match(/bounds="\[(\d+),(\d+)\]\[(\d+),(\d+)\]"/); if (t && t.trim() && b) out.push({ name: t.trim(), cx: Math.round((+b[1] + +b[3]) / 2), cy: Math.round((+b[2] + +b[4]) / 2) }); } return out; } const isKept = (name: string) => KEEP.some(k => name === k); async function main() { if (process.env.RESET_EXCEPT !== '1') { console.error('已阻止。RESET_EXCEPT=1 重跑。'); process.exit(2); } const driver = createDriver(); await driver.createSession(); const d: any = driver; const hasDelete = () => driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Delete")'); const exitSelect = async () => { const f = await driver.findElementRaw('-android uiautomator', 'new UiSelector().text("Finish")'); if (f) { await driver.tapElement(f); await sleep(700); } }; try { console.log('保留:', KEEP.join(', ')); let total = 0; for (let round = 0; round < 30; round++) { await driver.goBackToHomepage(); await sleep(1800); await driver.dismissPopupIfPresent(); const all = devices(await driver.getSource()); const nonKept = all.filter(x => !isKept(x.name)); if (!nonKept.length) { console.log('只剩保留项,结束'); break; } // 长按第一个非保留卡进多选 const first = nonKept[0]; await d.longPress(first.cx, first.cy, 1.0); await sleep(1000); if (!(await hasDelete())) { await exitSelect(); console.log(`长按 ${first.name} 未进多选,跳过本轮`); continue; } let batch = [first.name]; // 勾选同屏其它非保留卡(重新读坐标,避免多选模式下偏移) for (const x of devices(await driver.getSource()).filter(o => !isKept(o.name) && o.name !== first.name)) { await driver.tap(x.cx, x.cy); await sleep(400); if (await hasDelete()) { batch.push(x.name); } else { await driver.tap(x.cx, x.cy); await sleep(300); } // 跨房间→取消该卡 } const del = await hasDelete(); if (!del) { await exitSelect(); continue; } await driver.tapElement(del); await sleep(900); for (let r = 0; r < 4; r++) { const s = await driver.getSource(); if (/Cancel|取消|delete|删除/i.test(s)) { let c: string | null = null; for (const t of ['Delete', 'OK', '删除', 'Confirm']) { c = await driver.findElementRaw('-android uiautomator', `new UiSelector().text("${t}")`); if (c) break; } if (c) { await driver.tapElement(c); break; } } await sleep(500); } await sleep(2000); total += batch.length; console.log(`本批删 ${batch.length}: ${batch.join(', ')}`); } await driver.goBackToHomepage(); await sleep(1500); console.log(`\n共删 ${total}。剩余:`, devices(await driver.getSource()).map(x => x.name).join(', ') || '(无)'); } finally { await driver.destroySession(); } } main().catch(e => { console.error('ERR:', e.message); process.exit(1); });