-
Notifications
You must be signed in to change notification settings - Fork 9.8k
feat(route/baidu): add BAIDU_COOKIE support and extract shared … #21663
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
FlanChanXwO
wants to merge
14
commits into
DIYgod:master
Choose a base branch
from
FlanChanXwO:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
e9453f8
feat(route/baidu): add support for BAIDU_COOKIE in various baidu tieb…
FlanChanXwO 7b880dd
refactor(forum): improve code readability and structure in forum.tsx
FlanChanXwO e083661
feat(post): enhance time parsing and reply link generation in post.tsx
FlanChanXwO e2d9a67
feat(route/baidu): refactor page content retrieval and enhance cookie…
FlanChanXwO 4b80d98
refactor(route/baidu): refactor cookie parsing logic and improve code…
FlanChanXwO 7d7efc7
feat(route/baidu/tieba): add BAIDU_COOKIE support and extract shared …
FlanChanXwO 47415db
style(baidu/tieba): fix code formatting and whitespace issues
FlanChanXwO 8b3a080
Merge remote-tracking branch 'origin/master'
FlanChanXwO fc7ae10
feat(route/baidu): add retry mechanism for transient errors in page c…
FlanChanXwO e486eb3
Merge branch 'DIYgod:master' into master
FlanChanXwO bf19f34
fix: improve page content retrieval
FlanChanXwO cf78d1e
style: auto format
github-actions[bot] 43322b7
fix: improve URL encoding and enhance content retrieval logic
FlanChanXwO 4bef130
feat(route/baidu/tieba): migrate forum route to use client API
FlanChanXwO File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,188 @@ | ||
| import { createHash } from 'node:crypto'; | ||
|
|
||
| import { Cookie } from 'tough-cookie'; | ||
|
|
||
| import { config } from '@/config'; | ||
| import ConfigNotFoundError from '@/errors/types/config-not-found'; | ||
| import cache from '@/utils/cache'; | ||
| import got from '@/utils/got'; | ||
| import { getPuppeteerPage } from '@/utils/puppeteer'; | ||
|
|
||
| /** | ||
| * 解析百度 cookie 字符串为 Puppeteer 可用的 cookie 对象数组 | ||
| * 正确处理包含 '=' 的 cookie 值 | ||
| */ | ||
| export function parseBaiduCookies(cookieStr: string): Array<{ name: string; value: string; domain: string }> { | ||
| return cookieStr | ||
| .split(';') | ||
| .map((c) => Cookie.parse(c.trim())) | ||
| .filter((c): c is Cookie => Boolean(c?.key)) | ||
| .map((c) => ({ | ||
| name: c.key, | ||
| value: c.value, | ||
| domain: '.tieba.baidu.com', | ||
| })); | ||
| } | ||
|
FlanChanXwO marked this conversation as resolved.
|
||
|
|
||
| /** | ||
| * 检查 HTML 内容是否包含百度安全验证页面 | ||
| */ | ||
| export function checkSecurityVerification(html: string): void { | ||
| if (html.includes('安全验证') || html.includes('百度安全验证')) { | ||
| throw new Error('Baidu security verification required. The cookie may be expired or invalid. Please update your BAIDU_COOKIE.'); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * 使用 Puppeteer 获取贴吧页面内容 | ||
| * 包含统一的 cookie 设置、安全验证检查和缓存逻辑 | ||
| * 带有重试机制处理瞬态错误 | ||
| */ | ||
| export async function getTiebaPageContent( | ||
| url: string, | ||
| cacheKey: string, | ||
| options: { | ||
| waitForSelector?: string; | ||
| timeout?: number; | ||
| retries?: number; | ||
| } = {} | ||
| ): Promise<string> { | ||
| const cookie = config.baidu.cookie; | ||
|
|
||
| if (!cookie) { | ||
| throw new ConfigNotFoundError('Baidu Tieba RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#baidu">BAIDU_COOKIE</a>'); | ||
| } | ||
|
|
||
| const cookies = parseBaiduCookies(cookie); | ||
| const { waitForSelector = '.thread-card-wrapper, .virtual-list-item, .thread-content-box, .thread-card', timeout = 3000, retries = 3 } = options; | ||
|
|
||
| const data = await cache.tryGet( | ||
| cacheKey, | ||
| async () => { | ||
| let lastError: Error | undefined; | ||
|
|
||
| /* eslint-disable no-await-in-loop -- Intentional sequential retry logic */ | ||
| for (let attempt = 0; attempt < retries; attempt++) { | ||
| const { page, destroy } = await getPuppeteerPage(url, { | ||
|
FlanChanXwO marked this conversation as resolved.
|
||
| onBeforeLoad: async (page) => { | ||
| if (cookies.length > 0) { | ||
| await page.setCookie(...cookies); | ||
| } | ||
| }, | ||
| gotoConfig: { waitUntil: 'domcontentloaded' }, | ||
| }); | ||
|
|
||
| try { | ||
| // 等待页面稳定 | ||
| await new Promise((resolve) => setTimeout(resolve, 2000)); | ||
|
|
||
| // 动态等待内容加载 | ||
| try { | ||
| await page.waitForSelector(waitForSelector, { timeout }); | ||
| } catch { | ||
| // 如果超时,继续执行 | ||
| } | ||
|
|
||
| const html = await page.content(); | ||
| checkSecurityVerification(html); | ||
| return html; | ||
| } catch (error) { | ||
| lastError = error as Error; | ||
| // 如果是最后一次尝试,抛出错误 | ||
| if (attempt === retries - 1) { | ||
| throw lastError; | ||
| } | ||
| // 等待后重试 | ||
| await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1))); | ||
| } finally { | ||
| await destroy(); | ||
| } | ||
| } | ||
| /* eslint-enable no-await-in-loop */ | ||
| throw lastError || new Error('Failed to fetch page content'); | ||
| }, | ||
| config.cache.routeExpire, | ||
| false | ||
| ); | ||
|
|
||
| return data as string; | ||
| } | ||
|
|
||
| /** | ||
| * 规范化 URL 为绝对地址 | ||
| */ | ||
| export function normalizeUrl(href: string, base: string = 'https://tieba.baidu.com'): string { | ||
| if (!href) { | ||
| return ''; | ||
| } | ||
| if (href.startsWith('http')) { | ||
| return href; | ||
| } | ||
| const path = href.startsWith('/') ? href : `/${href}`; | ||
| return `${base}${path}`; | ||
| } | ||
|
|
||
| /** | ||
| * 通过 /c/f/frs/page API 获取贴吧帖子列表 | ||
| * 使用贴吧客户端签名认证,无需 Puppeteer | ||
| */ | ||
| const TIEBA_CLIENT_SECRET = 'tiebaclient!!!'; | ||
|
|
||
| function computeSign(params: Record<string, string>): string { | ||
| const sortedKeys = Object.keys(params).toSorted(); | ||
| const raw = sortedKeys.map((key) => `${key}=${params[key]}`).join('') + TIEBA_CLIENT_SECRET; | ||
| return createHash('md5').update(raw).digest('hex'); | ||
| } | ||
|
|
||
| export async function getTiebaForumData(params: { kw: string; cid?: string; isGood?: boolean; sortBy?: string }): Promise<any> { | ||
| const cookie = config.baidu.cookie; | ||
| if (!cookie) { | ||
| throw new ConfigNotFoundError('Baidu Tieba RSS is disabled due to the lack of <a href="https://docs.rsshub.app/deploy/config#baidu">BAIDU_COOKIE</a>'); | ||
| } | ||
|
|
||
| const bduss = cookie.match(/BDUSS=([^;]+)/)?.[1] || ''; | ||
| if (!bduss) { | ||
| throw new ConfigNotFoundError('BAIDU_COOKIE must contain BDUSS. Please check your cookie configuration.'); | ||
| } | ||
|
|
||
| const apiParams: Record<string, string> = { | ||
| _client_id: 'wappc_1234567890123_456', | ||
| _client_type: '2', | ||
| _client_version: '12.20.1.0', | ||
| _phone_imei: '000000000000000', | ||
| from: 'tieba', | ||
| kw: params.kw, | ||
| rn: '30', | ||
| pn: '1', | ||
| BDUSS: bduss, | ||
| }; | ||
|
|
||
| if (params.isGood) { | ||
| apiParams.is_good = '1'; | ||
| } | ||
| if (params.cid && params.cid !== '0') { | ||
| apiParams.cid = params.cid; | ||
| } | ||
| if (params.sortBy === 'replied') { | ||
| apiParams.sort_type = '1'; | ||
| } | ||
|
|
||
| apiParams.sign = computeSign(apiParams); | ||
|
|
||
| const url = 'https://tieba.baidu.com/c/f/frs/page'; | ||
| const cacheKey = `tieba:api:forum:${params.kw}:${params.cid || '0'}:${params.sortBy || 'created'}`; | ||
|
|
||
| const data = await cache.tryGet( | ||
| cacheKey, | ||
| async () => { | ||
| const { data: response } = await got.post(url, { | ||
| form: apiParams, | ||
| }); | ||
| return response; | ||
| }, | ||
| config.cache.routeExpire, | ||
| false | ||
| ); | ||
|
|
||
| return data; | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.