diff --git a/public/icons/coolapk.png b/public/icons/coolapk.png new file mode 100644 index 0000000..0da403b Binary files /dev/null and b/public/icons/coolapk.png differ diff --git a/server/sources/cankaoxiaoxi.ts b/server/sources/cankaoxiaoxi.ts new file mode 100644 index 0000000..958e895 --- /dev/null +++ b/server/sources/cankaoxiaoxi.ts @@ -0,0 +1,24 @@ +interface Res { + list: { + data: { + id: string + title: string + // 北京时间 + url: string + publishTime: string + } + }[] +} + +export default defineSource(async () => { + const res = await Promise.all(["zhongguo", "guandian", "gj"].map(k => $fetch(`https://china.cankaoxiaoxi.com/json/channel/${k}/list.json`) as Promise)) + if (!res?.[0]?.list?.length) throw new Error("Cannot fetch data") + return res.map(k => k.list).flat().map(k => ({ + id: k.data.id, + title: k.data.title, + extra: { + date: tranformToUTC(k.data.publishTime), + }, + url: k.data.url, + })).sort((m, n) => m.extra.date < n.extra.date ? 1 : -1).slice(0, 20) +}) diff --git a/server/sources/coolapk/index.ts b/server/sources/coolapk/index.ts new file mode 100644 index 0000000..0be6532 --- /dev/null +++ b/server/sources/coolapk/index.ts @@ -0,0 +1,37 @@ +import { load } from "cheerio" +import { genHeaders } from "./utils" + +interface Res { + data: { + id: string + // 多行 + message: string + // 起的标题 + editor_title: string + shareUrl: string + entityType: string + pubDate: string + // dayjs(dateline, 'X') + dateline: number + targetRow: { + // 374.4万热度 + subTitle: string + } + }[] +} + +export default defineSource(async () => { + const url = "https://api.coolapk.com/v6/page/dataList?url=%2Ffeed%2FstatList%3FcacheExpires%3D300%26statType%3Dday%26sortField%3Ddetailnum%26title%3D%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&title=%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&subTitle=&page=1" + const r: Res = await $fetch(url, { + headers: genHeaders(), + }) + if (!r.data || r.data.length === 0) throw new Error("Failed to fetch") + return r.data.filter(k => k.id).map(i => ({ + id: i.id, + title: i.editor_title || load(i.message).text().split("\n")[0], + url: i.shareUrl, + extra: { + date: new Date(i.dateline * 1000).getTime(), + }, + })).slice(0, 20) +}) diff --git a/server/sources/coolapk/utils.ts b/server/sources/coolapk/utils.ts new file mode 100644 index 0000000..62bbc2d --- /dev/null +++ b/server/sources/coolapk/utils.ts @@ -0,0 +1,34 @@ +// https://github.com/DIYgod/RSSHub/blob/master/lib/routes/coolapk/utils.ts +import { Buffer } from "node:buffer" +import md5 from "md5" + +function getRandomDEVICE_ID() { + const r = [10, 6, 6, 6, 14] + const id = r.map(i => Math.random().toString(36).substring(2, i)) + return id.join("-") +} + +function get_app_token() { + const DEVICE_ID = getRandomDEVICE_ID() + const now = Math.round(Date.now() / 1000) + const hex_now = `0x${now.toString(16)}` + const md5_now = md5(now.toString()) + const s = `token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?${md5_now}$${DEVICE_ID}&com.coolapk.market` + const md5_s = md5(Buffer.from(s).toString("base64")) + const token = md5_s + DEVICE_ID + hex_now + return token +} + +export function genHeaders() { + return { + "X-Requested-With": "XMLHttpRequest", + "X-App-Id": "com.coolapk.market", + "X-App-Token": get_app_token(), + "X-Sdk-Int": "29", + "X-Sdk-Locale": "zh-CN", + "X-App-Version": "11.0", + "X-Api-Version": "11", + "X-App-Code": "2101202", + "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 10; Redmi K30 5G MIUI/V12.0.3.0.QGICMXM) (#Build; Redmi; Redmi K30 5G; QKQ1.191222.002 test-keys; 10) +CoolMarket/11.0-2101202", + } +} diff --git a/server/sources/index.ts b/server/sources/index.ts index ad99904..597286d 100644 --- a/server/sources/index.ts +++ b/server/sources/index.ts @@ -4,16 +4,20 @@ import zaobao from "./zaobao" import v2ex from "./v2ex" import ithome from "./ithome" import zhihu from "./zhihu" +import cankaoxiaoxi from "./cankaoxiaoxi" +import coolapk from "./coolapk" export const sourcesFn = { weibo, zaobao, v2ex, ithome, + zhihu, + coolapk, + cankaoxiaoxi, "peopledaily": defineRSSSource("https://feedx.net/rss/people.xml", { hiddenDate: true, }), - "zhihu": zhihu, "sputniknewscn": defineRSSHubSource("/sputniknews/news/chinese"), "douyin": defineFallbackSource("douyin"), "aljazeeracn": defineRSSSource("https://feedx.net/rss/aljazeera.xml"), diff --git a/shared/data.ts b/shared/data.ts index 552ecd0..78dad1a 100644 --- a/shared/data.ts +++ b/shared/data.ts @@ -13,7 +13,7 @@ export const metadata: Metadata = { }, china: { name: "国内", - sources: ["peopledaily", "toutiao", "zhihu"], + sources: ["peopledaily", "toutiao", "zhihu", "cankaoxiaoxi"], }, world: { name: "国外", @@ -25,6 +25,6 @@ export const metadata: Metadata = { }, tech: { name: "科技", - sources: ["ithome"], + sources: ["ithome", "coolapk"], }, } diff --git a/shared/sources.ts b/shared/sources.ts index c3c35e1..2d2c085 100644 --- a/shared/sources.ts +++ b/shared/sources.ts @@ -1,11 +1,20 @@ import { typeSafeObjectFromEntries } from "./type.util" import type { OriginSource, Source, SourceID } from "./types" +const Time = { + half: 30 * 60 * 1000, + five: 5 * 60 * 1000, +} + export const originSources = { "v2ex": { name: "V2EX", home: "https://v2ex.com/", }, + "coolapk": { + name: "酷安", + home: "https://coolapk.com", + }, "wallstreetcn": { name: "华尔街见闻", home: "https://wallstreetcn.com/", @@ -13,14 +22,19 @@ export const originSources = { }, "sputniknewscn": { name: "俄罗斯卫星通讯社", - interval: 30 * 60 * 1000, + interval: Time.half, home: "https://sputniknews.cn", }, "aljazeeracn": { name: "半岛电视台", - interval: 30 * 60 * 1000, + interval: Time.half, home: "https://chinese.aljazeera.net", }, + "cankaoxiaoxi": { + name: "参考消息", + interval: Time.half, + home: "https://china.cankaoxiaoxi.com", + }, "36kr": { name: "36氪", home: "https://36kr.com", @@ -58,18 +72,13 @@ export const originSources = { }, "thepaper": { name: "澎湃新闻", - interval: 30 * 60 * 1000, + interval: Time.half, home: "https://www.thepaper.cn", }, "toutiao": { name: "今日头条", home: "https://www.toutiao.com", }, - "cankaoxiaoxi": { - name: "参考消息", - interval: 60 * 60 * 1000, - home: "http://www.cankaoxiaoxi.com", - }, "ithome": { name: "IT之家", home: "https://www.ithome.com",