From f30f27b9a6efad27e377590450442c29ffc71c2f Mon Sep 17 00:00:00 2001 From: chris zheng Date: Fri, 6 Feb 2026 01:37:00 +0800 Subject: [PATCH] feat(route): add route for ccmn.cn (#21070) * feat: add route for maven central * fix: change sort api to Array#toSorted * Update lib/routes/maven/central.ts Co-authored-by: Tony * fix: update url for maven namespace * fix: use category to tag items - remove custom query param - add categories for route * fix: ensure pubDate contained in data item * fix: extract date regex into a constant for better readability * feat(route): add route for ccmn.cn * Update lib/routes/ccmn/price-adjustment.tsx Co-authored-by: Tony * fix: use default generated ua --------- --- lib/routes/ccmn/namespace.ts | 7 + lib/routes/ccmn/price-adjustment.tsx | 203 +++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 lib/routes/ccmn/namespace.ts create mode 100644 lib/routes/ccmn/price-adjustment.tsx diff --git a/lib/routes/ccmn/namespace.ts b/lib/routes/ccmn/namespace.ts new file mode 100644 index 000000000..c8329def8 --- /dev/null +++ b/lib/routes/ccmn/namespace.ts @@ -0,0 +1,7 @@ +import type { Namespace } from '@/types'; + +export const namespace: Namespace = { + name: '长江有色网', + url: 'www.ccmn.cn', + lang: 'zh-CN', +}; diff --git a/lib/routes/ccmn/price-adjustment.tsx b/lib/routes/ccmn/price-adjustment.tsx new file mode 100644 index 000000000..830434cee --- /dev/null +++ b/lib/routes/ccmn/price-adjustment.tsx @@ -0,0 +1,203 @@ +import { load } from 'cheerio'; +import { renderToString } from 'hono/jsx/dom/server'; + +import type { Route } from '@/types'; +import got from '@/utils/got'; +import { parseDate } from '@/utils/parse-date'; +import timezone from '@/utils/timezone'; + +export const route: Route = { + path: '/price-adjustment/:category', + categories: ['other'], + example: '/ccmn/price-adjustment/copper', + parameters: { + category: { + description: '金属类别', + options: [ + { label: '铜', value: 'copper' }, + { label: '铝', value: 'alu' }, + { label: '锌', value: 'zn' }, + { label: '锡', value: 'sn' }, + { label: '铅', value: 'pb' }, + { label: '镍', value: 'ni' }, + ], + }, + }, + radar: [ + { + source: ['copper.ccmn.cn/:suffix', 'copper.ccmn.cn/:suffix/:subsuffix?'], + target: '/price-adjustment/copper', + }, + { + source: ['alu.ccmn.cn/:suffix', 'alu.ccmn.cn/:suffix/:subsuffix?'], + target: '/price-adjustment/alu', + }, + { + source: ['zn.ccmn.cn/:suffix', 'zn.ccmn.cn/:suffix/:subsuffix?'], + target: '/price-adjustment/zn', + }, + { + source: ['sn.ccmn.cn/:suffix', 'sn.ccmn.cn/:suffix/:subsuffix?'], + target: '/price-adjustment/sn', + }, + { + source: ['pb.ccmn.cn/:suffix', 'pb.ccmn.cn/:suffix/:subsuffix?'], + target: '/price-adjustment/pb', + }, + { + source: ['ni.ccmn.cn/:suffix', 'ni.ccmn.cn/:suffix/:subsuffix?'], + target: '/price-adjustment/ni', + }, + ], + features: { + supportRadar: true, + requireConfig: false, + requirePuppeteer: false, + antiCrawler: false, + supportBT: false, + supportPodcast: false, + supportScihub: false, + nsfw: false, + }, + name: '调价动态', + maintainers: ['chrisis58'], + handler, + description: '长江有色网的金属调价动态,包括铜、铝、锌、锡、铅、镍等金属。', +}; + +const SUB_DOMAIN_MAP = { + copper: 'copper.ccmn.cn', + alu: 'alu.ccmn.cn', + zn: 'zn.ccmn.cn', + sn: 'sn.ccmn.cn', + pb: 'pb.ccmn.cn', + ni: 'ni.ccmn.cn', +}; + +const READABLE_CATEGORIES = { + copper: '铜', + alu: '铝', + zn: '锌', + sn: '锡', + pb: '铅', + ni: '镍', +}; + +async function handler(ctx) { + const category = ctx.req.param('category'); + + const subDomain = SUB_DOMAIN_MAP[category]; + if (!subDomain) { + throw new Error('未知的金属类型'); + } + + const url = `https://${subDomain}`; + + const response = await got({ + method: 'get', + url, + }); + + const $ = load(response.data); + + const items = $('.content1-text-div') + .toArray() + .map((item) => { + const $item = $(item); + const dataId = $item.attr('data-id'); + + const $top = $item.find('.top'); + + const regionRaw = $top.find('.left').first(); + const region = regionRaw + .contents() + .filter((_, e) => e.type === 'text') + .text() + .trim(); + + const dateStr = $top.find('.time').text().trim(); + + const metal = $top.find('.right').first().text().trim(); + + const $priceSpan = $top.find('span.clearfloat'); + const minPriceLabel = $priceSpan.find('.left').text().trim(); + const maxPriceLabel = $priceSpan.find('.right').text().trim(); + + const $bottom = $item.find('.bottom'); + const $avgSpan = $bottom.find('.up_down_span'); + + const avgPrice = $avgSpan + .contents() + .filter((_, e) => e.type === 'text') + .text() + .trim(); + + const changeStr = $avgSpan.find('.up_down').text().trim(); + const changeNum = Number.parseFloat(changeStr); + + let icon; // 如果 change 为 0 或者无法解析,则不显示图标 + if (changeNum > 0) { + icon = '🔴'; + } else if (changeNum < 0) { + icon = '🟢'; + } + + const title = `${icon ? icon + ' ' : ''}${region} ${metal} 调价: ${changeStr} (均价 ${avgPrice})`; + + const description = renderToString( + + + + + + + + + + + + + + + + + + + + + + + +
+ 品类 + {metal}
+ 厂商 + {region}
+ 均价 + {avgPrice}
+ 涨跌 + + {icon ?? ''} + {changeStr} +
+ 价格范围 + + {minPriceLabel} - {maxPriceLabel} +
+ ); + + return { + title, + description, + link: `${url}/quota/${dataId}.html`, + guid: dataId, // 使用 data-id 作为唯一标识 + pubDate: timezone(parseDate(dateStr, 'MM-DD'), +8), + }; + }); + + return { + title: `长江有色网 - 调价动态 - ${READABLE_CATEGORIES[category]}`, + link: url, + item: items, + }; +}