{"record":{"id":"847158da613bacc7","repo":"jackwener/OpenCLI","slug":"youdao-note-url-must-include-an-id-query-parameter","errorCode":null,"errorMessage":"Youdao Note URL must include an id query parameter","messagePattern":"Youdao Note URL must include an id query parameter","errorType":"validation","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"clis/youdao/note.js","lineNumber":36,"sourceCode":"function normalizeShareUrl(raw) {\n  const value = String(raw ?? '').trim();\n  if (!value) {\n    throw new ArgumentError('youdao note url cannot be empty', 'Pass a full public share URL from Youdao Notes.');\n  }\n  let parsed;\n  try {\n    parsed = new URL(value);\n  } catch {\n    throw new ArgumentError('Invalid Youdao Note URL', 'Example: https://share.note.youdao.com/ynoteshare/index.html?id=...&type=note');\n  }\n  if (parsed.protocol !== 'https:' && parsed.protocol !== 'http:') {\n    throw new ArgumentError('Youdao Note URL must use http or https');\n  }\n  if (!ALLOWED_HOSTS.has(parsed.hostname)) {\n    throw new ArgumentError('Youdao Note URL must be under note.youdao.com or note.youdao.cn');\n  }\n  if (!parsed.searchParams.get('id')) {\n    throw new ArgumentError('Youdao Note URL must include an id query parameter');\n  }\n  const type = parsed.searchParams.get('type');\n  if (type && type !== 'note') {\n    throw new ArgumentError('youdao note only accepts shared note URLs', 'Shared notebooks are not implemented yet.');\n  }\n  return parsed.toString();\n}\n\nfunction formatYoudaoTimestamp(value) {\n  if (value == null || value === '') return '';\n  const numeric = Number(value);\n  if (!Number.isFinite(numeric) || numeric <= 0) return String(value);\n  const millis = numeric < 10_000_000_000 ? numeric * 1000 : numeric;\n  const date = new Date(millis);\n  if (Number.isNaN(date.getTime())) return String(value);\n  return date.toISOString();\n}\n","sourceCodeStart":18,"sourceCodeEnd":54,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/youdao/note.js#L18-L54","documentation":"ArgumentError thrown by normalizeShareUrl when the URL, though on an allowed host, has no 'id' query parameter (or it is empty). The id uniquely identifies the shared note on Youdao's side; without it the page cannot resolve a note and the extractor would return nothing useful.","triggerScenarios":"Passing 'https://share.note.youdao.com/ynoteshare/index.html?type=note' (no id), '...index.html?id=' (empty id), or a base URL where the id was lost by aggressive URL stripping/shortening or a redirect that dropped the query string.","commonSituations":"URL shorteners or analytics libs stripping query params; manually constructed URLs missing the id; saving only the path portion of the share link; copying the link before the share dialog finished generating the id.","solutions":["Include the id parameter: https://share.note.youdao.com/ynoteshare/index.html?id=<your-note-id>&type=note","Re-generate the share link in Youdao Notes and copy the complete URL including query string","Check your code isn't stripping search params (e.g. new URL(u).origin + pathname loses the id)"],"exampleFix":"// before\nconst url = new URL(shareLink).origin + new URL(shareLink).pathname;\nawait youdaoNote(url); // id dropped\n// after\nawait youdaoNote(shareLink); // keep full URL with ?id=...&type=note","handlingStrategy":"validation","validationCode":"function hasNoteId(u) {\n  try { return Boolean(new URL(u).searchParams.get('id')); } catch { return false; }\n}\nif (!hasNoteId(inputUrl)) throw new Error('share URL missing ?id= parameter');","typeGuard":"function urlHasIdParam(v) {\n  try { return new URL(v).searchParams.get('id') !== null; } catch { return false; }\n}","tryCatchPattern":"try {\n  await youdaoNote(url);\n} catch (e) {\n  if (String(e.message).includes('id query parameter')) {\n    // regenerate the share link; don't strip the query string\n  } else throw e;\n}","preventionTips":["Never rebuild a URL as origin+pathname; keep the full search string","Avoid URL shorteners/param strippers on share links","Copy links only after the share dialog finalizes"],"tags":["argument-error","query-parameter","validation","url"],"backgroundTag":"missing-required-query-parameter","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}