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
259 lines
10 KiB
Markdown
259 lines
10 KiB
Markdown
# TikTok
|
||
|
||
**Mode**: 🔐 Browser · **Domain**: `tiktok.com`
|
||
|
||
## Commands
|
||
|
||
| Command | Description |
|
||
|---------|-------------|
|
||
| `opencli tiktok profile` | Get user profile info |
|
||
| `opencli tiktok search` | Search videos |
|
||
| `opencli tiktok explore` | Trending videos from explore page |
|
||
| `opencli tiktok user` | Get recent videos from a user via page-context APIs |
|
||
| `opencli tiktok following` | List accounts you follow |
|
||
| `opencli tiktok friends` | Friend suggestions |
|
||
| `opencli tiktok live` | Browse live streams |
|
||
| `opencli tiktok notifications` | Get notifications |
|
||
| `opencli tiktok creator-videos` | List TikTok Studio creator videos and metrics |
|
||
| `opencli tiktok like` | Like a video |
|
||
| `opencli tiktok unlike` | Unlike a video |
|
||
| `opencli tiktok save` | Add to Favorites |
|
||
| `opencli tiktok unsave` | Remove from Favorites |
|
||
| `opencli tiktok follow` | Follow a user |
|
||
| `opencli tiktok unfollow` | Unfollow a user |
|
||
| `opencli tiktok comment` | Comment on a video |
|
||
|
||
## Usage Examples
|
||
|
||
```bash
|
||
# View a user's profile
|
||
opencli tiktok profile --username tiktok
|
||
|
||
# Search videos
|
||
opencli tiktok search "cooking" --limit 10
|
||
|
||
# Trending explore videos
|
||
opencli tiktok explore --limit 20
|
||
|
||
# Recent videos from a user
|
||
opencli tiktok user dictogo --limit 20
|
||
|
||
# Browse live streams
|
||
opencli tiktok live --limit 10
|
||
|
||
# List who you follow
|
||
opencli tiktok following
|
||
|
||
# List your TikTok Studio creator videos
|
||
opencli tiktok creator-videos --limit 20
|
||
|
||
# Friend suggestions
|
||
opencli tiktok friends --limit 10
|
||
|
||
# Like/unlike a video
|
||
opencli tiktok like "https://www.tiktok.com/@user/video/123"
|
||
opencli tiktok unlike "https://www.tiktok.com/@user/video/123"
|
||
|
||
# Save/unsave (Favorites)
|
||
opencli tiktok save "https://www.tiktok.com/@user/video/123"
|
||
opencli tiktok unsave "https://www.tiktok.com/@user/video/123"
|
||
|
||
# Follow/unfollow
|
||
opencli tiktok follow nasa
|
||
opencli tiktok unfollow nasa
|
||
|
||
# Comment on a video
|
||
opencli tiktok comment "https://www.tiktok.com/@user/video/123" "Great!"
|
||
|
||
# JSON output
|
||
opencli tiktok profile --username tiktok -f json
|
||
```
|
||
|
||
## Output
|
||
|
||
### `explore`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `index` | int | 1-based position in the recommend feed |
|
||
| `id` | string | TikTok video id; round-trips into video URL |
|
||
| `author` | string | `uniqueId` of the video author (without `@`) |
|
||
| `url` | string | Canonical `https://www.tiktok.com/@author/video/id` |
|
||
| `cover` | string | Cover image URL (may be empty) |
|
||
| `title` | string | Cleaned description (synonym of `desc`) |
|
||
| `desc` | string | Cleaned description (whitespace collapsed, ≤500 chars) |
|
||
| `plays` | int \| null | Play count (`null` if upstream did not expose) |
|
||
| `likes` | int \| null | Digg count |
|
||
| `comments` | int \| null | Comment count |
|
||
| `shares` | int \| null | Share count |
|
||
| `createTime` | int \| null | Unix seconds when the video was posted |
|
||
|
||
### `user`
|
||
|
||
Same video columns as `explore`, plus:
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `source` | string | `profile-api`, `bootstrap`, or lower-authority `search-fallback` |
|
||
|
||
`user` resolves the profile `secUid`, pages `/api/post/item_list/`, and uses
|
||
exact-author `/api/search/general/full/` only as a fallback when profile data is
|
||
short. `source` lets callers distinguish first-party profile rows from fallback
|
||
search rows.
|
||
|
||
### `friends`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `index` | int | 1-based position in the suggestion list |
|
||
| `username` | string | `uniqueId` of the suggested account (no `@`) |
|
||
| `name` | string | Display nickname (falls back to `username`) |
|
||
| `secUid` | string | TikTok internal stable id (round-trips into other endpoints) |
|
||
| `verified` | bool | `true` when the account carries a verified badge |
|
||
| `followers` | int \| null | Follower count if exposed by the suggestion payload |
|
||
| `following` | int \| null | Following count if exposed |
|
||
| `url` | string | Canonical profile URL |
|
||
|
||
### `following`
|
||
|
||
Same column shape as `friends`. `secUid` and follower / following counts come
|
||
from `/api/user/list/?scene=21` (TikTok's own following endpoint), which is the
|
||
same data their web client renders on the page.
|
||
|
||
### `notifications`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `index` | int | 1-based position |
|
||
| `id` | string | Notice id (or `idx-<n>` fallback when upstream omits) |
|
||
| `from` | string | `uniqueId` of the actor (without `@`); empty for system notices |
|
||
| `text` | string | Cleaned notice text (≤220 chars) |
|
||
| `createTime` | int \| null | Unix seconds when the notice fired |
|
||
|
||
`--type` accepts `all` / `likes` / `comments` / `mentions` / `followers`
|
||
(any other value raises `ArgumentError`, no silent default).
|
||
|
||
### `live`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `index` | int | 1-based position |
|
||
| `streamer` | string | Host's `uniqueId` (without `@`) |
|
||
| `name` | string | Host's display nickname |
|
||
| `title` | string | Stream title (≤200 chars, whitespace collapsed) |
|
||
| `viewers` | int \| null | Current viewer count |
|
||
| `likes` | int \| null | Cumulative like count for the room |
|
||
| `secUid` | string | Host's TikTok internal stable id |
|
||
| `url` | string | Canonical `/@streamer/live` URL |
|
||
|
||
### `comment` / `follow` / `unfollow` (write commands)
|
||
|
||
These three commands click the live UI button + verify the post-click state
|
||
before returning. They never return a silent failure row — every failure
|
||
path raises a typed error. The `result` enum makes idempotent fast paths
|
||
(already-following / already-not-following / already-friends) explicit, so
|
||
callers can distinguish "we just did it" from "it was already done".
|
||
|
||
#### `comment`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `url` | string | Canonical video URL the comment was posted on |
|
||
| `text` | string | Comment text actually submitted (trimmed, ≤150 chars) |
|
||
| `result` | enum | `posted` (only — TikTok permits duplicate comments, no idempotent fast path) |
|
||
|
||
#### `follow`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `username` | string | Target user's `uniqueId` (without `@`) |
|
||
| `url` | string | Canonical profile URL |
|
||
| `result` | enum | `followed` / `already-following` / `already-friends` (mutual) |
|
||
|
||
#### `unfollow`
|
||
|
||
| Column | Type | Notes |
|
||
|--------|------|-------|
|
||
| `username` | string | Target user's `uniqueId` (without `@`) |
|
||
| `url` | string | Canonical profile URL |
|
||
| `result` | enum | `unfollowed` / `already-not-following` |
|
||
|
||
## Validation (no silent clamp)
|
||
|
||
`--limit` is validated upfront and `ArgumentError` is thrown for `0`, negative,
|
||
non-integer or out-of-range values — no silent clamp to the cap. Per-command
|
||
caps:
|
||
|
||
| Command | Default | Max |
|
||
|---------|--------:|----:|
|
||
| `explore` | 20 | 120 |
|
||
| `user` | 20 | 120 |
|
||
| `friends` | 20 | 100 |
|
||
| `following` | 20 | 200 |
|
||
| `notifications` | 15 | 100 |
|
||
| `live` | 10 | 60 |
|
||
|
||
`null` semantics: a numeric column returning `null` means upstream did not
|
||
expose that field on this row (e.g. some live cards omit `like_count`). A
|
||
column never returns `0` as an unknown sentinel. Authentication / empty result
|
||
states raise `AuthRequiredError` / `EmptyResultError` instead of returning
|
||
empty rows — callers can treat any returned row as real data.
|
||
|
||
### Write commands — typed errors and retryability
|
||
|
||
`comment` / `follow` / `unfollow` validate input upfront and verify post-click
|
||
state. Failure modes:
|
||
|
||
| Failure | Typed error | `retryable` (in `hint`) |
|
||
|---------|-------------|-------------------------|
|
||
| Empty / overlong / malformed input | `ArgumentError` | n/a |
|
||
| Not logged in (no session cookie + no viewer secUid) | `AuthRequiredError` | n/a |
|
||
| Required button missing (UI changed, blocked, private account) | `CommandExecutionError` | follow / unfollow: `true` (idempotent); comment: `false` |
|
||
| State did not flip within timeout (likes/follows count unchanged) | `CommandExecutionError` | follow / unfollow: `true`; comment: `false` |
|
||
| Captcha / rate-limit popup detected | `CommandExecutionError` | same as above |
|
||
|
||
The `retryable=` flag is encoded in the error `hint` string in the form
|
||
`retryable=<true\|false> reason=<...>` so downstream agents and scripts can
|
||
grep it without parsing structured metadata. **Comment is `retryable=false`**
|
||
because the server may still have accepted the comment when our state-verify
|
||
times out (server-fan-out semantics) — auto-retrying would double-post.
|
||
**Follow / unfollow are `retryable=true`** because TikTok dedupes the relation
|
||
flip server-side, so a transient blip can be safely retried.
|
||
|
||
> Importing `retryable` as a first-class metadata layer in OpenCLI core is a
|
||
> candidate for follow-up — for now we keep the contract human-readable.
|
||
|
||
## Implementation Notes
|
||
|
||
`explore` / `user` / `friends` / `following` / `notifications` / `live` all run inside
|
||
the live page (`Strategy.COOKIE` + `browser: true`) and call TikTok's own
|
||
internal JSON endpoints with `fetch(..., { credentials: 'include' })`. The
|
||
session cookie + `msToken` come from the logged-in browser, the same way
|
||
TikTok's web client requests them. Each command first reads the warm
|
||
`__UNIVERSAL_DATA_FOR_REHYDRATION__` snapshot for fast first-page results, then
|
||
falls back to the corresponding API endpoint when more rows are requested
|
||
(`/api/recommend/item_list/`, `/api/user/detail/`, `/api/post/item_list/`,
|
||
`/api/search/general/full/`, `/api/recommend/user/`, `/api/user/list/`,
|
||
`/api/notice/multi/`, `/api/live/discover/get/`).
|
||
|
||
This refactor applies the page-context API baseline across TikTok read commands:
|
||
typed errors, full numeric stats columns, and no DOM-link scraping.
|
||
|
||
`comment` / `follow` / `unfollow` (Route 1) keep the UI button as the
|
||
trigger and harden every transition with state verification + typed errors.
|
||
We **do not** call `/api/commit/follow/user/` or `/api/comment/publish/`
|
||
directly: those endpoints require X-Bogus signing engineering, which is a
|
||
separate scope. The IIFE checks `sessionid`/`sid_tt` cookies and the
|
||
`__UNIVERSAL_DATA_FOR_REHYDRATION__` viewer snapshot for logged-in state,
|
||
then asserts a button-text flip (Follow → Following etc.) or comment-list
|
||
delta within 5–8 seconds. Captcha / rate-limit popups
|
||
(`.secsdk-captcha-wrapper`, "Too many requests" body text) are detected
|
||
both before and after the click so a flash captcha cannot masquerade as a
|
||
successful state transition.
|
||
|
||
## Prerequisites
|
||
|
||
- Chrome running and **logged into** tiktok.com
|
||
- [Browser Bridge extension](/guide/browser-bridge) installed
|
||
- `creator-videos` requires access to TikTok Studio for the logged-in creator account
|