From 372edd4a16e3e6f9518830aff05c0ca4815a023e Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 6 Oct 2026 09:22:51 +0800 Subject: [PATCH 1/2] fix(route/smzdm): drop cookie hard requirement --- lib/routes/mafengwo/utils.ts | 2 +- lib/routes/smzdm/article.ts | 53 ++++++++++++--------- lib/routes/smzdm/baoliao.ts | 36 +++++++------- lib/routes/smzdm/haowen-fenlei.ts | 75 ++++++++++++++--------------- lib/routes/smzdm/haowen.ts | 79 +++++++++++++++---------------- lib/routes/smzdm/keyword.ts | 42 +++++++--------- lib/routes/smzdm/product.ts | 29 +++++++----- lib/routes/smzdm/ranking.ts | 10 +--- lib/routes/smzdm/utils.ts | 6 +-- 9 files changed, 162 insertions(+), 170 deletions(-) diff --git a/lib/routes/mafengwo/utils.ts b/lib/routes/mafengwo/utils.ts index cebce8bf32ff..fd56f68dc60b 100644 --- a/lib/routes/mafengwo/utils.ts +++ b/lib/routes/mafengwo/utils.ts @@ -21,7 +21,7 @@ const generateWTsfp = (url: string) => { loadts: now, fingerprint, abnormal: '0'.repeat(32), - referer: encodeURIComponent('https://www.mafengwo.cn/'), + referer: encodeURIComponent(`${new URL(url).origin}/`), uri: encodeURIComponent(url), checksum: md5(url + fingerprint + salt + now), }); diff --git a/lib/routes/smzdm/article.ts b/lib/routes/smzdm/article.ts index 043dd031377e..544f6c2b9324 100644 --- a/lib/routes/smzdm/article.ts +++ b/lib/routes/smzdm/article.ts @@ -1,10 +1,9 @@ import { load } from 'cheerio'; +import pMap from 'p-map'; -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -19,6 +18,7 @@ export const route: Route = { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -39,47 +39,56 @@ export const route: Route = { }; async function handler(ctx) { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const link = `https://zhiyou.smzdm.com/member/${ctx.req.param('uid')}/article/`; - const response = await got(link, { + const response = await ofetch.raw(link, { headers: getHeaders(), }); - const $ = load(response.data); + const cookie = getHeaders().cookie || [...response.headers.getSetCookie().map((c) => c.split(';', 1)[0]), `x-waf-captcha-referer=${link}`].join('; '); + const $ = load(response._data); const title = $('.info-stuff-nickname a').text(); - const list = $('.pandect-content-stuff') + const list = $('.pandect-content-common') .toArray() .map((item): DataItem => { const $item = $(item); + const a = $item.find('.pandect-content-title a'); return { - title: $item.find('.pandect-content-title a').text(), - link: $item.find('.pandect-content-title a').attr('href'), + title: a.text(), + description: $item.find('.pandect-content-detail').text(), + link: a.attr('href'), pubDate: timezone(parseDate($item.find('.pandect-content-time').text(), ['YYYY-MM-DD', 'MM-DD HH:mm']), 8), + image: $item.find('.pandect-content-img img').attr('src'), }; }); - const out = await Promise.all( - list.map((item) => + const out = await pMap( + list, + (item) => cache.tryGet(item.link!, async () => { - const response = await got(item.link, { - headers: getHeaders(), + const response = await ofetch(item.link!, { + headers: { + cookie, + }, }); - const $ = load(response.data); - item.description = $('.m-contant article').html(); - item.pubDate = timezone(parseDate($('meta[property="og:release_date"]').attr('content')!, 'YYYY-MM-DD HH:mm:ss'), 8); - item.author = $('meta[property="og:author"]').attr('content'); + const $ = load(response); + const article = $('.m-contant article'); + article.find('h1, .recommend-tab, input, .the-end').remove(); + item.description = article.html() ?? item.description; + const ldJson = JSON.parse($('script[type="application/ld+json"]:contains("datePublished")').text() || '{}'); + item.pubDate = $('meta[property="og:release_date"]').length ? timezone(parseDate($('meta[property="og:release_date"]').attr('content')!, 'YYYY-MM-DD HH:mm:ss'), 8) : item.pubDate; + item.author = ldJson.author?.name; + item.category = [...(ldJson.about?.map((a) => a.name) || []), ...(ldJson.mentions?.map((m) => m.name) || [])]; return item; - }) - ) + }), + { concurrency: 3 } ); return { title: `${title}-什么值得买`, + description: $('.info-stuff-words div').text(), + image: `https:${$('.avatar-img').attr('src')}`, link, item: out, }; diff --git a/lib/routes/smzdm/baoliao.ts b/lib/routes/smzdm/baoliao.ts index 6bacc6fb69b8..0ec2010ef9df 100644 --- a/lib/routes/smzdm/baoliao.ts +++ b/lib/routes/smzdm/baoliao.ts @@ -1,10 +1,9 @@ import { load } from 'cheerio'; +import pMap from 'p-map'; -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; @@ -19,6 +18,7 @@ export const route: Route = { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -39,16 +39,13 @@ export const route: Route = { }; async function handler(ctx) { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const link = `https://zhiyou.smzdm.com/member/${ctx.req.param('uid')}/baoliao/`; - const response = await got(link, { + const response = await ofetch.raw(link, { headers: getHeaders(), }); - const $ = load(response.data); + const cookie = getHeaders().cookie || [...response.headers.getSetCookie().map((c) => c.split(';', 1)[0]), `x-waf-captcha-referer=${link}`].join('; '); + const $ = load(response._data); const title = $('.info-stuff-nickname').text(); const list = $('.pandect-content-stuff') @@ -62,24 +59,29 @@ async function handler(ctx) { }; }); - const out = await Promise.all( - list.map((item) => + const out = await pMap( + list, + (item) => cache.tryGet(item.link!, async () => { - const response = await got(item.link, { - headers: getHeaders(), + const response = await ofetch(item.link!, { + headers: { + cookie, + }, }); - const $ = load(response.data); + const $ = load(response); item.description = $('article.txt-detail').html(); - item.pubDate = timezone(parseDate($('.time').first().text().trim().replace('更新时间:', '')), 8); + item.pubDate = timezone(parseDate($('.time').text().replace('更新时间:', '')), 8); item.author = title; return item; - }) - ) + }), + { concurrency: 3 } ); return { title: `${title}的爆料 - 什么值得买`, + description: $('.info-stuff-words div').text(), + image: `https:${$('.avatar-img').attr('src')}`, link, item: out, }; diff --git a/lib/routes/smzdm/haowen-fenlei.ts b/lib/routes/smzdm/haowen-fenlei.ts index a90aa3271281..85574f51ee5d 100644 --- a/lib/routes/smzdm/haowen-fenlei.ts +++ b/lib/routes/smzdm/haowen-fenlei.ts @@ -1,24 +1,24 @@ import { load } from 'cheerio'; +import pMap from 'p-map'; -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; -import type { Route } from '@/types'; +import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { parseDate } from '@/utils/parse-date'; import timezone from '@/utils/timezone'; import { getHeaders } from './utils'; export const route: Route = { - path: '/haowen/fenlei/:name/:sort?', + path: '/haowen/fenlei/:name', categories: ['shopping'], example: '/smzdm/haowen/fenlei/shenghuodianqi', - parameters: { name: '分类名,可在 URL 中查看', sort: '排序方式,默认为最新' }, + parameters: { name: '分类名,可在 URL 中查看' }, features: { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -30,56 +30,50 @@ export const route: Route = { }, radar: [ { - source: ['post.smzdm.com/fenlei/:name'], + source: ['www.smzdm.com/fenlei/:name'], target: '/haowen/fenlei/:name', }, ], name: '好文分类', maintainers: ['LogicJake'], handler, - description: `| 最新 | 周排行 | 月排行 | -| ---- | ------ | ------ | -| 0 | 7 | 30 |`, }; async function handler(ctx) { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const name = ctx.req.param('name'); - const sort = ctx.req.param('sort') || '0'; - - const link = sort === '0' ? `https://post.smzdm.com/fenlei/${name}/` : `https://post.smzdm.com/fenlei/${name}/hot_${sort}/`; + const link = `https://www.smzdm.com/fenlei/${name}/`; - const response = await got.get(link, { + const response = await ofetch.raw(link, { headers: getHeaders(), }); - const $ = load(response.data); - const title = $('div.crumbs.nav-crumbs').text().split('>').pop(); + const cookie = getHeaders().cookie || [...response.headers.getSetCookie().map((c) => c.split(';', 1)[0]), `x-waf-captcha-referer=${link}`].join('; '); + const $ = load(response._data); + const title = $('title').text().split('_', 1)[0]; - const list = $('div.list.post-list') - .toArray() - .map((item) => { - const $item = $(item); - return { - title: $item.find('h2.item-name a').text(), - link: $item.find('h2.item-name a').attr('href'), - description: $item.find('.item-info').html(), - author: $item.find('.nickname').text(), - pubDate: timezone(parseDate($item.find('span.time').text(), ['HH:mm', 'MM-DD HH:mm', 'YYYY-MM-DD HH:mm']), 8), - }; - }); + const payload = JSON.parse($('#__NUXT_DATA__').text()); + const list: DataItem[] = payload + .filter((entry) => entry instanceof Object && 'article_title' in entry && 'article_no_format_date' in entry) + .map((entry) => ({ + title: payload[entry.article_title], + link: payload[entry.article_url], + description: payload[entry.article_content], + image: payload[entry.article_pic], + category: [payload[entry.article_channel_name], ...(payload[entry.article_tag] ?? []).map((tag) => payload[payload[tag].article_title])], + pubDate: timezone(parseDate(payload[entry.article_no_format_date]), 8), + })); - const out = await Promise.all( - list.map((item) => + const out = await pMap( + list, + (item) => cache.tryGet(item.link!, async () => { try { - const response = await got(item.link, { - headers: getHeaders(), + const response = await ofetch(item.link!, { + headers: { + cookie, + }, }); - const $ = load(response.data); - item.description = $('article').html(); + const $ = load(response); + item.description = $('article').html() ?? item.description; item.pubDate = timezone(parseDate($('meta[property="og:release_date"]').attr('content')!), 8); item.author = $('meta[property="og:author"]').attr('content')!; } catch { @@ -87,12 +81,13 @@ async function handler(ctx) { } return item; - }) - ) + }), + { concurrency: 3 } ); return { title: `${title}- 什么值得买好文分类`, + image: $('.avatar-img').attr('src'), link, item: out, }; diff --git a/lib/routes/smzdm/haowen.ts b/lib/routes/smzdm/haowen.ts index 78672245eca1..728ddd63786a 100644 --- a/lib/routes/smzdm/haowen.ts +++ b/lib/routes/smzdm/haowen.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; +import pMap from 'p-map'; -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; @@ -10,6 +9,12 @@ import timezone from '@/utils/timezone'; import { getHeaders } from './utils'; +const units = { + 1: '今日热门', + 7: '周热门', + 30: '月热门', +}; + export const route: Route = { path: '/haowen/:day?', categories: ['shopping'], @@ -17,11 +22,7 @@ export const route: Route = { parameters: { day: { description: '以天为时间跨度,默认为 `1`', - options: [ - { value: '1', label: '今日热门' }, - { value: '7', label: '周热门' }, - { value: '30', label: '月热门' }, - ], + options: Object.entries(units).map(([value, label]) => ({ value, label })), default: '1', }, }, @@ -29,6 +30,7 @@ export const route: Route = { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -44,35 +46,36 @@ export const route: Route = { }; async function handler(ctx) { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const day = ctx.req.param('day') ?? '1'; const link = `https://post.smzdm.com/hot_${day}/`; - const response = await ofetch(link, { - headers: getHeaders(), + const response = await ofetch.raw('https://post.smzdm.com/rank/json_more/', { + query: { unit: day }, + headers: { + accept: 'application/json, text/javascript, */*; q=0.01', + ...getHeaders(), + }, }); - const $ = load(response); - const title = $('li.filter-tab.active').text(); + const cookie = getHeaders().cookie || [...response.headers.getSetCookie().map((c) => c.split(';', 1)[0]), `x-waf-captcha-referer=${link}`].join('; '); - const list = $('li.feed-row-wide') - .toArray() - .map((item) => { - const $item = $(item); - return { - title: $item.find('h5.z-feed-title a').text(), - link: $item.find('h5.z-feed-title a').attr('href'), - pubDate: timezone(parseDate($item.find('span.z-publish-time').text()), 8), - }; - }); + const list: DataItem[] = response._data.data.map((item) => ({ + title: item.title, + link: item.article_url, + description: item.content, + author: item.nickname, + image: item.pic_url, + category: item.channel_name, + pubDate: timezone(parseDate(item.publish_time), 8), + })); - const out = await Promise.all( - list.map((item) => + const out = await pMap( + list, + (item) => cache.tryGet(item.link ?? '', async () => { const response = await ofetch(item.link ?? '', { - headers: getHeaders(), + headers: { + cookie, + }, }); const $ = load(response); const content = $('#articleId'); @@ -81,22 +84,18 @@ async function handler(ctx) { const releaseDate = $('meta[property="og:release_date"]').attr('content'); - const outItem: DataItem = { - title: item.title, - link: item.link, - description: content.html(), - pubDate: releaseDate ? timezone(parseDate(releaseDate), 8) : item.pubDate, - author: $('meta[property="og:author"]').attr('content') || '', - }; + item.description = content.html() ?? item.description; + item.pubDate = releaseDate ? timezone(parseDate(releaseDate), 8) : item.pubDate; + item.author = $('meta[property="og:author"]').attr('content') ?? item.author; - return outItem; - }) - ) + return item; + }), + { concurrency: 3 } ); return { - title: `${title}-什么值得买好文`, + title: `${units[day]}-什么值得买好文`, link, - item: out.filter((item): item is DataItem => item !== null), + item: out, }; } diff --git a/lib/routes/smzdm/keyword.ts b/lib/routes/smzdm/keyword.ts index ae6eec623a50..1dfb2c690b23 100644 --- a/lib/routes/smzdm/keyword.ts +++ b/lib/routes/smzdm/keyword.ts @@ -1,10 +1,9 @@ import { load } from 'cheerio'; -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; +import { wafFetch } from '@/routes/mafengwo/utils'; import type { Route } from '@/types'; import { ViewType } from '@/types'; -import got from '@/utils/got'; +import ofetch from '@/utils/ofetch'; import { getHeaders, parseSearchDate } from './utils'; @@ -18,6 +17,7 @@ export const route: Route = { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -33,27 +33,17 @@ export const route: Route = { }; async function handler(ctx) { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const keyword = ctx.req.param('keyword'); - const response = await got('https://search.smzdm.com', { - headers: { - ...getHeaders(), - Referer: `https://search.smzdm.com/?c=home&s=${encodeURIComponent(keyword)}&order=time&v=a`, - }, - searchParams: { - c: 'home', - s: keyword, - order: 'time', - v: 'a', - mx_v: 'a', - }, - }); - - const data = response.data; + const url = `https://search.smzdm.com/?${new URLSearchParams({ + c: 'home', + s: keyword, + order: 'time', + v: 'a', + mx_v: 'a', + })}`; + const headers = getHeaders(); + const data = await (headers.cookie ? ofetch(url, { headers }) : wafFetch(url)); const $ = load(data); const list = $('.feed-row-wide'); @@ -67,8 +57,12 @@ async function handler(ctx) { .map((item) => { const $item = $(item); return { - title: `${$item.find('.feed-block-title a').eq(0).text().trim()} - ${$item.find('.feed-block-title a').eq(1).text().trim()}`, - description: `${$item.find('.feed-block-descripe').contents().eq(2).text().trim()}
${$item.find('.feed-block-extras span').text().trim()}
`, + title: `${$item.find('.feed-block-title a').eq(0).text().trim()} - ${$item.find('.z-highlight').text().trim()}`, + description: `${$item.find('.feed-block-descripe-top').text()}
${$item.find('.feed-block-extras span').text()}
`, + category: $item + .find('.feed-block-tags a') + .toArray() + .map((tag) => $(tag).text()), pubDate: parseSearchDate($item.find('.feed-block-extras').contents().eq(0).text().trim()), link: $item.find('.feed-block-title a').attr('href'), }; diff --git a/lib/routes/smzdm/product.ts b/lib/routes/smzdm/product.ts index 845bfabe052e..c88b1229e12f 100644 --- a/lib/routes/smzdm/product.ts +++ b/lib/routes/smzdm/product.ts @@ -1,7 +1,6 @@ import { load } from 'cheerio'; +import pMap from 'p-map'; -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { Data, DataItem, Route } from '@/types'; import cache from '@/utils/cache'; import ofetch from '@/utils/ofetch'; @@ -17,6 +16,7 @@ export const route: Route = { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -38,16 +38,14 @@ export const route: Route = { }; async function handler(ctx): Promise { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const link = `https://wiki.smzdm.com/p/${ctx.req.param('id')}`; - const response = await ofetch(link, { + const listUrl = `${link}/jiage/`; + const response = await ofetch.raw(listUrl, { headers: getHeaders(), }); - const $ = load(response); + const cookie = getHeaders().cookie || [...response.headers.getSetCookie().map((c) => c.split(';', 1)[0]), `x-waf-captcha-referer=${listUrl}`].join('; '); + const $ = load(response._data); const title = $('title').text(); // get simple info from list @@ -68,11 +66,14 @@ async function handler(ctx): Promise { }); // get detail info from each item - const out = await Promise.all( - items.map((item) => + const out = await pMap( + items, + (item) => cache.tryGet(item.link!, async (): Promise => { const response = await ofetch(item.link!, { - headers: getHeaders(), + headers: { + cookie, + }, }); const $ = load(response); @@ -88,14 +89,16 @@ async function handler(ctx): Promise { } return item; - }) - ) + }), + { concurrency: 3 } ); const filteredOut = out.filter((result) => result !== null); return { title, + description: $('.pinpai-info').text(), + image: $('.pp-img img').attr('src'), link, item: filteredOut, }; diff --git a/lib/routes/smzdm/ranking.ts b/lib/routes/smzdm/ranking.ts index ea8ba8ab9e29..60cd2f5349ab 100644 --- a/lib/routes/smzdm/ranking.ts +++ b/lib/routes/smzdm/ranking.ts @@ -1,9 +1,6 @@ -import { config } from '@/config'; -import ConfigNotFoundError from '@/errors/types/config-not-found'; import type { Route } from '@/types'; import { ViewType } from '@/types'; import got from '@/utils/got'; -import timezone from '@/utils/timezone'; import { getHeaders } from './utils'; @@ -206,6 +203,7 @@ export const route: Route = { requireConfig: [ { name: 'SMZDM_COOKIE', + optional: true, description: '什么值得买登录后的 Cookie 值', }, ], @@ -221,10 +219,6 @@ export const route: Route = { }; async function handler(ctx) { - if (!config.smzdm.cookie) { - throw new ConfigNotFoundError('什么值得买排行榜 is disabled due to the lack of SMZDM_COOKIE'); - } - const { rank_type, rank_id, hour } = ctx.req.param(); // When the hour is 3, some special rank_id require a special hour num @@ -232,6 +226,7 @@ async function handler(ctx) { const response = await got('https://www.smzdm.com/top/json_more', { headers: { + accept: 'application/json, text/javascript, */*; q=0.01', Referer: 'https://www.smzdm.com/top', ...getHeaders(), }, @@ -252,7 +247,6 @@ async function handler(ctx) { item: list.map((item) => ({ title: `${item.article_title} - ${item.article_price}`, description: `${item.article_title} - ${item.article_price}
`, - pubDate: timezone(item.article_pubdate, 8), link: item.article_url, })), }; diff --git a/lib/routes/smzdm/utils.ts b/lib/routes/smzdm/utils.ts index d01ff54ebd55..f08d11244564 100644 --- a/lib/routes/smzdm/utils.ts +++ b/lib/routes/smzdm/utils.ts @@ -15,8 +15,4 @@ export const parseSearchDate = (value: string, now = new Date()): Date | undefin return parseDateInTimezone(`${year}-${value}`, 8); }; -export const getHeaders = () => ({ - accept: 'application/json, text/javascript, */*; q=0.01', - cookie: config.smzdm.cookie!, - 'x-requested-with': 'XMLHttpRequest', -}); +export const getHeaders = (): Record => (config.smzdm.cookie ? { cookie: config.smzdm.cookie } : {}); From ba56eec882b9520d3849a54ffcea89796d43a30c Mon Sep 17 00:00:00 2001 From: Tony Date: Tue, 6 Oct 2026 09:45:23 +0800 Subject: [PATCH 2/2] fix(parse-date-in-timezone): correct import path for utc plugin --- lib/utils/parse-date-in-timezone.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/utils/parse-date-in-timezone.ts b/lib/utils/parse-date-in-timezone.ts index 8261d927961c..7b22d2d440db 100644 --- a/lib/utils/parse-date-in-timezone.ts +++ b/lib/utils/parse-date-in-timezone.ts @@ -1,5 +1,5 @@ import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; +import utc from 'dayjs/plugin/utc.js'; dayjs.extend(utc);