@skrillex1224/android-toolkit 1.0.8 → 1.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser.js +1 -1
- package/dist/browser.js.map +1 -1
- package/dist/index.cjs +77 -74
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +77 -74
- package/dist/index.js.map +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -28,7 +28,7 @@ var Code = Object.freeze({
|
|
|
28
28
|
ContentUnavailable: 30010003,
|
|
29
29
|
SourceExtractionFailed: 30010004,
|
|
30
30
|
AutomationFailed: 30010005,
|
|
31
|
-
|
|
31
|
+
AppRuntimeUnavailable: 30010008,
|
|
32
32
|
AppNotInstalled: 30010009
|
|
33
33
|
});
|
|
34
34
|
var Status = Object.freeze({
|
|
@@ -253,7 +253,7 @@ function safeJson(value) {
|
|
|
253
253
|
|
|
254
254
|
// src/device.js
|
|
255
255
|
var execFileAsync = promisify(execFile);
|
|
256
|
-
var
|
|
256
|
+
var Device2 = {
|
|
257
257
|
adbExec,
|
|
258
258
|
adbShell,
|
|
259
259
|
forceStopApp,
|
|
@@ -634,7 +634,7 @@ function createApifyKit(options = {}) {
|
|
|
634
634
|
if (failActor) {
|
|
635
635
|
let base64 = "";
|
|
636
636
|
try {
|
|
637
|
-
base64 = await
|
|
637
|
+
base64 = await Device2.screenshotBase64(ctx);
|
|
638
638
|
} catch (error) {
|
|
639
639
|
base64 = `\u622A\u56FE\u5931\u8D25: ${error?.message || String(error)}`;
|
|
640
640
|
}
|
|
@@ -752,7 +752,7 @@ var DeviceView = {
|
|
|
752
752
|
normalizeSelector
|
|
753
753
|
};
|
|
754
754
|
async function snapshot(ctx, options = {}) {
|
|
755
|
-
const xml = options.xml || await
|
|
755
|
+
const xml = options.xml || await Device2.dumpUiXml(ctx);
|
|
756
756
|
const parsed = parser.parse(xml);
|
|
757
757
|
const roots = toArray(parsed?.hierarchy?.node).map((node) => normalizeNode(node, null, 0));
|
|
758
758
|
const flat = [];
|
|
@@ -936,6 +936,12 @@ function selectorLabel(selector) {
|
|
|
936
936
|
}
|
|
937
937
|
|
|
938
938
|
// src/device-input.js
|
|
939
|
+
var SCROLL_MAX_SWIPES = 24;
|
|
940
|
+
var SCROLL_STABLE_ROUNDS = 1;
|
|
941
|
+
var SCROLL_SWIPE_RATIO = 0.72;
|
|
942
|
+
var SCROLL_DURATION_MS = 360;
|
|
943
|
+
var SCROLL_SETTLE_MS = 500;
|
|
944
|
+
var SCROLL_HASH_SOURCE = "screenshot";
|
|
939
945
|
var DeviceInput = {
|
|
940
946
|
click,
|
|
941
947
|
tap: click,
|
|
@@ -949,12 +955,12 @@ var DeviceInput = {
|
|
|
949
955
|
};
|
|
950
956
|
async function click(ctx, selectorOrPoint, options = {}) {
|
|
951
957
|
if (isPoint(selectorOrPoint)) {
|
|
952
|
-
await
|
|
958
|
+
await Device2.tapAbsolute(ctx, selectorOrPoint.x, selectorOrPoint.y);
|
|
953
959
|
return { point: selectorOrPoint };
|
|
954
960
|
}
|
|
955
961
|
if (isBounds(selectorOrPoint)) {
|
|
956
962
|
const point2 = centerOf(selectorOrPoint);
|
|
957
|
-
await
|
|
963
|
+
await Device2.tapAbsolute(ctx, point2.x, point2.y);
|
|
958
964
|
return { point: point2 };
|
|
959
965
|
}
|
|
960
966
|
const target = await DeviceView.find(ctx, selectorOrPoint, options);
|
|
@@ -973,7 +979,7 @@ async function click(ctx, selectorOrPoint, options = {}) {
|
|
|
973
979
|
actual: simplifyNode(actual),
|
|
974
980
|
point
|
|
975
981
|
});
|
|
976
|
-
await
|
|
982
|
+
await Device2.tapAbsolute(ctx, point.x, point.y);
|
|
977
983
|
await sleep(Number(options.settleMs || 250));
|
|
978
984
|
return { target, actual, point };
|
|
979
985
|
}
|
|
@@ -984,7 +990,7 @@ async function fill(ctx, selector, text2, options = {}) {
|
|
|
984
990
|
settleMs: Number(options.focusSettleMs || 350)
|
|
985
991
|
});
|
|
986
992
|
try {
|
|
987
|
-
await
|
|
993
|
+
await Device2.typeText(ctx, value);
|
|
988
994
|
} catch (error) {
|
|
989
995
|
throw new CrawlerError({
|
|
990
996
|
message: `automation_failed: View \u5199\u5165\u6587\u672C\u5931\u8D25 ${JSON.stringify(selector)}`,
|
|
@@ -1001,10 +1007,10 @@ async function fill(ctx, selector, text2, options = {}) {
|
|
|
1001
1007
|
return { target: clicked.actual || clicked.target, chars: value.length };
|
|
1002
1008
|
}
|
|
1003
1009
|
async function press(ctx, key) {
|
|
1004
|
-
await
|
|
1010
|
+
await Device2.pressKey(ctx, key);
|
|
1005
1011
|
}
|
|
1006
1012
|
async function pressEnter2(ctx) {
|
|
1007
|
-
await
|
|
1013
|
+
await Device2.pressEnter(ctx);
|
|
1008
1014
|
}
|
|
1009
1015
|
async function scroll(ctx, selector, direction, options = {}) {
|
|
1010
1016
|
const node = await DeviceView.find(ctx, selector, options);
|
|
@@ -1012,8 +1018,8 @@ async function scroll(ctx, selector, direction, options = {}) {
|
|
|
1012
1018
|
}
|
|
1013
1019
|
async function scrollBounds(ctx, bounds2, direction, options = {}) {
|
|
1014
1020
|
const box = bounds2;
|
|
1015
|
-
const ratio = Number(options.swipeRatio ||
|
|
1016
|
-
const durationMs = Number(options.durationMs ||
|
|
1021
|
+
const ratio = Number(options.swipeRatio || SCROLL_SWIPE_RATIO);
|
|
1022
|
+
const durationMs = Number(options.durationMs || SCROLL_DURATION_MS);
|
|
1017
1023
|
const x = box.centerX;
|
|
1018
1024
|
const distance = Math.max(40, box.height * ratio);
|
|
1019
1025
|
const down = String(direction || "").toLowerCase() === "down";
|
|
@@ -1025,8 +1031,8 @@ async function scrollBounds(ctx, bounds2, direction, options = {}) {
|
|
|
1025
1031
|
x,
|
|
1026
1032
|
y: down ? Math.min(box.bottom - 10, from.y + distance) : Math.max(box.top + 10, from.y - distance)
|
|
1027
1033
|
};
|
|
1028
|
-
await
|
|
1029
|
-
await sleep(Number(options.settleMs ||
|
|
1034
|
+
await Device2.swipe(ctx, from, to, durationMs);
|
|
1035
|
+
await sleep(Number(options.settleMs || SCROLL_SETTLE_MS));
|
|
1030
1036
|
}
|
|
1031
1037
|
async function scrollToEnd(ctx, selector, options = {}) {
|
|
1032
1038
|
return scrollUntilStable(ctx, selector, "up", options);
|
|
@@ -1035,56 +1041,30 @@ async function scrollToTop(ctx, selector, options = {}) {
|
|
|
1035
1041
|
return scrollUntilStable(ctx, selector, "down", options);
|
|
1036
1042
|
}
|
|
1037
1043
|
async function scrollUntilStable(ctx, selector, direction, options = {}) {
|
|
1038
|
-
const maxSwipes = Math.max(1, Number(options.maxSwipes ||
|
|
1039
|
-
const stableRoundsTarget = Math.max(1, Number(options.stableRounds ||
|
|
1040
|
-
const hashSource = String(options.stability || options.hashSource || "view");
|
|
1041
|
-
if (hashSource === "screenshot") {
|
|
1042
|
-
return scrollUntilScreenshotStable(ctx, selector, direction, options, maxSwipes, stableRoundsTarget, hashSource);
|
|
1043
|
-
}
|
|
1044
|
-
let lastHash = "";
|
|
1045
|
-
let stableRounds = 0;
|
|
1046
|
-
for (let index = 0; index < maxSwipes; index += 1) {
|
|
1047
|
-
const currentHash = await scrollStateHash(ctx, selector, options, hashSource);
|
|
1048
|
-
if (currentHash === lastHash) {
|
|
1049
|
-
stableRounds += 1;
|
|
1050
|
-
if (stableRounds >= stableRoundsTarget) {
|
|
1051
|
-
return { swipes: index, stableRounds, hash: currentHash, hashSource, capped: false };
|
|
1052
|
-
}
|
|
1053
|
-
} else {
|
|
1054
|
-
stableRounds = 0;
|
|
1055
|
-
lastHash = currentHash;
|
|
1056
|
-
}
|
|
1057
|
-
await scroll(ctx, selector, direction, options);
|
|
1058
|
-
}
|
|
1059
|
-
return { swipes: maxSwipes, stableRounds, hash: lastHash, hashSource, capped: true };
|
|
1060
|
-
}
|
|
1061
|
-
async function scrollUntilScreenshotStable(ctx, selector, direction, options, maxSwipes, stableRoundsTarget, hashSource) {
|
|
1044
|
+
const maxSwipes = Math.max(1, Number(options.maxSwipes || SCROLL_MAX_SWIPES));
|
|
1045
|
+
const stableRoundsTarget = Math.max(1, Number(options.stableRounds || SCROLL_STABLE_ROUNDS));
|
|
1062
1046
|
const node = await DeviceView.find(ctx, selector, options);
|
|
1063
1047
|
const bounds2 = node.bounds;
|
|
1064
|
-
let lastHash = await scrollStateHash(ctx
|
|
1048
|
+
let lastHash = await scrollStateHash(ctx);
|
|
1065
1049
|
let stableRounds = 0;
|
|
1066
1050
|
for (let index = 0; index < maxSwipes; index += 1) {
|
|
1067
1051
|
await scrollBounds(ctx, bounds2, direction, options);
|
|
1068
|
-
const currentHash = await scrollStateHash(ctx
|
|
1052
|
+
const currentHash = await scrollStateHash(ctx);
|
|
1069
1053
|
if (currentHash === lastHash) {
|
|
1070
1054
|
stableRounds += 1;
|
|
1071
1055
|
if (stableRounds >= stableRoundsTarget) {
|
|
1072
|
-
return { swipes: index + 1, stableRounds, hash: currentHash, hashSource, capped: false };
|
|
1056
|
+
return { swipes: index + 1, stableRounds, hash: currentHash, hashSource: SCROLL_HASH_SOURCE, capped: false };
|
|
1073
1057
|
}
|
|
1074
1058
|
} else {
|
|
1075
1059
|
stableRounds = 0;
|
|
1076
1060
|
lastHash = currentHash;
|
|
1077
1061
|
}
|
|
1078
1062
|
}
|
|
1079
|
-
return { swipes: maxSwipes, stableRounds, hash: lastHash, hashSource, capped: true };
|
|
1063
|
+
return { swipes: maxSwipes, stableRounds, hash: lastHash, hashSource: SCROLL_HASH_SOURCE, capped: true };
|
|
1080
1064
|
}
|
|
1081
|
-
async function scrollStateHash(ctx
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
return createHash2("sha256").update(png).digest("hex");
|
|
1085
|
-
}
|
|
1086
|
-
const node = await DeviceView.find(ctx, selector, options);
|
|
1087
|
-
return DeviceView.hashNode(node);
|
|
1065
|
+
async function scrollStateHash(ctx) {
|
|
1066
|
+
const png = await Device2.screenshotPng(ctx);
|
|
1067
|
+
return createHash2("sha256").update(png).digest("hex");
|
|
1088
1068
|
}
|
|
1089
1069
|
function nearestClickable(node) {
|
|
1090
1070
|
let current = node;
|
|
@@ -1161,8 +1141,8 @@ async function query(ctx, options = {}) {
|
|
|
1161
1141
|
const dbPaths = await resolveDeviceDbPaths(ctx, config);
|
|
1162
1142
|
if (dbPaths.length === 0) {
|
|
1163
1143
|
throw new CrawlerError({
|
|
1164
|
-
message: "
|
|
1165
|
-
code: Code.
|
|
1144
|
+
message: "app_runtime_unavailable: sqlite database not found",
|
|
1145
|
+
code: Code.AppRuntimeUnavailable,
|
|
1166
1146
|
context: { dbDir: config.dbDir, dbPath: config.dbPath, dbNamePrefix: config.dbNamePrefix }
|
|
1167
1147
|
});
|
|
1168
1148
|
}
|
|
@@ -1211,8 +1191,8 @@ async function resolveDeviceDbPaths(ctx, config) {
|
|
|
1211
1191
|
maxBuffer: 2 * 1024 * 1024
|
|
1212
1192
|
}).catch((error) => {
|
|
1213
1193
|
throw new CrawlerError({
|
|
1214
|
-
message: `
|
|
1215
|
-
code: Code.
|
|
1194
|
+
message: `app_runtime_unavailable: sqlite dbDir unavailable ${error?.message || String(error)}`,
|
|
1195
|
+
code: Code.AppRuntimeUnavailable,
|
|
1216
1196
|
context: { dbDir: config.dbDir }
|
|
1217
1197
|
});
|
|
1218
1198
|
});
|
|
@@ -1237,8 +1217,8 @@ async function pullDatabaseSnapshot(ctx, dbPath, localDir) {
|
|
|
1237
1217
|
await adbSuShell(ctx, copyCommand, { timeoutMs: 3e4, maxBuffer: 4 * 1024 * 1024 });
|
|
1238
1218
|
await adbPull(adbPath, serial, remoteDb, localDb).catch((error) => {
|
|
1239
1219
|
throw new CrawlerError({
|
|
1240
|
-
message: `
|
|
1241
|
-
code: Code.
|
|
1220
|
+
message: `app_runtime_unavailable: sqlite snapshot pull failed ${error?.message || String(error)}`,
|
|
1221
|
+
code: Code.AppRuntimeUnavailable,
|
|
1242
1222
|
context: { dbPath }
|
|
1243
1223
|
});
|
|
1244
1224
|
});
|
|
@@ -1251,7 +1231,7 @@ async function pullDatabaseSnapshot(ctx, dbPath, localDir) {
|
|
|
1251
1231
|
}
|
|
1252
1232
|
}
|
|
1253
1233
|
async function adbSuShell(ctx, command, options = {}) {
|
|
1254
|
-
return
|
|
1234
|
+
return Device2.adbShell(ctx, [`su -c ${shellQuote(command)}`], options);
|
|
1255
1235
|
}
|
|
1256
1236
|
async function queryLocalSQLite(dbPath, config) {
|
|
1257
1237
|
const payloadPath = `${dbPath}.query.json`;
|
|
@@ -1688,6 +1668,11 @@ var DEFAULT_COLUMNS = 3;
|
|
|
1688
1668
|
var DEFAULT_SETTLE_MS = 550;
|
|
1689
1669
|
var DEFAULT_TIMEOUT_MS = 5e4;
|
|
1690
1670
|
var DEFAULT_POLL_INTERVAL_MS = 800;
|
|
1671
|
+
var RESET_TO_TOP_MAX_SWIPES = 16;
|
|
1672
|
+
var RESET_TO_TOP_STABLE_ROUNDS = 1;
|
|
1673
|
+
var CAPTURE_SCROLL_STEP_RATIO = 0.56;
|
|
1674
|
+
var CAPTURE_SCROLL_DURATION_MS = 360;
|
|
1675
|
+
var SPRITE_GAP = 16;
|
|
1691
1676
|
var Share = {
|
|
1692
1677
|
captureScreen,
|
|
1693
1678
|
captureLink
|
|
@@ -1697,18 +1682,34 @@ async function captureScreen(ctx, options = {}) {
|
|
|
1697
1682
|
const frameBuffers = [];
|
|
1698
1683
|
const seen = /* @__PURE__ */ new Set();
|
|
1699
1684
|
const targetSelector = options.selector || { id: "message_list" };
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1685
|
+
const resetToTop = options.resetToTop !== false;
|
|
1686
|
+
let topResult = null;
|
|
1687
|
+
if (resetToTop) {
|
|
1688
|
+
topResult = await DeviceInput.scrollToTop(ctx, targetSelector, {
|
|
1689
|
+
maxSwipes: RESET_TO_TOP_MAX_SWIPES,
|
|
1690
|
+
stableRounds: RESET_TO_TOP_STABLE_ROUNDS,
|
|
1691
|
+
settleMs: 400
|
|
1692
|
+
});
|
|
1693
|
+
Logger.info("captureScreen scrollToTop", {
|
|
1694
|
+
swipes: topResult?.swipes,
|
|
1695
|
+
stableRounds: topResult?.stableRounds,
|
|
1696
|
+
hashSource: topResult?.hashSource,
|
|
1697
|
+
capped: topResult?.capped === true
|
|
1711
1698
|
});
|
|
1699
|
+
if (topResult?.capped === true) {
|
|
1700
|
+
throw new CrawlerError({
|
|
1701
|
+
message: `source_extraction_failed: \u622A\u56FE\u524D\u672A\u80FD\u786E\u8BA4\u56DE\u5230\u9876\u90E8 swipes=${topResult.swipes} stableRounds=${topResult.stableRounds}`,
|
|
1702
|
+
code: Code.SourceExtractionFailed,
|
|
1703
|
+
context: {
|
|
1704
|
+
selector: targetSelector,
|
|
1705
|
+
swipes: topResult.swipes,
|
|
1706
|
+
stableRounds: topResult.stableRounds,
|
|
1707
|
+
hashSource: topResult.hashSource
|
|
1708
|
+
}
|
|
1709
|
+
});
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
for (let index = 0; index < DEFAULT_CAPTURE_COUNT; index += 1) {
|
|
1712
1713
|
await sleep(DEFAULT_SETTLE_MS);
|
|
1713
1714
|
const png = await Device.screenshotPng(ctx);
|
|
1714
1715
|
const hash = createHash3("sha256").update(png).digest("hex");
|
|
@@ -1717,18 +1718,21 @@ async function captureScreen(ctx, options = {}) {
|
|
|
1717
1718
|
frameBuffers.push(png);
|
|
1718
1719
|
if (index >= DEFAULT_CAPTURE_COUNT - 1) break;
|
|
1719
1720
|
await DeviceInput.scroll(ctx, targetSelector, "up", {
|
|
1720
|
-
swipeRatio:
|
|
1721
|
-
durationMs:
|
|
1721
|
+
swipeRatio: CAPTURE_SCROLL_STEP_RATIO,
|
|
1722
|
+
durationMs: CAPTURE_SCROLL_DURATION_MS,
|
|
1722
1723
|
settleMs: DEFAULT_SETTLE_MS
|
|
1723
1724
|
}).catch(() => {
|
|
1724
1725
|
});
|
|
1725
1726
|
}
|
|
1726
|
-
const sprite = await composeSprite(frameBuffers, { columns: DEFAULT_COLUMNS, gap:
|
|
1727
|
+
const sprite = await composeSprite(frameBuffers, { columns: DEFAULT_COLUMNS, gap: SPRITE_GAP });
|
|
1727
1728
|
const compression = resolveImageCompression(options);
|
|
1728
1729
|
const base64 = await compressImageBufferToBase64(sprite, compression);
|
|
1729
1730
|
Logger.success("Share.captureScreen", {
|
|
1730
1731
|
duration: Logger.duration(startedAt),
|
|
1731
1732
|
frameCount: frameBuffers.length,
|
|
1733
|
+
scrollStepRatio: CAPTURE_SCROLL_STEP_RATIO,
|
|
1734
|
+
resetToTop,
|
|
1735
|
+
topCapped: topResult?.capped === true,
|
|
1732
1736
|
base64Bytes: Math.ceil(base64.length)
|
|
1733
1737
|
});
|
|
1734
1738
|
return `data:image/jpeg;base64,${base64}`;
|
|
@@ -1743,7 +1747,6 @@ async function captureLink(ctx, options = {}) {
|
|
|
1743
1747
|
});
|
|
1744
1748
|
}
|
|
1745
1749
|
const timeoutMs = Number(options.timeoutMs || DEFAULT_TIMEOUT_MS);
|
|
1746
|
-
const pollIntervalMs = Number(options.pollIntervalMs || DEFAULT_POLL_INTERVAL_MS);
|
|
1747
1750
|
const deadline = Date.now() + timeoutMs;
|
|
1748
1751
|
const prefix = String(share.prefix || "").trim();
|
|
1749
1752
|
Logger.start("Share.captureLink", { actor: actorInfo.key, prefix, timeoutMs });
|
|
@@ -1765,7 +1768,7 @@ async function captureLink(ctx, options = {}) {
|
|
|
1765
1768
|
if (link && link === beforeLink) {
|
|
1766
1769
|
lastEvent = { ...event, rejected: "same_as_baseline", beforeLink };
|
|
1767
1770
|
}
|
|
1768
|
-
await sleep(
|
|
1771
|
+
await sleep(DEFAULT_POLL_INTERVAL_MS);
|
|
1769
1772
|
}
|
|
1770
1773
|
throw new CrawlerError({
|
|
1771
1774
|
message: "source_extraction_failed: \u672A\u6355\u83B7\u5206\u4EAB\u94FE\u63A5",
|
|
@@ -1861,7 +1864,7 @@ async function run(handler, options = {}) {
|
|
|
1861
1864
|
const kit = {
|
|
1862
1865
|
Launch,
|
|
1863
1866
|
ApifyKit: apifyKit,
|
|
1864
|
-
Device,
|
|
1867
|
+
Device: Device2,
|
|
1865
1868
|
DeviceInput,
|
|
1866
1869
|
DeviceView,
|
|
1867
1870
|
DeviceSQLite,
|
|
@@ -1912,7 +1915,7 @@ var useAndroidToolKit = () => {
|
|
|
1912
1915
|
DeviceInput,
|
|
1913
1916
|
DeviceView,
|
|
1914
1917
|
DeviceSQLite,
|
|
1915
|
-
Device,
|
|
1918
|
+
Device: Device2,
|
|
1916
1919
|
Mutation,
|
|
1917
1920
|
Share,
|
|
1918
1921
|
Constants: constants_exports,
|