{"record":{"id":"528dc1d844f4aa8c","repo":"jackwener/OpenCLI","slug":"douyin-user-videos-requires-a-sec-uid","errorCode":null,"errorMessage":"douyin user-videos requires a sec_uid","messagePattern":"douyin user-videos requires a sec_uid","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/douyin/user-videos.js","lineNumber":18,"sourceCode":"import { cli, Strategy } from '@jackwener/opencli/registry';\nimport { ArgumentError, CliError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';\nimport { fetchDouyinComments, fetchDouyinUserVideos } from './_shared/public-api.js';\nexport const MAX_USER_VIDEOS_LIMIT = 20;\nexport const USER_VIDEO_COMMENT_CONCURRENCY = 4;\nexport const DEFAULT_COMMENT_LIMIT = 10;\n// sec_uid is base64url. Anything else (a nickname, a numeric UID, a search term)\n// still resolves to a Douyin page, so without this guard the scrape silently\n// returns some other account's videos.\n// Current Douyin sec_uid values use the stable `MS4wLjABAAAA` prefix followed\n// by a long base64url payload. Requiring the prefix prevents ordinary ASCII\n// nicknames and long numeric UIDs from passing a shape-only character check.\nconst SEC_UID_PATTERN = /^MS4wLjABAAAA[A-Za-z0-9_-]{8,}$/;\nconst 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.';\nexport function normalizeSecUid(input) {\n    const raw = String(input ?? '').trim();\n    if (!raw)\n        throw new ArgumentError('douyin user-videos requires a sec_uid', SEC_UID_HINT);\n    const fromUrl = raw.match(/douyin\\.com\\/user\\/([A-Za-z0-9_-]+)/);\n    const candidate = fromUrl ? fromUrl[1] : raw;\n    if (!SEC_UID_PATTERN.test(candidate))\n        throw new ArgumentError(`\"${raw}\" is not a Douyin sec_uid`, SEC_UID_HINT);\n    return candidate;\n}\nexport function normalizeUserVideosLimit(limit) {\n    const numeric = Number(limit);\n    if (!Number.isFinite(numeric))\n        return MAX_USER_VIDEOS_LIMIT;\n    return Math.min(MAX_USER_VIDEOS_LIMIT, Math.max(1, Math.round(numeric)));\n}\nexport function normalizeCommentLimit(limit) {\n    const numeric = Number(limit);\n    if (!Number.isFinite(numeric))\n        return DEFAULT_COMMENT_LIMIT;\n    return Math.min(DEFAULT_COMMENT_LIMIT, Math.max(1, Math.round(numeric)));\n}","sourceCodeStart":1,"sourceCodeEnd":36,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/user-videos.js#L1-L36","documentation":"An ArgumentError thrown by normalizeSecUid when the sec_uid input is empty (after trimming / nullish coercion). Douyin user-videos requires a sec_uid to build the https://www.douyin.com/user/<sec_uid> page URL, and an empty value would produce a meaningless request. The error message carries a hint explaining the expected shape.","triggerScenarios":"Calling `douyin user-videos` (or secUid resolution path) with an empty --sec-uid flag, undefined/undefined-config value, or a value that is only whitespace.","commonSituations":"Config/env variable for the sec_uid not set; a shell variable expanding to empty; passing a profile URL that failed to parse earlier and yielded an empty string downstream.","solutions":["Provide the sec_uid, e.g. `douyin user-videos --sec-uid MS4wLjABAAAA...`.","Paste the last path segment of the creator's profile URL https://www.douyin.com/user/<sec_uid>.","In scripts, check the variable is non-empty before invoking the command."],"exampleFix":"// before\nawait run(['douyin', 'user-videos', '--sec-uid', process.env.DY_SEC_UID]);\n// after\nif (!process.env.DY_SEC_UID) throw new Error('DY_SEC_UID is not set');\nawait run(['douyin', 'user-videos', '--sec-uid', process.env.DY_SEC_UID]);","handlingStrategy":"validation","validationCode":"const secUid = (input ?? '').trim();\nif (!secUid) throw new Error('sec_uid is required: last path segment of https://www.douyin.com/user/<sec_uid>');","typeGuard":"function hasSecUid(v) {\n  return typeof v === 'string' && v.trim().length > 0;\n}","tryCatchPattern":"try {\n  await run('douyin user-videos', { sec_uid });\n} catch (e) {\n  if (e.name === 'ArgumentError' && /requires a sec_uid/.test(e.message)) {\n    console.error('Set --sec-uid from the profile URL');\n    process.exitCode = 2;\n    return;\n  }\n  throw e;\n}","preventionTips":["Read sec_uid from a checked config value, not an unguarded env var.","Fail fast in your own code when the value is empty before shelling out."],"tags":["argument-error","douyin","missing-argument","input-validation"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}