{"record":{"id":"34e88de7e72f8b67","repo":"jackwener/OpenCLI","slug":"xiaohongshu-unfollow-profile-url-must-be-user-pr","errorCode":null,"errorMessage":"xiaohongshu/unfollow: profile URL must be /user/profile/<userId>","messagePattern":"xiaohongshu/unfollow: profile URL must be /user/profile/<userId>","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/xiaohongshu/unfollow.js","lineNumber":50,"sourceCode":"    }\n    return inner;\n}\n\nfunction assertUserId(raw) {\n    const input = String(raw ?? '').trim();\n    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 `","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaohongshu/unfollow.js#L32-L68","documentation":"assertUserId accepts only canonical profile URLs whose path is exactly /user/profile/<userId> with an 8-32 alphanumeric ID. This ArgumentError is thrown when the URL is a valid https xiaohongshu.com URL but the path does not match that pattern, so no user ID can be extracted.","triggerScenarios":"Passing 'https://www.xiaohongshu.com/user/profile/' (missing ID); 'https://www.xiaohongshu.com/explore/<noteId>' (a note URL, not a user profile); 'https://www.xiaohongshu.com/user/profile/<id>/follow' (extra path segments); an ID with non-alphanumeric characters or shorter/longer than 32 chars; a user page with a custom path like /user/profile/<id>?tab=... is fine, but /users/ or /profile/ variants fail.","commonSituations":"Grabbing the URL of a note/board/search page instead of the user's profile; copying a URL while the site was still redirecting (e.g. /discovery/item); URL-encoding the path; using a truncated URL from a log line.","solutions":["Open the target user's page in a browser and copy the address while on their profile: it must look like https://www.xiaohongshu.com/user/profile/<userId>","Extract the ID from the URL yourself and pass just the ID: xiaohongshu unfollow 5d8f88dc0000000001005d3a","Remove extra path segments or trailing junk after the 8-32 char alphanumeric ID (a single trailing slash is allowed)","Confirm you have a profile URL, not a note (/explore/), board (/board/), or search (/search_result/) URL"],"exampleFix":"// before\n$ xiaohongshu unfollow 'https://www.xiaohongshu.com/explore/65f0a1b2000000001203e4c5'\n// after\n$ xiaohongshu unfollow 'https://www.xiaohongshu.com/user/profile/5d8f88dc0000000001005d3a'","handlingStrategy":"validation","validationCode":"const m = new URL(input).pathname.match(/^\\/user\\/profile\\/([a-zA-Z0-9]{8,32})\\/?$/);\nif (!m) throw new Error('Not a profile URL: ' + input);","typeGuard":"function isProfileUrl(input) {\n  try { return /^\\/user\\/profile\\/([a-zA-Z0-9]{8,32})\\/?$/.test(new URL(String(input).trim()).pathname); }\n  catch { return false; }\n}","tryCatchPattern":"try { await cli.unfollow({ 'user-id': url }); } catch (e) { if (String(e.message).includes('/user/profile/<userId>')) { /* extract ID or get the correct profile URL */ } else throw e; }","preventionTips":["Only copy the URL while actually on the user's profile page","Confirm the path starts with /user/profile/","Strip query/extra path segments — keep only /user/profile/<id>","Store user IDs, not arbitrary page URLs, in your data"],"tags":["argument-validation","url-parsing","cli"],"backgroundTag":"invalid-url-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}