9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
89 lines
4.7 KiB
Markdown
89 lines
4.7 KiB
Markdown
# Xiaoe (小鹅通)
|
|
|
|
**Mode**: 🔐 Browser · **Domain**: `study.xiaoe-tech.com` / `*.h5.xet.citv.cn`
|
|
|
|
## Commands
|
|
|
|
| Command | Description |
|
|
|---------|-------------|
|
|
| `opencli xiaoe courses` | List purchased courses with course URLs and shop names |
|
|
| `opencli xiaoe detail <url>` | Read course metadata such as title, price, student count, and shop |
|
|
| `opencli xiaoe catalog <url>` | Read the full course outline for normal courses, columns, and big columns |
|
|
| `opencli xiaoe play-url <url>` | Resolve the M3U8 playback URL for video lessons or live replays |
|
|
| `opencli xiaoe content <url>` | Extract rich-text lesson or page content as plain text |
|
|
|
|
## Usage Examples
|
|
|
|
```bash
|
|
# List purchased courses
|
|
opencli xiaoe courses
|
|
|
|
# Read course metadata
|
|
opencli xiaoe detail "https://appxxxx.h5.xet.citv.cn/p/course/ecourse/v_xxxxx"
|
|
|
|
# Read the course outline
|
|
opencli xiaoe catalog "https://appxxxx.h5.xet.citv.cn/p/course/ecourse/v_xxxxx"
|
|
|
|
# Resolve a lesson M3U8 URL
|
|
opencli xiaoe play-url "https://appxxxx.h5.xet.citv.cn/v1/course/video/v_xxxxx?product_id=p_xxxxx" -f json
|
|
|
|
# Extract page content
|
|
opencli xiaoe content "https://appxxxx.h5.xet.citv.cn/v1/course/text/t_xxxxx"
|
|
```
|
|
|
|
## Output
|
|
|
|
### `content`
|
|
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| `title` | string | `document.title` of the rich-text page |
|
|
| `content` | string | Trimmed `innerText` from the first matching content selector (`.rich-text-wrap`, `.content-wrap`, `.article-content`, `.text-content`, `.course-detail`, `.detail-content`, `[class*="richtext"]`, `[class*="rich-text"]`, `.ql-editor`); falls through to `<main>` → `#app` → `<body>` if no selector matches. **No silent truncation** — full extracted text is returned. |
|
|
| `content_length` | int | `content.length` — useful for caller-side truncation decisions |
|
|
| `image_count` | int | Count of `<img>` whose `src` is xiaoe-hosted and not a `data:` URI. No silent slice — counts the entire page. |
|
|
|
|
Empty `content` (e.g. login expired, page renders an empty shell) raises
|
|
`EmptyResultError` instead of returning a row with `content=''`.
|
|
|
|
### `catalog`
|
|
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| `ch` | int | 1-based chapter index |
|
|
| `chapter` | string | Chapter title (or course name for column resources) |
|
|
| `no` | int | 1-based section index within the chapter |
|
|
| `title` | string | Section title |
|
|
| `type` | string | Resource label: `图文` / `直播` / `音频` / `视频` / `专栏` / `大专栏`; unknown types pass through as the raw `String(t)` rather than being silently swallowed |
|
|
| `resource_id` | string | Xiaoe resource identifier (`p_xxx` / `v_xxx` / `l_xxx` / `a_xxx` / `i_xxx`) |
|
|
| `url` | string | Canonical playback / reading URL — `''` when xiaoe did not expose enough fields to construct one (no synthetic URL) |
|
|
| `status` | string | `已完成` / `<n>%` / `未学` for normal courses; `已完成` / `<n>节` / `''` for column resources |
|
|
|
|
### `courses`
|
|
|
|
| Column | Type | Notes |
|
|
|--------|------|-------|
|
|
| `title` | string | Course title from the card |
|
|
| `shop` | string | Shop name (`shop_name`) or app name (`app_name`); empty when the Vue tree did not expose either |
|
|
| `url` | string | Canonical course URL: `entry.h5_url` → `entry.url` → built from `app_id` + `resource_id` (column courses get `/v1/course/column/<id>?type=3`, everything else gets `/p/course/ecourse/<id>`); returns `''` when none of the three are available — never a synthetic partial URL |
|
|
|
|
## Validation
|
|
|
|
Empty results raise `EmptyResultError`, never silent `[]` — for browser
|
|
adapters this almost always indicates the cookie has expired or the URL
|
|
is not a course page. The positional `url` argument is required for `content` /
|
|
`catalog` / `detail` / `play-url`; missing `url` raises
|
|
`ArgumentError` before any browser navigation happens.
|
|
|
|
## Prerequisites
|
|
|
|
- Chrome running and **logged into** the target Xiaoe shop
|
|
- [Browser Bridge extension](/guide/browser-bridge) installed
|
|
|
|
## Notes
|
|
|
|
- `courses` starts from `study.xiaoe-tech.com` and matches purchased course cards back to Vue data to recover shop names and course URLs
|
|
- `catalog` supports normal courses, columns, and big columns by reading Vuex / Vue component state after the course page loads
|
|
- `play-url` uses a direct API path for video lessons and falls back to runtime resource inspection for live replays
|
|
- Cross-shop course URLs are preserved, so you can take a URL from `courses` and pass it directly into `detail`, `catalog`, `play-url`, or `content`
|
|
- `catalog`, `courses`, and `content` extract pure helpers (`typeLabel`, `buildItemUrl`, `chapterUrlPath`, `buildCourseUrl`, `pickContentText`, `countXiaoeImages`) that are unit-tested directly in `clis/xiaoe/xiaoe.test.js` — the in-page IIFEs embed those same functions via `${fn.toString()}` so the live and the test paths share one source of truth
|