{"record":{"id":"afeb48c5ab7b17cb","repo":"jackwener/OpenCLI","slug":"not-found-afeb48","errorCode":"NOT_FOUND","errorMessage":"Episode not found","messagePattern":"Episode not found","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/xiaoyuzhou/download.js","lineNumber":30,"sourceCode":"    access: 'read',\n    description: 'Download Xiaoyuzhou episode audio',\n    domain: 'www.xiaoyuzhoufm.com',\n    strategy: Strategy.LOCAL,\n    browser: false,\n    args: [\n        { name: 'id', positional: true, required: true, help: 'Episode ID (eid from podcast-episodes output)' },\n        { name: 'output', default: './xiaoyuzhou-downloads', help: 'Output directory' },\n    ],\n    columns: ['title', 'podcast', 'status', 'size', 'file'],\n    func: async (args) => {\n        const credentials = loadXiaoyuzhouCredentials();\n        const response = await requestXiaoyuzhouJson('/v1/episode/get', {\n            query: { eid: args.id },\n            credentials,\n        });\n        const ep = response.data;\n        if (!ep) {\n            throw new CliError('NOT_FOUND', 'Episode not found', 'Please check the ID');\n        }\n        const audioUrl = ep.media?.source?.url;\n        if (!audioUrl) {\n            throw new CliError('PARSE_ERROR', 'Audio URL not found in episode payload', 'Episode payload does not expose media.source.url');\n        }\n        const output = String(args.output || './xiaoyuzhou-downloads');\n        const ext = path.extname(new URL(audioUrl).pathname) || '.mp3';\n        const title = String(ep.title || 'episode');\n        const filename = `${args.id}_${sanitizeFilename(title, 80) || 'episode'}${ext}`;\n        const outputDir = path.join(output, String(args.id));\n        fs.mkdirSync(outputDir, { recursive: true });\n        const destPath = path.join(outputDir, filename);\n        const result = await httpDownload(audioUrl, destPath, {\n            timeout: 60000,\n        });\n        return [{\n                title,\n                podcast: ep.podcast?.title || '',","sourceCodeStart":12,"sourceCodeEnd":48,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/xiaoyuzhou/download.js#L12-L48","documentation":"Thrown by the Xiaoyuzhou download command (clis/xiaoyuzhou/download.js:30) as a CliError with code NOT_FOUND when the API call to /v1/episode/get succeeds but response.data is empty/null. The library interprets this as 'no episode exists for the given eid'. It includes the hint 'Please check the ID' because the overwhelmingly common cause is a wrong or mistyped episode ID.","triggerScenarios":"Running the xiaoyuzhou download command with an --id that the API does not resolve: nonexistent episode, truncated/mistyped ID, an episode that has been deleted or made private, or credentials (token) that lack access to the episode so the API returns empty data instead of 401.","commonSituations":"Copy-pasting an episode ID with missing characters; using an internal/preview ID not yet published; episode taken down by the podcaster; expired auth token causing the API to silently return empty data for restricted episodes.","solutions":["Verify the episode ID against the episode's share URL (the path segment after /episode/ in xiaoyuzhoufm.com links)","Re-authenticate / refresh credentials, then retry — an expired token can yield empty data","Try the same ID via the episode info command to confirm whether the API returns data at all","If the episode was deleted or is region-restricted, no ID variant will work — use a different episode"],"exampleFix":"// before\nawait cli.download({ id: '62d0f3ea9a1f2b1a3c4d5e6' });\n// after\nconst id = '62d0f3ea9a1f2b1a3c4d5e6f'; // full 24-char ID copied from episode URL\nawait cli.download({ id });","handlingStrategy":"validation","validationCode":"function isValidEpisodeId(id) {\n  return typeof id === 'string' && /^[0-9a-f]{24}$/.test(id);\n}\nif (!isValidEpisodeId(args.id)) throw new Error('Episode ID must be 24 hex chars copied from the episode URL');","typeGuard":"function hasEpisodeData(res) {\n  return res != null && typeof res === 'object' && res.data != null && typeof res.data === 'object';\n}","tryCatchPattern":"try {\n  await cli.download({ id });\n} catch (error) {\n  if (error.code === 'NOT_FOUND') {\n    console.error(`Episode ${id} does not exist — verify the ID from the episode share URL`);\n  } else throw error;\n}","preventionTips":["Always copy the full 24-char episode ID from the episode share URL","Distinguish episode IDs (eid) from podcast IDs (pid)","Refresh auth tokens before long sessions","Check the episode still exists in the app before scripting downloads"],"tags":["not-found","api","invalid-input","cli"],"backgroundTag":"resource-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}