{"record":{"id":"73873f009eb7f9d2","repo":"jackwener/OpenCLI","slug":"xiaohongshu-delete-note-note-url-must-be-an-exact","errorCode":null,"errorMessage":"xiaohongshu/delete-note: note URL must be an exact https://*.xiaohongshu.com URL","messagePattern":"xiaohongshu/delete-note: note URL must be an exact https://\\*\\.xiaohongshu\\.com URL","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/delete-note.js","lineNumber":75,"sourceCode":"function normalizeNoteId(input) {\n    const raw = String(input ?? '').trim();\n    if (!raw) {\n        throw new ArgumentError('xiaohongshu/delete-note: note-id cannot be empty');\n    }\n    if (NOTE_ID_RE.test(raw))\n        return raw.toLowerCase();\n    if (!/^https:\\/\\//i.test(raw)) {\n        throw new ArgumentError('xiaohongshu/delete-note: note-id must be a 24-character Xiaohongshu note ID or an exact Xiaohongshu note URL');\n    }\n    let url;\n    try {\n        url = new URL(raw);\n    }\n    catch {\n        throw new ArgumentError('xiaohongshu/delete-note: invalid note URL');\n    }\n    if (url.protocol !== 'https:' || url.username || url.password || url.port || !isXiaohongshuHost(url.hostname)) {\n        throw new ArgumentError('xiaohongshu/delete-note: note URL must be an exact https://*.xiaohongshu.com URL');\n    }\n    const queryId = url.searchParams.get('noteId') || url.searchParams.get('note_id');\n    if (queryId && NOTE_ID_RE.test(queryId) && isSupportedQueryNoteUrl(url))\n        return queryId.toLowerCase();\n    const pathMatch = url.pathname.match(/^\\/(?:explore|note|search_result|discovery\\/item)\\/([0-9a-f]{24})\\/?$/i)\n        || url.pathname.match(/^\\/user\\/profile\\/[^/?#]+\\/([0-9a-f]{24})\\/?$/i);\n    if (pathMatch)\n        return pathMatch[1].toLowerCase();\n    throw new ArgumentError('xiaohongshu/delete-note: note URL must contain a 24-character note ID');\n}\nfunction buildLocateAndMaybeDeleteScript(noteId, shouldClick) {\n    return `\n      (cfg => {\n        const { targetId, shouldClick } = cfg;\n        const isVisible = (el) => !!el && el.offsetParent !== null;\n        const matchesNoteId = (impressionRaw) => {\n          if (!impressionRaw) return false;\n          try {","sourceCodeStart":57,"sourceCodeEnd":93,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/delete-note.js#L57-L93","documentation":"After parsing the URL, normalizeNoteId enforces a strict allowlist: https scheme only, no embedded username/password, no explicit port, and hostname must be xiaohongshu.com or a subdomain. Anything else fails validation — this prevents SSRF-style abuse and ensures the URL is a genuine Xiaohongshu note link.","triggerScenarios":"Passing an http:// URL, a URL with a port (e.g. https://xiaohongshu.com:8443/...), a URL with credentials (user:pass@host), a localhost/test URL, or a link to a different domain (e.g. a short-link redirector or mirror site) instead of an exact https://*.xiaohongshu.com URL.","commonSituations":"Using an http link from an old bookmark; testing against a local dev mirror; passing a xiaohongshu short-link domain or third-party aggregator link; credentials accidentally embedded from a proxy config; including an explicit :443 port in the URL.","solutions":["Use the canonical share URL form: https://www.xiaohongshu.com/explore/<24-char-id> (or /discovery/item/, /search_result/, /user/profile/<user>/<id>)","Remove any port, username, or password from the URL; ensure the scheme is exactly https://","Prefer passing the bare 24-character note ID directly instead of a URL","If the URL is a short link (xhslink.com), open it in a browser and copy the final expanded xiaohongshu.com URL"],"exampleFix":"// before\nopencli xiaohongshu delete-note --note 'http://xiaohongshu.com:443/explore/abc'\n// after\nopencli xiaohongshu delete-note --note 'https://www.xiaohongshu.com/explore/0123456789abcdef01234567'","handlingStrategy":"validation","validationCode":"function isCanonicalXhsUrl(raw) {\n  let u;\n  try { u = new URL(String(raw).trim()); } catch { return false; }\n  return u.protocol === 'https:'\n    && !u.username && !u.password && !u.port\n    && (u.hostname === 'xiaohongshu.com' || u.hostname.endsWith('.xiaohongshu.com'))\n    || /^[0-9a-f]{24}$/i.test(String(raw).trim());\n}","typeGuard":"function isXhsHost(hostname) {\n  const h = String(hostname || '').toLowerCase();\n  return h === 'xiaohongshu.com' || h.endsWith('.xiaohongshu.com');\n}","tryCatchPattern":"try {\n  await cli('xiaohongshu', 'delete-note', { note: url });\n} catch (err) {\n  if (err instanceof ArgumentError && err.message.includes('https://*.xiaohongshu.com')) {\n    console.error('Use an exact https://*.xiaohongshu.com URL (no port, no credentials) or a bare 24-char note ID.');\n  } else throw err;\n}","preventionTips":["Normalize links to https://www.xiaohongshu.com/explore/<id> before invoking the CLI","Strip any :port and user:pass@ segments from URLs constructed programmatically","Expand short links (xhslink.com) to their final xiaohongshu.com destination first"],"tags":["argument-validation","url-validation","security"],"backgroundTag":"invalid-url-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}