@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.
- package/dist/cdp/app-discovery.d.ts +57 -0
- package/dist/cdp/app-discovery.d.ts.map +1 -1
- package/dist/cdp/app-discovery.js +90 -7
- package/dist/cdp/app-discovery.js.map +1 -1
- package/dist/cdp/app-discovery.test.js +180 -9
- package/dist/cdp/app-discovery.test.js.map +1 -1
- package/dist/cdp/client.d.ts +8 -1
- package/dist/cdp/client.d.ts.map +1 -1
- package/dist/cdp/client.js +8 -1
- package/dist/cdp/client.js.map +1 -1
- package/dist/cdp/discovery.d.ts.map +1 -1
- package/dist/cdp/discovery.js +5 -1
- package/dist/cdp/discovery.js.map +1 -1
- package/dist/cdp/discovery.test.js +7 -0
- package/dist/cdp/discovery.test.js.map +1 -1
- package/dist/cdp/index.d.ts +1 -1
- package/dist/cdp/index.d.ts.map +1 -1
- package/dist/cdp/index.js +1 -1
- package/dist/cdp/index.js.map +1 -1
- package/dist/cdp/instance-discovery.d.ts.map +1 -1
- package/dist/cdp/instance-discovery.js +25 -10
- package/dist/cdp/instance-discovery.js.map +1 -1
- package/dist/cdp/instance-discovery.test.js +17 -0
- package/dist/cdp/instance-discovery.test.js.map +1 -1
- package/dist/cdp/testing/launch-chromium.d.ts +1 -1
- package/dist/cdp/testing/launch-chromium.js +2 -2
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +3 -0
- package/dist/db/client.js.map +1 -1
- package/dist/db/repositories/campaign-hard-delete.integration.test.js +4 -1
- package/dist/db/repositories/campaign-hard-delete.integration.test.js.map +1 -1
- package/dist/db/repositories/campaign.d.ts.map +1 -1
- package/dist/db/repositories/campaign.js +81 -33
- package/dist/db/repositories/campaign.js.map +1 -1
- package/dist/db/repositories/campaign.test.js +2 -2
- package/dist/db/repositories/collection-list.d.ts +11 -0
- package/dist/db/repositories/collection-list.d.ts.map +1 -1
- package/dist/db/repositories/collection-list.integration.test.js +6 -4
- package/dist/db/repositories/collection-list.integration.test.js.map +1 -1
- package/dist/db/repositories/collection-list.js +92 -2
- package/dist/db/repositories/collection-list.js.map +1 -1
- package/dist/db/testing/create-fixture.js +36 -2
- package/dist/db/testing/create-fixture.js.map +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/linkedin/dom-automation-retry.test.d.ts +2 -0
- package/dist/linkedin/dom-automation-retry.test.d.ts.map +1 -0
- package/dist/linkedin/dom-automation-retry.test.js +51 -0
- package/dist/linkedin/dom-automation-retry.test.js.map +1 -0
- package/dist/linkedin/dom-automation.d.ts +166 -6
- package/dist/linkedin/dom-automation.d.ts.map +1 -1
- package/dist/linkedin/dom-automation.js +496 -21
- package/dist/linkedin/dom-automation.js.map +1 -1
- package/dist/linkedin/humanized-mouse.d.ts +69 -0
- package/dist/linkedin/humanized-mouse.d.ts.map +1 -0
- package/dist/linkedin/humanized-mouse.js +109 -0
- package/dist/linkedin/humanized-mouse.js.map +1 -0
- package/dist/linkedin/index.d.ts +3 -2
- package/dist/linkedin/index.d.ts.map +1 -1
- package/dist/linkedin/index.js +3 -2
- package/dist/linkedin/index.js.map +1 -1
- package/dist/linkedin/selectors.d.ts +74 -48
- package/dist/linkedin/selectors.d.ts.map +1 -1
- package/dist/linkedin/selectors.js +64 -41
- package/dist/linkedin/selectors.js.map +1 -1
- package/dist/operations/add-people-to-collection.d.ts +1 -1
- package/dist/operations/add-people-to-collection.d.ts.map +1 -1
- package/dist/operations/add-people-to-collection.js +3 -6
- package/dist/operations/add-people-to-collection.js.map +1 -1
- package/dist/operations/build-linkedin-url.test.d.ts +2 -0
- package/dist/operations/build-linkedin-url.test.d.ts.map +1 -0
- package/dist/operations/build-linkedin-url.test.js +158 -0
- package/dist/operations/build-linkedin-url.test.js.map +1 -0
- package/dist/operations/campaign-add-action.d.ts +1 -1
- package/dist/operations/campaign-add-action.d.ts.map +1 -1
- package/dist/operations/campaign-add-action.js +3 -6
- package/dist/operations/campaign-add-action.js.map +1 -1
- package/dist/operations/campaign-create.d.ts +1 -1
- package/dist/operations/campaign-create.d.ts.map +1 -1
- package/dist/operations/campaign-create.js +3 -6
- package/dist/operations/campaign-create.js.map +1 -1
- package/dist/operations/campaign-delete.d.ts +1 -1
- package/dist/operations/campaign-delete.d.ts.map +1 -1
- package/dist/operations/campaign-delete.js +3 -6
- package/dist/operations/campaign-delete.js.map +1 -1
- package/dist/operations/campaign-erase.d.ts +1 -1
- package/dist/operations/campaign-erase.d.ts.map +1 -1
- package/dist/operations/campaign-erase.js +3 -6
- package/dist/operations/campaign-erase.js.map +1 -1
- package/dist/operations/campaign-exclude-add.d.ts +1 -1
- package/dist/operations/campaign-exclude-add.d.ts.map +1 -1
- package/dist/operations/campaign-exclude-add.js +3 -6
- package/dist/operations/campaign-exclude-add.js.map +1 -1
- package/dist/operations/campaign-exclude-list.d.ts +1 -1
- package/dist/operations/campaign-exclude-list.d.ts.map +1 -1
- package/dist/operations/campaign-exclude-list.js +3 -6
- package/dist/operations/campaign-exclude-list.js.map +1 -1
- package/dist/operations/campaign-exclude-remove.d.ts +1 -1
- package/dist/operations/campaign-exclude-remove.d.ts.map +1 -1
- package/dist/operations/campaign-exclude-remove.js +3 -6
- package/dist/operations/campaign-exclude-remove.js.map +1 -1
- package/dist/operations/campaign-export.d.ts +1 -1
- package/dist/operations/campaign-export.d.ts.map +1 -1
- package/dist/operations/campaign-export.js +3 -6
- package/dist/operations/campaign-export.js.map +1 -1
- package/dist/operations/campaign-get.d.ts +1 -1
- package/dist/operations/campaign-get.d.ts.map +1 -1
- package/dist/operations/campaign-get.js +3 -6
- package/dist/operations/campaign-get.js.map +1 -1
- package/dist/operations/campaign-list-people.d.ts +1 -1
- package/dist/operations/campaign-list-people.d.ts.map +1 -1
- package/dist/operations/campaign-list-people.js +3 -6
- package/dist/operations/campaign-list-people.js.map +1 -1
- package/dist/operations/campaign-list.d.ts +1 -1
- package/dist/operations/campaign-list.d.ts.map +1 -1
- package/dist/operations/campaign-list.js +3 -6
- package/dist/operations/campaign-list.js.map +1 -1
- package/dist/operations/campaign-move-next.d.ts +1 -1
- package/dist/operations/campaign-move-next.d.ts.map +1 -1
- package/dist/operations/campaign-move-next.js +3 -6
- package/dist/operations/campaign-move-next.js.map +1 -1
- package/dist/operations/campaign-remove-action.d.ts +1 -1
- package/dist/operations/campaign-remove-action.d.ts.map +1 -1
- package/dist/operations/campaign-remove-action.js +3 -6
- package/dist/operations/campaign-remove-action.js.map +1 -1
- package/dist/operations/campaign-remove-people.d.ts +1 -1
- package/dist/operations/campaign-remove-people.d.ts.map +1 -1
- package/dist/operations/campaign-remove-people.js +3 -6
- package/dist/operations/campaign-remove-people.js.map +1 -1
- package/dist/operations/campaign-reorder-actions.d.ts +1 -1
- package/dist/operations/campaign-reorder-actions.d.ts.map +1 -1
- package/dist/operations/campaign-reorder-actions.js +3 -6
- package/dist/operations/campaign-reorder-actions.js.map +1 -1
- package/dist/operations/campaign-retry.d.ts +1 -1
- package/dist/operations/campaign-retry.d.ts.map +1 -1
- package/dist/operations/campaign-retry.js +3 -6
- package/dist/operations/campaign-retry.js.map +1 -1
- package/dist/operations/campaign-start.d.ts +1 -1
- package/dist/operations/campaign-start.d.ts.map +1 -1
- package/dist/operations/campaign-start.js +3 -6
- package/dist/operations/campaign-start.js.map +1 -1
- package/dist/operations/campaign-statistics.d.ts +1 -1
- package/dist/operations/campaign-statistics.d.ts.map +1 -1
- package/dist/operations/campaign-statistics.js +3 -6
- package/dist/operations/campaign-statistics.js.map +1 -1
- package/dist/operations/campaign-status.d.ts +1 -1
- package/dist/operations/campaign-status.d.ts.map +1 -1
- package/dist/operations/campaign-status.js +3 -6
- package/dist/operations/campaign-status.js.map +1 -1
- package/dist/operations/campaign-stop.d.ts +1 -1
- package/dist/operations/campaign-stop.d.ts.map +1 -1
- package/dist/operations/campaign-stop.js +3 -6
- package/dist/operations/campaign-stop.js.map +1 -1
- package/dist/operations/campaign-update-action.d.ts +1 -1
- package/dist/operations/campaign-update-action.d.ts.map +1 -1
- package/dist/operations/campaign-update-action.js +3 -6
- package/dist/operations/campaign-update-action.js.map +1 -1
- package/dist/operations/campaign-update.d.ts +1 -1
- package/dist/operations/campaign-update.d.ts.map +1 -1
- package/dist/operations/campaign-update.js +3 -6
- package/dist/operations/campaign-update.js.map +1 -1
- package/dist/operations/check-replies.d.ts +3 -1
- package/dist/operations/check-replies.d.ts.map +1 -1
- package/dist/operations/check-replies.js +124 -17
- package/dist/operations/check-replies.js.map +1 -1
- package/dist/operations/check-replies.test.js +152 -17
- package/dist/operations/check-replies.test.js.map +1 -1
- package/dist/operations/collect-people.d.ts +1 -1
- package/dist/operations/collect-people.d.ts.map +1 -1
- package/dist/operations/collect-people.js +18 -12
- package/dist/operations/collect-people.js.map +1 -1
- package/dist/operations/collect-people.test.js +21 -5
- package/dist/operations/collect-people.test.js.map +1 -1
- package/dist/operations/comment-on-post.d.ts +4 -1
- package/dist/operations/comment-on-post.d.ts.map +1 -1
- package/dist/operations/comment-on-post.js +15 -19
- package/dist/operations/comment-on-post.js.map +1 -1
- package/dist/operations/comment-on-post.test.js +8 -6
- package/dist/operations/comment-on-post.test.js.map +1 -1
- package/dist/operations/create-collection.d.ts +1 -1
- package/dist/operations/create-collection.d.ts.map +1 -1
- package/dist/operations/create-collection.js +5 -7
- package/dist/operations/create-collection.js.map +1 -1
- package/dist/operations/create-collection.test.js +3 -1
- package/dist/operations/create-collection.test.js.map +1 -1
- package/dist/operations/delete-collection.d.ts +1 -1
- package/dist/operations/delete-collection.d.ts.map +1 -1
- package/dist/operations/delete-collection.js +3 -6
- package/dist/operations/delete-collection.js.map +1 -1
- package/dist/operations/dismiss-errors.d.ts +11 -5
- package/dist/operations/dismiss-errors.d.ts.map +1 -1
- package/dist/operations/dismiss-errors.js +68 -34
- package/dist/operations/dismiss-errors.js.map +1 -1
- package/dist/operations/dismiss-errors.test.js +129 -8
- package/dist/operations/dismiss-errors.test.js.map +1 -1
- package/dist/operations/endorse-skills.test.d.ts +2 -0
- package/dist/operations/endorse-skills.test.d.ts.map +1 -0
- package/dist/operations/endorse-skills.test.js +70 -0
- package/dist/operations/endorse-skills.test.js.map +1 -0
- package/dist/operations/enrich-profile.test.d.ts +2 -0
- package/dist/operations/enrich-profile.test.d.ts.map +1 -0
- package/dist/operations/enrich-profile.test.js +73 -0
- package/dist/operations/enrich-profile.test.js.map +1 -0
- package/dist/operations/ephemeral-action.d.ts +2 -1
- package/dist/operations/ephemeral-action.d.ts.map +1 -1
- package/dist/operations/ephemeral-action.js +5 -7
- package/dist/operations/ephemeral-action.js.map +1 -1
- package/dist/operations/ephemeral-action.test.d.ts +2 -0
- package/dist/operations/ephemeral-action.test.d.ts.map +1 -0
- package/dist/operations/ephemeral-action.test.js +159 -0
- package/dist/operations/ephemeral-action.test.js.map +1 -0
- package/dist/operations/follow-person.test.d.ts +2 -0
- package/dist/operations/follow-person.test.d.ts.map +1 -0
- package/dist/operations/follow-person.test.js +57 -0
- package/dist/operations/follow-person.test.js.map +1 -0
- package/dist/operations/get-action-budget.d.ts +1 -1
- package/dist/operations/get-action-budget.d.ts.map +1 -1
- package/dist/operations/get-action-budget.js +3 -6
- package/dist/operations/get-action-budget.js.map +1 -1
- package/dist/operations/get-errors.d.ts +5 -1
- package/dist/operations/get-errors.d.ts.map +1 -1
- package/dist/operations/get-errors.js +55 -33
- package/dist/operations/get-errors.js.map +1 -1
- package/dist/operations/get-errors.test.js +54 -55
- package/dist/operations/get-errors.test.js.map +1 -1
- package/dist/operations/get-feed.d.ts +83 -4
- package/dist/operations/get-feed.d.ts.map +1 -1
- package/dist/operations/get-feed.js +400 -153
- package/dist/operations/get-feed.js.map +1 -1
- package/dist/operations/get-feed.test.js +416 -190
- package/dist/operations/get-feed.test.js.map +1 -1
- package/dist/operations/get-post-engagers.d.ts +6 -3
- package/dist/operations/get-post-engagers.d.ts.map +1 -1
- package/dist/operations/get-post-engagers.js +278 -78
- package/dist/operations/get-post-engagers.js.map +1 -1
- package/dist/operations/get-post-engagers.test.js +292 -14
- package/dist/operations/get-post-engagers.test.js.map +1 -1
- package/dist/operations/get-post-stats.d.ts +8 -3
- package/dist/operations/get-post-stats.d.ts.map +1 -1
- package/dist/operations/get-post-stats.js +101 -37
- package/dist/operations/get-post-stats.js.map +1 -1
- package/dist/operations/get-post-stats.test.js +137 -2
- package/dist/operations/get-post-stats.test.js.map +1 -1
- package/dist/operations/get-post.d.ts +9 -150
- package/dist/operations/get-post.d.ts.map +1 -1
- package/dist/operations/get-post.js +356 -210
- package/dist/operations/get-post.js.map +1 -1
- package/dist/operations/get-post.test.js +210 -387
- package/dist/operations/get-post.test.js.map +1 -1
- package/dist/operations/get-profile-activity.d.ts +13 -92
- package/dist/operations/get-profile-activity.d.ts.map +1 -1
- package/dist/operations/get-profile-activity.js +305 -105
- package/dist/operations/get-profile-activity.js.map +1 -1
- package/dist/operations/get-profile-activity.test.js +277 -158
- package/dist/operations/get-profile-activity.test.js.map +1 -1
- package/dist/operations/get-throttle-status.d.ts +1 -1
- package/dist/operations/get-throttle-status.d.ts.map +1 -1
- package/dist/operations/get-throttle-status.js +4 -10
- package/dist/operations/get-throttle-status.js.map +1 -1
- package/dist/operations/import-people-from-collection.d.ts +1 -1
- package/dist/operations/import-people-from-collection.d.ts.map +1 -1
- package/dist/operations/import-people-from-collection.js +3 -6
- package/dist/operations/import-people-from-collection.js.map +1 -1
- package/dist/operations/import-people-from-urls.d.ts +1 -1
- package/dist/operations/import-people-from-urls.d.ts.map +1 -1
- package/dist/operations/import-people-from-urls.js +3 -6
- package/dist/operations/import-people-from-urls.js.map +1 -1
- package/dist/operations/index.d.ts +2 -2
- package/dist/operations/index.d.ts.map +1 -1
- package/dist/operations/index.js +2 -1
- package/dist/operations/index.js.map +1 -1
- package/dist/operations/like-person-posts.test.d.ts +2 -0
- package/dist/operations/like-person-posts.test.d.ts.map +1 -0
- package/dist/operations/like-person-posts.test.js +103 -0
- package/dist/operations/like-person-posts.test.js.map +1 -0
- package/dist/operations/list-collections.d.ts +1 -1
- package/dist/operations/list-collections.d.ts.map +1 -1
- package/dist/operations/list-collections.js +3 -6
- package/dist/operations/list-collections.js.map +1 -1
- package/dist/operations/message-person.test.d.ts +2 -0
- package/dist/operations/message-person.test.d.ts.map +1 -0
- package/dist/operations/message-person.test.js +108 -0
- package/dist/operations/message-person.test.js.map +1 -0
- package/dist/operations/navigate-away.d.ts +14 -0
- package/dist/operations/navigate-away.d.ts.map +1 -0
- package/dist/operations/navigate-away.js +24 -0
- package/dist/operations/navigate-away.js.map +1 -0
- package/dist/operations/navigate-away.test.d.ts +2 -0
- package/dist/operations/navigate-away.test.d.ts.map +1 -0
- package/dist/operations/navigate-away.test.js +51 -0
- package/dist/operations/navigate-away.test.js.map +1 -0
- package/dist/operations/query-messages.d.ts +1 -1
- package/dist/operations/query-messages.d.ts.map +1 -1
- package/dist/operations/query-messages.js +3 -6
- package/dist/operations/query-messages.js.map +1 -1
- package/dist/operations/react-to-post.d.ts +16 -4
- package/dist/operations/react-to-post.d.ts.map +1 -1
- package/dist/operations/react-to-post.js +86 -24
- package/dist/operations/react-to-post.js.map +1 -1
- package/dist/operations/react-to-post.test.js +55 -5
- package/dist/operations/react-to-post.test.js.map +1 -1
- package/dist/operations/remove-connection.test.d.ts +2 -0
- package/dist/operations/remove-connection.test.d.ts.map +1 -0
- package/dist/operations/remove-connection.test.js +45 -0
- package/dist/operations/remove-connection.test.js.map +1 -0
- package/dist/operations/remove-people-from-collection.d.ts +1 -1
- package/dist/operations/remove-people-from-collection.d.ts.map +1 -1
- package/dist/operations/remove-people-from-collection.js +3 -6
- package/dist/operations/remove-people-from-collection.js.map +1 -1
- package/dist/operations/resolve-linkedin-entity.d.ts.map +1 -1
- package/dist/operations/resolve-linkedin-entity.js +2 -2
- package/dist/operations/resolve-linkedin-entity.js.map +1 -1
- package/dist/operations/resolve-linkedin-entity.test.d.ts +2 -0
- package/dist/operations/resolve-linkedin-entity.test.d.ts.map +1 -0
- package/dist/operations/resolve-linkedin-entity.test.js +343 -0
- package/dist/operations/resolve-linkedin-entity.test.js.map +1 -0
- package/dist/operations/scrape-messaging-history.d.ts +2 -1
- package/dist/operations/scrape-messaging-history.d.ts.map +1 -1
- package/dist/operations/scrape-messaging-history.js +113 -18
- package/dist/operations/scrape-messaging-history.js.map +1 -1
- package/dist/operations/scrape-messaging-history.test.js +109 -12
- package/dist/operations/scrape-messaging-history.test.js.map +1 -1
- package/dist/operations/search-posts.d.ts +20 -112
- package/dist/operations/search-posts.d.ts.map +1 -1
- package/dist/operations/search-posts.js +369 -170
- package/dist/operations/search-posts.js.map +1 -1
- package/dist/operations/search-posts.test.js +273 -234
- package/dist/operations/search-posts.test.js.map +1 -1
- package/dist/operations/send-inmail.test.d.ts +2 -0
- package/dist/operations/send-inmail.test.d.ts.map +1 -0
- package/dist/operations/send-inmail.test.js +108 -0
- package/dist/operations/send-inmail.test.js.map +1 -0
- package/dist/operations/send-invite.test.d.ts +2 -0
- package/dist/operations/send-invite.test.d.ts.map +1 -0
- package/dist/operations/send-invite.test.js +59 -0
- package/dist/operations/send-invite.test.js.map +1 -0
- package/dist/operations/types.d.ts +27 -1
- package/dist/operations/types.d.ts.map +1 -1
- package/dist/operations/types.js +14 -1
- package/dist/operations/types.js.map +1 -1
- package/dist/operations/visit-profile.d.ts +1 -1
- package/dist/operations/visit-profile.d.ts.map +1 -1
- package/dist/operations/visit-profile.js +3 -6
- package/dist/operations/visit-profile.js.map +1 -1
- package/dist/services/account-resolution.d.ts +10 -4
- package/dist/services/account-resolution.d.ts.map +1 -1
- package/dist/services/account-resolution.js +63 -5
- package/dist/services/account-resolution.js.map +1 -1
- package/dist/services/app.d.ts +6 -2
- package/dist/services/app.d.ts.map +1 -1
- package/dist/services/app.js +18 -6
- package/dist/services/app.js.map +1 -1
- package/dist/services/app.test.js +41 -7
- package/dist/services/app.test.js.map +1 -1
- package/dist/services/campaign.d.ts +40 -4
- package/dist/services/campaign.d.ts.map +1 -1
- package/dist/services/campaign.js +176 -10
- package/dist/services/campaign.js.map +1 -1
- package/dist/services/campaign.test.js +53 -15
- package/dist/services/campaign.test.js.map +1 -1
- package/dist/services/collection.d.ts +16 -21
- package/dist/services/collection.d.ts.map +1 -1
- package/dist/services/collection.js +34 -47
- package/dist/services/collection.js.map +1 -1
- package/dist/services/collection.test.js +30 -91
- package/dist/services/collection.test.js.map +1 -1
- package/dist/services/ephemeral-campaign.d.ts +6 -0
- package/dist/services/ephemeral-campaign.d.ts.map +1 -1
- package/dist/services/ephemeral-campaign.js +54 -10
- package/dist/services/ephemeral-campaign.js.map +1 -1
- package/dist/services/ephemeral-campaign.test.js +23 -10
- package/dist/services/ephemeral-campaign.test.js.map +1 -1
- package/dist/services/errors.d.ts +2 -1
- package/dist/services/errors.d.ts.map +1 -1
- package/dist/services/errors.js +6 -3
- package/dist/services/errors.js.map +1 -1
- package/dist/services/index.d.ts +1 -2
- package/dist/services/index.d.ts.map +1 -1
- package/dist/services/index.js +1 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/instance-context.d.ts +5 -1
- package/dist/services/instance-context.d.ts.map +1 -1
- package/dist/services/instance-context.js +87 -28
- package/dist/services/instance-context.js.map +1 -1
- package/dist/services/instance-context.test.js +5 -1
- package/dist/services/instance-context.test.js.map +1 -1
- package/dist/services/instance-lifecycle.d.ts.map +1 -1
- package/dist/services/instance-lifecycle.js +32 -1
- package/dist/services/instance-lifecycle.js.map +1 -1
- package/dist/services/instance-lifecycle.test.js +52 -1
- package/dist/services/instance-lifecycle.test.js.map +1 -1
- package/dist/services/instance.d.ts +37 -9
- package/dist/services/instance.d.ts.map +1 -1
- package/dist/services/instance.js +100 -25
- package/dist/services/instance.js.map +1 -1
- package/dist/services/instance.test.js +157 -0
- package/dist/services/instance.test.js.map +1 -1
- package/dist/services/launcher.d.ts +47 -3
- package/dist/services/launcher.d.ts.map +1 -1
- package/dist/services/launcher.js +205 -33
- package/dist/services/launcher.js.map +1 -1
- package/dist/services/launcher.test.js +133 -6
- package/dist/services/launcher.test.js.map +1 -1
- package/dist/services/source-type-registry.d.ts +8 -0
- package/dist/services/source-type-registry.d.ts.map +1 -1
- package/dist/services/source-type-registry.js +39 -0
- package/dist/services/source-type-registry.js.map +1 -1
- package/dist/services/source-type-registry.test.js +31 -1
- package/dist/services/source-type-registry.test.js.map +1 -1
- package/dist/services/status.d.ts +6 -2
- package/dist/services/status.d.ts.map +1 -1
- package/dist/services/status.js +67 -34
- package/dist/services/status.js.map +1 -1
- package/dist/services/status.test.js +9 -2
- package/dist/services/status.test.js.map +1 -1
- package/dist/testing/e2e-helpers.d.ts +47 -1
- package/dist/testing/e2e-helpers.d.ts.map +1 -1
- package/dist/testing/e2e-helpers.js +244 -7
- package/dist/testing/e2e-helpers.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types/account.d.ts +1 -1
- package/dist/types/campaign.d.ts +1 -1
- package/dist/types/campaign.d.ts.map +1 -1
- package/dist/types/feed.d.ts +1 -3
- package/dist/types/feed.d.ts.map +1 -1
- package/dist/types/index.d.ts +0 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/cdp-port.d.ts.map +1 -1
- package/dist/utils/cdp-port.js +3 -1
- package/dist/utils/cdp-port.js.map +1 -1
- package/dist/utils/cdp-port.test.js +1 -1
- package/dist/utils/cdp-port.test.js.map +1 -1
- package/dist/utils/delay.d.ts +79 -0
- package/dist/utils/delay.d.ts.map +1 -1
- package/dist/utils/delay.js +118 -0
- package/dist/utils/delay.js.map +1 -1
- package/dist/utils/delay.test.js +111 -1
- package/dist/utils/delay.test.js.map +1 -1
- package/dist/utils/index.d.ts +2 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +2 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/session-pacer.d.ts +27 -0
- package/dist/utils/session-pacer.d.ts.map +1 -0
- package/dist/utils/session-pacer.js +55 -0
- package/dist/utils/session-pacer.js.map +1 -0
- package/dist/utils/session-pacer.test.d.ts +2 -0
- package/dist/utils/session-pacer.test.d.ts.map +1 -0
- package/dist/utils/session-pacer.test.js +111 -0
- package/dist/utils/session-pacer.test.js.map +1 -0
- package/package.json +1 -1
- package/dist/linkedin/__tests__/selectors.integration.test.d.ts +0 -2
- package/dist/linkedin/__tests__/selectors.integration.test.d.ts.map +0 -1
- package/dist/linkedin/__tests__/selectors.integration.test.js +0 -258
- package/dist/linkedin/__tests__/selectors.integration.test.js.map +0 -1
- package/dist/types/search-posts.d.ts +0 -22
- package/dist/types/search-posts.d.ts.map +0 -1
- package/dist/types/search-posts.js +0 -4
- package/dist/types/search-posts.js.map +0 -1
- package/dist/voyager/index.d.ts +0 -2
- package/dist/voyager/index.d.ts.map +0 -1
- package/dist/voyager/index.js +0 -4
- package/dist/voyager/index.js.map +0 -1
- package/dist/voyager/interceptor.d.ts +0 -100
- package/dist/voyager/interceptor.d.ts.map +0 -1
- package/dist/voyager/interceptor.integration.test.d.ts +0 -2
- package/dist/voyager/interceptor.integration.test.d.ts.map +0 -1
- package/dist/voyager/interceptor.integration.test.js +0 -89
- package/dist/voyager/interceptor.integration.test.js.map +0 -1
- package/dist/voyager/interceptor.js +0 -235
- package/dist/voyager/interceptor.js.map +0 -1
- package/dist/voyager/interceptor.test.d.ts +0 -2
- package/dist/voyager/interceptor.test.d.ts.map +0 -1
- package/dist/voyager/interceptor.test.js +0 -372
- 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 {
|
|
6
|
-
import {
|
|
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
|
-
//
|
|
10
|
+
// In-page DOM scraping script
|
|
9
11
|
// ---------------------------------------------------------------------------
|
|
10
12
|
/**
|
|
11
|
-
*
|
|
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
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
*
|
|
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
|
|
33
|
-
if (!
|
|
256
|
+
export function parseTimestamp(raw) {
|
|
257
|
+
if (!raw)
|
|
34
258
|
return null;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
|
280
|
+
* Build a LinkedIn post URL from an activity URN.
|
|
62
281
|
*/
|
|
63
|
-
|
|
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
|
-
*
|
|
287
|
+
* Convert raw DOM-scraped posts into normalised FeedPost entries.
|
|
68
288
|
*/
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
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
|
-
*
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
*
|
|
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
|
|
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
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
203
|
-
|
|
204
|
-
|
|
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
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
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();
|