@lhremote/core 0.8.0 → 0.10.0

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 (480) hide show
  1. package/dist/cdp/app-discovery.d.ts +57 -0
  2. package/dist/cdp/app-discovery.d.ts.map +1 -1
  3. package/dist/cdp/app-discovery.js +90 -7
  4. package/dist/cdp/app-discovery.js.map +1 -1
  5. package/dist/cdp/app-discovery.test.js +180 -9
  6. package/dist/cdp/app-discovery.test.js.map +1 -1
  7. package/dist/cdp/client.d.ts +8 -1
  8. package/dist/cdp/client.d.ts.map +1 -1
  9. package/dist/cdp/client.js +8 -1
  10. package/dist/cdp/client.js.map +1 -1
  11. package/dist/cdp/discovery.d.ts.map +1 -1
  12. package/dist/cdp/discovery.js +5 -1
  13. package/dist/cdp/discovery.js.map +1 -1
  14. package/dist/cdp/discovery.test.js +7 -0
  15. package/dist/cdp/discovery.test.js.map +1 -1
  16. package/dist/cdp/index.d.ts +1 -1
  17. package/dist/cdp/index.d.ts.map +1 -1
  18. package/dist/cdp/index.js +1 -1
  19. package/dist/cdp/index.js.map +1 -1
  20. package/dist/cdp/instance-discovery.d.ts.map +1 -1
  21. package/dist/cdp/instance-discovery.js +25 -10
  22. package/dist/cdp/instance-discovery.js.map +1 -1
  23. package/dist/cdp/instance-discovery.test.js +17 -0
  24. package/dist/cdp/instance-discovery.test.js.map +1 -1
  25. package/dist/cdp/testing/launch-chromium.d.ts +1 -1
  26. package/dist/cdp/testing/launch-chromium.js +2 -2
  27. package/dist/db/client.d.ts.map +1 -1
  28. package/dist/db/client.js +3 -0
  29. package/dist/db/client.js.map +1 -1
  30. package/dist/db/repositories/campaign-hard-delete.integration.test.js +4 -1
  31. package/dist/db/repositories/campaign-hard-delete.integration.test.js.map +1 -1
  32. package/dist/db/repositories/campaign.d.ts.map +1 -1
  33. package/dist/db/repositories/campaign.js +81 -33
  34. package/dist/db/repositories/campaign.js.map +1 -1
  35. package/dist/db/repositories/campaign.test.js +2 -2
  36. package/dist/db/repositories/collection-list.d.ts +11 -0
  37. package/dist/db/repositories/collection-list.d.ts.map +1 -1
  38. package/dist/db/repositories/collection-list.integration.test.js +6 -4
  39. package/dist/db/repositories/collection-list.integration.test.js.map +1 -1
  40. package/dist/db/repositories/collection-list.js +92 -2
  41. package/dist/db/repositories/collection-list.js.map +1 -1
  42. package/dist/db/testing/create-fixture.js +36 -2
  43. package/dist/db/testing/create-fixture.js.map +1 -1
  44. package/dist/index.d.ts +6 -6
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/index.js +6 -6
  47. package/dist/index.js.map +1 -1
  48. package/dist/linkedin/dom-automation-retry.test.d.ts +2 -0
  49. package/dist/linkedin/dom-automation-retry.test.d.ts.map +1 -0
  50. package/dist/linkedin/dom-automation-retry.test.js +51 -0
  51. package/dist/linkedin/dom-automation-retry.test.js.map +1 -0
  52. package/dist/linkedin/dom-automation.d.ts +166 -6
  53. package/dist/linkedin/dom-automation.d.ts.map +1 -1
  54. package/dist/linkedin/dom-automation.js +496 -21
  55. package/dist/linkedin/dom-automation.js.map +1 -1
  56. package/dist/linkedin/humanized-mouse.d.ts +69 -0
  57. package/dist/linkedin/humanized-mouse.d.ts.map +1 -0
  58. package/dist/linkedin/humanized-mouse.js +109 -0
  59. package/dist/linkedin/humanized-mouse.js.map +1 -0
  60. package/dist/linkedin/index.d.ts +3 -2
  61. package/dist/linkedin/index.d.ts.map +1 -1
  62. package/dist/linkedin/index.js +3 -2
  63. package/dist/linkedin/index.js.map +1 -1
  64. package/dist/linkedin/selectors.d.ts +74 -48
  65. package/dist/linkedin/selectors.d.ts.map +1 -1
  66. package/dist/linkedin/selectors.js +64 -41
  67. package/dist/linkedin/selectors.js.map +1 -1
  68. package/dist/operations/add-people-to-collection.d.ts +1 -1
  69. package/dist/operations/add-people-to-collection.d.ts.map +1 -1
  70. package/dist/operations/add-people-to-collection.js +3 -6
  71. package/dist/operations/add-people-to-collection.js.map +1 -1
  72. package/dist/operations/build-linkedin-url.test.d.ts +2 -0
  73. package/dist/operations/build-linkedin-url.test.d.ts.map +1 -0
  74. package/dist/operations/build-linkedin-url.test.js +158 -0
  75. package/dist/operations/build-linkedin-url.test.js.map +1 -0
  76. package/dist/operations/campaign-add-action.d.ts +1 -1
  77. package/dist/operations/campaign-add-action.d.ts.map +1 -1
  78. package/dist/operations/campaign-add-action.js +3 -6
  79. package/dist/operations/campaign-add-action.js.map +1 -1
  80. package/dist/operations/campaign-create.d.ts +1 -1
  81. package/dist/operations/campaign-create.d.ts.map +1 -1
  82. package/dist/operations/campaign-create.js +3 -6
  83. package/dist/operations/campaign-create.js.map +1 -1
  84. package/dist/operations/campaign-delete.d.ts +1 -1
  85. package/dist/operations/campaign-delete.d.ts.map +1 -1
  86. package/dist/operations/campaign-delete.js +3 -6
  87. package/dist/operations/campaign-delete.js.map +1 -1
  88. package/dist/operations/campaign-erase.d.ts +1 -1
  89. package/dist/operations/campaign-erase.d.ts.map +1 -1
  90. package/dist/operations/campaign-erase.js +3 -6
  91. package/dist/operations/campaign-erase.js.map +1 -1
  92. package/dist/operations/campaign-exclude-add.d.ts +1 -1
  93. package/dist/operations/campaign-exclude-add.d.ts.map +1 -1
  94. package/dist/operations/campaign-exclude-add.js +3 -6
  95. package/dist/operations/campaign-exclude-add.js.map +1 -1
  96. package/dist/operations/campaign-exclude-list.d.ts +1 -1
  97. package/dist/operations/campaign-exclude-list.d.ts.map +1 -1
  98. package/dist/operations/campaign-exclude-list.js +3 -6
  99. package/dist/operations/campaign-exclude-list.js.map +1 -1
  100. package/dist/operations/campaign-exclude-remove.d.ts +1 -1
  101. package/dist/operations/campaign-exclude-remove.d.ts.map +1 -1
  102. package/dist/operations/campaign-exclude-remove.js +3 -6
  103. package/dist/operations/campaign-exclude-remove.js.map +1 -1
  104. package/dist/operations/campaign-export.d.ts +1 -1
  105. package/dist/operations/campaign-export.d.ts.map +1 -1
  106. package/dist/operations/campaign-export.js +3 -6
  107. package/dist/operations/campaign-export.js.map +1 -1
  108. package/dist/operations/campaign-get.d.ts +1 -1
  109. package/dist/operations/campaign-get.d.ts.map +1 -1
  110. package/dist/operations/campaign-get.js +3 -6
  111. package/dist/operations/campaign-get.js.map +1 -1
  112. package/dist/operations/campaign-list-people.d.ts +1 -1
  113. package/dist/operations/campaign-list-people.d.ts.map +1 -1
  114. package/dist/operations/campaign-list-people.js +3 -6
  115. package/dist/operations/campaign-list-people.js.map +1 -1
  116. package/dist/operations/campaign-list.d.ts +1 -1
  117. package/dist/operations/campaign-list.d.ts.map +1 -1
  118. package/dist/operations/campaign-list.js +3 -6
  119. package/dist/operations/campaign-list.js.map +1 -1
  120. package/dist/operations/campaign-move-next.d.ts +1 -1
  121. package/dist/operations/campaign-move-next.d.ts.map +1 -1
  122. package/dist/operations/campaign-move-next.js +3 -6
  123. package/dist/operations/campaign-move-next.js.map +1 -1
  124. package/dist/operations/campaign-remove-action.d.ts +1 -1
  125. package/dist/operations/campaign-remove-action.d.ts.map +1 -1
  126. package/dist/operations/campaign-remove-action.js +3 -6
  127. package/dist/operations/campaign-remove-action.js.map +1 -1
  128. package/dist/operations/campaign-remove-people.d.ts +1 -1
  129. package/dist/operations/campaign-remove-people.d.ts.map +1 -1
  130. package/dist/operations/campaign-remove-people.js +3 -6
  131. package/dist/operations/campaign-remove-people.js.map +1 -1
  132. package/dist/operations/campaign-reorder-actions.d.ts +1 -1
  133. package/dist/operations/campaign-reorder-actions.d.ts.map +1 -1
  134. package/dist/operations/campaign-reorder-actions.js +3 -6
  135. package/dist/operations/campaign-reorder-actions.js.map +1 -1
  136. package/dist/operations/campaign-retry.d.ts +1 -1
  137. package/dist/operations/campaign-retry.d.ts.map +1 -1
  138. package/dist/operations/campaign-retry.js +3 -6
  139. package/dist/operations/campaign-retry.js.map +1 -1
  140. package/dist/operations/campaign-start.d.ts +1 -1
  141. package/dist/operations/campaign-start.d.ts.map +1 -1
  142. package/dist/operations/campaign-start.js +3 -6
  143. package/dist/operations/campaign-start.js.map +1 -1
  144. package/dist/operations/campaign-statistics.d.ts +1 -1
  145. package/dist/operations/campaign-statistics.d.ts.map +1 -1
  146. package/dist/operations/campaign-statistics.js +3 -6
  147. package/dist/operations/campaign-statistics.js.map +1 -1
  148. package/dist/operations/campaign-status.d.ts +1 -1
  149. package/dist/operations/campaign-status.d.ts.map +1 -1
  150. package/dist/operations/campaign-status.js +3 -6
  151. package/dist/operations/campaign-status.js.map +1 -1
  152. package/dist/operations/campaign-stop.d.ts +1 -1
  153. package/dist/operations/campaign-stop.d.ts.map +1 -1
  154. package/dist/operations/campaign-stop.js +3 -6
  155. package/dist/operations/campaign-stop.js.map +1 -1
  156. package/dist/operations/campaign-update-action.d.ts +1 -1
  157. package/dist/operations/campaign-update-action.d.ts.map +1 -1
  158. package/dist/operations/campaign-update-action.js +3 -6
  159. package/dist/operations/campaign-update-action.js.map +1 -1
  160. package/dist/operations/campaign-update.d.ts +1 -1
  161. package/dist/operations/campaign-update.d.ts.map +1 -1
  162. package/dist/operations/campaign-update.js +3 -6
  163. package/dist/operations/campaign-update.js.map +1 -1
  164. package/dist/operations/check-replies.d.ts +3 -1
  165. package/dist/operations/check-replies.d.ts.map +1 -1
  166. package/dist/operations/check-replies.js +124 -17
  167. package/dist/operations/check-replies.js.map +1 -1
  168. package/dist/operations/check-replies.test.js +152 -17
  169. package/dist/operations/check-replies.test.js.map +1 -1
  170. package/dist/operations/collect-people.d.ts +1 -1
  171. package/dist/operations/collect-people.d.ts.map +1 -1
  172. package/dist/operations/collect-people.js +18 -12
  173. package/dist/operations/collect-people.js.map +1 -1
  174. package/dist/operations/collect-people.test.js +21 -5
  175. package/dist/operations/collect-people.test.js.map +1 -1
  176. package/dist/operations/comment-on-post.d.ts +4 -1
  177. package/dist/operations/comment-on-post.d.ts.map +1 -1
  178. package/dist/operations/comment-on-post.js +15 -19
  179. package/dist/operations/comment-on-post.js.map +1 -1
  180. package/dist/operations/comment-on-post.test.js +8 -6
  181. package/dist/operations/comment-on-post.test.js.map +1 -1
  182. package/dist/operations/create-collection.d.ts +1 -1
  183. package/dist/operations/create-collection.d.ts.map +1 -1
  184. package/dist/operations/create-collection.js +5 -7
  185. package/dist/operations/create-collection.js.map +1 -1
  186. package/dist/operations/create-collection.test.js +3 -1
  187. package/dist/operations/create-collection.test.js.map +1 -1
  188. package/dist/operations/delete-collection.d.ts +1 -1
  189. package/dist/operations/delete-collection.d.ts.map +1 -1
  190. package/dist/operations/delete-collection.js +3 -6
  191. package/dist/operations/delete-collection.js.map +1 -1
  192. package/dist/operations/dismiss-errors.d.ts +11 -5
  193. package/dist/operations/dismiss-errors.d.ts.map +1 -1
  194. package/dist/operations/dismiss-errors.js +68 -34
  195. package/dist/operations/dismiss-errors.js.map +1 -1
  196. package/dist/operations/dismiss-errors.test.js +129 -8
  197. package/dist/operations/dismiss-errors.test.js.map +1 -1
  198. package/dist/operations/endorse-skills.test.d.ts +2 -0
  199. package/dist/operations/endorse-skills.test.d.ts.map +1 -0
  200. package/dist/operations/endorse-skills.test.js +70 -0
  201. package/dist/operations/endorse-skills.test.js.map +1 -0
  202. package/dist/operations/enrich-profile.test.d.ts +2 -0
  203. package/dist/operations/enrich-profile.test.d.ts.map +1 -0
  204. package/dist/operations/enrich-profile.test.js +73 -0
  205. package/dist/operations/enrich-profile.test.js.map +1 -0
  206. package/dist/operations/ephemeral-action.d.ts +2 -1
  207. package/dist/operations/ephemeral-action.d.ts.map +1 -1
  208. package/dist/operations/ephemeral-action.js +5 -7
  209. package/dist/operations/ephemeral-action.js.map +1 -1
  210. package/dist/operations/ephemeral-action.test.d.ts +2 -0
  211. package/dist/operations/ephemeral-action.test.d.ts.map +1 -0
  212. package/dist/operations/ephemeral-action.test.js +159 -0
  213. package/dist/operations/ephemeral-action.test.js.map +1 -0
  214. package/dist/operations/follow-person.test.d.ts +2 -0
  215. package/dist/operations/follow-person.test.d.ts.map +1 -0
  216. package/dist/operations/follow-person.test.js +57 -0
  217. package/dist/operations/follow-person.test.js.map +1 -0
  218. package/dist/operations/get-action-budget.d.ts +1 -1
  219. package/dist/operations/get-action-budget.d.ts.map +1 -1
  220. package/dist/operations/get-action-budget.js +3 -6
  221. package/dist/operations/get-action-budget.js.map +1 -1
  222. package/dist/operations/get-errors.d.ts +5 -1
  223. package/dist/operations/get-errors.d.ts.map +1 -1
  224. package/dist/operations/get-errors.js +55 -33
  225. package/dist/operations/get-errors.js.map +1 -1
  226. package/dist/operations/get-errors.test.js +54 -55
  227. package/dist/operations/get-errors.test.js.map +1 -1
  228. package/dist/operations/get-feed.d.ts +83 -4
  229. package/dist/operations/get-feed.d.ts.map +1 -1
  230. package/dist/operations/get-feed.js +400 -153
  231. package/dist/operations/get-feed.js.map +1 -1
  232. package/dist/operations/get-feed.test.js +416 -190
  233. package/dist/operations/get-feed.test.js.map +1 -1
  234. package/dist/operations/get-post-engagers.d.ts +6 -3
  235. package/dist/operations/get-post-engagers.d.ts.map +1 -1
  236. package/dist/operations/get-post-engagers.js +278 -78
  237. package/dist/operations/get-post-engagers.js.map +1 -1
  238. package/dist/operations/get-post-engagers.test.js +292 -14
  239. package/dist/operations/get-post-engagers.test.js.map +1 -1
  240. package/dist/operations/get-post-stats.d.ts +8 -3
  241. package/dist/operations/get-post-stats.d.ts.map +1 -1
  242. package/dist/operations/get-post-stats.js +101 -37
  243. package/dist/operations/get-post-stats.js.map +1 -1
  244. package/dist/operations/get-post-stats.test.js +137 -2
  245. package/dist/operations/get-post-stats.test.js.map +1 -1
  246. package/dist/operations/get-post.d.ts +9 -150
  247. package/dist/operations/get-post.d.ts.map +1 -1
  248. package/dist/operations/get-post.js +356 -210
  249. package/dist/operations/get-post.js.map +1 -1
  250. package/dist/operations/get-post.test.js +210 -387
  251. package/dist/operations/get-post.test.js.map +1 -1
  252. package/dist/operations/get-profile-activity.d.ts +13 -92
  253. package/dist/operations/get-profile-activity.d.ts.map +1 -1
  254. package/dist/operations/get-profile-activity.js +305 -105
  255. package/dist/operations/get-profile-activity.js.map +1 -1
  256. package/dist/operations/get-profile-activity.test.js +277 -158
  257. package/dist/operations/get-profile-activity.test.js.map +1 -1
  258. package/dist/operations/get-throttle-status.d.ts +1 -1
  259. package/dist/operations/get-throttle-status.d.ts.map +1 -1
  260. package/dist/operations/get-throttle-status.js +4 -10
  261. package/dist/operations/get-throttle-status.js.map +1 -1
  262. package/dist/operations/import-people-from-collection.d.ts +1 -1
  263. package/dist/operations/import-people-from-collection.d.ts.map +1 -1
  264. package/dist/operations/import-people-from-collection.js +3 -6
  265. package/dist/operations/import-people-from-collection.js.map +1 -1
  266. package/dist/operations/import-people-from-urls.d.ts +1 -1
  267. package/dist/operations/import-people-from-urls.d.ts.map +1 -1
  268. package/dist/operations/import-people-from-urls.js +3 -6
  269. package/dist/operations/import-people-from-urls.js.map +1 -1
  270. package/dist/operations/index.d.ts +2 -2
  271. package/dist/operations/index.d.ts.map +1 -1
  272. package/dist/operations/index.js +2 -1
  273. package/dist/operations/index.js.map +1 -1
  274. package/dist/operations/like-person-posts.test.d.ts +2 -0
  275. package/dist/operations/like-person-posts.test.d.ts.map +1 -0
  276. package/dist/operations/like-person-posts.test.js +103 -0
  277. package/dist/operations/like-person-posts.test.js.map +1 -0
  278. package/dist/operations/list-collections.d.ts +1 -1
  279. package/dist/operations/list-collections.d.ts.map +1 -1
  280. package/dist/operations/list-collections.js +3 -6
  281. package/dist/operations/list-collections.js.map +1 -1
  282. package/dist/operations/message-person.test.d.ts +2 -0
  283. package/dist/operations/message-person.test.d.ts.map +1 -0
  284. package/dist/operations/message-person.test.js +108 -0
  285. package/dist/operations/message-person.test.js.map +1 -0
  286. package/dist/operations/navigate-away.d.ts +14 -0
  287. package/dist/operations/navigate-away.d.ts.map +1 -0
  288. package/dist/operations/navigate-away.js +24 -0
  289. package/dist/operations/navigate-away.js.map +1 -0
  290. package/dist/operations/navigate-away.test.d.ts +2 -0
  291. package/dist/operations/navigate-away.test.d.ts.map +1 -0
  292. package/dist/operations/navigate-away.test.js +51 -0
  293. package/dist/operations/navigate-away.test.js.map +1 -0
  294. package/dist/operations/query-messages.d.ts +1 -1
  295. package/dist/operations/query-messages.d.ts.map +1 -1
  296. package/dist/operations/query-messages.js +3 -6
  297. package/dist/operations/query-messages.js.map +1 -1
  298. package/dist/operations/react-to-post.d.ts +16 -4
  299. package/dist/operations/react-to-post.d.ts.map +1 -1
  300. package/dist/operations/react-to-post.js +86 -24
  301. package/dist/operations/react-to-post.js.map +1 -1
  302. package/dist/operations/react-to-post.test.js +55 -5
  303. package/dist/operations/react-to-post.test.js.map +1 -1
  304. package/dist/operations/remove-connection.test.d.ts +2 -0
  305. package/dist/operations/remove-connection.test.d.ts.map +1 -0
  306. package/dist/operations/remove-connection.test.js +45 -0
  307. package/dist/operations/remove-connection.test.js.map +1 -0
  308. package/dist/operations/remove-people-from-collection.d.ts +1 -1
  309. package/dist/operations/remove-people-from-collection.d.ts.map +1 -1
  310. package/dist/operations/remove-people-from-collection.js +3 -6
  311. package/dist/operations/remove-people-from-collection.js.map +1 -1
  312. package/dist/operations/resolve-linkedin-entity.d.ts.map +1 -1
  313. package/dist/operations/resolve-linkedin-entity.js +2 -2
  314. package/dist/operations/resolve-linkedin-entity.js.map +1 -1
  315. package/dist/operations/resolve-linkedin-entity.test.d.ts +2 -0
  316. package/dist/operations/resolve-linkedin-entity.test.d.ts.map +1 -0
  317. package/dist/operations/resolve-linkedin-entity.test.js +343 -0
  318. package/dist/operations/resolve-linkedin-entity.test.js.map +1 -0
  319. package/dist/operations/scrape-messaging-history.d.ts +2 -1
  320. package/dist/operations/scrape-messaging-history.d.ts.map +1 -1
  321. package/dist/operations/scrape-messaging-history.js +113 -18
  322. package/dist/operations/scrape-messaging-history.js.map +1 -1
  323. package/dist/operations/scrape-messaging-history.test.js +109 -12
  324. package/dist/operations/scrape-messaging-history.test.js.map +1 -1
  325. package/dist/operations/search-posts.d.ts +20 -112
  326. package/dist/operations/search-posts.d.ts.map +1 -1
  327. package/dist/operations/search-posts.js +369 -170
  328. package/dist/operations/search-posts.js.map +1 -1
  329. package/dist/operations/search-posts.test.js +273 -234
  330. package/dist/operations/search-posts.test.js.map +1 -1
  331. package/dist/operations/send-inmail.test.d.ts +2 -0
  332. package/dist/operations/send-inmail.test.d.ts.map +1 -0
  333. package/dist/operations/send-inmail.test.js +108 -0
  334. package/dist/operations/send-inmail.test.js.map +1 -0
  335. package/dist/operations/send-invite.test.d.ts +2 -0
  336. package/dist/operations/send-invite.test.d.ts.map +1 -0
  337. package/dist/operations/send-invite.test.js +59 -0
  338. package/dist/operations/send-invite.test.js.map +1 -0
  339. package/dist/operations/types.d.ts +27 -1
  340. package/dist/operations/types.d.ts.map +1 -1
  341. package/dist/operations/types.js +14 -1
  342. package/dist/operations/types.js.map +1 -1
  343. package/dist/operations/visit-profile.d.ts +1 -1
  344. package/dist/operations/visit-profile.d.ts.map +1 -1
  345. package/dist/operations/visit-profile.js +3 -6
  346. package/dist/operations/visit-profile.js.map +1 -1
  347. package/dist/services/account-resolution.d.ts +10 -4
  348. package/dist/services/account-resolution.d.ts.map +1 -1
  349. package/dist/services/account-resolution.js +63 -5
  350. package/dist/services/account-resolution.js.map +1 -1
  351. package/dist/services/app.d.ts +6 -2
  352. package/dist/services/app.d.ts.map +1 -1
  353. package/dist/services/app.js +18 -6
  354. package/dist/services/app.js.map +1 -1
  355. package/dist/services/app.test.js +41 -7
  356. package/dist/services/app.test.js.map +1 -1
  357. package/dist/services/campaign.d.ts +40 -4
  358. package/dist/services/campaign.d.ts.map +1 -1
  359. package/dist/services/campaign.js +176 -10
  360. package/dist/services/campaign.js.map +1 -1
  361. package/dist/services/campaign.test.js +53 -15
  362. package/dist/services/campaign.test.js.map +1 -1
  363. package/dist/services/collection.d.ts +16 -21
  364. package/dist/services/collection.d.ts.map +1 -1
  365. package/dist/services/collection.js +34 -47
  366. package/dist/services/collection.js.map +1 -1
  367. package/dist/services/collection.test.js +30 -91
  368. package/dist/services/collection.test.js.map +1 -1
  369. package/dist/services/ephemeral-campaign.d.ts +6 -0
  370. package/dist/services/ephemeral-campaign.d.ts.map +1 -1
  371. package/dist/services/ephemeral-campaign.js +54 -10
  372. package/dist/services/ephemeral-campaign.js.map +1 -1
  373. package/dist/services/ephemeral-campaign.test.js +23 -10
  374. package/dist/services/ephemeral-campaign.test.js.map +1 -1
  375. package/dist/services/errors.d.ts +2 -1
  376. package/dist/services/errors.d.ts.map +1 -1
  377. package/dist/services/errors.js +6 -3
  378. package/dist/services/errors.js.map +1 -1
  379. package/dist/services/index.d.ts +1 -2
  380. package/dist/services/index.d.ts.map +1 -1
  381. package/dist/services/index.js +1 -2
  382. package/dist/services/index.js.map +1 -1
  383. package/dist/services/instance-context.d.ts +5 -1
  384. package/dist/services/instance-context.d.ts.map +1 -1
  385. package/dist/services/instance-context.js +87 -28
  386. package/dist/services/instance-context.js.map +1 -1
  387. package/dist/services/instance-context.test.js +5 -1
  388. package/dist/services/instance-context.test.js.map +1 -1
  389. package/dist/services/instance-lifecycle.d.ts.map +1 -1
  390. package/dist/services/instance-lifecycle.js +32 -1
  391. package/dist/services/instance-lifecycle.js.map +1 -1
  392. package/dist/services/instance-lifecycle.test.js +52 -1
  393. package/dist/services/instance-lifecycle.test.js.map +1 -1
  394. package/dist/services/instance.d.ts +37 -9
  395. package/dist/services/instance.d.ts.map +1 -1
  396. package/dist/services/instance.js +100 -25
  397. package/dist/services/instance.js.map +1 -1
  398. package/dist/services/instance.test.js +157 -0
  399. package/dist/services/instance.test.js.map +1 -1
  400. package/dist/services/launcher.d.ts +47 -3
  401. package/dist/services/launcher.d.ts.map +1 -1
  402. package/dist/services/launcher.js +205 -33
  403. package/dist/services/launcher.js.map +1 -1
  404. package/dist/services/launcher.test.js +133 -6
  405. package/dist/services/launcher.test.js.map +1 -1
  406. package/dist/services/source-type-registry.d.ts +8 -0
  407. package/dist/services/source-type-registry.d.ts.map +1 -1
  408. package/dist/services/source-type-registry.js +39 -0
  409. package/dist/services/source-type-registry.js.map +1 -1
  410. package/dist/services/source-type-registry.test.js +31 -1
  411. package/dist/services/source-type-registry.test.js.map +1 -1
  412. package/dist/services/status.d.ts +6 -2
  413. package/dist/services/status.d.ts.map +1 -1
  414. package/dist/services/status.js +67 -34
  415. package/dist/services/status.js.map +1 -1
  416. package/dist/services/status.test.js +9 -2
  417. package/dist/services/status.test.js.map +1 -1
  418. package/dist/testing/e2e-helpers.d.ts +47 -1
  419. package/dist/testing/e2e-helpers.d.ts.map +1 -1
  420. package/dist/testing/e2e-helpers.js +244 -7
  421. package/dist/testing/e2e-helpers.js.map +1 -1
  422. package/dist/testing/index.d.ts +1 -1
  423. package/dist/testing/index.d.ts.map +1 -1
  424. package/dist/testing/index.js +1 -1
  425. package/dist/testing/index.js.map +1 -1
  426. package/dist/types/account.d.ts +1 -1
  427. package/dist/types/campaign.d.ts +1 -1
  428. package/dist/types/campaign.d.ts.map +1 -1
  429. package/dist/types/feed.d.ts +1 -3
  430. package/dist/types/feed.d.ts.map +1 -1
  431. package/dist/types/index.d.ts +0 -1
  432. package/dist/types/index.d.ts.map +1 -1
  433. package/dist/utils/cdp-port.d.ts.map +1 -1
  434. package/dist/utils/cdp-port.js +3 -1
  435. package/dist/utils/cdp-port.js.map +1 -1
  436. package/dist/utils/cdp-port.test.js +1 -1
  437. package/dist/utils/cdp-port.test.js.map +1 -1
  438. package/dist/utils/delay.d.ts +79 -0
  439. package/dist/utils/delay.d.ts.map +1 -1
  440. package/dist/utils/delay.js +118 -0
  441. package/dist/utils/delay.js.map +1 -1
  442. package/dist/utils/delay.test.js +111 -1
  443. package/dist/utils/delay.test.js.map +1 -1
  444. package/dist/utils/index.d.ts +2 -1
  445. package/dist/utils/index.d.ts.map +1 -1
  446. package/dist/utils/index.js +2 -1
  447. package/dist/utils/index.js.map +1 -1
  448. package/dist/utils/session-pacer.d.ts +27 -0
  449. package/dist/utils/session-pacer.d.ts.map +1 -0
  450. package/dist/utils/session-pacer.js +55 -0
  451. package/dist/utils/session-pacer.js.map +1 -0
  452. package/dist/utils/session-pacer.test.d.ts +2 -0
  453. package/dist/utils/session-pacer.test.d.ts.map +1 -0
  454. package/dist/utils/session-pacer.test.js +111 -0
  455. package/dist/utils/session-pacer.test.js.map +1 -0
  456. package/package.json +1 -1
  457. package/dist/linkedin/__tests__/selectors.integration.test.d.ts +0 -2
  458. package/dist/linkedin/__tests__/selectors.integration.test.d.ts.map +0 -1
  459. package/dist/linkedin/__tests__/selectors.integration.test.js +0 -258
  460. package/dist/linkedin/__tests__/selectors.integration.test.js.map +0 -1
  461. package/dist/types/search-posts.d.ts +0 -22
  462. package/dist/types/search-posts.d.ts.map +0 -1
  463. package/dist/types/search-posts.js +0 -4
  464. package/dist/types/search-posts.js.map +0 -1
  465. package/dist/voyager/index.d.ts +0 -2
  466. package/dist/voyager/index.d.ts.map +0 -1
  467. package/dist/voyager/index.js +0 -4
  468. package/dist/voyager/index.js.map +0 -1
  469. package/dist/voyager/interceptor.d.ts +0 -100
  470. package/dist/voyager/interceptor.d.ts.map +0 -1
  471. package/dist/voyager/interceptor.integration.test.d.ts +0 -2
  472. package/dist/voyager/interceptor.integration.test.d.ts.map +0 -1
  473. package/dist/voyager/interceptor.integration.test.js +0 -89
  474. package/dist/voyager/interceptor.integration.test.js.map +0 -1
  475. package/dist/voyager/interceptor.js +0 -235
  476. package/dist/voyager/interceptor.js.map +0 -1
  477. package/dist/voyager/interceptor.test.d.ts +0 -2
  478. package/dist/voyager/interceptor.test.d.ts.map +0 -1
  479. package/dist/voyager/interceptor.test.js +0 -372
  480. package/dist/voyager/interceptor.test.js.map +0 -1
@@ -1,164 +1,355 @@
1
1
  // SPDX-License-Identifier: AGPL-3.0-only
2
2
  // Copyright (C) 2026 Oleksii PELYKH
3
+ import { resolveInstancePort } from "../cdp/index.js";
3
4
  import { CDPClient } from "../cdp/client.js";
4
5
  import { discoverTargets } from "../cdp/discovery.js";
5
- import { VoyagerInterceptor } from "../voyager/interceptor.js";
6
- import { DEFAULT_CDP_PORT } from "../constants.js";
6
+ import { humanizedScrollY, humanizedScrollToByIndex, retryInteraction } from "../linkedin/dom-automation.js";
7
+ import { delay as utilsDelay, gaussianDelay, gaussianBetween, maybeHesitate, maybeBreak, simulateReadingTime } from "../utils/delay.js";
8
+ import { navigateAwayIf } from "./navigate-away.js";
7
9
  // ---------------------------------------------------------------------------
8
- // Parsing helpers
10
+ // In-page DOM scraping script
9
11
  // ---------------------------------------------------------------------------
10
12
  /**
11
- * Resolve a text value that may be a string or `{ text: string }`.
13
+ * JavaScript source evaluated inside the LinkedIn page context via
14
+ * `Runtime.evaluate`. Returns an array of {@link RawDomPost} objects
15
+ * (without URNs — those are extracted separately via the three-dot menu).
16
+ *
17
+ * ## Discovery strategy (2026-03 onwards)
18
+ *
19
+ * LinkedIn's SSR feed uses `div[data-testid="mainFeed"]` as the feed
20
+ * list (`role="list"`) and `div[role="listitem"]` for each post.
21
+ * CSS class names are obfuscated hashes (CSS Modules), so the script
22
+ * relies on semantic attributes and structural heuristics.
23
+ *
24
+ * Post URNs are NOT available in the DOM. They are extracted in a
25
+ * separate phase by opening each post's three-dot menu, clicking
26
+ * "Copy link to post", and deriving the URN from the captured URL.
12
27
  */
13
- function resolveText(value) {
14
- if (value === undefined || value === null)
15
- return null;
16
- if (typeof value === "string")
17
- return value;
18
- return value.text ?? null;
28
+ const SCRAPE_FEED_POSTS_SCRIPT = `(() => {
29
+ const posts = [];
30
+
31
+ // --- Step 1: Find the feed list via data-testid ---
32
+ const feedList = document.querySelector('[data-testid="mainFeed"]');
33
+ if (!feedList) return posts;
34
+
35
+ // --- Step 2: Iterate listitem children ---
36
+ const items = feedList.querySelectorAll('div[role="listitem"]');
37
+ for (const wrapper of items) {
38
+ // The listitem wraps the actual post content in nested divs.
39
+ // Some listitems may be zero-height (virtualized/hidden) or
40
+ // non-post items (composer, suggestions).
41
+ const item = wrapper;
42
+ if (item.offsetHeight < 100) continue;
43
+
44
+ // Detect real posts: must have a three-dot menu button
45
+ const menuBtn = item.querySelector('button[aria-label^="Open control menu for post"]');
46
+ if (!menuBtn) continue;
47
+
48
+ // --- Author info ---
49
+ let authorName = null;
50
+ let authorHeadline = null;
51
+ let authorProfileUrl = null;
52
+
53
+ const authorLink = item.querySelector('a[href*="/in/"], a[href*="/company/"]');
54
+ if (authorLink) {
55
+ authorProfileUrl = authorLink.href.split('?')[0] || null;
56
+ const nameEl = authorLink.querySelector('span[dir="ltr"], span[aria-hidden="true"]')
57
+ || authorLink;
58
+ const rawName = (nameEl.textContent || '').trim();
59
+ authorName = rawName || null;
60
+ }
61
+
62
+ // Author headline: look for a short descriptive text near the author.
63
+ const allSpans = item.querySelectorAll('span');
64
+ for (const span of allSpans) {
65
+ const txt = (span.textContent || '').trim();
66
+ if (
67
+ txt &&
68
+ txt.length > 5 &&
69
+ txt.length < 200 &&
70
+ txt !== authorName &&
71
+ !txt.match(/^\\d+[smhdw]$/) &&
72
+ !txt.match(/^\\d[\\d,]*\\s+(reactions?|comments?|reposts?|likes?)$/i) &&
73
+ !txt.match(/^Follow$|^Promoted$/i)
74
+ ) {
75
+ authorHeadline = txt;
76
+ break;
77
+ }
78
+ }
79
+
80
+ // --- Post text ---
81
+ let text = null;
82
+ const ltrSpans = item.querySelectorAll('span[dir="ltr"]');
83
+ let longestText = '';
84
+ for (const span of ltrSpans) {
85
+ const txt = (span.textContent || '').trim();
86
+ if (txt.length > longestText.length && txt !== authorName && txt !== authorHeadline) {
87
+ longestText = txt;
88
+ }
89
+ }
90
+ if (longestText.length > 20) {
91
+ text = longestText;
92
+ }
93
+
94
+ // --- Media type ---
95
+ let mediaType = null;
96
+ if (item.querySelector('video')) {
97
+ mediaType = 'video';
98
+ } else if (item.querySelector('img[src*="media.licdn.com"]')) {
99
+ const imgs = item.querySelectorAll('img[src*="media.licdn.com"]');
100
+ for (const img of imgs) {
101
+ if (img.offsetHeight > 100) { mediaType = 'image'; break; }
102
+ }
103
+ }
104
+
105
+ // --- Engagement counts ---
106
+ const itemText = item.textContent || '';
107
+
108
+ function parseCount(pattern) {
109
+ const m = itemText.match(pattern);
110
+ if (!m) return 0;
111
+ const raw = m[1].replace(/,/g, '');
112
+ const num = parseInt(raw, 10);
113
+ return isNaN(num) ? 0 : num;
114
+ }
115
+
116
+ const reactionCount = parseCount(/(\\d[\\d,]*)\\s+reactions?/i);
117
+ const commentCount = parseCount(/(\\d[\\d,]*)\\s+comments?/i);
118
+ const shareCount = parseCount(/(\\d[\\d,]*)\\s+reposts?/i);
119
+
120
+ // --- Timestamp ---
121
+ let timestamp = null;
122
+ const timeEl = item.querySelector('time');
123
+ if (timeEl) {
124
+ const dt = timeEl.getAttribute('datetime');
125
+ if (dt) timestamp = dt;
126
+ }
127
+ if (!timestamp) {
128
+ const timeMatch = itemText.match(/(?:^|\\s)(\\d+[smhdw])(?:\\s|$|\\u00B7|\\xB7)/);
129
+ if (timeMatch) timestamp = timeMatch[1];
130
+ }
131
+
132
+ posts.push({
133
+ url: null,
134
+ authorName: authorName,
135
+ authorHeadline: authorHeadline,
136
+ authorProfileUrl: authorProfileUrl,
137
+ text: text,
138
+ mediaType: mediaType,
139
+ reactionCount: reactionCount,
140
+ commentCount: commentCount,
141
+ shareCount: shareCount,
142
+ timestamp: timestamp,
143
+ });
144
+ }
145
+
146
+ return posts;
147
+ })()`;
148
+ /**
149
+ * Legacy scraping script using structural heuristics to find the feed
150
+ * container. Used by search-posts which navigates to search result
151
+ * pages where `data-testid="mainFeed"` is not present.
152
+ *
153
+ * @internal Exported for reuse by search-posts.
154
+ */
155
+ export { SCRAPE_FEED_POSTS_SCRIPT as SCRAPE_FEED_SCRIPT };
156
+ // ---------------------------------------------------------------------------
157
+ // URL capture via three-dot menu → "Copy link to post"
158
+ // ---------------------------------------------------------------------------
159
+ /** CSS selector for feed post menu buttons. */
160
+ const FEED_MENU_BUTTON_SELECTOR = '[data-testid="mainFeed"] div[role="listitem"] button[aria-label^="Open control menu for post"]';
161
+ /**
162
+ * Capture the post URL for a single feed item by opening its three-dot
163
+ * menu and clicking "Copy link to post".
164
+ *
165
+ * Requires the clipboard interceptor to be installed beforehand via
166
+ * {@link installClipboardInterceptor}.
167
+ *
168
+ * @returns The post URL (query params stripped) or `null` if capture failed.
169
+ */
170
+ async function capturePostUrl(client, postIndex, mouse) {
171
+ const MAX_ATTEMPTS = 3;
172
+ for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt++) {
173
+ await maybeHesitate(); // Probabilistic pause before menu interaction
174
+ // Reset clipboard capture
175
+ await client.evaluate(`window.__capturedClipboard = null;`);
176
+ // Scroll the menu button into view (humanized when mouse available)
177
+ await humanizedScrollToByIndex(client, FEED_MENU_BUTTON_SELECTOR, postIndex, mouse);
178
+ // Click the menu button
179
+ const clicked = await client.evaluate(`(() => {
180
+ const btns = document.querySelectorAll(
181
+ ${JSON.stringify(FEED_MENU_BUTTON_SELECTOR)}
182
+ );
183
+ const btn = btns[${postIndex}];
184
+ if (!btn) return false;
185
+ btn.click();
186
+ return true;
187
+ })()`);
188
+ if (!clicked)
189
+ return null; // No menu button — structural, retrying won't help
190
+ await gaussianDelay(700, 100, 500, 900);
191
+ // Click "Copy link to post" menu item
192
+ await client.evaluate(`(() => {
193
+ for (const el of document.querySelectorAll('[role="menuitem"]')) {
194
+ if (el.textContent.trim() === 'Copy link to post') {
195
+ el.click();
196
+ return;
197
+ }
198
+ }
199
+ })()`);
200
+ await gaussianDelay(550, 75, 400, 700);
201
+ // Read captured URL
202
+ const postUrl = await client.evaluate(`window.__capturedClipboard`);
203
+ if (postUrl) {
204
+ // Strip query parameters
205
+ return postUrl.split("?")[0] ?? postUrl;
206
+ }
207
+ // Dismiss any open menu before retrying
208
+ await client.evaluate(`(() => {
209
+ document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
210
+ })()`);
211
+ // Escalating retry delays: longer waits on later attempts
212
+ const retryDelays = [
213
+ { mean: 700, stdDev: 200 },
214
+ { mean: 1_200, stdDev: 400 },
215
+ { mean: 2_500, stdDev: 800 },
216
+ ];
217
+ const rd = retryDelays[attempt] ?? retryDelays[2];
218
+ await gaussianDelay(rd.mean, rd.stdDev, rd.mean * 0.5, rd.mean * 1.5);
219
+ // 50% chance of a small "confusion" scroll to reset visual state
220
+ if (Math.random() < 0.5) {
221
+ const scrollDist = Math.round(gaussianBetween(75, 15, 50, 100));
222
+ const dir = Math.random() < 0.5 ? -1 : 1;
223
+ await humanizedScrollY(client, scrollDist * dir, 300, 400, mouse);
224
+ await gaussianDelay(300, 100, 150, 500);
225
+ }
226
+ }
227
+ return null;
228
+ }
229
+ /**
230
+ * Install a clipboard interceptor that captures `navigator.clipboard.writeText`
231
+ * calls into `window.__capturedClipboard`. Required because Electron's
232
+ * clipboard API is broken (readText returns `{}`).
233
+ */
234
+ async function installClipboardInterceptor(client) {
235
+ await client.evaluate(`navigator.clipboard.writeText = function(text) {
236
+ window.__capturedClipboard = text;
237
+ return Promise.resolve();
238
+ };`);
19
239
  }
240
+ // ---------------------------------------------------------------------------
241
+ // Parsing helpers
242
+ // ---------------------------------------------------------------------------
20
243
  /**
21
244
  * Extract hashtags from post text.
22
245
  */
23
- function extractHashtags(text) {
246
+ export function extractHashtags(text) {
24
247
  if (!text)
25
248
  return [];
26
249
  const matches = text.match(/#[\w\u00C0-\u024F]+/g);
27
250
  return matches ? [...new Set(matches.map((t) => t.slice(1)))] : [];
28
251
  }
29
252
  /**
30
- * Infer media type from Voyager content metadata.
253
+ * Parse a relative timestamp string (e.g. "52m", "16h", "2d", "1w") or an
254
+ * ISO date into epoch milliseconds. Returns null for unrecognised formats.
31
255
  */
32
- function inferMediaType(content) {
33
- if (!content)
256
+ export function parseTimestamp(raw) {
257
+ if (!raw)
34
258
  return null;
35
- if (content.mediaCategory) {
36
- const cat = content.mediaCategory.toLowerCase();
37
- if (cat.includes("image"))
38
- return "image";
39
- if (cat.includes("video"))
40
- return "video";
41
- if (cat.includes("article"))
42
- return "article";
43
- if (cat.includes("document"))
44
- return "document";
45
- return cat;
46
- }
47
- const type = content.$type ?? "";
48
- if (type.includes("Image"))
49
- return "image";
50
- if (type.includes("Video"))
51
- return "video";
52
- if (type.includes("Article") || content.navigationUrl)
53
- return "article";
54
- if (type.includes("Document"))
55
- return "document";
56
- if (type)
57
- return type.split(".").pop()?.toLowerCase() ?? null;
58
- return null;
259
+ // ISO datetime
260
+ const asDate = Date.parse(raw);
261
+ if (!isNaN(asDate))
262
+ return asDate;
263
+ // Relative time: Ns, Nm, Nh, Nd, Nw
264
+ const match = raw.match(/^(\d+)([smhdw])$/);
265
+ if (!match)
266
+ return null;
267
+ const value = parseInt(match[1] ?? "0", 10);
268
+ const unit = match[2] ?? "";
269
+ const now = Date.now();
270
+ const multipliers = {
271
+ s: 1_000,
272
+ m: 60_000,
273
+ h: 3_600_000,
274
+ d: 86_400_000,
275
+ w: 604_800_000,
276
+ };
277
+ return now - value * (multipliers[unit] ?? 0);
59
278
  }
60
279
  /**
61
- * Build a LinkedIn post URL from an update URN.
280
+ * Build a LinkedIn post URL from an activity URN.
62
281
  */
63
- function buildPostUrl(urn) {
282
+ /** @internal Exported for reuse by search-posts. */
283
+ export function buildPostUrl(urn) {
64
284
  return `https://www.linkedin.com/feed/update/${urn}/`;
65
285
  }
66
286
  /**
67
- * Parse the Voyager feed response into normalised FeedPost entries.
287
+ * Convert raw DOM-scraped posts into normalised FeedPost entries.
68
288
  */
69
- function parseFeedResponse(raw) {
70
- const elements = raw.data?.elements ?? raw.elements ?? [];
71
- const metadata = raw.data?.metadata ?? raw.metadata;
72
- const included = raw.included ?? [];
73
- // Build lookup for included entities (actors, social details)
74
- const entitiesByUrn = new Map();
75
- for (const entity of included) {
76
- if (entity.entityUrn) {
77
- entitiesByUrn.set(entity.entityUrn, entity);
78
- }
79
- }
80
- const posts = [];
81
- for (const el of elements) {
82
- const urn = el.updateUrn ?? el.urn;
83
- if (!urn)
84
- continue;
85
- // Resolve actor — inline or via included entities
86
- let authorName = "";
87
- let authorHeadline = null;
88
- let authorProfileUrl = null;
89
- if (el.actor) {
90
- authorName = resolveText(el.actor.name) ?? "";
91
- authorHeadline = resolveText(el.actor.description);
92
- authorProfileUrl = el.actor.navigationUrl ?? null;
93
- }
94
- else if (el["*actor"]) {
95
- const actorEntity = entitiesByUrn.get(el["*actor"]);
96
- if (actorEntity) {
97
- // Person actor
98
- if (actorEntity.firstName || actorEntity.lastName) {
99
- authorName = [actorEntity.firstName, actorEntity.lastName]
100
- .filter(Boolean)
101
- .join(" ");
102
- authorHeadline =
103
- resolveText(actorEntity.headline) ??
104
- actorEntity.occupation ??
105
- null;
106
- if (actorEntity.publicIdentifier) {
107
- authorProfileUrl = `https://www.linkedin.com/in/${actorEntity.publicIdentifier}/`;
108
- }
109
- }
110
- else {
111
- // Company / page actor
112
- authorName = resolveText(actorEntity.name) ?? "";
113
- authorHeadline = resolveText(actorEntity.description);
114
- authorProfileUrl = actorEntity.navigationUrl ?? null;
115
- }
116
- }
117
- }
118
- // Post text
119
- const text = resolveText(el.commentary?.text) ?? null;
120
- // Media type
121
- const mediaType = inferMediaType(el.content);
122
- // Engagement counts inline or via included social detail
123
- let reactionCount = 0;
124
- let commentCount = 0;
125
- let shareCount = 0;
126
- const socialCounts = el.socialDetail?.totalSocialActivityCounts;
127
- if (socialCounts) {
128
- reactionCount = socialCounts.numLikes ?? 0;
129
- commentCount = socialCounts.numComments ?? 0;
130
- shareCount = socialCounts.numShares ?? 0;
131
- }
132
- else if (el["*socialDetail"]) {
133
- const socialEntity = entitiesByUrn.get(el["*socialDetail"]);
134
- if (socialEntity?.totalSocialActivityCounts) {
135
- reactionCount = socialEntity.totalSocialActivityCounts.numLikes ?? 0;
136
- commentCount = socialEntity.totalSocialActivityCounts.numComments ?? 0;
137
- shareCount = socialEntity.totalSocialActivityCounts.numShares ?? 0;
138
- }
289
+ /** @internal Exported for reuse by search-posts. */
290
+ export function mapRawPosts(raw) {
291
+ return raw.map((r) => ({
292
+ url: r.url ?? null,
293
+ authorName: r.authorName,
294
+ authorHeadline: r.authorHeadline,
295
+ authorProfileUrl: r.authorProfileUrl,
296
+ authorPublicId: null,
297
+ text: r.text,
298
+ mediaType: r.mediaType,
299
+ reactionCount: r.reactionCount,
300
+ commentCount: r.commentCount,
301
+ shareCount: r.shareCount,
302
+ timestamp: parseTimestamp(r.timestamp),
303
+ hashtags: extractHashtags(r.text),
304
+ }));
305
+ }
306
+ // ---------------------------------------------------------------------------
307
+ // Scroll helper
308
+ // ---------------------------------------------------------------------------
309
+ /** @internal Exported for reuse by other operations. */
310
+ export const delay = utilsDelay;
311
+ /**
312
+ * Scroll the feed down by a randomised viewport-like distance.
313
+ *
314
+ * The distance varies between 600–1000 px per scroll to avoid the
315
+ * detection signal of a perfectly uniform scroll cadence.
316
+ *
317
+ * When a {@link HumanizedMouse} is provided, scrolling uses incremental
318
+ * mouse-wheel strokes (150 px / 25 ms) that mimic a physical scroll
319
+ * wheel. Falls back to a single CDP `mouseWheel` event otherwise.
320
+ *
321
+ * @internal Exported for reuse by search-posts.
322
+ */
323
+ export async function scrollFeed(client, mouse) {
324
+ const distance = Math.round(gaussianBetween(800, 100, 600, 1_000));
325
+ const x = Math.round(gaussianBetween(350, 100, 150, 550));
326
+ const y = Math.round(gaussianBetween(400, 80, 250, 550));
327
+ await humanizedScrollY(client, distance, x, y, mouse);
328
+ }
329
+ // ---------------------------------------------------------------------------
330
+ // Wait for feed to load
331
+ // ---------------------------------------------------------------------------
332
+ /** @internal Exported for reuse by search-posts. */
333
+ export async function waitForFeedLoad(client, timeoutMs = 15_000) {
334
+ const deadline = Date.now() + timeoutMs;
335
+ while (Date.now() < deadline) {
336
+ const ready = await client.evaluate(`(() => {
337
+ const feed = document.querySelector('[data-testid="mainFeed"]');
338
+ if (!feed) return false;
339
+ const items = feed.querySelectorAll('div[role="listitem"]');
340
+ // Ready when at least one listitem has a post menu button
341
+ for (const item of items) {
342
+ if (item.querySelector('button[aria-label^="Open control menu for post"]')) {
343
+ return true;
139
344
  }
140
- // Timestamp
141
- const timestamp = el.createdAt ?? el.publishedAt ?? null;
142
- // Hashtags
143
- const hashtags = extractHashtags(text);
144
- posts.push({
145
- urn,
146
- url: buildPostUrl(urn),
147
- authorName,
148
- authorHeadline,
149
- authorProfileUrl,
150
- authorPublicId: null,
151
- text,
152
- mediaType,
153
- reactionCount,
154
- commentCount,
155
- shareCount,
156
- timestamp,
157
- hashtags,
158
- });
345
+ }
346
+ return false;
347
+ })()`);
348
+ if (ready)
349
+ return;
350
+ await delay(500);
159
351
  }
160
- const nextCursor = metadata?.paginationToken ?? metadata?.nextPageToken ?? null;
161
- return { posts, nextCursor };
352
+ throw new Error("Timed out waiting for feed posts to appear in the DOM");
162
353
  }
163
354
  // ---------------------------------------------------------------------------
164
355
  // Public API
@@ -166,19 +357,20 @@ function parseFeedResponse(raw) {
166
357
  /**
167
358
  * Read the LinkedIn home feed and return structured post data.
168
359
  *
169
- * Connects to the LinkedIn webview in LinkedHelper and calls the
170
- * Voyager feed updates API. Supports cursor-based pagination: the
171
- * first call returns the first page; pass the returned `nextCursor`
172
- * in subsequent calls to retrieve additional pages.
360
+ * Navigates to the feed page and extracts posts from the rendered DOM.
361
+ * Supports cursor-based pagination: the first call returns the first page;
362
+ * pass the returned `nextCursor` in subsequent calls to retrieve additional
363
+ * pages via scroll + re-scrape.
173
364
  *
174
365
  * @param input - Pagination parameters and CDP connection options.
175
366
  * @returns Feed posts with a cursor for the next page.
176
367
  */
177
368
  export async function getFeed(input) {
178
- const cdpPort = input.cdpPort ?? DEFAULT_CDP_PORT;
369
+ const cdpPort = await resolveInstancePort(input.cdpPort, input.cdpHost);
179
370
  const cdpHost = input.cdpHost ?? "127.0.0.1";
180
371
  const allowRemote = input.allowRemote ?? false;
181
372
  const count = input.count ?? 10;
373
+ const cursor = input.cursor ?? null;
182
374
  // Enforce loopback guard
183
375
  if (!allowRemote && cdpHost !== "127.0.0.1" && cdpHost !== "localhost") {
184
376
  throw new Error(`Non-loopback CDP host "${cdpHost}" requires --allow-remote. ` +
@@ -193,25 +385,80 @@ export async function getFeed(input) {
193
385
  const client = new CDPClient(cdpPort, { host: cdpHost, allowRemote });
194
386
  await client.connect(linkedInTarget.id);
195
387
  try {
196
- const voyager = new VoyagerInterceptor(client);
197
- let path = `/voyager/api/feed/dash/feedUpdates` +
198
- `?count=${String(count)}&q=feedByType&moduleKey=feed`;
199
- if (input.cursor) {
200
- path += `&paginationToken=${encodeURIComponent(input.cursor)}`;
388
+ const mouse = input.mouse ?? null;
389
+ // Navigate away if already on the feed page to force a fresh load
390
+ await navigateAwayIf(client, "/feed");
391
+ await client.navigate("https://www.linkedin.com/feed/");
392
+ // Wait for the initial feed content to render
393
+ await waitForFeedLoad(client);
394
+ // Collect posts — scroll to load more if needed
395
+ const maxScrollAttempts = 10;
396
+ let allPosts = [];
397
+ let previousCount = 0;
398
+ // If resuming with a cursor, we need to scroll past already-seen posts
399
+ const cursorUrl = cursor;
400
+ for (let scroll = 0; scroll <= maxScrollAttempts; scroll++) {
401
+ const countBeforeScroll = previousCount;
402
+ const scraped = await client.evaluate(SCRAPE_FEED_POSTS_SCRIPT);
403
+ allPosts = scraped ?? [];
404
+ // Determine which posts to return
405
+ const available = allPosts.length;
406
+ if (available >= count && !cursorUrl)
407
+ break;
408
+ // No new posts appeared after scroll — stop
409
+ if (allPosts.length === previousCount && scroll > 0)
410
+ break;
411
+ previousCount = allPosts.length;
412
+ // Scroll to load more
413
+ if (scroll < maxScrollAttempts) {
414
+ await scrollFeed(client, mouse);
415
+ // Progressive session fatigue: delays increase with each scroll
416
+ const fatigueMultiplier = 1 + scroll * 0.1;
417
+ // Scale delay by newly visible content volume
418
+ const newPostCount = allPosts.length - countBeforeScroll;
419
+ const contentBonus = Math.min(newPostCount * gaussianBetween(350, 75, 200, 500), 3_000);
420
+ await gaussianDelay(1_500 * fatigueMultiplier + contentBonus, 150 * fatigueMultiplier, 1_200 * fatigueMultiplier + contentBonus, 1_800 * fatigueMultiplier + contentBonus);
421
+ // Reading simulation: pause proportional to visible content volume.
422
+ // Estimate ~300 chars per newly visible post (headline + snippet).
423
+ if (newPostCount > 0) {
424
+ await simulateReadingTime(newPostCount * 300);
425
+ }
426
+ await maybeBreak();
427
+ }
201
428
  }
202
- const response = await voyager.fetch(path);
203
- if (response.status !== 200) {
204
- throw new Error(`Voyager API returned HTTP ${String(response.status)} for feed`);
429
+ // --- URL extraction phase ---
430
+ // Open each post's three-dot menu, click "Copy link to post", and
431
+ // derive the URN from the captured URL. This populates the urn/url
432
+ // fields that the scrape script left null.
433
+ await installClipboardInterceptor(client);
434
+ for (let i = 0; i < allPosts.length; i++) {
435
+ const post = allPosts[i];
436
+ if (!post)
437
+ continue;
438
+ if (i > 0)
439
+ await gaussianDelay(550, 125, 300, 800); // Inter-post delay
440
+ await maybeBreak();
441
+ const url = await retryInteraction(() => capturePostUrl(client, i, mouse));
442
+ if (url) {
443
+ post.url = url;
444
+ }
205
445
  }
206
- const body = response.body;
207
- if (body === null || typeof body !== "object") {
208
- throw new Error("Voyager API returned an unexpected response format for feed");
446
+ // Slice the result window
447
+ let startIdx = 0;
448
+ if (cursorUrl) {
449
+ const cursorIdx = allPosts.findIndex((p) => p.url === cursorUrl);
450
+ if (cursorIdx >= 0) {
451
+ startIdx = cursorIdx + 1;
452
+ }
209
453
  }
210
- const parsed = parseFeedResponse(body);
211
- return {
212
- posts: parsed.posts,
213
- nextCursor: parsed.nextCursor,
214
- };
454
+ const window = allPosts.slice(startIdx, startIdx + count);
455
+ const posts = mapRawPosts(window);
456
+ // Determine next cursor
457
+ const hasMore = startIdx + count < allPosts.length;
458
+ const lastPost = window[window.length - 1];
459
+ const nextCursor = hasMore && lastPost ? lastPost.url : null;
460
+ await gaussianDelay(800, 300, 300, 1_800); // Post-action dwell
461
+ return { posts, nextCursor };
215
462
  }
216
463
  finally {
217
464
  client.disconnect();