{"record":{"id":"cfd5b328ec78e8ef","repo":"DIYgod/RSSHub","slug":"techflow-api-returned-an-unexpected-non-json-respo","errorCode":null,"errorMessage":"TechFlow API returned an unexpected non-JSON response.","messagePattern":"TechFlow API returned an unexpected non-JSON response\\.","errorType":"exception","errorClass":"Error","httpStatus":503,"severity":"error","filePath":"lib/routes/techflowpost/utils.ts","lineNumber":91,"sourceCode":"    }\n    return `acw_sc__v2=${getAcwScV2ByArg1(arg1)}`;\n}\n\nasync function requestApi<T>(endpoint: string, referer: string, searchParams?: Record<string, string | number | boolean>) {\n    const request = () =>\n        got.get(`${apiRootUrl}${endpoint}`, {\n            searchParams,\n            headers: getHeaders(referer),\n        });\n\n    const { data } = await request();\n    if (typeof data !== 'string') {\n        return data as T;\n    }\n\n    const cookie = getAcwScV2Cookie(data);\n    if (!cookie) {\n        throw new Error('TechFlow API returned an unexpected non-JSON response.');\n    }\n\n    acwScV2Cookie = cookie;\n    const retryResponse = await request();\n    if (typeof retryResponse.data === 'string') {\n        throw new TypeError('TechFlow API still returned an anti-crawler challenge after retry.');\n    }\n\n    return retryResponse.data as T;\n}\n\nfunction getPictureUrl(picture?: string) {\n    if (!picture) {\n        return;\n    }\n    return new URL(picture, uploadRootUrl).href;\n}\n","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/DIYgod/RSSHub/blob/bed535e0879dc71c5aff6f1e7bd1ac21ede40115/lib/routes/techflowpost/utils.ts#L73-L109","documentation":"Thrown by techflowpost/utils.ts:91 via `throw new Error(...)` (NOT an InvalidParameterError) when the TechFlow API returns a string body that is NOT the expected Aliyun acw_sc__v2 anti-bot challenge. Flow: first request returns JSON -> return it; if it returns a string, the code assumes it is the acw_sc__v2 challenge page and looks for `var arg1='...'` to derive a cookie via getAcwScV2Cookie. If arg1 is not found, the string is some OTHER non-JSON response (error page, maintenance page, different challenge) and this error fires. A follow-up TypeError at line 97 covers the case where the retry still returns a string. The derived acwScV2Cookie is module-level and reused across calls.","triggerScenarios":"TechFlow swaps its WAF/challenge mechanism away from acw_sc__v2; the API returns an HTML error or maintenance page; a Cloudflare interstitial is served; getAcwScV2ByArg1 (imported from ../5eplay/utils) no longer matches the site's current arg1 algorithm.","commonSituations":"Site provider changes anti-bot vendor; scheduled maintenance returning a static page; the acw_sc__v2 algorithm is versioned and the decoder is stale; config.trueUA not sent so a stricter challenge is returned.","solutions":["Reproduce the request with the same UA/headers and inspect the raw string body to see what is actually being returned.","If the challenge format changed, update getAcwScV2Cookie / getAcwScV2ByArg1 to match the new arg1 extraction and decoding.","If transient (maintenance or Cloudflare burst), retry later.","Confirm config.trueUA is being sent (getHeaders sets it) — a non-realistic UA often triggers a stricter challenge."],"exampleFix":"// before\nconst cookie = getAcwScV2Cookie(data);\nif (!cookie) {\n    throw new Error('TechFlow API returned an unexpected non-JSON response.');\n}\n// after (surface the actual body to speed up diagnosis)\nconst cookie = getAcwScV2Cookie(data);\nif (!cookie) {\n    throw new Error(`TechFlow API returned an unexpected non-JSON response (first 200 chars): ${data.slice(0, 200)}`);\n}","handlingStrategy":"retry","validationCode":"// You cannot fully pre-validate an anti-bot challenge; sanity-check inputs instead.\n// Ensure a realistic UA is sent (config.trueUA) and the referer matches the site.\nfunction hasRealisticHeaders(headers: Record<string, string>): boolean {\n  return Boolean(headers['user-agent']) && Boolean(headers['referer']);\n}","typeGuard":"function isAcwScV2Challenge(body: string): boolean {\n  return typeof body === 'string' && /var arg1='[^']*';/.test(body);\n}","tryCatchPattern":"// Anti-bot failures are often transient — retry with backoff, refresh the cookie each time.\nasync function requestWithRetry<T>(endpoint: string, referer: string, attempts = 3): Promise<T> {\n  for (let i = 0; i < attempts; i++) {\n    try {\n      return await requestApi<T>(endpoint, referer);\n    } catch (e) {\n      const last = i === attempts - 1;\n      if (last || !(e instanceof Error) || !/unexpected non-JSON/i.test(e.message)) throw e;\n      acwScV2Cookie = ''; // force a fresh challenge solve\n      await wait(Math.pow(2, i) * 1000); // host-provided wait primitive\n    }\n  }\n}","preventionTips":["Always send config.trueUA and a matching Referer (getHeaders already does this).","If the site drops acw_sc__v2, update getAcwScV2Cookie / getAcwScV2ByArg1 to the new challenge.","Reset the module-level acwScV2Cookie on failure so retries solve a fresh challenge."],"tags":["anti-crawler","upstream-change","network","techflowpost","waf","wrong-error-type"],"backgroundTag":null,"analyzedSha":"bed535e0879dc71c5aff6f1e7bd1ac21ede40115","analyzedAt":"2026-08-12T19:29:35.364Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}