diff --git a/lib/routes/dxy/board.ts b/lib/routes/dxy/board.ts new file mode 100644 index 00000000000000..35b55bba66e024 --- /dev/null +++ b/lib/routes/dxy/board.ts @@ -0,0 +1,96 @@ +import { Route } from '@/types'; +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { phoneBaseUrl, webBaseUrl, generateNonce, sign, getPost } from './utils'; +import { config } from '@/config'; +import { BoardInfo, PostListData } from './types'; + +export const route: Route = { + path: '/bbs/board/:boardId', + categories: ['bbs'], + example: '/dxy/bbs/board/46', + parameters: { specialId: '板块 ID,可在对应板块页 URL 中找到' }, + name: '板块', + maintainers: ['TonyRL'], + radar: [ + { + source: ['www.dxy.cn/bbs/newweb/pc/category/:boardIdId'], + target: '/bbs/board/:boardIdId', + }, + { + source: ['3g.dxy.cn/bbs/board/:boardIdId'], + target: '/bbs/board/:boardIdId', + }, + ], + handler, +}; + +async function handler(ctx) { + const { boardId } = ctx.req.param(); + const { limit = '20' } = ctx.req.query(); + + const boardDetail = (await cache.tryGet(`dxy:board:detail:${boardId}`, async () => { + const detailParams = { + boardId, + timestamp: Date.now(), + noncestr: generateNonce(8, 'number'), + }; + + const detail = await ofetch(`${phoneBaseUrl}/bbsapi/bbs/board/detail`, { + query: { + ...detailParams, + sign: sign(detailParams), + }, + }); + if (detail.code !== 'success') { + throw new Error(detail.message); + } + return detail.data; + })) as BoardInfo; + + const boardList = (await cache.tryGet( + `dxy:board:list:${boardId}`, + async () => { + const listParams = { + boardId, + postType: '0', + orderType: '1', + pageNum: '1', + pageSize: limit, + timestamp: Date.now(), + noncestr: generateNonce(8, 'number'), + }; + + const recommendList = await ofetch(`${phoneBaseUrl}/bbsapi/bbs/board/post/list`, { + query: { + ...listParams, + sign: sign(listParams), + }, + }); + if (recommendList.code !== 'success') { + throw new Error(recommendList.message); + } + return recommendList.data; + }, + config.cache.routeExpire, + false + )) as PostListData; + + const list = boardList.result.map((item) => ({ + title: item.subject, + author: item.postUser.nickname, + category: [boardDetail.title], + link: `${webBaseUrl}/bbs/newweb/pc/post/${item.postId}`, + postId: item.postId, + })); + + const items = await Promise.all(list.map((item) => getPost(item, cache.tryGet))); + + return { + title: boardDetail.title, + description: `${boardDetail.postCount} 內容 ${boardDetail.followCount} 关注`, + link: `${webBaseUrl}/bbs/newweb/pc/category/${boardId}`, + image: boardDetail.boardAvatar, + item: items, + }; +} diff --git a/lib/routes/dxy/profile/thread.ts b/lib/routes/dxy/profile/thread.ts index df9b2adffd5286..a34b6fb91249f2 100644 --- a/lib/routes/dxy/profile/thread.ts +++ b/lib/routes/dxy/profile/thread.ts @@ -89,7 +89,7 @@ async function handler(ctx) { description: postInfo.simpleBody, pubDate: parseDate(createdTime, 'x'), author: postInfo.postUser.nickname, - category: postInfo.boardInfo.title, + category: [postInfo.boardInfo.title], link: `${webBaseUrl}/bbs/newweb/pc/post/${entityId}`, postId: entityId, }; diff --git a/lib/routes/dxy/special.ts b/lib/routes/dxy/special.ts index c277ae7dd3832f..2ef12239a40f22 100644 --- a/lib/routes/dxy/special.ts +++ b/lib/routes/dxy/special.ts @@ -81,7 +81,7 @@ async function handler(ctx) { description: postInfo.simpleBody, pubDate: parseDate(dataTime, 'x'), author: postInfo.postUser.nickname, - category: postInfo.postSpecial.specialName, + category: [postInfo.postSpecial.specialName], link: `${webBaseUrl}/bbs/newweb/pc/post/${entityId}`, postId: entityId, }; diff --git a/lib/routes/dxy/types.ts b/lib/routes/dxy/types.ts new file mode 100644 index 00000000000000..aae3926c93b38a --- /dev/null +++ b/lib/routes/dxy/types.ts @@ -0,0 +1,85 @@ +interface BoardPerm { + errCode: number; +} +interface PostPerm { + errCode: number; +} + +export interface BoardInfo { + id: number; + boardId: number; + title: string; + shortTitle: string; + postCount: number; + followCount: number; + boardAvatar: string; + boardPerm: BoardPerm; + followStatus: boolean; +} + +interface PostUser { + userId: number; + avatar: string; + username: string; + nickname: string; + bbsStatus: number; + userTitle: UserTitle; +} + +interface PostDetail { + postId: number; + boardId: number; + reads: number; + pv: number; + subject: string; + postUser: PostUser; + screenUrlList: string[]; + videoScreen: boolean; + isEditorRecommend: boolean; +} + +export interface PostListData { + pageNum: number; + pageSize: number; + total: number; + result: PostDetail[]; + empty: boolean; +} + +interface UserTitle { + type: number; + titles: string[]; +} + +interface TagInfo { + tagName: string; +} + +interface Post { + id: number; + createTime: number; + subject: string; + body: string; + reads: number; + pv: number; + ipLocation: string; + isCurrentUser: boolean; + anonymous: boolean; + boardInfo: BoardInfo; + postPerm: PostPerm; + showStatus: boolean; + postUser: PostUser; + hintInfos: false[]; + isDisableRepost: boolean; + tagInfos: TagInfo[]; + isVoteActivityPost: boolean; + isShowCaseTag: boolean; + isPaidContent: boolean; + isNeedPay: boolean; +} + +export interface PostData { + code: string; + message: string; + data: Post; +} diff --git a/lib/routes/dxy/utils.ts b/lib/routes/dxy/utils.ts index eaba4b5a5f9a11..e670bf0ed814ef 100644 --- a/lib/routes/dxy/utils.ts +++ b/lib/routes/dxy/utils.ts @@ -1,7 +1,8 @@ import CryptoJS from 'crypto-js'; -import got from '@/utils/got'; -import { load } from 'cheerio'; +import ofetch from '@/utils/ofetch'; +import * as cheerio from 'cheerio'; import { parseDate } from '@/utils/parse-date'; +import { PostData } from './types'; const APP_SIGN_KEY = '4bTogwpz7RzNO2VTFtW7zcfRkAE97ox6ZSgcQi7FgYdqrHqKB7aGqEZ4o7yssa2aEXoV3bQwh12FFgVNlpyYk2Yjm9d2EZGeGu3'; const phoneBaseUrl = 'https://3g.dxy.cn'; @@ -39,8 +40,8 @@ const getPost = (item, tryGet) => noncestr: generateNonce(8, 'number'), }; - const { data: post } = await got('https://www.dxy.cn/bbs/newweb/post/detail', { - searchParams: { + const post = await ofetch('https://www.dxy.cn/bbs/newweb/post/detail', { + query: { ...postParams, sign: sign(postParams), }, @@ -49,16 +50,24 @@ const getPost = (item, tryGet) => throw new Error(post.message); } - const $ = load(post.data.body, null, false); + const $ = cheerio.load(post.data.body, null, false); $('img').each((_, img) => { img = $(img); - img.removeAttr('data-osrc'); - img.removeAttr('data-hsrc'); + if (img.data('osrc')) { + img.attr('src', img.data('osrc')); + img.removeAttr('data-osrc'); + } + if (img.data('hsrc')) { + img.attr('src', img.data('hsrc')); + img.removeAttr('data-hsrc'); + } }); item.description = $.html(); - item.updated = parseDate(post.data.lastEditTime, 'x'); + item.pubDate = parseDate(post.data.createTime, 'x'); + item.updated = post.data.lastEditTime ? parseDate(post.data.lastEditTime, 'x') : item.pubDate; + item.category = [...new Set([item.category, ...post.data.tagInfos.map((tag) => tag.tagName)])]; return item; });