@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/index.js CHANGED
@@ -28,7 +28,7 @@ var Code = Object.freeze({
28
28
  ContentUnavailable: 30010003,
29
29
  SourceExtractionFailed: 30010004,
30
30
  AutomationFailed: 30010005,
31
- DataAccessUnavailable: 30010008,
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 Device = {
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 Device.screenshotBase64(ctx);
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 Device.dumpUiXml(ctx);
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 Device.tapAbsolute(ctx, selectorOrPoint.x, selectorOrPoint.y);
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 Device.tapAbsolute(ctx, point2.x, point2.y);
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 Device.tapAbsolute(ctx, point.x, point.y);
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 Device.typeText(ctx, value);
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 Device.pressKey(ctx, key);
1010
+ await Device2.pressKey(ctx, key);
1005
1011
  }
1006
1012
  async function pressEnter2(ctx) {
1007
- await Device.pressEnter(ctx);
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 || 0.72);
1016
- const durationMs = Number(options.durationMs || 360);
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 Device.swipe(ctx, from, to, durationMs);
1029
- await sleep(Number(options.settleMs || 500));
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 || 20));
1039
- const stableRoundsTarget = Math.max(1, Number(options.stableRounds || 2));
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, selector, options, hashSource);
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, selector, options, hashSource);
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, selector, options, hashSource) {
1082
- if (hashSource === "screenshot") {
1083
- const png = await Device.screenshotPng(ctx);
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: "data_access_unavailable: sqlite database not found",
1165
- code: Code.DataAccessUnavailable,
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: `data_access_unavailable: sqlite dbDir unavailable ${error?.message || String(error)}`,
1215
- code: Code.DataAccessUnavailable,
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: `data_access_unavailable: sqlite snapshot pull failed ${error?.message || String(error)}`,
1241
- code: Code.DataAccessUnavailable,
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 Device.adbShell(ctx, [`su -c ${shellQuote(command)}`], options);
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
- await Device.hideKeyboard(ctx, { attempts: 2, settleMs: 350 }).catch(() => {
1701
- });
1702
- await DeviceInput.scrollToTop(ctx, targetSelector, {
1703
- maxSwipes: Number(options.scrollToTopMaxSwipes || 12),
1704
- stableRounds: 2,
1705
- settleMs: 400
1706
- }).catch((error) => {
1707
- Logger.warn("captureScreen scrollToTop skipped", { message: error?.message || String(error) });
1708
- });
1709
- for (let index = 0; index < DEFAULT_CAPTURE_COUNT; index += 1) {
1710
- await Device.hideKeyboard(ctx, { attempts: 1, settleMs: 250 }).catch(() => {
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: 0.72,
1721
- durationMs: 360,
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: 16 });
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(pollIntervalMs);
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,