{"record":{"id":"98972e93d4b0c1ea","repo":"jackwener/OpenCLI","slug":"error-98972e","errorCode":null,"errorMessage":"未登录，请先在浏览器中登录贝壳找房","messagePattern":"未登录，请先在浏览器中登录贝壳找房","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/ke/utils.js","lineNumber":60,"sourceCode":"        return { href: '', title: '', body_text: '' };\n    }\n    return {\n        href: cleanText(result.href),\n        title: cleanText(result.title),\n        body_text: cleanText(result.body_text),\n    };\n}\n\nexport function assertNotBlocked(state) {\n    const { href, title, body_text } = state;\n    if (href.includes('hip.ke.com/captcha') || href.includes('/captcha')) {\n        throw new AuthRequiredError('ke.com', '触发了验证码，请先在浏览器中完成验证');\n    }\n    if (CAPTCHA_TEXT_PATTERNS.some(p => title.includes(p) || body_text.includes(p))) {\n        throw new AuthRequiredError('ke.com', '触发了验证码，请先在浏览器中完成滑块验证');\n    }\n    if (LOGIN_TEXT_PATTERNS.some(p => title.includes(p))) {\n        throw new AuthRequiredError('ke.com', '未登录，请先在浏览器中登录贝壳找房');\n    }\n}\n\nexport async function gotoKe(page, url) {\n    await page.goto(url, { settleMs: 2500 });\n    await page.wait(2);\n    const state = await readPageState(page);\n    assertNotBlocked(state);\n    return state;\n}\n\n/**\n * Fetch a ke.com JSON API from inside the browser context (credentials included).\n */\nexport async function fetchKeJson(page, url) {\n    const result = await page.evaluate(`(async () => {\n    const res = await fetch(${JSON.stringify(url)}, { credentials: 'include' });\n    if (!res.ok) return { __keErr: res.status };","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/ke/utils.js#L42-L78","documentation":"assertNotBlocked checks the page title for login prompts (请登录, 立即登录, 扫码登录, etc.). If the page effectively demands login, it throws AuthRequiredError instructing the user to sign into 贝壳找房 (ke.com) in the browser. It is thrown even when a session cookie nominally exists but the site still renders a login-gated page.","triggerScenarios":"Visiting a login-required ke.com page anonymously; the lianjia_token cookie expired so the server renders a login wall; the target page inherently requires authentication (e.g. saved favorites, agent contact).","commonSituations":"Token TTL expired overnight; user logged out elsewhere, invalidating the session; browsing public pages that happen to show a login banner in the title.","solutions":["Run the ke auth login flow and log in via the browser, then retry","Verify session validity with the ke auth verify command before batch runs","If a public page falsely matches a login pattern in its title, treat it as a selector false-positive and adjust LOGIN_TEXT_PATTERNS in clis/ke/utils.js"],"exampleFix":"// before\n$ opencli ke ershoufang search 四合院  # 未登录，请先在浏览器中登录贝壳找房\n// after\n$ opencli ke auth login && opencli ke ershoufang search 四合院","handlingStrategy":"try-catch","validationCode":"const state = await readPageState(page);\nconst loginWords = ['请登录','登录后','账号登录','手机登录','立即登录','扫码登录'];\nif (loginWords.some(w => state.title.includes(w))) {\n  console.log('Login wall detected — run `opencli ke auth login` first');\n  process.exit(2);\n}","typeGuard":null,"tryCatchPattern":"import { AuthRequiredError } from '@jackwener/opencli/errors';\ntry {\n  await gotoKe(page, url);\n} catch (e) {\n  if (e instanceof AuthRequiredError && e.message.includes('未登录')) {\n    console.error('Please log into ke.com in the managed browser, then retry.');\n    process.exitCode = 2;\n  } else throw e;\n}","preventionTips":["Run `ke auth verify` at the start of every session/batch","Re-login proactively — lianjia_token sessions expire","Watch for server-side logouts after signing in on another device","Check whether the target page legitimately requires auth before blaming the session"],"tags":["auth","login","ke"],"backgroundTag":"auth-session-required","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}