@web-auto/webauto 0.1.1 → 0.1.2

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.
Files changed (354) hide show
  1. package/apps/desktop-console/default-settings.json +1 -0
  2. package/apps/desktop-console/dist/main/index.mjs +1618 -0
  3. package/apps/desktop-console/{src → dist}/main/preload.mjs +10 -0
  4. package/apps/desktop-console/dist/renderer/index.js +3063 -0
  5. package/apps/desktop-console/entry/ui-console.mjs +299 -0
  6. package/apps/webauto/entry/account.mjs +356 -0
  7. package/apps/webauto/entry/lib/account-detect.mjs +160 -0
  8. package/apps/webauto/entry/lib/account-store.mjs +587 -0
  9. package/apps/webauto/entry/lib/profilepool.mjs +1 -1
  10. package/apps/webauto/entry/xhs-install.mjs +27 -3
  11. package/apps/webauto/entry/xhs-status.mjs +152 -0
  12. package/apps/webauto/entry/xhs-unified.mjs +595 -17
  13. package/bin/webauto.mjs +247 -12
  14. package/dist/apps/webauto/server.js +66 -0
  15. package/dist/modules/camo-backend/src/index.js +575 -0
  16. package/dist/modules/camo-backend/src/internal/BrowserSession.js +817 -0
  17. package/dist/modules/camo-backend/src/internal/ElementRegistry.js +61 -0
  18. package/dist/modules/camo-backend/src/internal/ProfileLock.js +85 -0
  19. package/dist/modules/camo-backend/src/internal/SessionManager.js +172 -0
  20. package/dist/modules/camo-backend/src/internal/container-matcher.js +852 -0
  21. package/dist/modules/camo-backend/src/internal/engine-manager.js +258 -0
  22. package/dist/modules/camo-backend/src/internal/fingerprint.js +203 -0
  23. package/dist/modules/camo-backend/src/internal/pageRuntime.js +29 -0
  24. package/dist/modules/camo-backend/src/internal/runtimeInjector.js +30 -0
  25. package/dist/modules/camo-backend/src/internal/state-bus.js +46 -0
  26. package/dist/modules/camo-backend/src/internal/storage-paths.js +36 -0
  27. package/dist/modules/camo-backend/src/internal/ws-server.js +1202 -0
  28. package/dist/modules/camo-runtime/src/utils/browser-service.mjs +423 -0
  29. package/dist/modules/camo-runtime/src/utils/config.mjs +77 -0
  30. package/dist/modules/container-registry/src/index.js +184 -0
  31. package/dist/modules/logging/src/index.js +92 -0
  32. package/dist/modules/operations/src/builtin.js +27 -0
  33. package/dist/modules/operations/src/container-binding.js +75 -0
  34. package/dist/modules/operations/src/executor.js +146 -0
  35. package/dist/modules/operations/src/operations/click.js +167 -0
  36. package/dist/modules/operations/src/operations/extract.js +204 -0
  37. package/dist/modules/operations/src/operations/find-child.js +17 -0
  38. package/dist/modules/operations/src/operations/highlight.js +138 -0
  39. package/dist/modules/operations/src/operations/key.js +61 -0
  40. package/dist/modules/operations/src/operations/navigate.js +148 -0
  41. package/dist/modules/operations/src/operations/scroll.js +126 -0
  42. package/dist/modules/operations/src/operations/type.js +190 -0
  43. package/dist/modules/operations/src/queue.js +100 -0
  44. package/dist/modules/operations/src/registry.js +11 -0
  45. package/dist/modules/operations/src/system/mouse.js +33 -0
  46. package/dist/modules/state/src/atomic-json.js +33 -0
  47. package/dist/modules/workflow/blocks/AnchorVerificationBlock.js +71 -0
  48. package/dist/modules/workflow/blocks/BehaviorRandomizer.js +26 -0
  49. package/dist/modules/workflow/blocks/CallWorkflowBlock.js +38 -0
  50. package/dist/modules/workflow/blocks/CloseDetailBlock.js +209 -0
  51. package/dist/modules/workflow/blocks/CollectBatch.js +137 -0
  52. package/dist/modules/workflow/blocks/CollectCommentsBlock.js +415 -0
  53. package/dist/modules/workflow/blocks/CollectSearchListBlock.js +599 -0
  54. package/dist/modules/workflow/blocks/CollectWeiboPosts.js +229 -0
  55. package/dist/modules/workflow/blocks/DetectPageStateBlock.js +259 -0
  56. package/dist/modules/workflow/blocks/EnsureLoginBlock.js +162 -0
  57. package/dist/modules/workflow/blocks/EnsureSession.js +426 -0
  58. package/dist/modules/workflow/blocks/ErrorClassifier.js +164 -0
  59. package/dist/modules/workflow/blocks/ErrorRecoveryBlock.js +319 -0
  60. package/dist/modules/workflow/blocks/ExpandCommentsBlock.js +1032 -0
  61. package/dist/modules/workflow/blocks/ExtractDetailBlock.js +310 -0
  62. package/dist/modules/workflow/blocks/ExtractPostFields.js +88 -0
  63. package/dist/modules/workflow/blocks/GenerateSmartReplyBlock.js +68 -0
  64. package/dist/modules/workflow/blocks/GoToSearchBlock.js +497 -0
  65. package/dist/modules/workflow/blocks/GracefulFallbackBlock.js +104 -0
  66. package/dist/modules/workflow/blocks/HighlightBlock.js +66 -0
  67. package/dist/modules/workflow/blocks/InitAutoScroll.js +65 -0
  68. package/dist/modules/workflow/blocks/LoadContainerDefinition.js +50 -0
  69. package/dist/modules/workflow/blocks/LoadContainerIndex.js +43 -0
  70. package/dist/modules/workflow/blocks/LocateAndGuardBlock.js +176 -0
  71. package/dist/modules/workflow/blocks/LoginRecoveryBlock.js +242 -0
  72. package/dist/modules/workflow/blocks/MatchContainers.js +64 -0
  73. package/dist/modules/workflow/blocks/MonitoringBlock.js +190 -0
  74. package/dist/modules/workflow/blocks/OpenDetailBlock.js +1240 -0
  75. package/dist/modules/workflow/blocks/OrganizeXhsNotesBlock.js +117 -0
  76. package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +270 -0
  77. package/dist/modules/workflow/blocks/PickSinglePost.js +69 -0
  78. package/dist/modules/workflow/blocks/ProgressTracker.js +125 -0
  79. package/dist/modules/workflow/blocks/RecordFixtureBlock.js +44 -0
  80. package/dist/modules/workflow/blocks/RenderMarkdown.js +48 -0
  81. package/dist/modules/workflow/blocks/SaveFile.js +54 -0
  82. package/dist/modules/workflow/blocks/ScrollNextBatch.js +72 -0
  83. package/dist/modules/workflow/blocks/SessionHealthBlock.js +73 -0
  84. package/dist/modules/workflow/blocks/StartBrowserService.js +45 -0
  85. package/dist/modules/workflow/blocks/ValidateContainerDefinition.js +67 -0
  86. package/dist/modules/workflow/blocks/ValidateExtract.js +35 -0
  87. package/dist/modules/workflow/blocks/WaitSearchPermitBlock.js +162 -0
  88. package/dist/modules/workflow/blocks/WaitStable.js +74 -0
  89. package/dist/modules/workflow/blocks/WarmupCommentsBlock.js +120 -0
  90. package/dist/modules/workflow/blocks/WorkflowExecutor.js +156 -0
  91. package/dist/modules/workflow/blocks/XiaohongshuCollectFromLinksBlock.js +1004 -0
  92. package/dist/modules/workflow/blocks/XiaohongshuCollectLinksBlock.js +1049 -0
  93. package/dist/modules/workflow/blocks/XiaohongshuFullCollectBlock.js +782 -0
  94. package/dist/modules/workflow/blocks/helpers/anchorVerify.js +198 -0
  95. package/dist/modules/workflow/blocks/helpers/asyncWorkQueue.js +53 -0
  96. package/dist/modules/workflow/blocks/helpers/commentScroller.js +334 -0
  97. package/dist/modules/workflow/blocks/helpers/commentSectionLocator.js +126 -0
  98. package/dist/modules/workflow/blocks/helpers/containerAnchors.js +301 -0
  99. package/dist/modules/workflow/blocks/helpers/debugArtifacts.js +6 -0
  100. package/dist/modules/workflow/blocks/helpers/downloadPaths.js +29 -0
  101. package/dist/modules/workflow/blocks/helpers/expandCommentsController.js +53 -0
  102. package/dist/modules/workflow/blocks/helpers/expandCommentsExtractor.js +129 -0
  103. package/dist/modules/workflow/blocks/helpers/macosVisionOcrPlugin.js +116 -0
  104. package/dist/modules/workflow/blocks/helpers/mergeXhsMarkdown.js +109 -0
  105. package/dist/modules/workflow/blocks/helpers/openDetailController.js +56 -0
  106. package/dist/modules/workflow/blocks/helpers/openDetailTypes.js +7 -0
  107. package/dist/modules/workflow/blocks/helpers/openDetailViewport.js +474 -0
  108. package/dist/modules/workflow/blocks/helpers/openDetailWaiter.js +104 -0
  109. package/dist/modules/workflow/blocks/helpers/operationLogger.js +195 -0
  110. package/dist/modules/workflow/blocks/helpers/persistedNotes.js +107 -0
  111. package/dist/modules/workflow/blocks/helpers/replyExpander.js +260 -0
  112. package/dist/modules/workflow/blocks/helpers/scrollIntoView.js +138 -0
  113. package/dist/modules/workflow/blocks/helpers/searchExecutor.js +328 -0
  114. package/dist/modules/workflow/blocks/helpers/searchGate.js +46 -0
  115. package/dist/modules/workflow/blocks/helpers/searchPageState.js +164 -0
  116. package/dist/modules/workflow/blocks/helpers/searchResultWaiter.js +64 -0
  117. package/dist/modules/workflow/blocks/helpers/simpleAnchor.js +134 -0
  118. package/dist/modules/workflow/blocks/helpers/smartReply.js +40 -0
  119. package/dist/modules/workflow/blocks/helpers/systemInput.js +635 -0
  120. package/dist/modules/workflow/blocks/helpers/targetCountMode.js +9 -0
  121. package/dist/modules/workflow/blocks/helpers/xhsCliArgs.js +80 -0
  122. package/dist/modules/workflow/blocks/helpers/xhsCommentDom.js +805 -0
  123. package/dist/modules/workflow/blocks/helpers/xhsNoteOrganizer.js +140 -0
  124. package/dist/modules/workflow/blocks/restore/RestorePhaseBlock.js +204 -0
  125. package/dist/modules/workflow/config/workflowRegistry.js +32 -0
  126. package/dist/modules/workflow/definitions/batch-collect-workflow.js +63 -0
  127. package/dist/modules/workflow/definitions/scroll-extract-workflow.js +74 -0
  128. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow-v2.js +81 -0
  129. package/dist/modules/workflow/definitions/xiaohongshu-collect-workflow.js +57 -0
  130. package/dist/modules/workflow/definitions/xiaohongshu-full-collect-workflow-v3.js +68 -0
  131. package/dist/modules/workflow/definitions/xiaohongshu-note-collect.js +49 -0
  132. package/dist/modules/workflow/definitions/xiaohongshu-phase1-workflow-v3.js +30 -0
  133. package/dist/modules/workflow/definitions/xiaohongshu-phase2-links-workflow-v3.js +40 -0
  134. package/dist/modules/workflow/definitions/xiaohongshu-phase3-collect-workflow-v1.js +54 -0
  135. package/dist/modules/workflow/definitions/xiaohongshu-phase34-from-links-workflow-v3.js +25 -0
  136. package/dist/modules/workflow/src/WeiboEventDrivenWorkflowRunner.js +308 -0
  137. package/dist/modules/workflow/src/context.js +70 -0
  138. package/dist/modules/workflow/src/index.js +5 -0
  139. package/dist/modules/workflow/src/orchestrator.js +230 -0
  140. package/dist/modules/workflow/src/runner.js +55 -0
  141. package/dist/modules/workflow/src/runtime.js +70 -0
  142. package/dist/modules/workflow/workflows/WeiboFeedExtractionWorkflow.js +359 -0
  143. package/dist/modules/workflow/workflows/XiaohongshuLoginWorkflow.js +110 -0
  144. package/dist/modules/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
  145. package/dist/modules/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
  146. package/dist/modules/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
  147. package/dist/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
  148. package/dist/modules/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
  149. package/dist/modules/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
  150. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
  151. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
  152. package/dist/modules/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
  153. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
  154. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
  155. package/dist/modules/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
  156. package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
  157. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
  158. package/dist/modules/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
  159. package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
  160. package/dist/modules/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
  161. package/dist/modules/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
  162. package/dist/modules/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
  163. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
  164. package/dist/modules/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
  165. package/dist/modules/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
  166. package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +42 -0
  167. package/dist/modules/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
  168. package/dist/modules/xiaohongshu/app/src/index.js +9 -0
  169. package/dist/modules/xiaohongshu/app/src/utils/checkpoints.js +222 -0
  170. package/dist/modules/xiaohongshu/app/src/utils/controllerAction.js +43 -0
  171. package/dist/services/controller/src/controller.js +1476 -0
  172. package/dist/services/controller/src/index.js +2 -0
  173. package/dist/services/controller/src/payload-normalizer.js +129 -0
  174. package/dist/services/shared/heartbeat.js +120 -0
  175. package/dist/services/shared/lib/errorHandler.js +2 -0
  176. package/dist/services/shared/serviceProcessLogger.js +139 -0
  177. package/dist/services/unified-api/RemoteBrowserSession.js +176 -0
  178. package/dist/services/unified-api/RemoteSessionManager.js +148 -0
  179. package/dist/services/unified-api/container-operations-handler.js +115 -0
  180. package/dist/services/unified-api/server.js +652 -0
  181. package/dist/services/unified-api/state-registry.js +274 -0
  182. package/dist/services/unified-api/task-persistence.js +66 -0
  183. package/dist/services/unified-api/task-state.js +130 -0
  184. package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +12 -5
  185. package/modules/xiaohongshu/app/pnpm-lock.yaml +24 -0
  186. package/package.json +37 -9
  187. package/.beads/README.md +0 -81
  188. package/.beads/config.yaml +0 -67
  189. package/.beads/interactions.jsonl +0 -0
  190. package/.beads/issues.jsonl +0 -180
  191. package/.beads/metadata.json +0 -4
  192. package/.claude/settings.local.json +0 -10
  193. package/.github/workflows/ci.yml +0 -55
  194. package/AGENTS.md +0 -253
  195. package/apps/desktop-console/README.md +0 -27
  196. package/apps/desktop-console/package-lock.json +0 -897
  197. package/apps/desktop-console/package.json +0 -20
  198. package/apps/desktop-console/scripts/build-and-install.mjs +0 -19
  199. package/apps/desktop-console/scripts/build.mjs +0 -45
  200. package/apps/desktop-console/scripts/test-preload.mjs +0 -13
  201. package/apps/desktop-console/src/main/config.mts +0 -26
  202. package/apps/desktop-console/src/main/core-daemon-manager.mts +0 -131
  203. package/apps/desktop-console/src/main/desktop-settings.mts +0 -267
  204. package/apps/desktop-console/src/main/heartbeat-watchdog.mts +0 -50
  205. package/apps/desktop-console/src/main/heartbeat-watchdog.test.mts +0 -68
  206. package/apps/desktop-console/src/main/index-streaming.test.mts +0 -20
  207. package/apps/desktop-console/src/main/index.mts +0 -980
  208. package/apps/desktop-console/src/main/profile-store.mts +0 -239
  209. package/apps/desktop-console/src/main/profile-store.test.mts +0 -54
  210. package/apps/desktop-console/src/main/state-bridge.mts +0 -114
  211. package/apps/desktop-console/src/main/task-state-types.ts +0 -32
  212. package/apps/desktop-console/src/renderer/hooks/use-task-state.mts +0 -120
  213. package/apps/desktop-console/src/renderer/index.mts +0 -133
  214. package/apps/desktop-console/src/renderer/index.test.mts +0 -34
  215. package/apps/desktop-console/src/renderer/path-helpers.mts +0 -46
  216. package/apps/desktop-console/src/renderer/path-helpers.test.mts +0 -14
  217. package/apps/desktop-console/src/renderer/tabs/debug.mts +0 -48
  218. package/apps/desktop-console/src/renderer/tabs/debug.test.mts +0 -22
  219. package/apps/desktop-console/src/renderer/tabs/logs.mts +0 -421
  220. package/apps/desktop-console/src/renderer/tabs/logs.test.mts +0 -27
  221. package/apps/desktop-console/src/renderer/tabs/preflight.mts +0 -486
  222. package/apps/desktop-console/src/renderer/tabs/preflight.test.mts +0 -33
  223. package/apps/desktop-console/src/renderer/tabs/profile-pool.mts +0 -213
  224. package/apps/desktop-console/src/renderer/tabs/results.mts +0 -171
  225. package/apps/desktop-console/src/renderer/tabs/run.test.mts +0 -63
  226. package/apps/desktop-console/src/renderer/tabs/runtime.mts +0 -151
  227. package/apps/desktop-console/src/renderer/tabs/settings.mts +0 -146
  228. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/account-flow.mts +0 -486
  229. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/guide-browser-check.mts +0 -56
  230. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/helpers.mts +0 -262
  231. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/layout-block.mts +0 -430
  232. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/live-stats.mts +0 -847
  233. package/apps/desktop-console/src/renderer/tabs/xiaohongshu/run-flow.mts +0 -443
  234. package/apps/desktop-console/src/renderer/tabs/xiaohongshu-state.mts +0 -425
  235. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.mts +0 -497
  236. package/apps/desktop-console/src/renderer/tabs/xiaohongshu.test.mts +0 -291
  237. package/apps/desktop-console/src/renderer/ui-components.mts +0 -31
  238. package/docs/README_camoufox_chinese.md +0 -141
  239. package/docs/USAGE_V3.md +0 -163
  240. package/docs/arch/OCR_MACOS_PLUGIN.md +0 -39
  241. package/docs/arch/PORTS.md +0 -40
  242. package/docs/arch/REGRESSION_CHECKLIST.md +0 -121
  243. package/docs/arch/SEARCH_GATE.md +0 -224
  244. package/docs/arch/VIEWPORT_SAFETY.md +0 -182
  245. package/docs/arch/XIAOHONGSHU_OFFLINE_MOCK_DESIGN.md +0 -267
  246. package/docs/xiaohongshu-container-driven-summary.md +0 -221
  247. package/docs/xiaohongshu-full-collect-runbook.md +0 -134
  248. package/docs/xiaohongshu-next-steps.md +0 -228
  249. package/docs/xiaohongshu-quickstart.md +0 -73
  250. package/docs/xiaohongshu-workflow-summary.md +0 -227
  251. package/modules/container-registry/tests/container-registry.test.ts +0 -16
  252. package/modules/logging/tests/logging.test.ts +0 -38
  253. package/modules/operations/tests/operations.test.ts +0 -22
  254. package/modules/operations/tests/viewport-filter.test.ts +0 -161
  255. package/modules/operations/tests/visible-only.test.ts +0 -250
  256. package/modules/session-manager/tests/session-manager.test.ts +0 -23
  257. package/modules/state/src/atomic-json.test.ts +0 -30
  258. package/modules/state/src/paths.test.ts +0 -59
  259. package/modules/state/src/xiaohongshu-collect-state.test.ts +0 -259
  260. package/modules/workflow/blocks/AnchorVerificationBlock.d.ts.map +0 -1
  261. package/modules/workflow/blocks/AnchorVerificationBlock.js.map +0 -1
  262. package/modules/workflow/blocks/DetectPageStateBlock.d.ts.map +0 -1
  263. package/modules/workflow/blocks/DetectPageStateBlock.js.map +0 -1
  264. package/modules/workflow/blocks/ErrorRecoveryBlock.d.ts.map +0 -1
  265. package/modules/workflow/blocks/ErrorRecoveryBlock.js.map +0 -1
  266. package/modules/workflow/blocks/WaitSearchPermitBlock.d.ts.map +0 -1
  267. package/modules/workflow/blocks/WaitSearchPermitBlock.js.map +0 -1
  268. package/modules/workflow/blocks/helpers/containerAnchors.d.ts.map +0 -1
  269. package/modules/workflow/blocks/helpers/containerAnchors.js.map +0 -1
  270. package/modules/workflow/blocks/helpers/downloadPaths.test.ts +0 -62
  271. package/modules/workflow/blocks/helpers/mergeXhsMarkdown.test.ts +0 -121
  272. package/modules/workflow/blocks/helpers/operationLogger.d.ts.map +0 -1
  273. package/modules/workflow/blocks/helpers/operationLogger.js.map +0 -1
  274. package/modules/workflow/blocks/helpers/persistedNotes.test.ts +0 -268
  275. package/modules/workflow/blocks/helpers/searchPageState.d.ts.map +0 -1
  276. package/modules/workflow/blocks/helpers/searchPageState.js.map +0 -1
  277. package/modules/workflow/blocks/helpers/targetCountMode.test.ts +0 -29
  278. package/modules/workflow/blocks/helpers/xhsCliArgs.test.ts +0 -75
  279. package/modules/workflow/tests/smartReply.test.ts +0 -32
  280. package/modules/xiaohongshu/app/src/blocks/Phase3Interact.matcher.test.ts +0 -33
  281. package/modules/xiaohongshu/app/src/utils/__tests__/checkpoints.test.ts +0 -141
  282. package/modules/xiaohongshu/app/tests/commentMatchDsl.test.ts +0 -50
  283. package/modules/xiaohongshu/app/tests/commentMatcher.test.ts +0 -46
  284. package/modules/xiaohongshu/app/tests/sharding.test.ts +0 -31
  285. package/package-scripts.json +0 -8
  286. package/runtime/infra/utils/README.md +0 -13
  287. package/runtime/infra/utils/scripts/README.md +0 -0
  288. package/runtime/infra/utils/scripts/development/eval-in-session.mjs +0 -40
  289. package/runtime/infra/utils/scripts/development/highlight-search-containers.mjs +0 -35
  290. package/runtime/infra/utils/scripts/service/kill-port.mjs +0 -24
  291. package/runtime/infra/utils/scripts/service/start-api.mjs +0 -39
  292. package/runtime/infra/utils/scripts/service/start-browser-service.mjs +0 -106
  293. package/runtime/infra/utils/scripts/service/stop-api.mjs +0 -18
  294. package/runtime/infra/utils/scripts/service/stop-browser-service.mjs +0 -104
  295. package/runtime/infra/utils/scripts/test-services.mjs +0 -94
  296. package/services/shared/heartbeat.test.ts +0 -102
  297. package/services/unified-api/__tests__/task-state.test.ts +0 -95
  298. package/sitecustomize.py +0 -19
  299. package/tests/README.md +0 -194
  300. package/tests/e2e/workflows/weibo-feed-extraction.test.ts +0 -171
  301. package/tests/fixtures/data/container-definitions.json +0 -67
  302. package/tests/fixtures/pages/simple-page.html +0 -69
  303. package/tests/integration/01-test-container-match.mjs +0 -188
  304. package/tests/integration/02-test-dom-branch.mjs +0 -161
  305. package/tests/integration/03-test-container-operation-system.mjs +0 -91
  306. package/tests/integration/05-test-container-lifecycle-events.mjs +0 -224
  307. package/tests/integration/05-test-container-lifecycle-with-events.mjs +0 -250
  308. package/tests/integration/06-test-container-dom-tree-drawing.mjs +0 -256
  309. package/tests/integration/07-test-weibo-container-lifecycle.mjs +0 -355
  310. package/tests/integration/08-test-weibo-feed-workflow.test.mjs +0 -164
  311. package/tests/integration/10-test-visual-analyzer.mjs +0 -312
  312. package/tests/integration/11-test-visual-loop.mjs +0 -284
  313. package/tests/integration/12-test-simple-visual-loop.mjs +0 -242
  314. package/tests/integration/13-test-visual-robust.mjs +0 -185
  315. package/tests/integration/14-test-visual-highlight-loop.mjs +0 -271
  316. package/tests/integration/inspect-page.mjs +0 -50
  317. package/tests/integration/run-all-tests.mjs +0 -95
  318. package/tests/patch_verification/CODEX_PATCH_TEST.md +0 -103
  319. package/tests/patch_verification/PHASE2_ANALYSIS.md +0 -179
  320. package/tests/patch_verification/PHASE2_OPTIMIZATION_REPORT.md +0 -55
  321. package/tests/patch_verification/PHASE2_TO_PHASE4_SUMMARY.md +0 -126
  322. package/tests/patch_verification/QUICK_TEST_SEQUENCE.md +0 -262
  323. package/tests/patch_verification/README.md +0 -143
  324. package/tests/patch_verification/RUN_TESTS.md +0 -60
  325. package/tests/patch_verification/TEST_EXECUTION.md +0 -99
  326. package/tests/patch_verification/TEST_PLAN.md +0 -328
  327. package/tests/patch_verification/TEST_RESULTS.md +0 -34
  328. package/tests/patch_verification/TOOL_TEST_PLAN.md +0 -48
  329. package/tests/patch_verification/run-tool-test.mjs +0 -121
  330. package/tests/patch_verification/temp_test_files/test01.txt +0 -1
  331. package/tests/patch_verification/temp_test_files/test02.txt +0 -3
  332. package/tests/patch_verification/temp_test_files/test02_gnu.txt +0 -3
  333. package/tests/patch_verification/temp_test_files/test03.txt +0 -1
  334. package/tests/patch_verification/temp_test_files/test03_multiline.txt +0 -5
  335. package/tests/patch_verification/temp_test_files/test04_function.ts +0 -5
  336. package/tests/patch_verification/temp_test_files/test05_import.ts +0 -4
  337. package/tests/patch_verification/temp_test_files/test06_special_chars.txt +0 -4
  338. package/tests/patch_verification/temp_test_files/test07_indentation.ts +0 -5
  339. package/tests/patch_verification/temp_test_files/test08_mismatch.txt +0 -1
  340. package/tests/patch_verification/temp_test_files/test_add_02.txt +0 -3
  341. package/tests/patch_verification/temp_test_files/test_simple.txt +0 -1
  342. package/tests/runner/TestReporter.mjs +0 -57
  343. package/tests/runner/TestRunner.mjs +0 -244
  344. package/tests/unit/commands/profile.test.mjs +0 -10
  345. package/tests/unit/container/change-notifier.test.mjs +0 -181
  346. package/tests/unit/lifecycle/session-registry.test.mjs +0 -135
  347. package/tests/unit/operations/registry.test.ts +0 -73
  348. package/tests/unit/utils/browser-service.test.mjs +0 -153
  349. package/tests/unit/utils/config.test.mjs +0 -166
  350. package/tests/unit/utils/fingerprint.test.mjs +0 -166
  351. package/tsconfig.json +0 -31
  352. package/tsconfig.services.json +0 -26
  353. /package/apps/desktop-console/{src → dist}/renderer/index.html +0 -0
  354. /package/apps/desktop-console/{src/renderer/tabs → dist/renderer}/run.mts +0 -0
@@ -0,0 +1,805 @@
1
+ /**
2
+ * XHS Comment DOM Probes
3
+ *
4
+ * 仅做只读 DOM 查询 + rect/文本提取,用于计算坐标、统计、判断是否到底。
5
+ * 禁止在这里做 element.click()/scrollIntoView()/location 跳转等行为。
6
+ */
7
+ import { logControllerActionError, logControllerActionResult, logControllerActionStart, } from './operationLogger.js';
8
+ export async function controllerAction(controllerUrl, action, payload = {}, timeoutMs = 10000) {
9
+ const opId = logControllerActionStart(action, payload, { source: 'xhsCommentDom' });
10
+ try {
11
+ const response = await fetch(controllerUrl, {
12
+ method: 'POST',
13
+ headers: { 'Content-Type': 'application/json' },
14
+ body: JSON.stringify({ action, payload }),
15
+ signal: AbortSignal.timeout
16
+ ? AbortSignal.timeout(timeoutMs)
17
+ : undefined,
18
+ });
19
+ if (!response.ok) {
20
+ throw new Error(`HTTP ${response.status}: ${await response.text()}`);
21
+ }
22
+ const data = await response.json();
23
+ const result = data.data || data;
24
+ logControllerActionResult(opId, action, result, { source: 'xhsCommentDom' });
25
+ return result;
26
+ }
27
+ catch (error) {
28
+ logControllerActionError(opId, action, error, payload, { source: 'xhsCommentDom' });
29
+ throw error;
30
+ }
31
+ }
32
+ export async function getViewport(controllerUrl, profile) {
33
+ try {
34
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
35
+ profile,
36
+ script: '({ innerWidth: window.innerWidth || 0, innerHeight: window.innerHeight || 0 })',
37
+ });
38
+ const payload = result.result || result.data?.result || result;
39
+ return {
40
+ innerWidth: Number(payload?.innerWidth ?? 0),
41
+ innerHeight: Number(payload?.innerHeight ?? 0),
42
+ };
43
+ }
44
+ catch {
45
+ return { innerWidth: 0, innerHeight: 0 };
46
+ }
47
+ }
48
+ export function computeVisibleFocusPoint(rect, viewport) {
49
+ const w = Number(viewport?.innerWidth || 0) || 0;
50
+ const h = Number(viewport?.innerHeight || 0) || 0;
51
+ const xCenter = rect.x + rect.width / 2;
52
+ const x = w ? clamp(xCenter, 20, w - 20) : xCenter;
53
+ const rectTop = rect.y;
54
+ const rectBottom = rect.y + rect.height;
55
+ const safeTop = 160;
56
+ const safeBottom = 120;
57
+ const topVisible = Math.max(rectTop, safeTop);
58
+ const bottomVisible = h ? Math.min(rectBottom, h - safeBottom) : rectBottom;
59
+ if (bottomVisible <= topVisible)
60
+ return null;
61
+ const y = (topVisible + bottomVisible) / 2;
62
+ return { x, y };
63
+ }
64
+ export async function isInputFocused(controllerUrl, profile) {
65
+ try {
66
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
67
+ profile,
68
+ script: `(() => {
69
+ const el = document.activeElement;
70
+ const tag = el && el.tagName ? el.tagName.toLowerCase() : '';
71
+ const type = (el && (el as any).type) ? String((el as any).type) : '';
72
+ const isInput = tag === 'input' || tag === 'textarea' || (el && (el as any).isContentEditable);
73
+ return { tag, type, isInput };
74
+ })()`,
75
+ });
76
+ const payload = result.result || result.data?.result || result;
77
+ return Boolean(payload?.isInput);
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * 聚焦评论区(只读计算坐标 + 可选 outline),返回可用于系统 click 的坐标。
85
+ */
86
+ export async function locateCommentsFocusPoint(controllerUrl, profile) {
87
+ try {
88
+ const focusResult = await controllerAction(controllerUrl, 'browser:execute', {
89
+ profile,
90
+ script: `(() => {
91
+ const root =
92
+ document.querySelector('.comments-el') ||
93
+ document.querySelector('.comment-list') ||
94
+ document.querySelector('.comments-container') ||
95
+ document.querySelector('[class*="comment-section"]');
96
+ if (!root) return null;
97
+
98
+ document.querySelectorAll('[data-webauto-highlight]').forEach((el) => {
99
+ if (el instanceof HTMLElement) el.style.outline = '';
100
+ el.removeAttribute('data-webauto-highlight');
101
+ });
102
+
103
+ const rect = root.getBoundingClientRect();
104
+ const vx2 = window.innerWidth;
105
+ const vy2 = window.innerHeight;
106
+
107
+ const topVisible = Math.max(rect.top, 10);
108
+ const bottomVisible = Math.min(rect.bottom, vy2 - 10);
109
+ if (bottomVisible <= topVisible) return null;
110
+ const yMid = (topVisible + bottomVisible) / 2;
111
+
112
+ const clamp = (n, lo, hi) => Math.min(Math.max(n, lo), hi);
113
+ const isImageLike = (el) => {
114
+ if (!el || !(el instanceof Element)) return false;
115
+ const tag = String(el.tagName || '').toUpperCase();
116
+ if (tag === 'IMG' || tag === 'VIDEO' || tag === 'PICTURE') return true;
117
+ try {
118
+ const cls = typeof el.className === 'string' ? el.className : '';
119
+ if (cls && /(image|img|photo|picture|zoom)/i.test(cls)) return true;
120
+ } catch {}
121
+ try {
122
+ const m = el.closest('img,video,picture');
123
+ if (m) return true;
124
+ } catch {}
125
+ return false;
126
+ };
127
+ const isDangerous = (hit) => {
128
+ if (!hit || !(hit instanceof Element)) return true;
129
+ try {
130
+ if (hit.closest && hit.closest('a[href]')) return true;
131
+ } catch {}
132
+ return isImageLike(hit);
133
+ };
134
+
135
+ // 选择一个“用于 hover/wheel 的安全点”:尽量靠右,且 elementFromPoint 不在图片/链接上
136
+ const candidates = [];
137
+ const ys = [0.35, 0.5, 0.65];
138
+ const xs = [0.92, 0.82, 0.72, 0.6, 0.5];
139
+ for (const fy of ys) {
140
+ const y = clamp(topVisible + (bottomVisible - topVisible) * fy, 140, vy2 - 140);
141
+ for (const fx of xs) {
142
+ const x = clamp(rect.left + rect.width * fx, 20, vx2 - 20);
143
+ candidates.push({ x, y });
144
+ }
145
+ }
146
+ // fallback: center
147
+ candidates.push({ x: clamp(rect.left + rect.width / 2, 20, vx2 - 20), y: clamp(yMid, 140, vy2 - 140) });
148
+
149
+ let picked = null;
150
+ for (const p of candidates) {
151
+ const hit = document.elementFromPoint(p.x, p.y);
152
+ if (!hit || !(hit instanceof HTMLElement)) continue;
153
+ // 必须落在 root 内
154
+ if (!(root === hit || (root.contains && root.contains(hit)))) continue;
155
+ if (isDangerous(hit)) continue;
156
+ picked = p;
157
+ break;
158
+ }
159
+ if (!picked) picked = candidates[candidates.length - 1];
160
+
161
+ if (root instanceof HTMLElement) {
162
+ root.style.outline = '4px solid magenta';
163
+ root.setAttribute('data-webauto-highlight', 'true');
164
+ }
165
+
166
+ return picked ? { x: picked.x, y: picked.y } : null;
167
+ })()`,
168
+ });
169
+ const payload = focusResult.result || focusResult.data?.result || focusResult;
170
+ if (!payload || typeof payload.x !== 'number' || typeof payload.y !== 'number')
171
+ return null;
172
+ return { x: Number(payload.x), y: Number(payload.y) };
173
+ }
174
+ catch {
175
+ return null;
176
+ }
177
+ }
178
+ export async function getCommentStats(controllerUrl, profile) {
179
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
180
+ profile,
181
+ script: `(() => {
182
+ const root =
183
+ document.querySelector('.comments-el') ||
184
+ document.querySelector('.comment-list') ||
185
+ document.querySelector('.comments-container') ||
186
+ document.querySelector('[class*="comment-section"]');
187
+ if (!root) {
188
+ const chatCountEl =
189
+ document.querySelector('.chat-wrapper .count') ||
190
+ document.querySelector('[class*="chat-wrapper"] .count') ||
191
+ document.querySelector('.chat-wrapper [class*="count"]');
192
+ const parseCount = (raw) => {
193
+ const t = (raw || '').toString().trim();
194
+ if (!t) return null;
195
+ const mWan = t.match(/^([0-9]+(?:\\.[0-9]+)?)\\s*万/);
196
+ if (mWan) {
197
+ const v = Number.parseFloat(mWan[1]);
198
+ if (!Number.isFinite(v)) return null;
199
+ return Math.round(v * 10000);
200
+ }
201
+ const digits = t.replace(/[^0-9]/g, '');
202
+ if (!digits.length) return null;
203
+ return Number(digits);
204
+ };
205
+ const parsed = parseCount(chatCountEl?.textContent || '');
206
+ return { hasRoot: false, count: 0, hasMore: false, total: parsed };
207
+ }
208
+
209
+ const items = Array.from(
210
+ root.querySelectorAll(
211
+ [
212
+ '.comment-item',
213
+ '[class*="comment-item"]',
214
+ "[class*='reply-item']",
215
+ "[class*='replyItem']",
216
+ "[class*='sub-comment']",
217
+ "[class*='subComment']",
218
+ ].join(', '),
219
+ ),
220
+ );
221
+ const emptyEl =
222
+ root.querySelector('.no-comments') ||
223
+ root.querySelector('.comment-empty') ||
224
+ root.querySelector('.empty-comment') ||
225
+ root.querySelector('.note-comment-empty') ||
226
+ root.querySelector('.empty-state');
227
+ const emptyText = (emptyEl?.textContent || '').replace(/\\s+/g, ' ').trim();
228
+ const rootText = (root.textContent || '').replace(/\\s+/g, ' ').trim();
229
+ const emptyHint =
230
+ Boolean(emptyEl) ||
231
+ /荒地/.test(emptyText) ||
232
+ /暂无评论|还没有评论|没有评论/.test(emptyText) ||
233
+ /荒地/.test(rootText);
234
+
235
+ // 空评论:允许以“空态”直接结束(符合“空评论也算结束”规则)
236
+ if (items.length === 0 && emptyHint) {
237
+ return { hasRoot: true, count: 0, hasMore: false, total: 0 };
238
+ }
239
+
240
+ const candidates = [];
241
+ const pushText = (el) => {
242
+ if (!el) return;
243
+ const t = (el.textContent || '').trim();
244
+ if (!t) return;
245
+ candidates.push(t);
246
+ };
247
+
248
+ const headerContainers = Array.from(document.querySelectorAll('.comments-el, .note-detail-mask, .note-detail'));
249
+ for (const container of headerContainers) {
250
+ if (!container) continue;
251
+ const els = container.querySelectorAll('*');
252
+ els.forEach(pushText);
253
+ }
254
+
255
+ if (!candidates.length) {
256
+ document.querySelectorAll('body *').forEach(el => {
257
+ const t = (el.textContent || '').trim();
258
+ if (!t) return;
259
+ if (t.length > 80) return;
260
+ if (/评论/.test(t) && /\\d+/.test(t)) {
261
+ candidates.push(t);
262
+ }
263
+ });
264
+ }
265
+
266
+ let total = null;
267
+ const pattern = /(?:共|全部)\\s*(\\d+)\\s*条评论/;
268
+ for (const text of candidates) {
269
+ const m = text.match(pattern);
270
+ if (m) {
271
+ total = Number(m[1]) || null;
272
+ break;
273
+ }
274
+ }
275
+
276
+ if (total === null) {
277
+ // 仅在评论 root 内部寻找计数,避免误读页面其它区域(如推荐/频道/聊天计数)
278
+ const chatCountEl =
279
+ root.querySelector('.chat-wrapper .count') ||
280
+ root.querySelector('[class*="chat-wrapper"] .count') ||
281
+ root.querySelector('.chat-wrapper [class*="count"]');
282
+ const parseCount = (raw) => {
283
+ const t = (raw || '').toString().trim();
284
+ if (!t) return null;
285
+ const mWan = t.match(/^([0-9]+(?:\\.[0-9]+)?)\\s*万/);
286
+ if (mWan) {
287
+ const v = Number.parseFloat(mWan[1]);
288
+ if (!Number.isFinite(v)) return null;
289
+ return Math.round(v * 10000);
290
+ }
291
+ const digits = t.replace(/[^0-9]/g, '');
292
+ if (!digits.length) return null;
293
+ return Number(digits);
294
+ };
295
+ const parsed = parseCount(chatCountEl?.textContent || '');
296
+ if (typeof parsed === 'number' && Number.isFinite(parsed)) {
297
+ total = parsed;
298
+ }
299
+ }
300
+
301
+ // hasMore:仅用于日志/参考,不作为“到底”判定依据
302
+ // 这里保守地以“是否存在展开按钮/扩展控件”为参考信号
303
+ const hasMore = !!root.querySelector('.show-more, .reply-expand, [class*="expand"]');
304
+
305
+ return {
306
+ hasRoot: true,
307
+ count: items.length,
308
+ hasMore,
309
+ total
310
+ };
311
+ })()`,
312
+ });
313
+ const payload = result.result || result.data?.result || result;
314
+ return {
315
+ hasRoot: Boolean(payload?.hasRoot),
316
+ count: Number(payload?.count || 0),
317
+ hasMore: Boolean(payload?.hasMore),
318
+ total: typeof payload?.total === 'number' ? payload.total : null,
319
+ };
320
+ }
321
+ export async function getCommentEndState(controllerUrl, profile) {
322
+ try {
323
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
324
+ profile,
325
+ script: `(() => {
326
+ const root =
327
+ document.querySelector('.comments-el') ||
328
+ document.querySelector('.comment-list') ||
329
+ document.querySelector('.comments-container') ||
330
+ document.querySelector('[class*="comment-section"]');
331
+ if (!root) {
332
+ return { endMarkerVisible: false, emptyStateVisible: false, reason: 'no root' };
333
+ }
334
+
335
+ const viewportH = window.innerHeight || 0;
336
+ const viewportW = window.innerWidth || 0;
337
+
338
+ const isVisible = (el) => {
339
+ if (!el) return false;
340
+ const style = window.getComputedStyle(el);
341
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') return false;
342
+ const r = el.getBoundingClientRect();
343
+ if (!r || r.width < 6 || r.height < 6) return false;
344
+ if (r.bottom <= 0 || r.top >= viewportH) return false;
345
+ if (r.right <= 0 || r.left >= viewportW) return false;
346
+ return true;
347
+ };
348
+
349
+ // 以容器定义的 selector 为准(同义集合)
350
+ const endSel = '.end-container, .comment-footer, .comment-end, [data-v-4a19279a][class*=\"end\"]';
351
+ const emptySel =
352
+ '.comment-empty, .empty-comment, .note-comment-empty, .empty-state, .no-comments, [class*=\"no-comments\"], [class*=\"empty\"][class*=\"comment\"]';
353
+
354
+ const endEl = root.querySelector(endSel) || document.querySelector(endSel);
355
+ const emptyEl = root.querySelector(emptySel) || document.querySelector(emptySel);
356
+
357
+ return {
358
+ endMarkerVisible: isVisible(endEl),
359
+ emptyStateVisible: isVisible(emptyEl),
360
+ };
361
+ })()`,
362
+ });
363
+ const payload = result.result || result.data?.result || result;
364
+ return {
365
+ endMarkerVisible: Boolean(payload?.endMarkerVisible),
366
+ emptyStateVisible: Boolean(payload?.emptyStateVisible),
367
+ };
368
+ }
369
+ catch {
370
+ return { endMarkerVisible: false, emptyStateVisible: false };
371
+ }
372
+ }
373
+ export async function getViewportFirstComment(controllerUrl, profile) {
374
+ try {
375
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
376
+ profile,
377
+ script: `(() => {
378
+ const root =
379
+ document.querySelector('.comments-el') ||
380
+ document.querySelector('.comment-list') ||
381
+ document.querySelector('.comments-container') ||
382
+ document.querySelector('[class*="comment-section"]');
383
+ if (!root) return null;
384
+
385
+ const viewBottom = window.innerHeight || document.documentElement.clientHeight || 0;
386
+ const items = Array.from(root.querySelectorAll('.comment-item, [class*="comment-item"]'));
387
+
388
+ let picked = null;
389
+ let pickedRect = null;
390
+
391
+ for (const el of items) {
392
+ if (!(el instanceof HTMLElement)) continue;
393
+ const rect = el.getBoundingClientRect();
394
+ const top = rect.top;
395
+ const bottom = rect.bottom;
396
+ const visibleTop = Math.max(top, 0);
397
+ const visibleBottom = Math.min(bottom, viewBottom);
398
+ if (visibleBottom <= visibleTop) continue;
399
+ picked = el;
400
+ pickedRect = rect;
401
+ break;
402
+ }
403
+
404
+ if (!picked || !pickedRect) return null;
405
+
406
+ const userEl =
407
+ picked.querySelector('[class*="name"],[class*="username"],.user-name') ||
408
+ picked.querySelector('[class*="author"]');
409
+ const contentEl =
410
+ picked.querySelector('[class*="content"],[class*="text"],.comment-content') ||
411
+ picked;
412
+
413
+ const id =
414
+ picked.getAttribute('data-id') ||
415
+ picked.getAttribute('data-comment-id') ||
416
+ picked.getAttribute('id') ||
417
+ '';
418
+ const userText = (userEl?.textContent || '').trim();
419
+ const contentText = (contentEl?.textContent || '').trim();
420
+
421
+ const keyBase =
422
+ id ||
423
+ (userText ? userText.slice(0, 16) : '') + '|' + contentText.slice(0, 32);
424
+ const key = keyBase || contentText.slice(0, 24) || 'unknown';
425
+
426
+ return {
427
+ key,
428
+ top: pickedRect.top,
429
+ bottom: pickedRect.bottom,
430
+ user: userText.slice(0, 24),
431
+ textSample: contentText.slice(0, 50)
432
+ };
433
+ })()`,
434
+ });
435
+ const payload = result.result || result.data?.result || result;
436
+ if (!payload || typeof payload !== 'object')
437
+ return null;
438
+ if (typeof payload.key !== 'string')
439
+ return null;
440
+ return {
441
+ key: String(payload.key),
442
+ top: Number(payload.top ?? 0),
443
+ bottom: Number(payload.bottom ?? 0),
444
+ user: typeof payload.user === 'string' ? payload.user : '',
445
+ textSample: typeof payload.textSample === 'string' ? payload.textSample : '',
446
+ };
447
+ }
448
+ catch {
449
+ return null;
450
+ }
451
+ }
452
+ export async function getScrollContainerInfo(controllerUrl, profile) {
453
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
454
+ profile,
455
+ script: `(() => {
456
+ const root =
457
+ document.querySelector('.comments-el') ||
458
+ document.querySelector('.comment-list') ||
459
+ document.querySelector('.comments-container') ||
460
+ document.querySelector('[class*="comment-section"]');
461
+ if (!root) return null;
462
+
463
+ let scrollContainer = null;
464
+ let current = root;
465
+ while (current && current !== document.body) {
466
+ const style = window.getComputedStyle(current);
467
+ const overflowY = style.overflowY || '';
468
+ const canScroll =
469
+ (overflowY === 'scroll' || overflowY === 'auto' || overflowY === 'overlay') &&
470
+ current.scrollHeight - current.clientHeight > 12;
471
+ if (canScroll) {
472
+ scrollContainer = current;
473
+ break;
474
+ }
475
+ current = current.parentElement;
476
+ }
477
+
478
+ if (!scrollContainer) return null;
479
+ const rect = scrollContainer.getBoundingClientRect();
480
+ const clamp = (n, lo, hi) => Math.min(Math.max(n, lo), hi);
481
+ const vw = window.innerWidth || document.documentElement.clientWidth || 0;
482
+ const vh = window.innerHeight || document.documentElement.clientHeight || 0;
483
+
484
+ const isImageLike = (el) => {
485
+ if (!el || !(el instanceof Element)) return false;
486
+ const tag = String(el.tagName || '').toUpperCase();
487
+ if (tag === 'IMG' || tag === 'VIDEO' || tag === 'PICTURE') return true;
488
+ try {
489
+ const cls = typeof el.className === 'string' ? el.className : '';
490
+ if (cls && /(image|img|photo|picture|zoom)/i.test(cls)) return true;
491
+ } catch {}
492
+ try {
493
+ const m = el.closest('img,video,picture');
494
+ if (m) return true;
495
+ } catch {}
496
+ return false;
497
+ };
498
+ const isBad = (hit) => {
499
+ if (!hit || !(hit instanceof Element)) return true;
500
+ try {
501
+ if (hit.closest && hit.closest('a[href]')) return true;
502
+ } catch {}
503
+ return isImageLike(hit);
504
+ };
505
+
506
+ // 选一个 wheel 安全点:优先靠右侧,避免落在评论图片上导致 wheel 被拦截(缩放/媒体)
507
+ const points = [];
508
+ const ys = [0.25, 0.45, 0.6, 0.75];
509
+ for (const fy of ys) {
510
+ const x = clamp(rect.right - 10, 20, Math.max(20, vw - 20));
511
+ const y = clamp(rect.top + rect.height * fy, 140, Math.max(140, vh - 140));
512
+ points.push({ x, y });
513
+ }
514
+ points.push({ x: clamp(rect.left + rect.width / 2, 20, Math.max(20, vw - 20)), y: clamp(rect.top + rect.height / 2, 140, Math.max(140, vh - 140)) });
515
+ let picked = points[points.length - 1];
516
+ for (const p of points) {
517
+ const hit = document.elementFromPoint(p.x, p.y);
518
+ if (!hit || !(hit instanceof HTMLElement)) continue;
519
+ if (!(scrollContainer === hit || scrollContainer.contains(hit))) continue;
520
+ if (isBad(hit)) continue;
521
+ picked = p;
522
+ break;
523
+ }
524
+ return {
525
+ x: picked.x,
526
+ y: picked.y,
527
+ scrollTop: scrollContainer.scrollTop,
528
+ scrollHeight: scrollContainer.scrollHeight,
529
+ clientHeight: scrollContainer.clientHeight
530
+ };
531
+ })()`,
532
+ }).catch(() => null);
533
+ const payload = result?.result || result?.data?.result || result;
534
+ if (!payload || typeof payload.x !== 'number' || typeof payload.y !== 'number')
535
+ return null;
536
+ return {
537
+ x: Number(payload.x),
538
+ y: Number(payload.y),
539
+ scrollTop: Number(payload.scrollTop ?? 0),
540
+ scrollHeight: Number(payload.scrollHeight ?? 0),
541
+ clientHeight: Number(payload.clientHeight ?? 0),
542
+ };
543
+ }
544
+ export async function getScrollContainerState(controllerUrl, profile) {
545
+ const info = await getScrollContainerInfo(controllerUrl, profile);
546
+ if (!info)
547
+ return null;
548
+ return {
549
+ scrollTop: info.scrollTop,
550
+ scrollHeight: info.scrollHeight,
551
+ clientHeight: info.clientHeight,
552
+ };
553
+ }
554
+ export async function getScrollStats(rootSelectors, controllerUrl, profile) {
555
+ const selectors = Array.isArray(rootSelectors)
556
+ ? rootSelectors.filter(Boolean)
557
+ : [];
558
+ try {
559
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
560
+ profile,
561
+ script: `(() => {
562
+ const roots = ${JSON.stringify(selectors)};
563
+ const pickRoot = () => {
564
+ for (const sel of roots) {
565
+ try {
566
+ const el = document.querySelector(sel);
567
+ if (el) return el;
568
+ } catch (_) {}
569
+ }
570
+ return null;
571
+ };
572
+
573
+ const isScrollable = (el) => {
574
+ if (!el) return false;
575
+ const style = window.getComputedStyle(el);
576
+ const overflowY = style.overflowY || '';
577
+ const canScroll =
578
+ (overflowY === 'scroll' || overflowY === 'auto' || overflowY === 'overlay') &&
579
+ el.scrollHeight - el.clientHeight > 24;
580
+ return canScroll;
581
+ };
582
+
583
+ const root = pickRoot();
584
+ let best = null;
585
+ if (root) {
586
+ let current = root;
587
+ let safetyUp = 0;
588
+ while (current && current !== document.body && safetyUp < 60) {
589
+ safetyUp += 1;
590
+ if (isScrollable(current)) {
591
+ best = current;
592
+ break;
593
+ }
594
+ current = current.parentElement;
595
+ }
596
+ }
597
+ if (!best) {
598
+ if (root) {
599
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
600
+ let node = walker.currentNode;
601
+ let safety = 0;
602
+ while (node && safety < 1200) {
603
+ safety += 1;
604
+ if (isScrollable(node)) {
605
+ best = node;
606
+ break;
607
+ }
608
+ node = walker.nextNode();
609
+ }
610
+ }
611
+ }
612
+ if (!best) {
613
+ if (isScrollable(document.documentElement)) best = document.documentElement;
614
+ else if (isScrollable(document.body)) best = document.body;
615
+ }
616
+
617
+ if (!best) {
618
+ return {
619
+ found: false,
620
+ scrollTop: 0,
621
+ scrollHeight: 0,
622
+ clientHeight: 0,
623
+ atTop: true,
624
+ atBottom: false,
625
+ };
626
+ }
627
+
628
+ const scrollTop = best.scrollTop || 0;
629
+ const scrollHeight = best.scrollHeight || 0;
630
+ const clientHeight = best.clientHeight || 0;
631
+ const atTop = scrollTop <= 2;
632
+ const noNeedScroll = scrollHeight > 0 && clientHeight > 0 && (scrollHeight - clientHeight) <= 24;
633
+ const atBottom = noNeedScroll || (scrollHeight > 0 && clientHeight > 0 && (scrollTop + clientHeight) >= (scrollHeight - 8));
634
+
635
+ return { found: true, scrollTop, scrollHeight, clientHeight, atTop, atBottom };
636
+ })()`,
637
+ });
638
+ const payload = result.result || result.data?.result || result || {};
639
+ return {
640
+ found: Boolean(payload?.found),
641
+ scrollTop: Number(payload?.scrollTop ?? 0),
642
+ scrollHeight: Number(payload?.scrollHeight ?? 0),
643
+ clientHeight: Number(payload?.clientHeight ?? 0),
644
+ atTop: Boolean(payload?.atTop),
645
+ atBottom: Boolean(payload?.atBottom),
646
+ };
647
+ }
648
+ catch {
649
+ return {
650
+ found: false,
651
+ scrollTop: 0,
652
+ scrollHeight: 0,
653
+ clientHeight: 0,
654
+ atTop: true,
655
+ atBottom: false,
656
+ };
657
+ }
658
+ }
659
+ /**
660
+ * 与 getScrollStats 选择同一个“实际滚动容器”,并返回其视口内中心点坐标(用于系统级 hover/click/wheel 对齐滚动目标)。
661
+ */
662
+ export async function getScrollTargetInfo(rootSelectors, controllerUrl, profile) {
663
+ const selectors = Array.isArray(rootSelectors)
664
+ ? rootSelectors.filter(Boolean)
665
+ : [];
666
+ try {
667
+ const result = await controllerAction(controllerUrl, 'browser:execute', {
668
+ profile,
669
+ script: `(() => {
670
+ const roots = ${JSON.stringify(selectors)};
671
+ const pickRoot = () => {
672
+ for (const sel of roots) {
673
+ try {
674
+ const el = document.querySelector(sel);
675
+ if (el) return el;
676
+ } catch (_) {}
677
+ }
678
+ return null;
679
+ };
680
+
681
+ const isScrollable = (el) => {
682
+ if (!el) return false;
683
+ const style = window.getComputedStyle(el);
684
+ const overflowY = style.overflowY || '';
685
+ const canScroll =
686
+ (overflowY === 'scroll' || overflowY === 'auto' || overflowY === 'overlay') &&
687
+ el.scrollHeight - el.clientHeight > 24;
688
+ return canScroll;
689
+ };
690
+
691
+ const root = pickRoot();
692
+ let best = null;
693
+ if (root) {
694
+ let current = root;
695
+ let safetyUp = 0;
696
+ while (current && current !== document.body && safetyUp < 60) {
697
+ safetyUp += 1;
698
+ if (isScrollable(current)) {
699
+ best = current;
700
+ break;
701
+ }
702
+ current = current.parentElement;
703
+ }
704
+ }
705
+ if (!best) {
706
+ if (root) {
707
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
708
+ let node = walker.currentNode;
709
+ let safety = 0;
710
+ while (node && safety < 1200) {
711
+ safety += 1;
712
+ if (isScrollable(node)) {
713
+ best = node;
714
+ break;
715
+ }
716
+ node = walker.nextNode();
717
+ }
718
+ }
719
+ }
720
+ if (!best) {
721
+ if (isScrollable(document.documentElement)) best = document.documentElement;
722
+ else if (isScrollable(document.body)) best = document.body;
723
+ }
724
+
725
+ if (!best) return null;
726
+
727
+ const scrollTop = best.scrollTop || 0;
728
+ const scrollHeight = best.scrollHeight || 0;
729
+ const clientHeight = best.clientHeight || 0;
730
+ const atTop = scrollTop <= 2;
731
+ const noNeedScroll = scrollHeight > 0 && clientHeight > 0 && (scrollHeight - clientHeight) <= 24;
732
+ const atBottom = noNeedScroll || (scrollHeight > 0 && clientHeight > 0 && (scrollTop + clientHeight) >= (scrollHeight - 8));
733
+
734
+ const r = best.getBoundingClientRect();
735
+ const vw = window.innerWidth || document.documentElement.clientWidth || 0;
736
+ const vh = window.innerHeight || document.documentElement.clientHeight || 0;
737
+ const clamp = (n, lo, hi) => Math.min(Math.max(n, lo), hi);
738
+ const isImageLike = (el) => {
739
+ if (!el || !(el instanceof Element)) return false;
740
+ const tag = String(el.tagName || '').toUpperCase();
741
+ if (tag === 'IMG' || tag === 'VIDEO' || tag === 'PICTURE') return true;
742
+ try {
743
+ const cls = typeof el.className === 'string' ? el.className : '';
744
+ if (cls && /(image|img|photo|picture|zoom)/i.test(cls)) return true;
745
+ } catch {}
746
+ try {
747
+ const m = el.closest('img,video,picture');
748
+ if (m) return true;
749
+ } catch {}
750
+ return false;
751
+ };
752
+ const isBad = (hit) => {
753
+ if (!hit || !(hit instanceof Element)) return true;
754
+ try {
755
+ if (hit.closest && hit.closest('a[href]')) return true;
756
+ } catch {}
757
+ return isImageLike(hit);
758
+ };
759
+
760
+ // wheel 安全点:优先靠右侧,避免落在评论图片上导致 wheel 被拦截
761
+ const points = [];
762
+ const ys = [0.25, 0.4, 0.55, 0.7];
763
+ for (const fy of ys) {
764
+ points.push({
765
+ x: clamp(r.right - 10, 20, Math.max(20, vw - 20)),
766
+ y: clamp(r.top + r.height * fy, 120, Math.max(120, vh - 120)),
767
+ });
768
+ }
769
+ points.push({ x: clamp(r.left + r.width / 2, 20, Math.max(20, vw - 20)), y: clamp(r.top + r.height / 2, 120, Math.max(120, vh - 120)) });
770
+
771
+ let picked = points[points.length - 1];
772
+ for (const p of points) {
773
+ const hit = document.elementFromPoint(p.x, p.y);
774
+ if (!hit || !(hit instanceof HTMLElement)) continue;
775
+ if (!(best === hit || best.contains(hit))) continue;
776
+ if (isBad(hit)) continue;
777
+ picked = p;
778
+ break;
779
+ }
780
+
781
+ return { found: true, scrollTop, scrollHeight, clientHeight, atTop, atBottom, x: picked.x, y: picked.y };
782
+ })()`,
783
+ });
784
+ const payload = result.result || result.data?.result || result;
785
+ if (!payload || typeof payload.x !== 'number' || typeof payload.y !== 'number')
786
+ return null;
787
+ return {
788
+ found: Boolean(payload.found),
789
+ scrollTop: Number(payload.scrollTop ?? 0),
790
+ scrollHeight: Number(payload.scrollHeight ?? 0),
791
+ clientHeight: Number(payload.clientHeight ?? 0),
792
+ atTop: Boolean(payload.atTop),
793
+ atBottom: Boolean(payload.atBottom),
794
+ x: Number(payload.x),
795
+ y: Number(payload.y),
796
+ };
797
+ }
798
+ catch {
799
+ return null;
800
+ }
801
+ }
802
+ function clamp(n, min, max) {
803
+ return Math.min(Math.max(n, min), max);
804
+ }
805
+ //# sourceMappingURL=xhsCommentDom.js.map