Files
Benjamin Liu 67ed9e9c81 feat(linkedin-learning): add search / trending / course read commands (#1657)
* feat(linkedin-learning): add search / trending / course read commands (#1021)

Closes #1021. Adds a new linkedin-learning site adapter with three
read-only commands against LinkedIn Learning's public learning-api
REST surface. Shares cookie session with linkedin.com; Learning
queries are not subject to the people-search CUL.

Commands:
- linkedin-learning search <keywords>  searchV2?q=keywords
- linkedin-learning trending            feedRecommendationGroups?q=learner
- linkedin-learning course <slug>      courses?q=slug

Endpoints were discovered via browser network capture on
/learning/search and /learning/<slug> pages: searchV2 returns a flat
list of courses/videos/paths keyed by entityType, headline.title.text
holds the canonical title, length is a TimeSpan in seconds, and rating
is averaged from ratingSum/ratingCount when averageRating is missing.

trending walks the carousels array on each recommendation group, flattens
cards across them, dedups by slug, and respects --limit. Group is
labeled with the carousel title (e.g. "Top picks for you") or the
upstream annotation tag (TOP_PICKS).

course accepts either a bare slug or a full /learning/<slug> URL, then
hits /learning-api/courses?q=slug. The detail endpoint omits rating
fields even when search reports them; this is documented in the
adapter doc rather than fixed via a second /reviews fetch to keep the
PR scoped to one endpoint per command.

CUL caveat: Learning's API has no per-month limit, so dev iterations
can be much more aggressive than the people-search adapter (#1649).
Three commands were live-verified against a logged-in account with
60s sleeps between calls (conservative for first-pass safety).

Tests: 28 unit tests across search.test.js (12), trending.test.js (6),
course.test.js (10) cover URL construction, limit validation, author
join, duration / rating coercion, row mapping, carousel flattening
and dedup, slug parsing from URL forms, and the standard auth /
empty / fetch-failure error paths.

Live verified:
- search "AI agent" --limit 3: 3 rows with title/instructor/rating
- trending --limit 3: 3 personalized course picks
- course agentic-ai-build-your-first-agentic-ai-system: title, 3932s
  duration, 18 videos, release date 2026-03-27

* fix(linkedin-learning): harden read result boundaries

* fix(linkedin-learning): require course title evidence

---------

Co-authored-by: jackwener <jakevingoo@gmail.com>
2026-05-19 13:17:33 +08:00

4.0 KiB

LinkedIn Learning

Mode: 🔐 Browser + Cookie · Domain: linkedin.com

Read-only adapter for LinkedIn Learning courses, videos, and learning paths. Shares cookie session with linkedin.com, no Commercial Use Limit (Learning queries are separate from people-search CUL).

Commands

Command Description
opencli linkedin-learning search Search courses, videos, and paths by keyword via learning-api/searchV2
opencli linkedin-learning trending Browse personalized recommendation carousels via learning-api/feedRecommendationGroups
opencli linkedin-learning course Course detail by slug or full /learning/<slug> URL via learning-api/courses?q=slug

Usage Examples

# Search
opencli linkedin-learning search "AI agent"
opencli linkedin-learning search "rust programming" --limit 20

# Personalized recommendations
opencli linkedin-learning trending --limit 10

# Course detail (slug or full URL)
opencli linkedin-learning course agentic-ai-build-your-first-agentic-ai-system
opencli linkedin-learning course https://www.linkedin.com/learning/agentic-ai-build-your-first-agentic-ai-system

# JSON output
opencli linkedin-learning search "data science" -f json

Columns

Column Notes
rank 1-based position in upstream order
type COURSE / VIDEO / LEARNING_PATH / etc
title From headline.title.text
instructor Joined firstName lastName of all authors
difficulty BEGINNER / INTERMEDIATE / etc (uppercase from searchV2)
duration_sec Length in seconds (empty if non-SECOND unit)
rating Average rating to 2 decimals, computed from ratingSum/ratingCount if no averageRating
rating_count Number of ratings
viewers Cumulative viewer count
url https://www.linkedin.com/learning/<slug>
Column Notes
rank 1-based across all carousels in document order
group Carousel title (e.g. "Top picks for you") or annotation (TOP_PICKS)
type Same as search
title Course / video title
difficulty Same as search
viewers Cumulative viewer count
url Course URL

Cards are deduplicated by slug across carousels (first-seen wins).

course

Column Notes
title Course title
slug Stable slug used in URL
description Full course description returned by /learning-api/courses?q=slug
difficulty Beginner / Intermediate / etc (mixed case from detail endpoint)
duration_sec Total length in seconds
videos_count Number of videos in the course
rating Average rating to 2 decimals (empty when /courses?q=slug omits ratings; see Caveats)
rating_count Number of ratings (empty when omitted)
released Activation date (YYYY-MM-DD)
url https://www.linkedin.com/learning/<slug>

Prerequisites

Caveats

  • The course-detail endpoint (/learning-api/courses?q=slug) does not always include rating / rating_count even when the search endpoint reports them for the same slug. Use search "<slug words>" to get ratings if needed; a future revision may make a second call to /learning-api/reviews?contentUrn=...&q=findByContent to fill these in.
  • trending returns personalized recommendations (carousel annotation: TOP_PICKS and similar), not a globally-ranked popularity list. The output reflects the logged-in user's recent activity and skills.
  • Voyager search-cluster endpoints (used by the standard linkedin/search jobs adapter and linkedin/people-search) do not serve Learning data; learning-api/* is the dedicated REST surface.

Limit Validation

  • search and trending cap --limit at 50 with strict-integer validation (no silent clamp).
  • course returns exactly one row by definition.