{"record":{"id":"3030e327c2e71fa9","repo":"jackwener/OpenCLI","slug":"30","errorCode":null,"errorMessage":"标题不能超过 30 字","messagePattern":"标题不能超过 30 字","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/douyin/draft.js","lineNumber":317,"sourceCode":"        { name: 'video', required: true, positional: true, help: '视频文件路径' },\n        { name: 'title', required: true, help: '视频标题（≤30字）' },\n        { name: 'caption', default: '', help: '正文内容（≤1000字，支持 #话题）' },\n        { name: 'cover', default: '', help: '封面图片路径' },\n        { name: 'visibility', default: 'public', choices: ['public', 'friends', 'private'] },\n    ],\n    columns: ['status', 'draft_id'],\n    func: async (page, kwargs) => {\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 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 coverPath = kwargs.cover;\n        if (coverPath) {\n            if (!fs.existsSync(path.resolve(coverPath))) {\n                throw new ArgumentError(`封面文件不存在: ${path.resolve(coverPath)}`);\n            }\n        }\n        if (!page.setFileInput) {\n            throw new CommandExecutionError('当前浏览器适配器不支持文件注入', '请使用 Browser Bridge 或支持 setFileInput 的浏览器模式');\n        }\n        const visibilityLabel = VISIBILITY_LABELS[kwargs.visibility] ?? VISIBILITY_LABELS.public;\n        await page.goto(DRAFT_UPLOAD_URL);\n        await page.wait({ selector: 'input[type=\"file\"]', timeout: 20 });\n        await dismissKnownModals(page);","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/draft.js#L299-L335","documentation":"The draft command enforces Douyin's 30-character title limit client-side. If kwargs.title (required) exceeds 30 JavaScript string units, an ArgumentError is thrown. Note the check uses .length (UTF-16 code units), so some emoji count as 2.","triggerScenarios":"Calling `douyin draft --title \"...\"` with a title whose .length > 30, typically long descriptive titles or titles heavy with emoji (each emoji can count as 2 units).","commonSituations":"Reusing titles written for YouTube/Twitter with longer limits; emoji-decorated titles; titles generated programmatically by concatenating tags.","solutions":["Shorten the title to 30 characters or fewer before invoking the command","Trim programmatically: `[...title].slice(0, 30).join('')` (array spread counts code points, safer than .slice)","If the title is built from a template, move variable text to caption (limit 1000)"],"exampleFix":"// before\nconst title = '我的超长标题……（40个字）';\n// after\nconst title = [...rawTitle].slice(0, 30).join('');","handlingStrategy":"validation","validationCode":"const title = [...rawTitle].join('');\nif ([...title].length > 30) throw new Error('Title exceeds Douyin 30-char limit');","typeGuard":null,"tryCatchPattern":"try {\n  await cli.draft({ title, video });\n} catch (e) {\n  if (e instanceof ArgumentError && e.message.includes('标题不能超过')) {\n    await cli.draft({ title: [...title].slice(0, 30).join(''), video });\n  } else throw e;\n}","preventionTips":["Truncate titles at generation time using [...str].length (code points), not .length","Keep titles under ~25 chars to leave room for emoji (which count as 2 UTF-16 units)","Put descriptive text in the caption instead of the title"],"tags":["validation","argument-error","limit-exceeded"],"backgroundTag":"input-length-limit","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}