{"record":{"id":"6e2c3e74ebd5fee8","repo":"jackwener/OpenCLI","slug":"xiaohongshu-unfollow-user-id-must-be-a-xiaohongsh","errorCode":null,"errorMessage":"xiaohongshu/unfollow: user-id must be a Xiaohongshu user ID (e.g. 5d8f88dc0000000001005d3a) or full profile URL","messagePattern":"xiaohongshu/unfollow: user-id must be a Xiaohongshu user ID \\(e\\.g\\. 5d8f88dc0000000001005d3a\\) or full profile URL","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/unfollow.js","lineNumber":56,"sourceCode":"    if (/^https?:\\/\\//i.test(input)) {\n        let parsed;\n        try {\n            parsed = new URL(input);\n        } catch {\n            throw new ArgumentError('xiaohongshu/unfollow: invalid profile URL');\n        }\n        if (parsed.protocol !== 'https:' || !isXiaohongshuHost(parsed.hostname)) {\n            throw new ArgumentError('xiaohongshu/unfollow: profile URL must be an exact https://*.xiaohongshu.com URL');\n        }\n        const match = parsed.pathname.match(/^\\/user\\/profile\\/([a-zA-Z0-9]{8,32})\\/?$/);\n        if (!match) {\n            throw new ArgumentError('xiaohongshu/unfollow: profile URL must be /user/profile/<userId>');\n        }\n        return match[1];\n    }\n    const userId = normalizeXhsUserId(raw);\n    if (!userId || !USER_ID_RE.test(userId)) {\n        throw new ArgumentError(\n            'xiaohongshu/unfollow: user-id must be a Xiaohongshu user ID (e.g. 5d8f88dc0000000001005d3a) or full profile URL',\n        );\n    }\n    return userId;\n}\n\n/**\n * Click the 已关注 / 已互关 button on the profile (idempotent if not following).\n * Returns `{ ok, state }` where state is 'unfollow-clicked' | 'not-following' | 'failed'.\n */\nfunction buildClickUnfollowScript() {\n    return `\n(() => {\n  const FOLLOW_LABELS = ['关注', '+ 关注', '+关注'];\n  const FOLLOWING_LABELS = ['已关注', '已互关', '互相关注'];\n\n  const isVisible = (el) => !!el && el.offsetParent !== null;\n  const textOf = (el) => (el.innerText || el.textContent || '').trim();","sourceCodeStart":38,"sourceCodeEnd":74,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/unfollow.js#L38-L74","documentation":"This is the fallback validation in assertUserId: when the user-id argument does not look like a URL, it is treated as a raw user ID, normalized, and checked against /^[a-zA-Z0-9]{8,32}$/. If it is empty or fails that pattern, this ArgumentError is thrown, meaning the argument is neither a valid profile URL nor a plausible Xiaohongshu user ID.","triggerScenarios":"Passing an empty string, a display name or nickname instead of an ID, a numeric-only phone-like string, an ID containing hyphens/underscores/non-ASCII characters, an ID shorter than 8 or longer than 32 characters, or forgetting the positional argument entirely (raw becomes undefined/empty after String(raw ?? '').trim()).","commonSituations":"Confusing the user's nickname/RED ID (小红书号) with the internal profile ID from /user/profile/<id>; pasting a name with spaces; truncating a copied ID; passing a URL without scheme like 'www.xiaohongshu.com/user/profile/<id>' (no http prefix so it is checked as an ID and fails).","solutions":["Get the true profile ID: open the user's page and copy the last path segment of /user/profile/<id> (e.g. 5d8f88dc0000000001005d3a) — not the 小红书号 shown on the profile card","Pass the full profile URL (with https://) if you do not want to extract the ID manually","Strip whitespace, hyphens, and any 'user:' prefixes from the value before passing it","Ensure the argument is non-empty and 8-32 alphanumeric characters"],"exampleFix":"// before\n$ xiaohongshu unfollow '小小美食家'   # nickname, not the ID\n// after\n$ xiaohongshu unfollow '5d8f88dc0000000001005d3a'","handlingStrategy":"validation","validationCode":"const USER_ID_RE = /^[a-zA-Z0-9]{8,32}$/;\nconst id = String(arg ?? '').trim();\nif (!USER_ID_RE.test(id) && !/^https?:\\/\\//.test(id)) throw new Error('Invalid xiaohongshu user id: ' + id);","typeGuard":"function isValidXhsUserId(v) { return /^[a-zA-Z0-9]{8,32}$/.test(String(v ?? '').trim()); }","tryCatchPattern":"try { await cli.unfollow({ 'user-id': id }); } catch (e) { if (String(e.message).includes('user-id must be')) { /* prompt for correct ID or profile URL */ } else throw e; }","preventionTips":["Use the internal profile ID from /user/profile/<id>, not the nickname or 小红书号","Validate IDs with /^[a-zA-Z0-9]{8,32}$/ before calling","Never pass the positional argument empty or undefined","Wrap scheme-less URLs in https:// or strip them down to the ID"],"tags":["argument-validation","cli","user-input"],"backgroundTag":"invalid-argument-value","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}