aurix-ai 2.7.3 → 2.7.5

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 (288) hide show
  1. package/bin/aurix.js +2 -2
  2. package/dist/agent/AgentLoop.d.ts.map +1 -1
  3. package/dist/agent/AgentLoop.js +1 -52
  4. package/dist/agent/AgentLoop.js.map +1 -1
  5. package/dist/cli/App.d.ts.map +1 -1
  6. package/dist/cli/App.js +6 -0
  7. package/dist/cli/App.js.map +1 -1
  8. package/dist/cli/commands.d.ts.map +1 -1
  9. package/dist/cli/commands.js +7 -0
  10. package/dist/cli/commands.js.map +1 -1
  11. package/dist/tools/Browser.d.ts.map +1 -1
  12. package/dist/tools/Browser.js +44 -615
  13. package/dist/tools/Browser.js.map +1 -1
  14. package/dist/tools/captcha/CaptchaRouter.d.ts +6 -0
  15. package/dist/tools/captcha/CaptchaRouter.d.ts.map +1 -0
  16. package/dist/tools/captcha/CaptchaRouter.js +371 -0
  17. package/dist/tools/captcha/CaptchaRouter.js.map +1 -0
  18. package/dist/tools/captcha/RecaptchaSolver.d.ts +2 -0
  19. package/dist/tools/captcha/RecaptchaSolver.d.ts.map +1 -0
  20. package/dist/tools/captcha/RecaptchaSolver.js +1114 -0
  21. package/dist/tools/captcha/RecaptchaSolver.js.map +1 -0
  22. package/dist/tools/captcha/common.d.ts +35 -0
  23. package/dist/tools/captcha/common.d.ts.map +1 -0
  24. package/dist/tools/captcha/common.js +445 -0
  25. package/dist/tools/captcha/common.js.map +1 -0
  26. package/dist/tools/captcha/index.d.ts +5 -0
  27. package/dist/tools/captcha/index.d.ts.map +1 -0
  28. package/dist/tools/captcha/index.js +4 -0
  29. package/dist/tools/captcha/index.js.map +1 -0
  30. package/package.json +2 -2
  31. package/scripts/build.cjs +0 -15
  32. package/scripts/run-task.mjs +86 -0
  33. package/training/captcha-training.json +930 -0
  34. package/native/token-counter/index.d.ts +0 -7
  35. package/native/token-counter/index.js +0 -316
  36. package/native/token-counter/node_modules/.package-lock.json +0 -568
  37. package/native/token-counter/node_modules/2/array.js +0 -25
  38. package/native/token-counter/node_modules/2/index.js +0 -12
  39. package/native/token-counter/node_modules/2/iterator.js +0 -13
  40. package/native/token-counter/node_modules/2/license.txt +0 -21
  41. package/native/token-counter/node_modules/2/map.js +0 -27
  42. package/native/token-counter/node_modules/2/number.js +0 -109
  43. package/native/token-counter/node_modules/2/object.js +0 -23
  44. package/native/token-counter/node_modules/2/package.json +0 -60
  45. package/native/token-counter/node_modules/2/readme.md +0 -246
  46. package/native/token-counter/node_modules/2/string.js +0 -11
  47. package/native/token-counter/node_modules/2/test.js +0 -520
  48. package/native/token-counter/node_modules/@lamansky/every/index.js +0 -16
  49. package/native/token-counter/node_modules/@lamansky/every/license.txt +0 -21
  50. package/native/token-counter/node_modules/@lamansky/every/package.json +0 -38
  51. package/native/token-counter/node_modules/@lamansky/every/readme.md +0 -46
  52. package/native/token-counter/node_modules/@lamansky/flatten/index.js +0 -5
  53. package/native/token-counter/node_modules/@lamansky/flatten/license.txt +0 -21
  54. package/native/token-counter/node_modules/@lamansky/flatten/package.json +0 -35
  55. package/native/token-counter/node_modules/@lamansky/flatten/readme.md +0 -41
  56. package/native/token-counter/node_modules/@napi-rs/cli/LICENSE +0 -21
  57. package/native/token-counter/node_modules/@napi-rs/cli/README.md +0 -96
  58. package/native/token-counter/node_modules/@napi-rs/cli/package.json +0 -65
  59. package/native/token-counter/node_modules/@napi-rs/cli/scripts/index.js +0 -51371
  60. package/native/token-counter/node_modules/add-counter/index.js +0 -3
  61. package/native/token-counter/node_modules/add-counter/license.txt +0 -21
  62. package/native/token-counter/node_modules/add-counter/package.json +0 -34
  63. package/native/token-counter/node_modules/add-counter/readme.md +0 -36
  64. package/native/token-counter/node_modules/array-pad/LICENSE +0 -22
  65. package/native/token-counter/node_modules/array-pad/README.md +0 -80
  66. package/native/token-counter/node_modules/array-pad/index.js +0 -30
  67. package/native/token-counter/node_modules/array-pad/package.json +0 -28
  68. package/native/token-counter/node_modules/arrify/index.js +0 -8
  69. package/native/token-counter/node_modules/arrify/license +0 -21
  70. package/native/token-counter/node_modules/arrify/package.json +0 -33
  71. package/native/token-counter/node_modules/arrify/readme.md +0 -36
  72. package/native/token-counter/node_modules/case-insensitive/index.js +0 -72
  73. package/native/token-counter/node_modules/case-insensitive/license.txt +0 -21
  74. package/native/token-counter/node_modules/case-insensitive/package.json +0 -39
  75. package/native/token-counter/node_modules/case-insensitive/readme.md +0 -39
  76. package/native/token-counter/node_modules/case-insensitive/test.js +0 -114
  77. package/native/token-counter/node_modules/class-chain/index.js +0 -32
  78. package/native/token-counter/node_modules/class-chain/license.txt +0 -21
  79. package/native/token-counter/node_modules/class-chain/package.json +0 -39
  80. package/native/token-counter/node_modules/class-chain/readme.md +0 -23
  81. package/native/token-counter/node_modules/class-chain/test.js +0 -77
  82. package/native/token-counter/node_modules/copy-own/index.js +0 -10
  83. package/native/token-counter/node_modules/copy-own/license.txt +0 -21
  84. package/native/token-counter/node_modules/copy-own/package.json +0 -37
  85. package/native/token-counter/node_modules/copy-own/readme.md +0 -67
  86. package/native/token-counter/node_modules/def-props/index.js +0 -43
  87. package/native/token-counter/node_modules/def-props/license.txt +0 -21
  88. package/native/token-counter/node_modules/def-props/node_modules/is-obj/index.d.ts +0 -22
  89. package/native/token-counter/node_modules/def-props/node_modules/is-obj/index.js +0 -6
  90. package/native/token-counter/node_modules/def-props/node_modules/is-obj/license +0 -9
  91. package/native/token-counter/node_modules/def-props/node_modules/is-obj/package.json +0 -34
  92. package/native/token-counter/node_modules/def-props/node_modules/is-obj/readme.md +0 -39
  93. package/native/token-counter/node_modules/def-props/package.json +0 -51
  94. package/native/token-counter/node_modules/def-props/readme.md +0 -117
  95. package/native/token-counter/node_modules/empty-iterator/index.js +0 -3
  96. package/native/token-counter/node_modules/empty-iterator/license.txt +0 -21
  97. package/native/token-counter/node_modules/empty-iterator/package.json +0 -33
  98. package/native/token-counter/node_modules/empty-iterator/readme.md +0 -23
  99. package/native/token-counter/node_modules/enforce-range/index.js +0 -15
  100. package/native/token-counter/node_modules/enforce-range/license.txt +0 -21
  101. package/native/token-counter/node_modules/enforce-range/node_modules/2/array.js +0 -43
  102. package/native/token-counter/node_modules/enforce-range/node_modules/2/index.js +0 -10
  103. package/native/token-counter/node_modules/enforce-range/node_modules/2/iterator.js +0 -26
  104. package/native/token-counter/node_modules/enforce-range/node_modules/2/license.txt +0 -21
  105. package/native/token-counter/node_modules/enforce-range/node_modules/2/map.js +0 -47
  106. package/native/token-counter/node_modules/enforce-range/node_modules/2/number.js +0 -33
  107. package/native/token-counter/node_modules/enforce-range/node_modules/2/object.js +0 -61
  108. package/native/token-counter/node_modules/enforce-range/node_modules/2/package.json +0 -41
  109. package/native/token-counter/node_modules/enforce-range/node_modules/2/readme.md +0 -210
  110. package/native/token-counter/node_modules/enforce-range/node_modules/2/string.js +0 -37
  111. package/native/token-counter/node_modules/enforce-range/node_modules/2/test.js +0 -413
  112. package/native/token-counter/node_modules/enforce-range/package.json +0 -36
  113. package/native/token-counter/node_modules/enforce-range/readme.md +0 -53
  114. package/native/token-counter/node_modules/english-list/.travis.yml +0 -6
  115. package/native/token-counter/node_modules/english-list/LICENSE +0 -22
  116. package/native/token-counter/node_modules/english-list/README.md +0 -44
  117. package/native/token-counter/node_modules/english-list/index.js +0 -34
  118. package/native/token-counter/node_modules/english-list/package.json +0 -31
  119. package/native/token-counter/node_modules/english-list/test.log +0 -6
  120. package/native/token-counter/node_modules/errate/index.js +0 -19
  121. package/native/token-counter/node_modules/errate/license.txt +0 -21
  122. package/native/token-counter/node_modules/errate/package.json +0 -39
  123. package/native/token-counter/node_modules/errate/readme.md +0 -79
  124. package/native/token-counter/node_modules/ffn/index.js +0 -9
  125. package/native/token-counter/node_modules/ffn/license.txt +0 -21
  126. package/native/token-counter/node_modules/ffn/package.json +0 -34
  127. package/native/token-counter/node_modules/ffn/readme.md +0 -51
  128. package/native/token-counter/node_modules/get-own-property/index.js +0 -5
  129. package/native/token-counter/node_modules/get-own-property/license.txt +0 -21
  130. package/native/token-counter/node_modules/get-own-property/package.json +0 -31
  131. package/native/token-counter/node_modules/get-own-property/readme.md +0 -22
  132. package/native/token-counter/node_modules/has-duplicates/index.js +0 -11
  133. package/native/token-counter/node_modules/has-duplicates/license.txt +0 -21
  134. package/native/token-counter/node_modules/has-duplicates/package.json +0 -32
  135. package/native/token-counter/node_modules/has-duplicates/readme.md +0 -23
  136. package/native/token-counter/node_modules/has-duplicates/test.js +0 -30
  137. package/native/token-counter/node_modules/if-else-throw/index.js +0 -8
  138. package/native/token-counter/node_modules/if-else-throw/license.txt +0 -21
  139. package/native/token-counter/node_modules/if-else-throw/package.json +0 -37
  140. package/native/token-counter/node_modules/if-else-throw/readme.md +0 -32
  141. package/native/token-counter/node_modules/is-array-of-length/index.js +0 -7
  142. package/native/token-counter/node_modules/is-array-of-length/license.txt +0 -21
  143. package/native/token-counter/node_modules/is-array-of-length/package.json +0 -42
  144. package/native/token-counter/node_modules/is-array-of-length/readme.md +0 -41
  145. package/native/token-counter/node_modules/is-class-of/index.js +0 -7
  146. package/native/token-counter/node_modules/is-class-of/license.txt +0 -21
  147. package/native/token-counter/node_modules/is-class-of/package.json +0 -35
  148. package/native/token-counter/node_modules/is-class-of/readme.md +0 -57
  149. package/native/token-counter/node_modules/is-global-object/index.js +0 -6
  150. package/native/token-counter/node_modules/is-global-object/license.txt +0 -21
  151. package/native/token-counter/node_modules/is-global-object/package.json +0 -34
  152. package/native/token-counter/node_modules/is-global-object/readme.md +0 -22
  153. package/native/token-counter/node_modules/is-instance-of/index.js +0 -28
  154. package/native/token-counter/node_modules/is-instance-of/license.txt +0 -21
  155. package/native/token-counter/node_modules/is-instance-of/package.json +0 -44
  156. package/native/token-counter/node_modules/is-instance-of/readme.md +0 -55
  157. package/native/token-counter/node_modules/is-iterable/index.js +0 -5
  158. package/native/token-counter/node_modules/is-iterable/license +0 -21
  159. package/native/token-counter/node_modules/is-iterable/package.json +0 -30
  160. package/native/token-counter/node_modules/is-iterable/readme.md +0 -23
  161. package/native/token-counter/node_modules/is-nil/LICENSE +0 -21
  162. package/native/token-counter/node_modules/is-nil/README.md +0 -47
  163. package/native/token-counter/node_modules/is-nil/index.js +0 -6
  164. package/native/token-counter/node_modules/is-nil/package.json +0 -46
  165. package/native/token-counter/node_modules/is-obj/index.js +0 -5
  166. package/native/token-counter/node_modules/is-obj/license +0 -21
  167. package/native/token-counter/node_modules/is-obj/package.json +0 -33
  168. package/native/token-counter/node_modules/is-obj/readme.md +0 -34
  169. package/native/token-counter/node_modules/is-object/.eslintignore +0 -1
  170. package/native/token-counter/node_modules/is-object/.eslintrc +0 -5
  171. package/native/token-counter/node_modules/is-object/.nycrc +0 -13
  172. package/native/token-counter/node_modules/is-object/.testem.json +0 -14
  173. package/native/token-counter/node_modules/is-object/CHANGELOG.md +0 -121
  174. package/native/token-counter/node_modules/is-object/LICENSE +0 -19
  175. package/native/token-counter/node_modules/is-object/README.md +0 -48
  176. package/native/token-counter/node_modules/is-object/index.js +0 -5
  177. package/native/token-counter/node_modules/is-object/package.json +0 -78
  178. package/native/token-counter/node_modules/is-object/test/index.js +0 -44
  179. package/native/token-counter/node_modules/is-plain-object/LICENSE +0 -21
  180. package/native/token-counter/node_modules/is-plain-object/README.md +0 -104
  181. package/native/token-counter/node_modules/is-plain-object/index.d.ts +0 -5
  182. package/native/token-counter/node_modules/is-plain-object/index.js +0 -37
  183. package/native/token-counter/node_modules/is-plain-object/package.json +0 -79
  184. package/native/token-counter/node_modules/isobject/LICENSE +0 -21
  185. package/native/token-counter/node_modules/isobject/README.md +0 -122
  186. package/native/token-counter/node_modules/isobject/index.d.ts +0 -5
  187. package/native/token-counter/node_modules/isobject/index.js +0 -12
  188. package/native/token-counter/node_modules/isobject/package.json +0 -74
  189. package/native/token-counter/node_modules/lodash.set/LICENSE +0 -47
  190. package/native/token-counter/node_modules/lodash.set/README.md +0 -18
  191. package/native/token-counter/node_modules/lodash.set/index.js +0 -990
  192. package/native/token-counter/node_modules/lodash.set/package.json +0 -17
  193. package/native/token-counter/node_modules/longest-first/index.js +0 -3
  194. package/native/token-counter/node_modules/longest-first/license.txt +0 -21
  195. package/native/token-counter/node_modules/longest-first/package.json +0 -36
  196. package/native/token-counter/node_modules/longest-first/readme.md +0 -29
  197. package/native/token-counter/node_modules/m-o/index.js +0 -27
  198. package/native/token-counter/node_modules/m-o/license.txt +0 -21
  199. package/native/token-counter/node_modules/m-o/node_modules/new-object/index.js +0 -23
  200. package/native/token-counter/node_modules/m-o/node_modules/new-object/license.txt +0 -21
  201. package/native/token-counter/node_modules/m-o/node_modules/new-object/package.json +0 -40
  202. package/native/token-counter/node_modules/m-o/node_modules/new-object/readme.md +0 -55
  203. package/native/token-counter/node_modules/m-o/package.json +0 -45
  204. package/native/token-counter/node_modules/m-o/readme.md +0 -87
  205. package/native/token-counter/node_modules/map-iter/index.js +0 -6
  206. package/native/token-counter/node_modules/map-iter/license.txt +0 -21
  207. package/native/token-counter/node_modules/map-iter/package.json +0 -40
  208. package/native/token-counter/node_modules/map-iter/readme.md +0 -46
  209. package/native/token-counter/node_modules/new-object/index.js +0 -5
  210. package/native/token-counter/node_modules/new-object/license.txt +0 -21
  211. package/native/token-counter/node_modules/new-object/package.json +0 -49
  212. package/native/token-counter/node_modules/new-object/readme.md +0 -145
  213. package/native/token-counter/node_modules/ofn/index.js +0 -22
  214. package/native/token-counter/node_modules/ofn/license.txt +0 -21
  215. package/native/token-counter/node_modules/ofn/package.json +0 -40
  216. package/native/token-counter/node_modules/ofn/readme.md +0 -63
  217. package/native/token-counter/node_modules/otherwise/index.js +0 -11
  218. package/native/token-counter/node_modules/otherwise/license.txt +0 -21
  219. package/native/token-counter/node_modules/otherwise/package.json +0 -38
  220. package/native/token-counter/node_modules/otherwise/readme.md +0 -29
  221. package/native/token-counter/node_modules/parser-factory/index.js +0 -138
  222. package/native/token-counter/node_modules/parser-factory/license.txt +0 -21
  223. package/native/token-counter/node_modules/parser-factory/node_modules/arrify/index.d.ts +0 -38
  224. package/native/token-counter/node_modules/parser-factory/node_modules/arrify/index.js +0 -23
  225. package/native/token-counter/node_modules/parser-factory/node_modules/arrify/license +0 -9
  226. package/native/token-counter/node_modules/parser-factory/node_modules/arrify/package.json +0 -35
  227. package/native/token-counter/node_modules/parser-factory/node_modules/arrify/readme.md +0 -39
  228. package/native/token-counter/node_modules/parser-factory/package.json +0 -38
  229. package/native/token-counter/node_modules/parser-factory/readme.md +0 -15
  230. package/native/token-counter/node_modules/pfn/index.js +0 -5
  231. package/native/token-counter/node_modules/pfn/license.txt +0 -21
  232. package/native/token-counter/node_modules/pfn/package.json +0 -41
  233. package/native/token-counter/node_modules/pfn/readme.md +0 -111
  234. package/native/token-counter/node_modules/pfn/strict.js +0 -8
  235. package/native/token-counter/node_modules/plainify/index.js +0 -5
  236. package/native/token-counter/node_modules/plainify/license.txt +0 -21
  237. package/native/token-counter/node_modules/plainify/package.json +0 -36
  238. package/native/token-counter/node_modules/plainify/readme.md +0 -47
  239. package/native/token-counter/node_modules/possible-function/changelog.md +0 -7
  240. package/native/token-counter/node_modules/possible-function/index.js +0 -43
  241. package/native/token-counter/node_modules/possible-function/license.txt +0 -21
  242. package/native/token-counter/node_modules/possible-function/package.json +0 -34
  243. package/native/token-counter/node_modules/possible-function/readme.md +0 -77
  244. package/native/token-counter/node_modules/possible-function/test.js +0 -32
  245. package/native/token-counter/node_modules/qfn/index.js +0 -11
  246. package/native/token-counter/node_modules/qfn/license.txt +0 -21
  247. package/native/token-counter/node_modules/qfn/package.json +0 -40
  248. package/native/token-counter/node_modules/qfn/readme.md +0 -45
  249. package/native/token-counter/node_modules/roadblock/index.js +0 -3
  250. package/native/token-counter/node_modules/roadblock/license.txt +0 -21
  251. package/native/token-counter/node_modules/roadblock/package.json +0 -40
  252. package/native/token-counter/node_modules/roadblock/readme.md +0 -59
  253. package/native/token-counter/node_modules/round-to/index.d.ts +0 -56
  254. package/native/token-counter/node_modules/round-to/index.js +0 -37
  255. package/native/token-counter/node_modules/round-to/license +0 -9
  256. package/native/token-counter/node_modules/round-to/package.json +0 -38
  257. package/native/token-counter/node_modules/round-to/readme.md +0 -71
  258. package/native/token-counter/node_modules/rtrim-array/index.js +0 -10
  259. package/native/token-counter/node_modules/rtrim-array/license.txt +0 -21
  260. package/native/token-counter/node_modules/rtrim-array/package.json +0 -41
  261. package/native/token-counter/node_modules/rtrim-array/readme.md +0 -75
  262. package/native/token-counter/node_modules/sbo/index.js +0 -25
  263. package/native/token-counter/node_modules/sbo/license.txt +0 -21
  264. package/native/token-counter/node_modules/sbo/package.json +0 -50
  265. package/native/token-counter/node_modules/sbo/readme.md +0 -105
  266. package/native/token-counter/node_modules/sorp/index.js +0 -3
  267. package/native/token-counter/node_modules/sorp/license.txt +0 -21
  268. package/native/token-counter/node_modules/sorp/package.json +0 -34
  269. package/native/token-counter/node_modules/sorp/readme.md +0 -25
  270. package/native/token-counter/node_modules/trim-call/index.js +0 -6
  271. package/native/token-counter/node_modules/trim-call/license.txt +0 -21
  272. package/native/token-counter/node_modules/trim-call/package.json +0 -40
  273. package/native/token-counter/node_modules/trim-call/readme.md +0 -80
  274. package/native/token-counter/node_modules/type-error/LICENSE +0 -21
  275. package/native/token-counter/node_modules/type-error/README.md +0 -24
  276. package/native/token-counter/node_modules/type-error/index.js +0 -35
  277. package/native/token-counter/node_modules/type-error/package.json +0 -11
  278. package/native/token-counter/node_modules/vfn/index.js +0 -21
  279. package/native/token-counter/node_modules/vfn/license.txt +0 -21
  280. package/native/token-counter/node_modules/vfn/package.json +0 -40
  281. package/native/token-counter/node_modules/vfn/readme.md +0 -81
  282. package/native/token-counter/node_modules/wfn/index.js +0 -43
  283. package/native/token-counter/node_modules/wfn/license.txt +0 -21
  284. package/native/token-counter/node_modules/wfn/package.json +0 -38
  285. package/native/token-counter/node_modules/wfn/readme.md +0 -81
  286. package/native/token-counter/package-lock.json +0 -578
  287. package/native/token-counter/package.json +0 -21
  288. package/native/token-counter/token-counter.linux-x64-gnu.node +0 -0
@@ -0,0 +1,1114 @@
1
+ import { homedir } from 'os';
2
+ import { join } from 'path';
3
+ import { readdirSync, readFileSync, writeFileSync, unlinkSync, appendFileSync } from 'fs';
4
+ import sharp from 'sharp';
5
+ import { visionClassify, readFileBase64, saveCaptchaTraining, findGridTiles, getTrainingHint, capthaiSolve, saveCapthaiTraining } from './common.js';
6
+ export async function solveCaptchaGrid(page, frame, provider) {
7
+ const results = [];
8
+ const isRecaptcha = provider === 'recaptcha';
9
+ const _t0 = Date.now();
10
+ const _elapsed = () => ((Date.now() - _t0) / 1000).toFixed(1);
11
+ const _dbg = (msg) => {
12
+ const line = `[${_elapsed()}s] ${msg}\n`;
13
+ try {
14
+ appendFileSync('/tmp/captcha-debug.log', line);
15
+ }
16
+ catch { }
17
+ results.push(msg);
18
+ };
19
+ try {
20
+ appendFileSync('/tmp/captcha-debug.log', `\n=== solveCaptchaGrid start (${provider}) ===\n`);
21
+ }
22
+ catch { }
23
+ let instruction = '';
24
+ try {
25
+ await page.waitForTimeout(3000);
26
+ _dbg(`frame url: ${frame.url().substring(0, 120)}`);
27
+ for (let waitRetry = 0; waitRetry < 3; waitRetry++) {
28
+ try {
29
+ await frame.locator('.rc-imageselect-instructions, .prompt-text, .prompt-text-h').first().waitFor({ state: 'visible', timeout: 5000 });
30
+ break;
31
+ }
32
+ catch {
33
+ if (waitRetry < 2) {
34
+ _dbg(`waiting for challenge render (retry ${waitRetry + 1})...`);
35
+ await page.waitForTimeout(2000);
36
+ }
37
+ }
38
+ }
39
+ const instrEl = frame.locator('.rc-imageselect-instructions, .prompt-text, .prompt-text-h, .rc-imageselect-payload-info, .geetest_tip_content, .mtcaptcha-label');
40
+ if (await instrEl.count() > 0) {
41
+ instruction = (await instrEl.first().textContent() || '').trim();
42
+ _dbg(`extraction method 1 (locator): "${instruction.substring(0, 80)}"`);
43
+ }
44
+ if (!instruction) {
45
+ const strongText = frame.locator('strong').first();
46
+ if (await strongText.count() > 0)
47
+ instruction = (await strongText.textContent() || '').trim();
48
+ if (instruction)
49
+ _dbg(`extraction method 2 (strong): "${instruction.substring(0, 80)}"`);
50
+ }
51
+ if (!instruction) {
52
+ try {
53
+ instruction = await frame.evaluate(() => {
54
+ const selectors = ['.rc-imageselect-instructions', '.prompt-text', '.prompt-text-h', '.rc-imageselect-desc', 'strong', 'h2', '.rc-imageselect-payload-info'];
55
+ for (const sel of selectors) {
56
+ const el = document.querySelector(sel);
57
+ if (el && el.textContent && el.textContent.trim())
58
+ return el.textContent.trim();
59
+ }
60
+ return '';
61
+ });
62
+ if (instruction)
63
+ _dbg(`extraction method 3 (evaluate): "${instruction.substring(0, 80)}"`);
64
+ }
65
+ catch (e) {
66
+ _dbg(`extraction method 3 error: ${e.message?.substring(0, 60)}`);
67
+ }
68
+ }
69
+ if (!instruction) {
70
+ const allText = await frame.locator('body').textContent().catch(() => '');
71
+ _dbg(`frame body text (${(allText || '').length} chars): "${(allText || '').substring(0, 200).replace(/\s+/g, ' ')}"`);
72
+ if (allText) {
73
+ const match = allText.match(/Select all (?:squares|images|areas|tiles)[^.!\n]{1,80}/i)
74
+ || allText.match(/(?:click|tap|choose|find|identify)[^.!\n]{1,80}(?:traffic|bus|bicycle|car|boat|bridge|crosswalk|fire|mountain|palm|stair|taxi|motorcycle|hydrant|sign|light)/i);
75
+ if (match)
76
+ instruction = match[0].trim();
77
+ if (instruction)
78
+ _dbg(`extraction method 4 (body regex): "${instruction.substring(0, 80)}"`);
79
+ }
80
+ }
81
+ if (!instruction) {
82
+ for (const f of page.frames()) {
83
+ if (f === frame)
84
+ continue;
85
+ try {
86
+ const txt = await f.evaluate(() => {
87
+ const selectors = ['.rc-imageselect-instructions', '.prompt-text', '.prompt-text-h', 'strong', 'h2'];
88
+ for (const sel of selectors) {
89
+ const el = document.querySelector(sel);
90
+ if (el && el.textContent && el.textContent.trim().length > 5)
91
+ return el.textContent.trim();
92
+ }
93
+ const body = document.body?.textContent || '';
94
+ const m = body.match(/Select all (?:squares|images|areas)[^.!\n]{1,80}/i);
95
+ return m ? m[0].trim() : '';
96
+ });
97
+ if (txt && txt.length > 5) {
98
+ instruction = txt;
99
+ _dbg(`extraction method 5 (other frame ${f.url().substring(0, 60)}): "${instruction.substring(0, 80)}"`);
100
+ break;
101
+ }
102
+ }
103
+ catch { }
104
+ }
105
+ }
106
+ if (!instruction) {
107
+ try {
108
+ const nestedFrames = await frame.locator('iframe').all();
109
+ _dbg(`nested iframes in bframe: ${nestedFrames.length}`);
110
+ for (const nf of nestedFrames) {
111
+ const nfHandle = await nf.elementHandle();
112
+ if (!nfHandle)
113
+ continue;
114
+ const nfFrame = await nfHandle.contentFrame();
115
+ if (!nfFrame)
116
+ continue;
117
+ const txt = await nfFrame.evaluate(() => {
118
+ const el = document.querySelector('.rc-imageselect-instructions, .prompt-text, strong, h2');
119
+ return el ? (el.textContent || '').trim() : '';
120
+ });
121
+ if (txt && txt.length > 5) {
122
+ instruction = txt;
123
+ _dbg(`extraction method 6 (nested iframe): "${instruction.substring(0, 80)}"`);
124
+ break;
125
+ }
126
+ }
127
+ }
128
+ catch (e) {
129
+ _dbg(`extraction method 6 error: ${e.message?.substring(0, 60)}`);
130
+ }
131
+ }
132
+ }
133
+ catch (e) {
134
+ _dbg(`instruction extraction outer error: ${e.message?.substring(0, 80)}`);
135
+ }
136
+ if (instruction && instruction.length < 10 && !/select|choose|find|click/i.test(instruction)) {
137
+ _dbg(`instruction too short/invalid: "${instruction}", retrying extraction...`);
138
+ instruction = '';
139
+ }
140
+ if (!instruction) {
141
+ await page.waitForTimeout(3000);
142
+ try {
143
+ instruction = await frame.evaluate(() => {
144
+ const el = document.querySelector('.rc-imageselect-instructions, .prompt-text, .prompt-text-h, strong');
145
+ return el ? (el.textContent || '').trim() : '';
146
+ });
147
+ if (instruction && instruction.length < 10 && !/select|choose|find|click/i.test(instruction))
148
+ instruction = '';
149
+ }
150
+ catch { }
151
+ }
152
+ if (!instruction) {
153
+ _dbg('Could not extract captcha instruction');
154
+ results.push('[WARN] Could not extract captcha instruction, cannot auto-solve');
155
+ return results.join('\n');
156
+ }
157
+ results.push(`Auto-solving: "${instruction}"`);
158
+ _dbg(`instruction: "${instruction}"`);
159
+ try {
160
+ const home = homedir();
161
+ for (const f of readdirSync(home)) {
162
+ if (/^\.aurix-tile-(\d+|after-\d+)\.png$/.test(f)) {
163
+ try {
164
+ unlinkSync(join(home, f));
165
+ }
166
+ catch { }
167
+ }
168
+ }
169
+ }
170
+ catch { }
171
+ let tiles = await findGridTiles(frame, provider);
172
+ for (let retry = 0; tiles.length === 0 && retry < 3; retry++) {
173
+ _dbg(`waiting for tiles (retry ${retry + 1}/3)...`);
174
+ await page.waitForTimeout(2000);
175
+ tiles = await findGridTiles(frame, provider);
176
+ }
177
+ _dbg(`found ${tiles.length} tiles`);
178
+ const cleanInstruction = instruction.replace(/If there are none.*$/i, '').replace(/Click verify once.*$/i, '').trim();
179
+ const objectMatch = cleanInstruction.match(/(?:Select all (?:squares|images) with(?: a)?|select all images with(?: a)?)\s+(.+)/i);
180
+ const objectName = objectMatch ? objectMatch[1].trim() : cleanInstruction;
181
+ const is3x3 = tiles.length <= 9;
182
+ let gridCols = is3x3 ? 3 : 4;
183
+ let gridRows = is3x3 ? 3 : 4;
184
+ let visibleTiles = tiles;
185
+ let actualTileCount = tiles.length;
186
+ try {
187
+ const tileBoxes = [];
188
+ for (const tile of tiles) {
189
+ try {
190
+ const box = await tile.boundingBox();
191
+ if (box && box.width > 5 && box.height > 5)
192
+ tileBoxes.push({ tile, box });
193
+ }
194
+ catch { }
195
+ }
196
+ if (tileBoxes.length >= 4) {
197
+ const firstY = tileBoxes[0].box.y;
198
+ const yTol = 15;
199
+ const firstRowTiles = tileBoxes.filter(tb => Math.abs(tb.box.y - firstY) < yTol);
200
+ const detectedCols = firstRowTiles.length;
201
+ const firstX = tileBoxes[0].box.x;
202
+ const xTol = 15;
203
+ const firstColTiles = tileBoxes.filter(tb => Math.abs(tb.box.x - firstX) < xTol);
204
+ const detectedRows = firstColTiles.length;
205
+ if (detectedCols >= 2 && detectedRows >= 2) {
206
+ gridCols = detectedCols;
207
+ gridRows = detectedRows;
208
+ const expectedVisible = gridRows * gridCols;
209
+ const lastVisibleX = tileBoxes[gridCols - 1].box.x + tileBoxes[gridCols - 1].box.width;
210
+ const lastVisibleY = tileBoxes[(gridRows - 1) * gridCols]
211
+ ? tileBoxes[(gridRows - 1) * gridCols].box.y + tileBoxes[(gridRows - 1) * gridCols].box.height
212
+ : tileBoxes[tileBoxes.length - 1].box.y + tileBoxes[tileBoxes.length - 1].box.height;
213
+ const vis = [];
214
+ for (const tb of tileBoxes) {
215
+ const cx = tb.box.x + tb.box.width / 2;
216
+ const cy = tb.box.y + tb.box.height / 2;
217
+ if (cx < lastVisibleX + 10 && cy < lastVisibleY + 10)
218
+ vis.push(tb.tile);
219
+ }
220
+ if (vis.length >= 4 && vis.length !== tiles.length) {
221
+ visibleTiles = vis;
222
+ actualTileCount = vis.length;
223
+ }
224
+ else {
225
+ actualTileCount = tileBoxes.length;
226
+ }
227
+ _dbg(`grid layout: ${gridRows}x${gridCols} (${actualTileCount} visible/${tiles.length} DOM, position-based)`);
228
+ }
229
+ else {
230
+ _dbg(`grid layout: ${gridRows}x${gridCols} (${tiles.length} tiles, position detect: cols=${detectedCols} rows=${detectedRows})`);
231
+ }
232
+ }
233
+ else {
234
+ _dbg(`grid layout: ${gridRows}x${gridCols} (${tiles.length} tiles, insufficient boxes)`);
235
+ }
236
+ }
237
+ catch (e) {
238
+ _dbg(`grid layout fallback: ${gridRows}x${gridCols} (${tiles.length} tiles, ${e.message})`);
239
+ }
240
+ let gridSize = `${gridRows}x${gridCols}`;
241
+ const gridScreenshotPath = join(homedir(), '.aurix-captcha-grid.png');
242
+ let gridShot = false;
243
+ let gridShotFromTable = false;
244
+ _dbg('extracting tile images from DOM via frame.evaluate...');
245
+ try {
246
+ const tileDataUrls = await frame.evaluate(async (expectedCount) => {
247
+ const tables = document.querySelectorAll('table');
248
+ let cells = [];
249
+ for (const table of tables) {
250
+ const tds = Array.from(table.querySelectorAll('td'));
251
+ if (tds.length >= expectedCount) {
252
+ cells = tds;
253
+ break;
254
+ }
255
+ if (tds.length >= 4 && tds.length > cells.length)
256
+ cells = tds;
257
+ }
258
+ if (cells.length === 0)
259
+ return { error: 'no table cells found', cellCount: 0, imgCount: 0 };
260
+ const firstImg = cells[0].querySelector('img');
261
+ const isSprite = firstImg && firstImg.naturalWidth > 0 &&
262
+ cells.every(c => {
263
+ const img = c.querySelector('img');
264
+ return img && img.src === firstImg.src;
265
+ });
266
+ const results = [];
267
+ const cols = cells.length <= 9 ? 3 : 4;
268
+ const rows = Math.ceil(cells.length / cols);
269
+ const debugInfo = [];
270
+ for (let i = 0; i < cells.length; i++) {
271
+ const cell = cells[i];
272
+ const img = cell.querySelector('img');
273
+ if (img && img.complete && img.naturalWidth > 0) {
274
+ if (isSprite) {
275
+ try {
276
+ const cs = getComputedStyle(img);
277
+ const wrapper = cell.querySelector('.rc-image-tile-wrapper');
278
+ const wcs = wrapper ? getComputedStyle(wrapper) : null;
279
+ const wW = wrapper ? parseInt(wcs.width) || 95 : 95;
280
+ const wH = wrapper ? parseInt(wcs.height) || 95 : 95;
281
+ let imgLeft = parseInt(cs.left) || 0;
282
+ let imgTop = parseInt(cs.top) || 0;
283
+ const imgML = parseInt(cs.marginLeft) || 0;
284
+ const imgMT = parseInt(cs.marginTop) || 0;
285
+ const transform = cs.transform;
286
+ let tx = 0, ty = 0;
287
+ if (transform && transform !== 'none') {
288
+ const m = transform.match(/matrix\(([^)]+)\)/);
289
+ if (m) {
290
+ const v = m[1].split(',').map(Number);
291
+ tx = v[4] || 0;
292
+ ty = v[5] || 0;
293
+ }
294
+ }
295
+ const offX = imgLeft + imgML + tx;
296
+ const offY = imgTop + imgMT + ty;
297
+ const scale = img.naturalWidth / (parseInt(cs.width) || img.offsetWidth || wW);
298
+ const sx = Math.max(0, -offX * scale);
299
+ const sy = Math.max(0, -offY * scale);
300
+ const sw = wW * scale;
301
+ const sh = wH * scale;
302
+ if (i < 4) {
303
+ debugInfo.push({
304
+ i, left: imgLeft, top: imgTop, ml: imgML, mt: imgMT,
305
+ tx, ty, offX, offY, scale: +scale.toFixed(3),
306
+ sx: Math.round(sx), sy: Math.round(sy), sw: Math.round(sw), sh: Math.round(sh),
307
+ cssWidth: cs.width, cssHeight: cs.height, position: cs.position,
308
+ natW: img.naturalWidth, natH: img.naturalHeight,
309
+ class: img.className
310
+ });
311
+ }
312
+ const canvas = document.createElement('canvas');
313
+ canvas.width = Math.round(sw);
314
+ canvas.height = Math.round(sh);
315
+ const ctx = canvas.getContext('2d');
316
+ if (ctx) {
317
+ ctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);
318
+ results.push(canvas.toDataURL('image/png'));
319
+ continue;
320
+ }
321
+ }
322
+ catch { }
323
+ }
324
+ else {
325
+ try {
326
+ const canvas = document.createElement('canvas');
327
+ canvas.width = img.naturalWidth;
328
+ canvas.height = img.naturalHeight;
329
+ const ctx = canvas.getContext('2d');
330
+ if (ctx) {
331
+ ctx.drawImage(img, 0, 0);
332
+ results.push(canvas.toDataURL('image/png'));
333
+ continue;
334
+ }
335
+ }
336
+ catch { }
337
+ }
338
+ try {
339
+ const resp = await fetch(img.src);
340
+ const buf = await resp.arrayBuffer();
341
+ const bytes = new Uint8Array(buf);
342
+ let binary = '';
343
+ for (let j = 0; j < bytes.length; j++)
344
+ binary += String.fromCharCode(bytes[j]);
345
+ results.push('data:image/png;base64,' + btoa(binary));
346
+ continue;
347
+ }
348
+ catch { }
349
+ }
350
+ results.push('');
351
+ }
352
+ return { results, cellCount: cells.length, imgCount: results.filter(r => r).length, debugInfo };
353
+ }, actualTileCount);
354
+ _dbg(`DOM extract: ${tileDataUrls.cellCount} cells, ${tileDataUrls.imgCount} images`);
355
+ if (tileDataUrls.debugInfo?.length) {
356
+ _dbg(`sprite debug (first 4): ${JSON.stringify(tileDataUrls.debugInfo)}`);
357
+ }
358
+ try {
359
+ const domInfo = await frame.evaluate((count) => {
360
+ const tables = document.querySelectorAll('table');
361
+ let cells = [];
362
+ for (const table of tables) {
363
+ const tds = Array.from(table.querySelectorAll('td'));
364
+ if (tds.length >= count) {
365
+ cells = tds;
366
+ break;
367
+ }
368
+ }
369
+ return cells.slice(0, 4).map((cell, i) => {
370
+ const img = cell.querySelector('img');
371
+ const bg = getComputedStyle(cell).backgroundImage;
372
+ const bgInner = cell.querySelector('[style*="background"]');
373
+ const bgStyle = bgInner ? getComputedStyle(bgInner).backgroundImage : '';
374
+ const bgPos = bgInner ? getComputedStyle(bgInner).backgroundPosition : '';
375
+ const bgSize = bgInner ? getComputedStyle(bgInner).backgroundSize : '';
376
+ return {
377
+ i,
378
+ imgSrc: img ? img.src.substring(0, 80) : null,
379
+ imgW: img?.naturalWidth,
380
+ imgH: img?.naturalHeight,
381
+ imgDisplay: img ? getComputedStyle(img).display : null,
382
+ bg: bg !== 'none' ? bg.substring(0, 80) : null,
383
+ bgStyle: bgStyle !== 'none' ? bgStyle.substring(0, 80) : null,
384
+ bgPos,
385
+ bgSize,
386
+ cellHTML: cell.innerHTML.substring(0, 200),
387
+ };
388
+ });
389
+ }, actualTileCount);
390
+ _dbg(`DOM info (first 4): ${JSON.stringify(domInfo).substring(0, 500)}`);
391
+ }
392
+ catch { }
393
+ if (tileDataUrls.imgCount && tileDataUrls.imgCount >= actualTileCount * 0.5) {
394
+ const tileBufs = [];
395
+ const dataResults = tileDataUrls.results;
396
+ for (let i = 0; i < Math.min(dataResults.length, actualTileCount); i++) {
397
+ const entry = dataResults[i];
398
+ if (!entry || !entry.startsWith('data:image/'))
399
+ continue;
400
+ try {
401
+ const b64 = entry.split(',')[1];
402
+ if (b64)
403
+ tileBufs.push({ idx: i, buf: Buffer.from(b64, 'base64') });
404
+ }
405
+ catch (e) {
406
+ _dbg(`tile ${i} decode failed: ${e.message?.substring(0, 60)}`);
407
+ }
408
+ }
409
+ if (tileBufs.length >= actualTileCount) {
410
+ try {
411
+ const meta0 = await sharp(tileBufs[0].buf).metadata();
412
+ const tw = meta0.width || 100, th = meta0.height || 100;
413
+ const gridW = tw * gridCols, gridH = th * gridRows;
414
+ const composites = [];
415
+ for (const { idx, buf } of tileBufs) {
416
+ const r = Math.floor(idx / gridCols), c = idx % gridCols;
417
+ composites.push({ input: buf, left: c * tw, top: r * th });
418
+ }
419
+ const composedBuf = await sharp({ create: { width: gridW, height: gridH, channels: 3, background: { r: 200, g: 200, b: 200 } } })
420
+ .composite(composites).png().toBuffer();
421
+ await sharp(composedBuf).toFile(gridScreenshotPath);
422
+ const stats = await sharp(composedBuf).stats();
423
+ const meanAll = stats.channels.reduce((s, c) => s + c.mean, 0) / stats.channels.length;
424
+ if (composedBuf.length > 5000 && meanAll < 250) {
425
+ gridShot = true;
426
+ gridShotFromTable = true;
427
+ _dbg(`composed grid from DOM: ${gridW}x${gridH} (buf=${composedBuf.length}, mean=${meanAll.toFixed(1)})`);
428
+ }
429
+ else {
430
+ _dbg(`DOM composed grid blank (buf=${composedBuf.length}, mean=${meanAll.toFixed(1)})`);
431
+ }
432
+ }
433
+ catch (e) {
434
+ _dbg(`DOM compose failed: ${e.message}`);
435
+ }
436
+ }
437
+ else {
438
+ _dbg(`DOM extract: only ${tileBufs.length}/${actualTileCount} tiles`);
439
+ }
440
+ }
441
+ }
442
+ catch (e) {
443
+ _dbg(`DOM extract error: ${e.message?.substring(0, 80)}`);
444
+ }
445
+ if (!gridShot) {
446
+ _dbg('DOM extract failed, trying fallback screenshots...');
447
+ const tryShot = async (label, fn) => {
448
+ try {
449
+ await fn();
450
+ const buf = readFileSync(gridScreenshotPath);
451
+ if (buf.length < 2000) {
452
+ _dbg(`${label}: too small (${buf.length}b), skipping`);
453
+ return false;
454
+ }
455
+ const stats = await sharp(buf).stats();
456
+ const mean = stats.channels.reduce((s, c) => s + c.mean, 0) / stats.channels.length;
457
+ if (mean > 250) {
458
+ _dbg(`${label}: blank (mean=${mean.toFixed(1)}), skipping`);
459
+ return false;
460
+ }
461
+ _dbg(`${label}: OK (${buf.length}b, mean=${mean.toFixed(1)})`);
462
+ return true;
463
+ }
464
+ catch (e) {
465
+ _dbg(`${label}: error ${e.message?.substring(0, 60)}`);
466
+ return false;
467
+ }
468
+ };
469
+ if (!gridShot && await tryShot('iframe-html', async () => {
470
+ await frame.locator('html').screenshot({ path: gridScreenshotPath, timeout: 10000 });
471
+ }))
472
+ gridShot = true;
473
+ if (!gridShot) {
474
+ try {
475
+ const iframeEl = page.frameLocator('iframe[src*="recaptcha"]').first().locator('html');
476
+ if (await iframeEl.count() > 0) {
477
+ if (await tryShot('iframe-from-parent', async () => {
478
+ await iframeEl.screenshot({ path: gridScreenshotPath, timeout: 10000 });
479
+ }))
480
+ gridShot = true;
481
+ }
482
+ }
483
+ catch { }
484
+ }
485
+ if (!gridShot) {
486
+ const tableSelectors = isRecaptcha
487
+ ? (is3x3
488
+ ? ['.rc-imageselect-table-33', '.rc-image-tile-33', 'table']
489
+ : ['.rc-imageselect-table-44', '.rc-image-tile-44', 'table'])
490
+ : ['.task', '.challenge-view'];
491
+ for (const sel of tableSelectors) {
492
+ if (gridShot)
493
+ break;
494
+ const el = frame.locator(sel).first();
495
+ if (await el.count() > 0 && await el.isVisible()) {
496
+ if (await tryShot(`selector:${sel}`, async () => {
497
+ await el.screenshot({ path: gridScreenshotPath, timeout: 10000 });
498
+ })) {
499
+ gridShot = true;
500
+ gridShotFromTable = true;
501
+ }
502
+ }
503
+ }
504
+ }
505
+ if (!gridShot && await tryShot('frame-body', async () => {
506
+ await frame.locator('body').screenshot({ path: gridScreenshotPath, timeout: 10000 });
507
+ }))
508
+ gridShot = true;
509
+ if (!gridShot) {
510
+ try {
511
+ const iframeBox = await page.locator('iframe[src*="recaptcha"][src*="bframe"]').first().boundingBox();
512
+ if (iframeBox) {
513
+ await page.screenshot({ path: gridScreenshotPath, clip: iframeBox });
514
+ const buf = readFileSync(gridScreenshotPath);
515
+ if (buf.length >= 2000) {
516
+ _dbg(`page-clip: OK (${buf.length}b)`);
517
+ gridShot = true;
518
+ }
519
+ else {
520
+ _dbg(`page-clip: too small (${buf.length}b)`);
521
+ }
522
+ }
523
+ }
524
+ catch { }
525
+ }
526
+ if (!gridShot) {
527
+ try {
528
+ await page.screenshot({ path: gridScreenshotPath });
529
+ gridShot = true;
530
+ _dbg('grid screenshot: page fallback');
531
+ }
532
+ catch { }
533
+ }
534
+ }
535
+ if (gridShot) {
536
+ try {
537
+ const origMeta = await sharp(gridScreenshotPath).metadata();
538
+ if ((origMeta.width || 0) > 0 && (origMeta.width || 0) < 200) {
539
+ const upscaledPath = gridScreenshotPath + '.up.png';
540
+ await sharp(gridScreenshotPath)
541
+ .resize((origMeta.width || 0) * 2, (origMeta.height || 0) * 2, { kernel: sharp.kernel.lanczos3 })
542
+ .png().toFile(upscaledPath);
543
+ const upBuf = readFileSync(upscaledPath);
544
+ writeFileSync(gridScreenshotPath, upBuf);
545
+ try {
546
+ unlinkSync(upscaledPath);
547
+ }
548
+ catch { }
549
+ _dbg(`upscaled grid: ${origMeta.width}x${origMeta.height} → ${origMeta.width * 2}x${origMeta.height * 2}`);
550
+ }
551
+ }
552
+ catch { }
553
+ }
554
+ const matchedIndices = [];
555
+ if (gridShot) {
556
+ try {
557
+ let tileLayout = '';
558
+ let idx = 0;
559
+ for (let r = 0; r < gridRows; r++) {
560
+ const rowTiles = [];
561
+ for (let c = 0; c < gridCols; c++) {
562
+ if (idx < actualTileCount)
563
+ rowTiles.push(`[${idx}]`);
564
+ idx++;
565
+ }
566
+ tileLayout += `Row ${r + 1}: ${rowTiles.join(' ')}\n`;
567
+ }
568
+ const trainingHint = getTrainingHint(objectName, gridSize, actualTileCount);
569
+ if (trainingHint)
570
+ _dbg(`training hint: ${trainingHint.substring(0, 100)}...`);
571
+ const objectDescriptions = {
572
+ 'fire hydrant': 'a red/yellow fire hydrant (short metal post with side nozzles, usually red, sometimes yellow)',
573
+ 'traffic light': 'traffic signal lights AND their pole/housing/support structure (the pole holding the light counts as traffic light)',
574
+ 'traffic signal': 'traffic signal lights AND their pole/housing/support structure (the pole holding the light counts as traffic light)',
575
+ 'bus': 'a large passenger bus (city bus, school bus, coach — NOT cars or vans)',
576
+ 'bicycle': 'a bicycle/bike (two wheels, frame, handlebars — may be parked or being ridden)',
577
+ 'motorcycle': 'a motorcycle/motorbike (two wheels with engine, NOT bicycles)',
578
+ 'car': 'a car/automobile (sedan, SUV, coupe — NOT buses, trucks, or motorcycles)',
579
+ 'crosswalk': 'crosswalk markings on the road (white stripes/zebra pattern on pavement)',
580
+ 'stairs': 'stairs/staircase (steps going up or down, railings)',
581
+ 'mountain': 'mountains (large rocky landforms, peaks, ridges)',
582
+ 'palm tree': 'palm trees (tall trunk with fan/feather-shaped fronds at top)',
583
+ 'taxi': 'a taxi cab (car with taxi sign on roof, often yellow)',
584
+ 'bridge': 'a bridge structure (span over water/road/valley)',
585
+ 'chimney': 'a chimney (vertical structure on roof for smoke)',
586
+ 'boat': 'a boat/vessel on water',
587
+ 'parking meter': 'a parking meter (coin-operated device on sidewalk next to parking spot)',
588
+ };
589
+ const objDesc = objectDescriptions[objectName.toLowerCase()] || objectName;
590
+ const sourceNote = gridShotFromTable
591
+ ? 'The image shows ONLY the tile grid. Each cell in the grid is one tile.'
592
+ : 'The image shows the full captcha frame. The tile grid is in the CENTER of the image. Ignore text/instructions above and buttons below the grid.';
593
+ const prompt = `${sourceNote}
594
+
595
+ Grid layout: ${gridSize} (${actualTileCount} tiles, 0-indexed)
596
+ ${tileLayout.trim()}
597
+
598
+ Task: Find tiles that show "${objectName}" — specifically: ${objDesc}
599
+
600
+ CRITICAL RULES:
601
+ - Typically 2-6 out of ${actualTileCount} tiles contain the target. Be SELECTIVE.
602
+ - Only YES if you CLEARLY see ${objectName} or a significant part of it in that tile.
603
+ - Supporting structures COUNT: poles/housings for traffic lights, frames/wheels for bicycles, etc.
604
+ - If uncertain about a tile, answer NO.
605
+ - DO NOT write long reasoning. Keep each tile note to 3 words maximum.
606
+
607
+ ${trainingHint}
608
+
609
+ Respond EXACTLY in this format — no extra text:
610
+ Analysis: Tile 0: [yes/no - 3 words], Tile 1: [yes/no - 3 words], ..., Tile ${actualTileCount - 1}: [yes/no - 3 words]
611
+ Answer: [comma-separated numbers, or "none"]`;
612
+ _dbg('calling visionClassify...');
613
+ let gridBase64;
614
+ try {
615
+ const origBuf = await sharp(gridScreenshotPath).metadata();
616
+ const targetDim = 800;
617
+ const resized = await sharp(gridScreenshotPath).resize(targetDim, targetDim, { fit: 'inside' }).png().toBuffer();
618
+ gridBase64 = resized.toString('base64');
619
+ _dbg(`grid image ${origBuf.width}x${origBuf.height} → ${targetDim}x${targetDim} (${gridBase64.length} chars)`);
620
+ }
621
+ catch {
622
+ gridBase64 = readFileBase64(gridScreenshotPath);
623
+ }
624
+ _dbg(`grid image loaded (${gridBase64.length} chars base64)`);
625
+ const response = await visionClassify(gridBase64, prompt);
626
+ _dbg(`visionClassify returned (${response.length} chars)`);
627
+ results.push(`Vision: ${response.split('\n').filter(l => l.trim()).join(' | ')}`);
628
+ const lines = response.split('\n');
629
+ let answerLine = '';
630
+ for (let i = lines.length - 1; i >= 0; i--) {
631
+ if (/^\s*answer\s*:/i.test(lines[i].trim())) {
632
+ answerLine = lines[i];
633
+ break;
634
+ }
635
+ }
636
+ const answerIndices = [];
637
+ if (answerLine && !/none/i.test(answerLine.replace(/answer\s*:/i, ''))) {
638
+ const nums = answerLine.match(/\d+/g);
639
+ if (nums) {
640
+ for (const n of nums) {
641
+ let idx = parseInt(n);
642
+ if (idx >= 0 && idx < actualTileCount)
643
+ answerIndices.push(idx);
644
+ }
645
+ }
646
+ }
647
+ const analysisIndices = [];
648
+ const fullText = response.replace(/\n/g, ' ');
649
+ const tilePattern = /Tile\s+(\d+)\s*:\s*\[?\s*(yes|no)\b/gi;
650
+ let m;
651
+ while ((m = tilePattern.exec(fullText)) !== null) {
652
+ const tidx = parseInt(m[1]);
653
+ if (m[2].toLowerCase() === 'yes' && tidx >= 0 && tidx < actualTileCount)
654
+ analysisIndices.push(tidx);
655
+ }
656
+ if (answerIndices.length > 0 && answerIndices.length >= analysisIndices.length) {
657
+ matchedIndices.push(...answerIndices);
658
+ }
659
+ else if (analysisIndices.length > 0) {
660
+ matchedIndices.push(...analysisIndices);
661
+ if (answerLine && answerIndices.length > 0 && answerIndices.length < analysisIndices.length) {
662
+ _dbg(`Answer line truncated (${answerIndices.length} vs ${analysisIndices.length} from analysis) — using analysis`);
663
+ }
664
+ }
665
+ if (matchedIndices.length === 0) {
666
+ for (const line of lines) {
667
+ const ynMatch = line.match(/Tile\s+(\d+)\s*:.*?-\s*(YES|NO)/i);
668
+ if (ynMatch) {
669
+ const tidx = parseInt(ynMatch[1]);
670
+ if (ynMatch[2].toUpperCase() === 'YES' && tidx >= 0 && tidx < actualTileCount)
671
+ matchedIndices.push(tidx);
672
+ continue;
673
+ }
674
+ const tileMention = line.match(/\*{1,2}(?:Tile|Cell)\s+(\d+)\*{1,2}|(?:tile|cell)\s+(\d+)\s*[:)]/i);
675
+ if (tileMention) {
676
+ const tidx = parseInt(tileMention[1] || tileMention[2]);
677
+ if (tidx >= 0 && tidx < actualTileCount) {
678
+ const lower = line.toLowerCase();
679
+ const isPositive = /\b(contains?|shows?|has|includes?|visible|present|yes)\b/i.test(lower);
680
+ const isNegative = /\b(no|none|not|empty|clear|does not|doesn't|without)\b/i.test(lower);
681
+ if (isPositive && !isNegative)
682
+ matchedIndices.push(tidx);
683
+ }
684
+ }
685
+ }
686
+ }
687
+ if (matchedIndices.length === 0) {
688
+ const lastLine = lines[lines.length - 1]?.trim() || '';
689
+ if (/^[\d\s,\-]+$/.test(lastLine)) {
690
+ const nums = lastLine.match(/\d+/g);
691
+ if (nums) {
692
+ for (const n of nums) {
693
+ let idx = parseInt(n);
694
+ if (idx >= 0 && idx < actualTileCount)
695
+ matchedIndices.push(idx);
696
+ }
697
+ }
698
+ }
699
+ }
700
+ const uniqueIndices = [...new Set(matchedIndices)];
701
+ matchedIndices.length = 0;
702
+ matchedIndices.push(...uniqueIndices);
703
+ const selectRatio = matchedIndices.length / actualTileCount;
704
+ if (selectRatio > 0.55 && actualTileCount >= 9) {
705
+ _dbg(`over-classification detected: ${matchedIndices.length}/${actualTileCount} (${(selectRatio * 100).toFixed(0)}%) — re-prompting with stricter criteria`);
706
+ matchedIndices.length = 0;
707
+ const strictPrompt = `Look at this reCAPTCHA grid (${gridSize}, ${actualTileCount} tiles).
708
+
709
+ I asked for "${objectName}" and got too many matches. Please re-evaluate VERY carefully.
710
+
711
+ For "${objectName}" (${objDesc}):
712
+ - Only YES if you can CLEARLY see the object itself (not just the general scene/road/building)
713
+ - Most tiles will be NO. Typical answer: 2-5 tiles out of ${actualTileCount}.
714
+ - Go tile by tile. For each tile, ask: "Is there a ${objectName} IN this specific tile?"
715
+ - Supporting structures COUNT: poles/housings for traffic lights, etc.
716
+ - DO NOT write long reasoning. Keep each note to 3 words maximum.
717
+
718
+ ${tileLayout.trim()}
719
+
720
+ Respond EXACTLY in this format — no extra text:
721
+ Analysis: Tile 0: [YES/NO - 3 words], Tile 1: [YES/NO - 3 words], ..., Tile ${actualTileCount - 1}: [YES/NO - 3 words]
722
+ Answer: [comma-separated numbers, or "none"]`;
723
+ const strictResponse = await visionClassify(gridBase64, strictPrompt);
724
+ _dbg(`strict vision returned (${strictResponse.length} chars)`);
725
+ const strictLines = strictResponse.split('\n');
726
+ let strictAnswer = '';
727
+ for (let i = strictLines.length - 1; i >= 0; i--) {
728
+ if (/^\s*answer\s*:/i.test(strictLines[i].trim())) {
729
+ strictAnswer = strictLines[i];
730
+ break;
731
+ }
732
+ }
733
+ if (strictAnswer && !/none/i.test(strictAnswer.replace(/answer\s*:/i, ''))) {
734
+ const nums = strictAnswer.match(/\d+/g);
735
+ if (nums) {
736
+ for (const n of nums) {
737
+ const idx = parseInt(n);
738
+ if (idx >= 0 && idx < actualTileCount)
739
+ matchedIndices.push(idx);
740
+ }
741
+ }
742
+ }
743
+ const strictUnique = [...new Set(matchedIndices)];
744
+ matchedIndices.length = 0;
745
+ matchedIndices.push(...strictUnique);
746
+ _dbg(`strict matched indices: [${matchedIndices.join(',')}]`);
747
+ }
748
+ // Self-verification pass: ask model to confirm its answer
749
+ if (matchedIndices.length > 0 && matchedIndices.length <= actualTileCount * 0.55) {
750
+ _dbg(`running self-verification pass (initial: [${matchedIndices.join(',')}])...`);
751
+ const selectedStr = matchedIndices.join(', ');
752
+ const unselectedIndices = [];
753
+ for (let i = 0; i < actualTileCount; i++) {
754
+ if (!matchedIndices.includes(i))
755
+ unselectedIndices.push(i);
756
+ }
757
+ const unselectedStr = unselectedIndices.join(', ');
758
+ const verifyPrompt = `You previously analyzed this reCAPTCHA grid (${gridSize}, ${actualTileCount} tiles) looking for "${objectName}" (${objDesc}).
759
+
760
+ Your answer was: tiles [${selectedStr}]
761
+ Tiles you said NO to: [${unselectedStr}]
762
+
763
+ Please VERIFY your answer:
764
+ 1. For each tile you selected [${selectedStr}]: Is there DEFINITELY ${objectName} in it? If any tile is uncertain, remove it.
765
+ 2. For each tile you rejected [${unselectedStr}]: Did you MISS any? Look carefully at each one.
766
+ 3. Supporting structures COUNT: poles/housings for traffic lights, frames/wheels for bicycles, etc.
767
+
768
+ Keep notes to 3 words max per tile.
769
+
770
+ ${tileLayout.trim()}
771
+
772
+ Respond EXACTLY:
773
+ Verify YES: [confirmed tiles] | Verify NO (remove): [tiles to remove] | Missed (add): [missed tiles]
774
+ Final: [comma-separated final tile numbers, or "none"]`;
775
+ const verifyResponse = await visionClassify(gridBase64, verifyPrompt);
776
+ _dbg(`verification returned (${verifyResponse.length} chars)`);
777
+ // Parse verification result
778
+ const verifyLines = verifyResponse.split('\n');
779
+ let finalLine = '';
780
+ for (let i = verifyLines.length - 1; i >= 0; i--) {
781
+ if (/^\s*final\s*:/i.test(verifyLines[i].trim())) {
782
+ finalLine = verifyLines[i];
783
+ break;
784
+ }
785
+ }
786
+ if (finalLine && !/none/i.test(finalLine.replace(/final\s*:/i, ''))) {
787
+ const finalNums = finalLine.match(/\d+/g);
788
+ if (finalNums) {
789
+ const verifiedIndices = [];
790
+ for (const n of finalNums) {
791
+ const idx = parseInt(n);
792
+ if (idx >= 0 && idx < actualTileCount)
793
+ verifiedIndices.push(idx);
794
+ }
795
+ if (verifiedIndices.length > 0) {
796
+ const before = `[${matchedIndices.join(',')}]`;
797
+ matchedIndices.length = 0;
798
+ matchedIndices.push(...new Set(verifiedIndices));
799
+ _dbg(`verified: ${before} → [${matchedIndices.join(',')}]`);
800
+ }
801
+ }
802
+ }
803
+ else {
804
+ // Try parsing from "Verify YES" / "Missed" / "Verify NO" lines
805
+ const vText = verifyResponse.replace(/\n/g, ' ');
806
+ const removeMatch = vText.match(/Verify\s+NO\s*(?:\(remove\))?\s*:\s*\[?\s*([\d,\s]+)/i);
807
+ const missedMatch = vText.match(/Missed\s*(?:\(add\))?\s*:\s*\[?\s*([\d,\s]+)/i);
808
+ if (removeMatch) {
809
+ const removeNums = (removeMatch[1].match(/\d+/g) || []).map(Number);
810
+ for (const r of removeNums) {
811
+ const idx = matchedIndices.indexOf(r);
812
+ if (idx >= 0)
813
+ matchedIndices.splice(idx, 1);
814
+ }
815
+ }
816
+ if (missedMatch) {
817
+ const addNums = (missedMatch[1].match(/\d+/g) || []).map(Number);
818
+ for (const a of addNums) {
819
+ if (a >= 0 && a < actualTileCount && !matchedIndices.includes(a))
820
+ matchedIndices.push(a);
821
+ }
822
+ }
823
+ if (removeMatch || missedMatch) {
824
+ _dbg(`verification adjusted: [${matchedIndices.join(',')}]`);
825
+ }
826
+ }
827
+ }
828
+ if (matchedIndices.length > actualTileCount * 0.5 && actualTileCount >= 9) {
829
+ _dbg(`post-verification over-select: ${matchedIndices.length}/${actualTileCount} — trimming to most confident`);
830
+ const trimPrompt = `reCAPTCHA grid (${gridSize}, ${actualTileCount} tiles). Looking for "${objectName}" (${objDesc}).
831
+ Current selection: [${matchedIndices.join(', ')}] — this is TOO MANY (${matchedIndices.length} tiles).
832
+ Typical answer: 2-5 tiles. Pick ONLY the ${Math.min(5, Math.ceil(actualTileCount * 0.35))} MOST OBVIOUS tiles that CLEARLY show ${objectName}.
833
+ ${tileLayout.trim()}
834
+ Respond with ONLY the tile numbers (comma-separated), nothing else:`;
835
+ const trimResp = await visionClassify(gridBase64, trimPrompt);
836
+ const trimNums = trimResp.match(/\d+/g);
837
+ if (trimNums) {
838
+ const trimmed = [];
839
+ for (const n of trimNums) {
840
+ const idx = parseInt(n);
841
+ if (idx >= 0 && idx < actualTileCount && !trimmed.includes(idx))
842
+ trimmed.push(idx);
843
+ }
844
+ if (trimmed.length > 0 && trimmed.length <= actualTileCount * 0.45) {
845
+ _dbg(`trimmed: [${matchedIndices.join(',')}] → [${trimmed.join(',')}]`);
846
+ matchedIndices.length = 0;
847
+ matchedIndices.push(...trimmed);
848
+ }
849
+ }
850
+ }
851
+ _dbg(`parsed matched indices: [${matchedIndices.join(',')}]`);
852
+ // TEMP: CapTCHAi training — REMOVE AFTER TRAINING
853
+ try {
854
+ _dbg('calling CapTCHAi for ground truth...');
855
+ const capthaiBuf = await sharp(gridScreenshotPath).resize(200, 200, { fit: 'inside' }).jpeg({ quality: 50 }).toBuffer();
856
+ _dbg(`CapTCHAi image: ${capthaiBuf.length} bytes`);
857
+ const capthaiB64 = capthaiBuf.toString('base64');
858
+ const capthaiResult = await capthaiSolve(capthaiB64, objectName, gridSize);
859
+ if (capthaiResult && capthaiResult.length > 0) {
860
+ const visionSet = new Set(matchedIndices);
861
+ const capthaiSet = new Set(capthaiResult);
862
+ const correct = matchedIndices.length === capthaiResult.length &&
863
+ matchedIndices.every(i => capthaiSet.has(i));
864
+ saveCapthaiTraining({
865
+ objectType: objectName,
866
+ gridSize,
867
+ capthaiIndices: capthaiResult,
868
+ visionIndices: [...matchedIndices],
869
+ correct,
870
+ timestamp: Date.now(),
871
+ });
872
+ _dbg(`CapTCHAi ground truth: [${capthaiResult.join(',')}] vs vision [${matchedIndices.join(',')}] → ${correct ? 'CORRECT ✅' : 'WRONG ❌'}`);
873
+ results.push(`CapTCHAi: [${capthaiResult.join(',')}] → ${correct ? '✅' : '❌'}`);
874
+ if (!correct) {
875
+ matchedIndices.length = 0;
876
+ matchedIndices.push(...capthaiResult);
877
+ _dbg(`using CapTCHAi answer: [${matchedIndices.join(',')}]`);
878
+ }
879
+ }
880
+ else {
881
+ _dbg('CapTCHAi returned no result');
882
+ }
883
+ }
884
+ catch (e) {
885
+ _dbg(`CapTCHAi training error: ${e.message}`);
886
+ }
887
+ // END TEMP: CapTCHAi training
888
+ }
889
+ catch (e) {
890
+ _dbg(`visionClassify FAILED: ${e.message}`);
891
+ }
892
+ }
893
+ _dbg(`matched [${matchedIndices.join(',')}]`);
894
+ if (matchedIndices.length === 0) {
895
+ const skipMentioned = /skip|none/i.test(instruction);
896
+ if (skipMentioned) {
897
+ _dbg('no matches found and instruction mentions skip — clicking skip button');
898
+ try {
899
+ const skipText = await frame.evaluate(() => {
900
+ const btn = document.querySelector('#recaptcha-verify-button, .rc-button-submit');
901
+ return btn ? btn.textContent || '' : '';
902
+ });
903
+ if (/skip/i.test(skipText)) {
904
+ await frame.evaluate(() => {
905
+ const btn = document.querySelector('#recaptcha-verify-button, .rc-button-submit');
906
+ if (btn)
907
+ btn.click();
908
+ });
909
+ await page.waitForTimeout(2000);
910
+ _dbg('skip button clicked via JS');
911
+ results.push('No matches found, clicked skip');
912
+ return results.join('\n');
913
+ }
914
+ }
915
+ catch (e) {
916
+ _dbg(`skip button click failed: ${e.message}`);
917
+ }
918
+ }
919
+ results.push('No matching tiles found, attempting verify anyway');
920
+ }
921
+ _dbg(`clicking ${matchedIndices.length} tiles`);
922
+ const clickedSet = new Set();
923
+ for (const idx of matchedIndices) {
924
+ try {
925
+ if (idx >= actualTileCount || idx >= visibleTiles.length)
926
+ continue;
927
+ try {
928
+ await frame.evaluate((tileIdx) => {
929
+ const tds = document.querySelectorAll('table td');
930
+ if (tds[tileIdx])
931
+ tds[tileIdx].click();
932
+ }, idx);
933
+ }
934
+ catch {
935
+ await visibleTiles[idx].click({ force: true, timeout: 3000 });
936
+ }
937
+ clickedSet.add(idx);
938
+ await page.waitForTimeout(300 + Math.random() * 400);
939
+ _dbg(`clicked tile ${idx}`);
940
+ }
941
+ catch (e) {
942
+ try {
943
+ const refreshed = await findGridTiles(frame, provider);
944
+ const refreshedVisible = [];
945
+ for (const t of refreshed) {
946
+ try {
947
+ if (await t.isVisible())
948
+ refreshedVisible.push(t);
949
+ }
950
+ catch { }
951
+ }
952
+ if (idx < refreshedVisible.length) {
953
+ try {
954
+ await frame.evaluate((tileIdx) => {
955
+ const tds = document.querySelectorAll('table td');
956
+ if (tds[tileIdx])
957
+ tds[tileIdx].click();
958
+ }, idx);
959
+ }
960
+ catch {
961
+ await refreshedVisible[idx].click({ force: true, timeout: 3000 });
962
+ }
963
+ clickedSet.add(idx);
964
+ await page.waitForTimeout(100 + Math.random() * 100);
965
+ _dbg(`clicked tile ${idx} (re-fetched)`);
966
+ }
967
+ }
968
+ catch (e2) {
969
+ _dbg(`FAILED to click tile ${idx}: ${e2.message}`);
970
+ }
971
+ }
972
+ }
973
+ // @ts-ignore — self-review disabled (was removing correct tiles and adding wrong ones)
974
+ if (false && isRecaptcha && clickedSet.size > 0) {
975
+ _dbg('self-review: checking selections...');
976
+ await page.waitForTimeout(300);
977
+ try {
978
+ const reviewPath = join(homedir(), '.aurix-captcha-review.png');
979
+ let reviewShot = false;
980
+ try {
981
+ await frame.locator('body').screenshot({ path: reviewPath, timeout: 5000 });
982
+ reviewShot = true;
983
+ }
984
+ catch { }
985
+ if (!reviewShot) {
986
+ try {
987
+ await page.screenshot({ path: reviewPath });
988
+ reviewShot = true;
989
+ }
990
+ catch { }
991
+ }
992
+ if (reviewShot) {
993
+ const selectedList = [...clickedSet].sort((a, b) => a - b).join(', ');
994
+ const reviewPrompt = `This is a reCAPTCHA grid. Tiles ${selectedList} are selected (checkmarked). Task: "${objectName}"\n\nCheck if any selected tiles DON'T contain ${objectName} (wrong), or if any unselected tiles DO contain ${objectName} (missed).\n\nRespond with ONLY ONE of these exact formats (no explanation):\nAdd: [tile numbers]\nRemove: [tile numbers]\nCorrect`;
995
+ const reviewBase64 = readFileBase64(reviewPath);
996
+ const reviewResponse = await visionClassify(reviewBase64, reviewPrompt);
997
+ _dbg(`self-review response: ${reviewResponse.split('\n').pop()?.trim()}`);
998
+ const addMatch = reviewResponse.match(/(?:add|also select|missed|need)[:\s]+\[?([\d,\s]+)\]?/i);
999
+ const removeMatch = reviewResponse.match(/(?:remove|deselect|unselect|wrong|incorrect)[:\s]+\[?([\d,\s]+)\]?/i);
1000
+ _dbg(`self-review addMatch: ${addMatch ? 'yes' : 'no'}, removeMatch: ${removeMatch ? 'yes' : 'no'}`);
1001
+ }
1002
+ }
1003
+ catch (e) {
1004
+ _dbg(`self-review failed: ${e.message}`);
1005
+ }
1006
+ }
1007
+ // @ts-ignore — dynamic tile analysis disabled
1008
+ if (false && isRecaptcha && is3x3 && clickedSet.size > 0) {
1009
+ _dbg('dynamic tile analysis disabled');
1010
+ }
1011
+ if (isRecaptcha && clickedSet.size > 0) {
1012
+ await page.waitForTimeout(1500 + Math.random() * 1000);
1013
+ }
1014
+ _dbg('clicking verify button...');
1015
+ try {
1016
+ const verifyClicked = await frame.evaluate(() => {
1017
+ const btn = document.querySelector('#recaptcha-verify-button, .rc-button-submit, .button-submit, [id*="verify"]');
1018
+ if (!btn)
1019
+ return false;
1020
+ btn.click();
1021
+ return true;
1022
+ });
1023
+ if (!verifyClicked) {
1024
+ let verifyBtn = frame.locator('#recaptcha-verify-button, .rc-button-submit, .button-submit, [id*="verify"]');
1025
+ if (await verifyBtn.count() === 0) {
1026
+ verifyBtn = frame.locator('button:has-text("Verify"), button:has-text("Next"), button:has-text("Submit")');
1027
+ }
1028
+ if (await verifyBtn.count() > 0) {
1029
+ await verifyBtn.first().click({ force: true, timeout: 5000 });
1030
+ }
1031
+ }
1032
+ _dbg('verify button clicked, waiting for result...');
1033
+ await page.waitForTimeout(3000);
1034
+ let hasToken = false;
1035
+ try {
1036
+ hasToken = await page.evaluate(() => {
1037
+ const el = document.getElementById('g-recaptcha-response');
1038
+ return !!(el && el.value && el.value.length > 10);
1039
+ });
1040
+ }
1041
+ catch { }
1042
+ let ariaChecked = false;
1043
+ try {
1044
+ for (const f of page.frames()) {
1045
+ if (f.url().includes('/recaptcha/') && f.url().includes('/anchor')) {
1046
+ const anchor = f.locator('#recaptcha-anchor[aria-checked="true"]');
1047
+ if (await anchor.count() > 0) {
1048
+ ariaChecked = true;
1049
+ break;
1050
+ }
1051
+ }
1052
+ }
1053
+ }
1054
+ catch { }
1055
+ if (hasToken || ariaChecked) {
1056
+ const verifyResultPath = join(homedir(), '.aurix-captcha-verify-result.png');
1057
+ await page.screenshot({ path: verifyResultPath }).catch(() => { });
1058
+ saveCaptchaTraining({ instruction: cleanInstruction, objectType: objectName, gridSize, gridCount: actualTileCount, tileCount: actualTileCount, matchedIndices: [...matchedIndices], timestamp: Date.now() });
1059
+ _dbg(`CAPTCHA SOLVED! (token=${hasToken}, aria=${ariaChecked})`);
1060
+ results.push(`[OK] CAPTCHA SOLVED! ✅`);
1061
+ results.push(`→ The form can now be submitted. Click the submit/register button to continue.`);
1062
+ return results.join('\n');
1063
+ }
1064
+ const currentFrames = page.frames();
1065
+ const stillHasBframe = currentFrames.some((f) => f.url().includes('/recaptcha/') && f.url().includes('/bframe'));
1066
+ if (!stillHasBframe) {
1067
+ const verifyResultPath = join(homedir(), '.aurix-captcha-verify-result.png');
1068
+ await page.screenshot({ path: verifyResultPath }).catch(() => { });
1069
+ saveCaptchaTraining({ instruction: cleanInstruction, objectType: objectName, gridSize, gridCount: actualTileCount, tileCount: actualTileCount, matchedIndices: [...matchedIndices], timestamp: Date.now() });
1070
+ _dbg('CAPTCHA SOLVED (bframe disappeared)');
1071
+ results.push(`[OK] CAPTCHA SOLVED! ✅`);
1072
+ results.push(`→ The form can now be submitted. Click the submit/register button to continue.`);
1073
+ return results.join('\n');
1074
+ }
1075
+ const errorEl = frame.locator('.rc-imageselect-incorrect-response, .error-message, .incorrect').first();
1076
+ const errorVisible = await errorEl.count() > 0 && await errorEl.isVisible().catch(() => false);
1077
+ if (errorVisible) {
1078
+ _dbg('verification FAILED - error shown');
1079
+ results.push('Verification failed, challenge will retry');
1080
+ return results.join('\n');
1081
+ }
1082
+ const newChallenge = await frame.locator('.rc-imageselect-instructions, .prompt-text').count();
1083
+ if (newChallenge > 0) {
1084
+ const newInstr = (await frame.locator('.rc-imageselect-instructions, .prompt-text').first().textContent() || '').trim();
1085
+ if (newInstr !== instruction) {
1086
+ _dbg(`new challenge appeared: "${newInstr}"`);
1087
+ saveCaptchaTraining({ instruction: cleanInstruction, objectType: objectName, gridSize, gridCount: actualTileCount, tileCount: actualTileCount, matchedIndices: [...matchedIndices], visionResponse: results.join('\n').slice(0, 500), timestamp: Date.now() });
1088
+ results.push(`New challenge appeared: "${newInstr}"`);
1089
+ return results.join('\n');
1090
+ }
1091
+ _dbg('same challenge still present after verify');
1092
+ results.push('Same challenge still present');
1093
+ return results.join('\n');
1094
+ }
1095
+ if (matchedIndices.length === 0) {
1096
+ _dbg('verification uncertain — 0 tiles were selected, likely not solved');
1097
+ results.push('Verification uncertain — no tiles were selected');
1098
+ return results.join('\n');
1099
+ }
1100
+ const verifyResultPath = join(homedir(), '.aurix-captcha-verify-result.png');
1101
+ await page.screenshot({ path: verifyResultPath }).catch(() => { });
1102
+ saveCaptchaTraining({ instruction: cleanInstruction, objectType: objectName, gridSize, gridCount: actualTileCount, tileCount: actualTileCount, matchedIndices: [...matchedIndices], timestamp: Date.now() });
1103
+ _dbg('CAPTCHA SOLVED (no error, no challenge)');
1104
+ results.push(`[OK] CAPTCHA SOLVED! ✅`);
1105
+ results.push(`→ The form can now be submitted. Click the submit/register button to continue.`);
1106
+ return results.join('\n');
1107
+ }
1108
+ catch (e) {
1109
+ _dbg(`Verify FAILED: ${e.message}`);
1110
+ results.push(`Verify failed: ${e.message}`);
1111
+ return results.join('\n');
1112
+ }
1113
+ }
1114
+ //# sourceMappingURL=RecaptchaSolver.js.map