jackwener/OpenCLI · error · ArgumentError

${raw} is not a Douyin sec_uid

Error message

${raw} is not a Douyin sec_uid

What it means

An ArgumentError thrown by normalizeSecUid when the provided value does not match SEC_UID_PATTERN (^MS4wLjABAAAA[A-Za-z0-9_-]{8,}$). Douyin sec_uids are long tokens beginning with 'MS4wLjABAAAA'; the pattern deliberately rejects nicknames and long numeric UIDs that would otherwise pass a shape-only character check. If the input is a profile URL, the last path segment is extracted first and the original raw string is reported in the message.

Source

Thrown at clis/douyin/user-videos.js:22

export const MAX_USER_VIDEOS_LIMIT = 20;
export const USER_VIDEO_COMMENT_CONCURRENCY = 4;
export const DEFAULT_COMMENT_LIMIT = 10;
// sec_uid is base64url. Anything else (a nickname, a numeric UID, a search term)
// still resolves to a Douyin page, so without this guard the scrape silently
// returns some other account's videos.
// Current Douyin sec_uid values use the stable `MS4wLjABAAAA` prefix followed
// by a long base64url payload. Requiring the prefix prevents ordinary ASCII
// nicknames and long numeric UIDs from passing a shape-only character check.
const SEC_UID_PATTERN = /^MS4wLjABAAAA[A-Za-z0-9_-]{8,}$/;
const SEC_UID_HINT = 'sec_uid looks like MS4wLjABAAAA… — it is the last path segment of https://www.douyin.com/user/<sec_uid>, not a nickname or a numeric UID.';
export function normalizeSecUid(input) {
    const raw = String(input ?? '').trim();
    if (!raw)
        throw new ArgumentError('douyin user-videos requires a sec_uid', SEC_UID_HINT);
    const fromUrl = raw.match(/douyin\.com\/user\/([A-Za-z0-9_-]+)/);
    const candidate = fromUrl ? fromUrl[1] : raw;
    if (!SEC_UID_PATTERN.test(candidate))
        throw new ArgumentError(`"${raw}" is not a Douyin sec_uid`, SEC_UID_HINT);
    return candidate;
}
export function normalizeUserVideosLimit(limit) {
    const numeric = Number(limit);
    if (!Number.isFinite(numeric))
        return MAX_USER_VIDEOS_LIMIT;
    return Math.min(MAX_USER_VIDEOS_LIMIT, Math.max(1, Math.round(numeric)));
}
export function normalizeCommentLimit(limit) {
    const numeric = Number(limit);
    if (!Number.isFinite(numeric))
        return DEFAULT_COMMENT_LIMIT;
    return Math.min(DEFAULT_COMMENT_LIMIT, Math.max(1, Math.round(numeric)));
}
async function mapInBatches(items, concurrency, mapper) {
    const results = [];
    for (let index = 0; index < items.length; index += concurrency) {
        const chunk = items.slice(index, index + concurrency);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Open https://www.douyin.com/user/<sec_uid> in a browser and copy the full token after /user/ (starts with MS4wLjABAAAA).
  2. Verify the token is at least 'MS4wLjABAAAA' plus 8+ chars of [A-Za-z0-9_-] and contains no spaces or URL-encoding.
  3. If you only have a nickname, resolve it to the profile in a browser first and take the sec_uid from the URL.
  4. If passing a URL, ensure it is the canonical douyin.com/user/... form so the extractor picks the right segment.

Example fix

// before
await run(['douyin', 'user-videos', '--sec-uid', '987654321']); // numeric uid
// after
await run(['douyin', 'user-videos', '--sec-uid', 'MS4wLjABAAAAxxxxxxxxxxxxxxxx']);
Defensive patterns

Strategy: type-guard

Validate before calling

const SEC_UID_RE = /^MS4wLjABAAAA[A-Za-z0-9_-]{8,}$/;
const raw = String(input ?? '').trim();
const candidate = raw.match(/douyin\.com\/user\/([A-Za-z0-9_-]+)/)?.[1] ?? raw;
if (!SEC_UID_RE.test(candidate)) throw new Error(`not a Douyin sec_uid: ${raw}`);

Type guard

function isDouyinSecUid(v) {
  const s = typeof v === 'string' ? v.trim() : '';
  const c = s.match(/douyin\.com\/user\/([A-Za-z0-9_-]+)/)?.[1] ?? s;
  return /^MS4wLjABAAAA[A-Za-z0-9_-]{8,}$/.test(c);
}

Try / catch

try {
  await run('douyin user-videos', { sec_uid });
} catch (e) {
  if (e.name === 'ArgumentError' && /not a Douyin sec_uid/.test(e.message)) {
    console.error('sec_uid must start with MS4wLjABAAAA — copy the last path segment of the profile URL');
    process.exitCode = 2;
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling `douyin user-videos --sec-uid <value>` where value is a numeric UID, a nickname/handle, a truncated token, a full URL whose extracted segment still fails the pattern, or a sec_uid from another Douyin-like format.

Common situations: Passing the display name instead of the sec_uid; using the numeric 'user id' shown in some dashboards; copying an incomplete token (e.g. shell ate special chars); confusing Douyin sec_uid with TikTok's numeric IDs.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/6ae5f781e929a5d0. Report an issue: GitHub.