{"record":{"id":"b19a9c619d25b47d","repo":"jackwener/OpenCLI","slug":"weread-official-label-contains-invalid-charact","errorCode":null,"errorMessage":"weread-official: ${label} contains invalid characters","messagePattern":"weread-official: (.+?) contains invalid characters","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/weread-official/utils.js","lineNumber":254,"sourceCode":"export function parseRange(range) {\n    const text = String(range ?? '').trim();\n    const match = text.match(/^(\\d+)-(\\d+)$/);\n    if (!match) return { rangeStart: '', rangeEnd: '' };\n    return { rangeStart: match[1], rangeEnd: match[2] };\n}\n\n// ── Argument validation ─────────────────────────────────────────────────────\n\nexport function requireText(value, label) {\n    const text = String(value ?? '').trim();\n    if (!text) throw new ArgumentError(`weread-official: ${label} cannot be empty`);\n    return text;\n}\n\nexport function requireBookId(value, label = 'bookId') {\n    const text = requireText(value, label);\n    if (!/^[A-Za-z0-9_-]+$/.test(text)) {\n        throw new ArgumentError(`weread-official: ${label} contains invalid characters`, 'Pass a bookId from `weread-official search`.');\n    }\n    return text;\n}\n\nexport function requirePositiveInt(value, label, { defaultValue, max } = {}) {\n    if (value === undefined || value === null || value === '') {\n        if (defaultValue === undefined) {\n            throw new ArgumentError(`weread-official: ${label} is required`);\n        }\n        return defaultValue;\n    }\n    const text = String(value).trim();\n    if (!/^\\d+$/.test(text)) {\n        throw new ArgumentError(`weread-official: ${label} must be a positive integer`);\n    }\n    const n = Number(text);\n    if (!Number.isSafeInteger(n) || n < 1) {\n        throw new ArgumentError(`weread-official: ${label} must be a positive integer`);","sourceCodeStart":236,"sourceCodeEnd":272,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread-official/utils.js#L236-L272","documentation":"requireBookId first trims via requireText, then enforces /^[A-Za-z0-9_-]+$/ so only gateway-safe book identifiers are sent. Anything containing URL prefixes, slashes, punctuation, or unicode throws ArgumentError with the hint to use a bookId from `weread-official search`.","triggerScenarios":"Passing a full WeRead URL like https://weread.qq.com/web/bookDetail/abc123 instead of the id; pasting an id with trailing spaces/newlines is fine, but quotes, '%xx' encodings, or CJK characters are not; fabricating/guessing a bookId.","commonSituations":"Copying a book link from the browser and using the whole URL as bookId; storing ids in CSV where Excel mangles long numeric ids; using an ISBN or internal numeric id that is not the gateway's book format.","solutions":["Run `weread-official search --query <title>` and use the bookId field from its output verbatim.","Strip URL prefixes: take only the final path segment of a bookDetail link.","Validate the id locally against /^[A-Za-z0-9_-]+$/ before calling.","Regenerate rather than hand-edit bookIds from notes or spreadsheets."],"exampleFix":"// before\nconst bookId = 'https://weread.qq.com/web/bookDetail/wra_abc123';\n// after\nconst bookId = 'wra_abc123'; // from `weread-official search` output","handlingStrategy":"validation","validationCode":"const BOOK_ID_RE = /^[A-Za-z0-9_-]+$/;\nif (!BOOK_ID_RE.test(bookId)) throw new Error(`bookId must match ${BOOK_ID_RE} — got: ${bookId}`);","typeGuard":"const isValidBookId = (v) => typeof v === 'string' && /^[A-Za-z0-9_-]+$/.test(v.trim());","tryCatchPattern":"try {\n  return await highlights(requireBookId(rawBookId));\n} catch (e) {\n  if (e instanceof ArgumentError && /invalid characters/.test(e.message)) {\n    console.error('bookId malformed — run `weread-official search` and copy bookId exactly');\n    process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["Never paste full WeRead URLs where a bookId is expected","Store bookIds without spreadsheet auto-formatting (quote them in CSV)","Run requireBookId on every bookId before any gateway call","Copy bookId values from tool output, not from browser address bars"],"tags":["argument-validation","input-format","bookid"],"backgroundTag":"invalid-identifier-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}