{"record":{"id":"133fdbe3c878b024","repo":"jackwener/OpenCLI","slug":"1point3acres-thread","errorCode":null,"errorMessage":"1point3acres thread","messagePattern":"1point3acres thread","errorType":"exception","errorClass":"EmptyResultError","httpStatus":null,"severity":"info","filePath":"clis/1point3acres/thread.js","lineNumber":46,"sourceCode":"        { name: 'limit', type: 'int', default: 10, help: '返回楼层条数（默认 10，含主楼）' },\n        { name: 'contentLimit', type: 'int', default: 400, help: '每楼正文截断长度（默认 400 字符，最少 50）' },\n    ],\n    columns: ['floor', 'pid', 'author', 'postTime', 'content', 'url'],\n    func: async (args) => {\n        const tid = String(args.tid || '').trim();\n        if (!/^\\d+$/.test(tid)) {\n            throw new ArgumentError('tid must be a numeric thread id');\n        }\n        const page = normalizePositiveInteger(args.page, 1, 'page');\n        const limit = normalizePositiveInteger(args.limit, 10, 'limit');\n        const contentLimit = normalizePositiveInteger(args.contentLimit, 400, 'contentLimit', { min: 50 });\n\n        const url = `${BASE}/thread-${tid}-${page}-1.html`;\n        const html = await fetchHtml(url);\n\n        // Sanity: real thread page will contain postlist + at least one post div.\n        if (!/id=\"postlist\"/.test(html) && !/id=\"post_\\d+\"/.test(html)) {\n            throw new EmptyResultError('1point3acres thread', `帖子 ${tid} 不存在或被删除`);\n        }\n\n        // Split posts: each post block is bounded by <div id=\"post_<PID>\">…</div> next post or postlist end.\n        // NOTE: intermediate objects intentionally use postId/body/offset (not pid/html/start) to\n        // avoid being mistaken for row-shaped objects by the silent-column-drop audit.\n        const postBlocks = [];\n        const re = /<div id=\"post_(\\d+)\"[^>]*>/g;\n        const offsets = [];\n        let m;\n        while ((m = re.exec(html))) offsets.push({ postId: m[1], offset: m.index });\n        for (let i = 0; i < offsets.length; i++) {\n            const segStart = offsets[i].offset;\n            const segEnd = i + 1 < offsets.length ? offsets[i + 1].offset : html.length;\n            postBlocks.push({ postId: offsets[i].postId, body: html.slice(segStart, segEnd) });\n        }\n\n        const rows = [];\n        for (let i = 0; i < postBlocks.length && rows.length < limit; i++) {","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/1point3acres/thread.js#L28-L64","documentation":"EmptyResultError thrown when the fetched thread page contains neither the `postlist` container nor any `post_<pid>` divs — i.e. the HTML returned does not look like a real thread page. The library treats this as 'the thread does not exist or was deleted' rather than attempting to parse garbage HTML.","triggerScenarios":"Requesting `thread-<tid>-<page>-1.html` where the tid is invalid, the thread was deleted/moderated, or a page number beyond the last page was requested and the site returns an error/notice page instead of post markup.","commonSituations":"Threads removed by moderators (common on the forum); typos in the tid; requesting page 99 of a 3-page thread; login-walled threads where the guest view is an error notice.","solutions":["Verify the tid is correct by opening the thread URL in a browser","Try page 1 to confirm the thread itself exists","Check whether the thread requires login/permissions the fetch (guest) session lacks","If the page renders in a browser but the CLI fails, the HTML structure check may be outdated — update the marker regex"],"exampleFix":"// before\nthread({ tid: '2856313', page: 50 })  // EmptyResultError on out-of-range page\n// after\nthread({ tid: '2856313', page: 1 })","handlingStrategy":"try-catch","validationCode":"if (!/^\\d+$/.test(String(tid ?? '').trim())) throw new Error('tid must be numeric');","typeGuard":"null","tryCatchPattern":"try {\n  const posts = await thread({ tid, page });\n} catch (e) {\n  if (e instanceof EmptyResultError) {\n    // treat as thread-missing: skip or notify\n  } else throw e;\n}","preventionTips":["Start with page 1 for new tids","Handle moderated/deleted threads in batch jobs","Cross-check with search results before deep-paging"],"tags":["empty-result","thread-not-found","scraping"],"backgroundTag":"resource-not-found","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}