{"record":{"id":"6d6dad5db7e31dba","repo":"jackwener/OpenCLI","slug":"bilibili-label-api-returned-a-malformed-payload-6d6dad","errorCode":null,"errorMessage":"Bilibili ${label} API returned a malformed payload","messagePattern":"Bilibili (.+?) API returned a malformed payload","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/bilibili/utils.js","lineNumber":228,"sourceCode":"      const res = await fetch(${urlJs}, { credentials: \"include\" });\n      return await res.json();\n    }\n  `);\n}\n/**\n * Bilibili write APIs return a JSON envelope `{ code, message, data }`. A non-zero\n * `code` carries either an auth/permission failure (login expired, CSRF rejected,\n * forbidden) or an application-level error (rate limit, validation, etc.). These\n * two helpers route the envelope to the right typed error so every write adapter\n * surfaces login problems as `AuthRequiredError`, not a generic execution error.\n */\nexport function isAuthLikeBilibiliError(code, message) {\n    return code === -101 || code === -111 || code === -403 || /csrf|登录|账号|权限|forbidden|permission|login/i.test(String(message ?? ''));\n}\n\nexport function requireOkPayload(payload, label) {\n    if (!payload || typeof payload !== 'object' || Array.isArray(payload) || !Object.hasOwn(payload, 'code')) {\n        throw new CommandExecutionError(`Bilibili ${label} API returned a malformed payload`);\n    }\n    if (payload.code !== 0) {\n        const message = payload.message ?? 'unknown error';\n        if (isAuthLikeBilibiliError(payload.code, message)) {\n            throw new AuthRequiredError('bilibili.com', `Bilibili ${label} API requires login or permission: ${message} (${payload.code})`);\n        }\n        throw new CommandExecutionError(`Bilibili ${label} API failed: ${message} (${payload.code})`);\n    }\n    return payload.data;\n}\n\n/**\n * POST form-encoded params to a Bilibili API endpoint.\n * Runs inside the logged-in browser context and auto-attaches the bili_jct CSRF token,\n * which Bilibili requires on every authenticated write request.\n */\nexport async function apiPost(page, path, opts = {}) {\n    const params = opts.params ?? {};","sourceCodeStart":210,"sourceCodeEnd":246,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/bilibili/utils.js#L210-L246","documentation":"requireOkPayload enforces that a Bilibili API response is a non-null, non-array object containing a `code` field before interpreting it. If the body does not have this shape, the library cannot tell success from failure and throws instead of dereferencing undefined fields.","triggerScenarios":"Passing a payload to requireOkPayload(payload, label) that is null, undefined, an Array, a primitive, or an object without a `code` property — e.g., raw fetch text never parsed as JSON, an HTML error page, or an empty 204 body.","commonSituations":"Bilibili returning an HTML anti-bot/risk-control page (HTTP 412/403) with a non-JSON body; response.json() failing upstream and null being passed along; a WAF/CDN block page; network middleware returning empty bodies on 304.","solutions":["Log the raw HTTP status and body — if it's HTML, you were likely blocked by risk control; slow down or send proper headers/cookies.","Ensure the response was parsed with response.json() and that parsing succeeded before calling requireOkPayload.","Check that requests include a realistic User-Agent and Referer; missing headers often trigger non-JSON block pages.","If a proxy/CDN is interfering, retry or route around it; verify the endpoint URL is correct."],"exampleFix":"// before\nconst payload = await res.text(); // string, not parsed JSON\nrequireOkPayload(payload, 'view');\n// after\nconst payload = await res.json();\nrequireOkPayload(payload, 'view');","handlingStrategy":"try-catch","validationCode":"const text = await res.text();\nlet payload; try { payload = JSON.parse(text); } catch { throw new Error(`Non-JSON Bilibili response (HTTP ${res.status}): ${text.slice(0,200)}`); }","typeGuard":"function isBiliPayload(p){ return !!p && typeof p==='object' && !Array.isArray(p) && Object.hasOwn(p,'code'); }","tryCatchPattern":"try { const data = requireOkPayload(payload, 'view'); } catch (e) { if (/malformed payload/.test(e.message)) { logRawBody(); /* likely anti-bot HTML or unparsed body */ } throw e; }","preventionTips":["Always parse responses with response.json() and handle parse failure before requireOkPayload.","Send realistic User-Agent/Referer headers to avoid HTML risk-control pages.","Check HTTP status; 412/403 from WAF returns non-JSON bodies.","Log raw bodies on parse/shape failures to distinguish blocking from schema drift."],"tags":["api","validation","bilibili","json","payload"],"backgroundTag":"malformed-api-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}