rsshub
Version:
Make RSS Great Again!
199 lines (177 loc) • 8.24 kB
text/typescript
import { Route, DataItem } from '@/types';
import ofetch from '@/utils/ofetch';
import { load } from 'cheerio';
import { parseDate } from '@/utils/parse-date';
import { finishArticleItem } from '@/utils/wechat-mp';
import logger from '@/utils/logger';
const host = 'https://weixin.sogou.com';
const hardcodedCookie = 'SNUID=78725B470A0EF2C3F97AA5EB0BBF95C1; ABTEST=0|1680917938|v1; SUID=8F7B1C682B83A20A000000006430C5B2; PHPSESSID=le2lak0vghad5c98ijd3t51ls4; IPLOC=USUS5';
interface SogouItemInternal extends DataItem {
_internal: {
isWeChatLink: boolean;
};
}
async function fetchAndParsePage(wechatId: string): Promise<SogouItemInternal[]> {
const searchUrl = `${host}/weixin`;
let response;
try {
const responseHtml = await ofetch(searchUrl, {
query: {
ie: 'utf8',
s_from: 'input',
_sug_: 'n',
_sug_type_: '1',
type: '2',
query: wechatId,
page: '1',
},
headers: {
Referer: host,
Cookie: hardcodedCookie,
},
});
response = { data: responseHtml };
} catch (error) {
logger.error(`Failed to fetch Sogou search for ${wechatId}: ${error instanceof Error ? error.message : String(error)}`);
return [];
}
const $ = load(response.data);
const list = $('ul.news-list > li').toArray();
const pageItemsPromises = list.map(async (li): Promise<SogouItemInternal | null> => {
const $li = $(li);
const title = $li.find('h3 > a').text().trim();
const sogouLinkHref = $li.find('h3 > a').attr('href');
if (!sogouLinkHref) {
logger.warn(`Skipping item with missing link for wechatId: ${wechatId}`);
return null;
}
const sogouLink = host + sogouLinkHref;
const description = $li.find('p.txt-info').text().trim();
const timeScript = $li.find('span.s2 script').html();
const timeMatch = timeScript?.match(/timeConvert\('(\d+)'\)/);
const pubDate = timeMatch ? parseDate(Number.parseInt(timeMatch[1]) * 1000) : undefined;
let realLink = sogouLink;
try {
const linkResponse = await ofetch.raw(sogouLink, {
headers: {
Referer: searchUrl,
Cookie: hardcodedCookie,
},
redirect: 'manual',
ignoreResponseError: true,
});
let location = linkResponse.headers?.get('location');
if (location) {
if (!location.startsWith('http')) {
try {
location = new URL(location, sogouLink).toString();
} catch (error) {
logger.warn(`Invalid redirect location "${location}" for title "${title}" (wechatId: ${wechatId}): ${error instanceof Error ? error.message : String(error)}`);
location = null;
}
}
if (typeof location === 'string' && location) {
if (location.startsWith('http://mp.weixin.qq.com') || location.startsWith('https://mp.weixin.qq.com')) {
realLink = location;
} else {
try {
const intermediateResponse = await ofetch.raw(location, {
headers: {
Referer: sogouLink,
Cookie: hardcodedCookie,
},
redirect: 'manual',
ignoreResponseError: true,
});
const intermediateLocation = intermediateResponse.headers?.get('location');
if (intermediateLocation && (intermediateLocation.startsWith('http://mp.weixin.qq.com') || intermediateLocation.startsWith('https://mp.weixin.qq.com'))) {
realLink = intermediateLocation;
} else {
// logger.warn(`Could not resolve final WeChat link for title "${title}" (wechatId: ${wechatId}) after intermediate redirect`);
}
} catch (error) {
logger.warn(`Failed to resolve intermediate redirect for title "${title}" (wechatId: ${wechatId}): ${error instanceof Error ? error.message : String(error)}`);
}
}
}
} else {
logger.debug(`No redirect location found for title "${title}" (wechatId: ${wechatId})`);
}
} catch (error: unknown) {
const errorMsg = error instanceof Error ? error.message : String(error);
if (typeof error === 'object' && error !== null && 'response' in error && typeof error.response === 'object' && error.response !== null && 'status' in error.response) {
logger.debug(`Redirect request failed for "${title}" (wechatId: ${wechatId}) with status ${error.response.status}: ${errorMsg}`);
} else {
logger.debug(`Redirect request failed for "${title}" (wechatId: ${wechatId}): ${errorMsg}`);
}
}
const isWeChatLink = realLink.startsWith('http://mp.weixin.qq.com') || realLink.startsWith('https://mp.weixin.qq.com');
const author = $li.find('span.all-time-y2').text().trim();
return {
title,
link: realLink,
description,
author,
pubDate,
guid: realLink,
_internal: {
isWeChatLink,
},
} as SogouItemInternal;
});
return (await Promise.all(pageItemsPromises)).filter((item): item is SogouItemInternal => item !== null);
}
export const route: Route = {
path: '/sogou/:id',
categories: ['new-media'],
example: '/wechat/sogou/qimao0908',
parameters: { id: '公众号 id, 打开 weixin.sogou.com 并搜索相应公众号, 在 URL 中找到 id' },
features: {
requireConfig: false,
requirePuppeteer: false,
antiCrawler: true,
supportBT: false,
supportPodcast: false,
supportScihub: false,
},
name: '公众号(搜狗来源)',
maintainers: ['EthanWng97', 'pseudoyu'],
handler,
};
async function handler(ctx) {
const wechatId = ctx.req.param('id');
const allItems: SogouItemInternal[] = await fetchAndParsePage(wechatId);
const firstPageFirstItem = allItems[0];
const accountTitle = firstPageFirstItem?.author || wechatId;
const finalItemsPromises = allItems.map(async (item: SogouItemInternal): Promise<DataItem | null> => {
let resultItem: DataItem | SogouItemInternal = item;
if (item._internal.isWeChatLink) {
try {
resultItem = await finishArticleItem(item);
} catch (error) {
logger.debug(`finishArticleItem failed for ${item.link}: ${error instanceof Error ? error.message : String(error)}`);
}
}
if (resultItem && typeof resultItem === 'object') {
const finalItem: DataItem = {
title: resultItem.title,
link: resultItem.link,
description: resultItem.description,
author: resultItem.author,
pubDate: resultItem.pubDate,
guid: resultItem.guid,
...(resultItem.content && { content: resultItem.content }),
};
return finalItem;
}
logger.debug(`Unexpected null or non-object item during final processing for link: ${item?.link}`);
return null;
});
const finalItems: DataItem[] = (await Promise.all(finalItemsPromises)).filter((item): item is DataItem => item !== null);
return {
title: `${accountTitle} 的微信公众号`,
link: `${host}/weixin?query=${wechatId}`,
description: `${accountTitle} 的微信公众号`,
item: finalItems,
};
}