linkedin: domain skills for send-connection-request + connection-search-scrape#348
linkedin: domain skills for send-connection-request + connection-search-scrape#348frumza wants to merge 1 commit into
Conversation
…ch-scrape Two new LinkedIn domain skills: - send-connection-request.md: invite flow with paywall detection, rate-limit notes, coordinate-click fallback. Covers the 'Connect is an <a> not a <button>' gotcha + the iframe-paywall detection pattern. - connection-search-scrape.md: keyword-filtered People-search via connectionOf filter. Anchor-innerText extraction pattern (robust to LinkedIn's rotating CSS-module class names), pagination, anti-bot guidance, empirical keyword hit-rate table, self-contained runnable script.
⛔ Skill review blockedAn automated security review found 3 finding(s) across 1 file(s).
Skill authorship is restricted to maintainers. Please do not attempt to self-fix — a maintainer will review and follow up. |
There was a problem hiding this comment.
2 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="domain-skills/linkedin/connection-search-scrape.md">
<violation number="1" location="domain-skills/linkedin/connection-search-scrape.md:173">
P1: `parse_card` mishandles cards where `• <Degree>` is rendered on a separate line, causing field misalignment (degree/headline parsed incorrectly).</violation>
</file>
<file name="domain-skills/linkedin/send-connection-request.md">
<violation number="1" location="domain-skills/linkedin/send-connection-request.md:61">
P2: Missing guard after modal polling loop can cause null-reference crash when 'Send without a note' button is not found</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review, or fix all with cubic.
| def parse_card(text, href): | ||
| lines = [l.strip() for l in text.split("\n") if l.strip()] | ||
| if not lines: return None | ||
| parts = lines[0].split("•") |
There was a problem hiding this comment.
P1: parse_card mishandles cards where • <Degree> is rendered on a separate line, causing field misalignment (degree/headline parsed incorrectly).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/linkedin/connection-search-scrape.md, line 173:
<comment>`parse_card` mishandles cards where `• <Degree>` is rendered on a separate line, causing field misalignment (degree/headline parsed incorrectly).</comment>
<file context>
@@ -0,0 +1,259 @@
+def parse_card(text, href):
+ lines = [l.strip() for l in text.split("\n") if l.strip()]
+ if not lines: return None
+ parts = lines[0].split("•")
+ name = parts[0].strip()
+ degree = parts[1].strip() if len(parts) > 1 else ""
</file context>
| break | ||
|
|
||
| # 4. Click Send without a note | ||
| js("""document.querySelector('button[aria-label="Send without a note"]').click()""") |
There was a problem hiding this comment.
P2: Missing guard after modal polling loop can cause null-reference crash when 'Send without a note' button is not found
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At domain-skills/linkedin/send-connection-request.md, line 61:
<comment>Missing guard after modal polling loop can cause null-reference crash when 'Send without a note' button is not found</comment>
<file context>
@@ -0,0 +1,150 @@
+ break
+
+# 4. Click Send without a note
+js("""document.querySelector('button[aria-label="Send without a note"]').click()""")
+time.sleep(2.5)
+
</file context>
| js("""document.querySelector('button[aria-label="Send without a note"]').click()""") | |
| js("""(() => { const btn = document.querySelector('button[aria-label=\"Send without a note\"]'); if (!btn) return 'modal-not-found'; btn.click(); return 'clicked'; })()""") |
|
Thanks both bots. Acknowledging the On the
I have working fixes ready for both if a maintainer wants them applied here. Alternatively I'm happy to pull this PR and resubmit a leaner version if scope is the concern, or close it entirely if external skill PRs aren't being accepted at all. Whichever route works for the project. |
What this adds
Two new LinkedIn domain skills in
domain-skills/linkedin/:send-connection-request.md— invite flow (with + without note), free-tier paywall detection, rate-limit notes, coordinate-click fallback when the JS scope can't reach. Covers the non-obvious "Connect is rendered as an<a>, not a<button>" gotcha that breaks the obviousbutton[aria-label^="Connect"]selector.connection-search-scrape.md— keyword-filtered People-search via theconnectionOffilter. Mines a target user's network surface accessible from the asker's 1st+2nd-degree graph. Anchor-innerTextextraction pattern (robust to LinkedIn's rotating CSS-module class names), pagination, anti-bot guidance, keyword strategy, and a self-contained runnable script.Why this is worth its own pair of domain skills
The non-obvious bits I think are durable enough to be worth capturing:
For
send-connection-request.md:<a>, not a<button>. The obviousbutton[aria-label^="Connect"]returns the wrong (hidden, 0×0 geometry) element when queryingbuttononly.linkedin.com/preload/that CDP doesn't expose through the normal iframe target map.textareaexists."For
connection-search-scrape.md:_83309bd4 _6e63fa0b d343d86c) that rotate per deploy. Selector-hunting against them is a maintenance trap.a[href*="/in/"]and readinnerTextoff the anchor — the anchor's text carries the entire structured card payload (name + degree + headline + location + mutuals + summary highlights).connectionOfURL parameter takes a percent-encoded JSON array of MEMBER_URNs. Bare brackets silently 400 or return empty.Tested against
Both skills were developed and validated against real flows:
send-connection-request.md— multiple invite sends across free + paywall states.connection-search-scrape.md— full keyword sweep against a target with a regional emerging-markets business network (~380 unique results, 39 keywords, ~10 min wall-time, no rate-limiting or block).Scope
Two new markdown-only files in
domain-skills/linkedin/. No changes tohelpers.py,daemon.py, or any other harness internals. Consistent with the existing domain-skill shape (selector gotchas → framework quirks → end-to-end flow → traps).Summary by cubic
Adds two LinkedIn domain skills to automate connection invites and scrape a target’s connections via keyword-filtered People search. Improves reliability with robust selectors, paywall detection, anti-bot guidance, and a runnable script.
domain-skills/linkedin/send-connection-request.md: Handles Connect as an<a>(not<button>), full invite modal flow, detects add-note iframe paywall (free-tier cap), includes coordinate-click fallback and rate-limit guidance.domain-skills/linkedin/connection-search-scrape.md: Keyword-filtered People search usingconnectionOfwith percent-encoded JSON, parses/in/anchors’innerText(resilient to rotating classes), server-side pagination rules, real-Chrome anti-bot tips, and a self-contained Python script with keyword strategy.Written for commit 031bf47. Summary will update on new commits.