{"record":{"id":"3466b05baccdea52","repo":"jackwener/OpenCLI","slug":"30-3466b0","errorCode":null,"errorMessage":"标题不能超过 30 字","messagePattern":"标题不能超过 30 字","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/douyin/publish.js","lineNumber":124,"sourceCode":"        { name: 'hotspot', default: '', help: '关联热点词' },\n        { name: 'no_safety_check', type: 'bool', default: false, help: '跳过内容安全检测' },\n        { name: 'sync_toutiao', type: 'bool', default: false, help: '同步发布到头条' },\n    ],\n    columns: ['status', 'aweme_id', 'url', 'publish_time'],\n    func: async (page, kwargs) => {\n        // ── Fail-fast validation ────────────────────────────────────────────\n        const videoPath = path.resolve(kwargs.video);\n        if (!fs.existsSync(videoPath)) {\n            throw new ArgumentError(`视频文件不存在: ${videoPath}`);\n        }\n        const ext = path.extname(videoPath).toLowerCase();\n        if (!['.mp4', '.mov', '.avi', '.webm'].includes(ext)) {\n            throw new ArgumentError(`不支持的视频格式: ${ext}（支持 mp4/mov/avi/webm）`);\n        }\n        const fileSize = fs.statSync(videoPath).size;\n        const title = kwargs.title;\n        if (title.length > 30) {\n            throw new ArgumentError('标题不能超过 30 字');\n        }\n        const caption = kwargs.caption || '';\n        if (caption.length > 1000) {\n            throw new ArgumentError('正文不能超过 1000 字');\n        }\n        const timingTs = toUnixSeconds(kwargs.schedule);\n        validateTiming(timingTs);\n        const visibilityType = VISIBILITY_MAP[kwargs.visibility] ?? 0;\n        const coverPath = kwargs.cover;\n        if (coverPath) {\n            if (!fs.existsSync(path.resolve(coverPath))) {\n                throw new ArgumentError(`封面文件不存在: ${path.resolve(coverPath)}`);\n            }\n        }\n        // ── Phase 1: upload credentials ────────────────────────────────────\n        const credentials = await getUploadAuthV5Credentials(page);\n        // ── Phase 2: Apply TOS upload URL ───────────────────────────────────\n        const tosUploadInfo = await applyVideoUploadInner(fileSize, credentials);","sourceCodeStart":106,"sourceCodeEnd":142,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/publish.js#L106-L142","documentation":"ArgumentError thrown when the publish title exceeds 30 characters (title.length > 30). Douyin enforces a 30-char limit on video titles, so the library validates it up front.","triggerScenarios":"Passing --title (or kwargs.title) whose string length is 31+ — note this is JS string length, so emoji/CJK count per code unit (clis/douyin/publish.js:124).","commonSituations":"Long descriptive titles; titles with emoji where surrogate pairs inflate .length; titles assembled dynamically from templates exceeding the cap; localized text longer than expected.","solutions":["Shorten the title to 30 characters or fewer.","Trim the title programmatically before calling (accounting for surrogate pairs if using emoji).","Move extra text into caption (up to 1000 chars) instead of the title.","Count length with Array.from(title).length to approximate user-perceived length before trimming."],"exampleFix":"// before\nconst title = '我的超长视频标题...超过三十个字的一段很长的描述文字继续';\nawait publish({ video, title });\n// after\nconst raw = '我的超长视频标题...';\nconst title = Array.from(raw).slice(0, 30).join('');\nawait publish({ video, title });","handlingStrategy":"validation","validationCode":"const title = Array.from(rawTitle);\nif (title.length > 30) throw new Error(`title too long (${title.length}/30): trim before publishing`);","typeGuard":null,"tryCatchPattern":"try {\n  await publish({ video, title: rawTitle });\n} catch (e) {\n  if (String(e.message).includes('标题不能超过 30 字')) {\n    const trimmed = Array.from(rawTitle).slice(0, 30).join('');\n    return publish({ video, title: trimmed });\n  }\n  throw e;\n}","preventionTips":["Trim/validate titles to 30 code points before calling publish.","Remember JS .length counts UTF-16 units — use Array.from for emoji-heavy titles.","Move long text into caption (limit 1000 chars) instead of title.","Add a unit test asserting generated titles meet the limit."],"tags":["argument-validation","douyin","input-length"],"backgroundTag":"input-length-exceeded","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}