From 5a653b6ef4a22ae58a9e6812a4daf8aaa61340c9 Mon Sep 17 00:00:00 2001 From: nuomi1 Date: Fri, 4 Oct 2024 22:23:53 +0800 Subject: [PATCH] feat(route/mi): refactor crowdfunding with ofetch and art-template (#16974) * feat(route/mi): refactor crowdfunding with ofetch and art-template * fix: remove unnecessary diff * refactor: use flatMap --------- --- lib/routes/mi/crowdfunding.ts | 65 ++++++++++++------- lib/routes/mi/templates/crowdfunding.art | 28 +++++++++ lib/routes/mi/types.ts | 40 ++++++++++++ lib/routes/mi/utils.ts | 80 ++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 22 deletions(-) create mode 100644 lib/routes/mi/templates/crowdfunding.art create mode 100644 lib/routes/mi/types.ts create mode 100644 lib/routes/mi/utils.ts diff --git a/lib/routes/mi/crowdfunding.ts b/lib/routes/mi/crowdfunding.ts index fcd27a08c566f..eeb805a6b7f12 100644 --- a/lib/routes/mi/crowdfunding.ts +++ b/lib/routes/mi/crowdfunding.ts @@ -1,37 +1,58 @@ -import { Route } from '@/types'; -import got from '@/utils/got'; +import { Data, DataItem, Route, ViewType } from '@/types'; +import { CrowdfundingDetailInfo, CrowdfundingList } from './types'; +import utils from './utils'; export const route: Route = { path: '/crowdfunding', categories: ['shopping'], example: '/mi/crowdfunding', name: '小米众筹', - maintainers: ['DIYgod'], + maintainers: ['DIYgod', 'nuomi1'], handler, + features: { + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportRadar: true, + supportBT: false, + supportPodcast: false, + supportScihub: false, + }, + radar: [ + { + source: ['m.mi.com/crowdfunding/home'], + target: '/crowdfunding', + }, + ], + view: ViewType.Notifications, +}; + +const getDetails = async (list: CrowdfundingList[]) => { + const result: Promise[] = list.flatMap((section) => section.items.map((item) => utils.getCrowdfundingItem(item))); + return await Promise.all(result); }; +const getDataItem = (item: CrowdfundingDetailInfo) => + ({ + title: item.project_name, + description: utils.renderCrowdfunding(item), + link: `https://m.mi.com/crowdfunding/proddetail/${item.project_id}`, + image: item.big_image, + language: 'zh-cn', + }) as DataItem; + async function handler() { - const response = await got({ - method: 'post', - url: 'http://api.m.mi.com/v1/microwd/home', - headers: { - 'Mishop-Client-Id': '180100031055', - 'User-Agent': 'MiShop/4.3.68 (iPhone; iOS 12.0.1; Scale/3.00)', - 'IOS-App-Version': '4.3.68', - 'IOS-Version': 'system=12.0.1&device=iPhone10,3', - }, - }); - const list = response.data.data.list.flatMap((a) => a.items || []); + const list = await utils.getCrowdfundingList(); + const details = await getDetails(list); + + const items: DataItem[] = details.map((item) => getDataItem(item)); return { title: '小米众筹', - link: '', + link: 'https://m.mi.com/crowdfunding/home', + item: items, allowEmpty: true, - item: - list && - list.map((item) => ({ - title: item.product_name, - description: `
价格:${item.product_price}元`, - })), - }; + image: 'https://m.mi.com/static/img/icons/apple-touch-icon-152x152.png', + language: 'zh-cn', + } as Data; } diff --git a/lib/routes/mi/templates/crowdfunding.art b/lib/routes/mi/templates/crowdfunding.art new file mode 100644 index 0000000000000..a58e19aca9a13 --- /dev/null +++ b/lib/routes/mi/templates/crowdfunding.art @@ -0,0 +1,28 @@ + +
+{{ project_name }} +
+{{ project_desc }} +
+众筹价:{{ price }} 元,建议零售价:{{ product_market_price }} 元 +
+众筹开始:{{ start_time_desc }},众筹结束:{{ end_time_desc }} +
+物流:{{ send_info }} +
+ + + + + + + + {{ each support_list }} + + + + + + {{ /each }} + +
档位价格描述
{{ $value.name }}{{ $value.price }} 元{{ $value.support_desc }}
diff --git a/lib/routes/mi/types.ts b/lib/routes/mi/types.ts new file mode 100644 index 0000000000000..6b7cf53ac8dac --- /dev/null +++ b/lib/routes/mi/types.ts @@ -0,0 +1,40 @@ +export interface DataResponse { + data: Data; +} + +export interface CrowdfundingData { + list: CrowdfundingList[]; +} + +export interface CrowdfundingList { + items: CrowdfundingItem[]; +} + +export interface CrowdfundingItem { + project_id: number; + product_market_price: string; +} + +export interface CrowdfundingDetailData { + crowd_funding_info: CrowdfundingDetailInfo; +} + +export interface CrowdfundingDetailInfo { + big_image: string; + end_time: number; + end_time_desc: string; // injected + price: string; + product_market_price: string; // injected + project_desc: string; + project_id: number; + project_name: string; + start_time: number; + start_time_desc: string; // injected + support_list: CrowdfundingDetailSupportList[]; +} + +export interface CrowdfundingDetailSupportList { + name: string; + price: string; + support_desc: string; +} diff --git a/lib/routes/mi/utils.ts b/lib/routes/mi/utils.ts new file mode 100644 index 0000000000000..a0839f489a0ee --- /dev/null +++ b/lib/routes/mi/utils.ts @@ -0,0 +1,80 @@ +import { getCurrentPath } from '@/utils/helpers'; +const __dirname = getCurrentPath(import.meta.url); + +import cache from '@/utils/cache'; +import ofetch from '@/utils/ofetch'; +import { art } from '@/utils/render'; +import dayjs from 'dayjs'; +import 'dayjs/locale/zh-cn'; +import localizedFormat from 'dayjs/plugin/localizedFormat'; +import timezone from 'dayjs/plugin/timezone'; +import utc from 'dayjs/plugin/utc'; +import path from 'path'; +import { CrowdfundingData, CrowdfundingDetailData, CrowdfundingDetailInfo, CrowdfundingItem, CrowdfundingList, DataResponse } from './types'; + +dayjs.extend(localizedFormat); +dayjs.extend(timezone); +dayjs.extend(utc); + +/** + * 获取众筹项目列表 + * + * @returns {Promise} 众筹项目列表。 + */ +export const getCrowdfundingList = async (): Promise => { + const response = await ofetch>('https://m.mi.com/v1/crowd/crowd_home', { + headers: { + referrer: 'https://m.mi.com/', + }, + method: 'POST', + }); + return response.data.list; +}; + +/** + * 获取众筹项目详情并缓存 + * + * @param {CrowdfundingItem} item - 众筹项目。 + * @returns {Promise} 众筹项目详情。 + */ +export const getCrowdfundingItem = (item: CrowdfundingItem): Promise => + cache.tryGet(`mi:crowdfunding:${item.project_id}`, async () => { + const response = await ofetch>('https://m.mi.com/v1/crowd/crowd_detail', { + headers: { + referrer: 'https://m.mi.com/crowdfunding/home', + }, + method: 'POST', + query: { + project_id: item.project_id, + }, + }); + // 建议零售价 + if (response.data.crowd_funding_info.product_market_price === undefined) { + response.data.crowd_funding_info.product_market_price = item.product_market_price; + } + // 众筹开始 + if (response.data.crowd_funding_info.start_time_desc === undefined) { + response.data.crowd_funding_info.start_time_desc = formatDate(response.data.crowd_funding_info.start_time); + } + // 众筹结束 + if (response.data.crowd_funding_info.end_time_desc === undefined) { + response.data.crowd_funding_info.end_time_desc = formatDate(response.data.crowd_funding_info.end_time); + } + return response.data.crowd_funding_info; + }) as Promise; + +/** + * 渲染众筹项目模板 + * + * @param {CrowdfundingDetailInfo} item - 众筹项目详情。 + * @returns {string} 渲染后的众筹项目模板字符串。 + */ +export const renderCrowdfunding = (item: CrowdfundingDetailInfo): string => art(path.join(__dirname, 'templates/crowdfunding.art'), item); + +const formatDate = (timestamp: number): string => dayjs.unix(timestamp).tz('Asia/Shanghai').locale('zh-cn').format('lll'); + +export default { + getCrowdfundingList, + getCrowdfundingItem, + renderCrowdfunding, +};