feat(apify): LinkedIn scrape returns posts when `posts` is requested

Merged
SW
4mo ago
+47-6

Summary

Implements recoupable/chat#1838. Contract: recoupable/docs#260 — merge order docs → api. Base is main per the current direct-to-main flow; I'll sync test after merge.

One-file change: startLinkedinProfileScraping(handle, posts?) — when posts is defined, runs harvestapi/linkedin-profile-posts with {targetUrls: [url], maxPosts: posts}; when omitted, the existing profile actor with byte-identical input (unit-tested). Handle guards (in/company/school legacy rows), full-URL passthrough, webhooks, and error handling unchanged and re-asserted for the posts branch.

This closes the last platform gap in the posts param: LinkedIn post items carry engagement (likes, comments, shares, reaction-type breakdown) — richer than the connector, which can't see comments/shares on personal posts and has no list-my-posts endpoint at all.

Billing: no change needed (per the #1838 evaluation). Actor pricing $0.002/post + $0.00005 start, verified on live run aKsHPucJ3thxuosSd (5 posts → $0.01005, exactly list). Under the shipped 5 + posts rule: posts=100 → ~$0.20 cost vs $1.05 charged (~5.2× margin); YouTube remains the binding worst case.

Tests (TDD, red → green)

3 new cases written first and confirmed failing (2 RED before implementation):

  • posts=20 → posts actor + {targetUrls, maxPosts: 20}
  • posts branch keeps full-URL passthrough + invalid-handle guards
  • posts omitted → profile actor, byte-identical legacy input

lib/apify: 77/77. Full suite passes. tsc --noEmit: 200 pre-existing errors, none new. ESLint clean.

Preview verification to follow as a PR comment (pinned check: post 7478135671589634048 shows comments: 1).

🤖 Generated with Claude Code


Summary by cubic

Adds optional posts depth to LinkedIn scraping: when provided, we return recent post items with engagement counts via harvestapi/linkedin-profile-posts; when omitted, we keep the existing profile snapshot.

  • New Features
    • startLinkedinProfileScraping(handle, posts?) selects harvestapi/linkedin-profile-posts with { targetUrls: [url], maxPosts: posts } when posts is set.
    • When posts is omitted, uses harvestapi/linkedin-profile-scraper with { urls: [url] }.