{"record":{"id":"47517150699b7e31","repo":"jackwener/OpenCLI","slug":"nowcoder-detail-only-accepts-canonical-https-www","errorCode":null,"errorMessage":"nowcoder detail only accepts canonical https://www.nowcoder.com post URLs","messagePattern":"nowcoder detail only accepts canonical https://www\\.nowcoder\\.com post URLs","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/nowcoder/posts.js","lineNumber":192,"sourceCode":"    return rows.slice(0, limit);\n}\n\nexport function parseNowcoderPostTarget(raw) {\n    const value = typeof raw === 'string' ? raw.trim() : '';\n    if (NUMERIC_ID_PATTERN.test(value)) return { post_type: 'content', value };\n    if (UUID_PATTERN.test(value)) return { post_type: 'moment', value: value.toLowerCase() };\n\n    let url;\n    try {\n        url = new URL(value);\n    }\n    catch {\n        throw new ArgumentError('nowcoder detail requires a numeric content ID, moment UUID, or canonical Nowcoder URL');\n    }\n    const host = url.hostname.toLowerCase();\n    if (url.protocol !== 'https:' || url.username || url.password || url.port || url.hash\n        || (host !== 'nowcoder.com' && host !== 'www.nowcoder.com')) {\n        throw new ArgumentError('nowcoder detail only accepts canonical https://www.nowcoder.com post URLs');\n    }\n    const content = url.pathname.match(/^\\/discuss\\/([1-9]\\d*)\\/?$/);\n    if (content) return { post_type: 'content', value: content[1] };\n    const moment = url.pathname.match(/^\\/feed\\/main\\/detail\\/([0-9a-f]{32})\\/?$/i);\n    if (moment) return { post_type: 'moment', value: moment[1].toLowerCase() };\n    throw new ArgumentError('Unsupported Nowcoder URL; expected /discuss/<content-id> or /feed/main/detail/<moment-uuid>');\n}\n\nexport function projectNowcoderDetail(data, target) {\n    if (!isRecord(data) || !isRecord(data.frequencyData)) throw new CommandExecutionError('Nowcoder detail returned malformed post data');\n    const isContent = target.post_type === 'content';\n    const expectedEntityType = isContent ? CONTENT_ENTITY_TYPE : MOMENT_TYPE;\n    if (data.entityType !== expectedEntityType) throw new CommandExecutionError('Nowcoder detail returned a mismatched post entity type');\n    const uuid = requiredUuid(data.uuid, `${target.post_type} uuid`);\n    const entityId = requiredId(data.entityId, `${target.post_type} entity id`);\n    const id = isContent ? requiredId(data.id, 'content id') : uuid;\n    if (isContent ? id !== target.value : uuid !== target.value) throw new CommandExecutionError('Nowcoder detail returned a different post identity');\n    if (!isContent && entityId !== requiredId(data.id, 'moment id')) throw new CommandExecutionError('Nowcoder detail returned mismatched moment entity ids');","sourceCodeStart":174,"sourceCodeEnd":210,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/nowcoder/posts.js#L174-L210","documentation":"If the input parses as a URL but is not a canonical Nowcoder post link, parseNowcoderPostTarget throws this ArgumentError. The URL must use https, have no credentials, port, or hash, and its host must be nowcoder.com or www.nowcoder.com. This enforces that only unambiguous, official post URLs are accepted so the target can be mapped deterministically to a post id.","triggerScenarios":"A URL-parseable input that fails the canonicality check: http:// instead of https://, embedded username/password or a port, a #fragment, or a host other than nowcoder.com/www.nowcoder.com (e.g., m.nowcoder.com, a mirror, or a different site entirely).","commonSituations":"Users copying http:// links from old bookmarks; mobile-host links (m.nowcoder.com); URLs with tracking fragments (#comment-1) or query-ordered ports; pasting links from other sites into the nowcoder detail command.","solutions":["Rewrite the URL to start with https://www.nowcoder.com/.","Strip any #fragment, port, or credentials from the URL.","For mobile links (m.nowcoder.com), reconstruct the canonical /discuss/<id> path manually.","Alternatively bypass URL parsing by passing the bare numeric content ID or moment UUID directly."],"exampleFix":"// before\nnowcoder detail http://m.nowcoder.com/discuss/1234567#comments\n// after\nnowcoder detail https://www.nowcoder.com/discuss/1234567","handlingStrategy":"validation","validationCode":"function isCanonicalNowcoderUrl(raw) {\n    let url;\n    try { url = new URL(typeof raw === 'string' ? raw.trim() : ''); } catch { return false; }\n    const host = url.hostname.toLowerCase();\n    return url.protocol === 'https:' && !url.username && !url.password && !url.port && !url.hash\n        && (host === 'nowcoder.com' || host === 'www.nowcoder.com')\n        && (/^\\/discuss\\/[1-9]\\d*\\/?$/.test(url.pathname) || /^\\/feed\\/main\\/detail\\/[0-9a-f]{32}\\/?$/i.test(url.pathname));\n}","typeGuard":"function isCanonicalNowcoderPostUrl(value) { try { const u = new URL(value); return u.protocol === 'https:' && (u.hostname === 'www.nowcoder.com' || u.hostname === 'nowcoder.com') && /^\\/discuss\\/[1-9]\\d*\\/?$|^\\/feed\\/main\\/detail\\/[0-9a-f]{32}\\/?$/i.test(u.pathname); } catch { return false; } }","tryCatchPattern":"try {\n    target = parseNowcoderPostTarget(raw);\n} catch (error) {\n    if (error instanceof ArgumentError && String(error.message).includes('canonical')) {\n        console.error('Rewrite the link as https://www.nowcoder.com/discuss/<id> or /feed/main/detail/<uuid>, with no port, credentials, or #fragment');\n    }\n    throw error;\n}","preventionTips":["Normalize http:// and mobile-host links to https://www.nowcoder.com before use","Strip #fragments, ports, and credentials from copied URLs","Prefer the bare post id/UUID when the canonical URL is uncertain","Run isCanonicalNowcoderPostUrl as a pre-check in scripts"],"tags":["argument-validation","url-parsing","user-input"],"backgroundTag":"invalid-argument-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}