mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
feat: add some sources
This commit is contained in:
parent
3e425cbfce
commit
f24a8834ea
@ -58,6 +58,7 @@
|
|||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-use": "^17.5.1",
|
"react-use": "^17.5.1",
|
||||||
"sonner": "^1.5.0",
|
"sonner": "^1.5.0",
|
||||||
|
"uncrypto": "^0.1.3",
|
||||||
"zod": "^3.23.8"
|
"zod": "^3.23.8"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
@ -113,6 +113,9 @@ importers:
|
|||||||
sonner:
|
sonner:
|
||||||
specifier: ^1.5.0
|
specifier: ^1.5.0
|
||||||
version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
version: 1.5.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||||
|
uncrypto:
|
||||||
|
specifier: ^0.1.3
|
||||||
|
version: 0.1.3
|
||||||
zod:
|
zod:
|
||||||
specifier: ^3.23.8
|
specifier: ^3.23.8
|
||||||
version: 3.23.8
|
version: 3.23.8
|
||||||
|
BIN
public/icons/cls.png
Normal file
BIN
public/icons/cls.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.8 KiB |
@ -20,7 +20,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
|
|||||||
|
|
||||||
const cacheTable = useCache()
|
const cacheTable = useCache()
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
if (cacheTable) {
|
if (process.env.NODE_ENV === "production" && cacheTable) {
|
||||||
if (process.env.INIT_TABLE !== "false") await cacheTable.init()
|
if (process.env.INIT_TABLE !== "false") await cacheTable.init()
|
||||||
const cache = await cacheTable.get(id)
|
const cache = await cacheTable.get(id)
|
||||||
if (cache) {
|
if (cache) {
|
||||||
|
@ -2,7 +2,8 @@ import type { NewsItem } from "@shared/types"
|
|||||||
import { load } from "cheerio"
|
import { load } from "cheerio"
|
||||||
|
|
||||||
const quick = defineSource(async () => {
|
const quick = defineSource(async () => {
|
||||||
const url = "https://www.36kr.com/newsflashes"
|
const baseURL = "https://www.36kr.com"
|
||||||
|
const url = `${baseURL}/newsflashes`
|
||||||
const response = await $fetch(url) as any
|
const response = await $fetch(url) as any
|
||||||
const $ = load(response)
|
const $ = load(response)
|
||||||
const news: NewsItem[] = []
|
const news: NewsItem[] = []
|
||||||
@ -15,7 +16,7 @@ const quick = defineSource(async () => {
|
|||||||
const relativeDate = $el.find(".time")
|
const relativeDate = $el.find(".time")
|
||||||
if (url && title && relativeDate) {
|
if (url && title && relativeDate) {
|
||||||
news.push({
|
news.push({
|
||||||
url: `https://www.36kr.com${url}`,
|
url: `${baseURL}${url}`,
|
||||||
title,
|
title,
|
||||||
id: url,
|
id: url,
|
||||||
extra: {
|
extra: {
|
||||||
|
@ -12,7 +12,6 @@ interface Res {
|
|||||||
|
|
||||||
export default defineSource(async () => {
|
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<Res>))
|
const res = await Promise.all(["zhongguo", "guandian", "gj"].map(k => $fetch(`https://china.cankaoxiaoxi.com/json/channel/${k}/list.json`) as Promise<Res>))
|
||||||
if (!res?.[0]?.list?.length) throw new Error("Cannot fetch data")
|
|
||||||
return res.map(k => k.list).flat().map(k => ({
|
return res.map(k => k.list).flat().map(k => ({
|
||||||
id: k.data.id,
|
id: k.data.id,
|
||||||
title: k.data.title,
|
title: k.data.title,
|
||||||
@ -20,5 +19,5 @@ export default defineSource(async () => {
|
|||||||
date: tranformToUTC(k.data.publishTime),
|
date: tranformToUTC(k.data.publishTime),
|
||||||
},
|
},
|
||||||
url: k.data.url,
|
url: k.data.url,
|
||||||
})).sort((m, n) => m.extra.date < n.extra.date ? 1 : -1).slice(0, 30)
|
})).sort((m, n) => m.extra.date < n.extra.date ? 1 : -1)
|
||||||
})
|
})
|
||||||
|
68
server/sources/cls/index.ts
Normal file
68
server/sources/cls/index.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { getSearchParams } from "./utils"
|
||||||
|
|
||||||
|
interface Item {
|
||||||
|
id: number
|
||||||
|
title?: string
|
||||||
|
brief: string
|
||||||
|
shareurl: string
|
||||||
|
// need *1000
|
||||||
|
ctime: number
|
||||||
|
// 1
|
||||||
|
is_ad: number
|
||||||
|
}
|
||||||
|
interface TelegraphRes {
|
||||||
|
data: {
|
||||||
|
roll_data: Item[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Depthes {
|
||||||
|
data: {
|
||||||
|
top_article: Item[]
|
||||||
|
depth_list: Item[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const depth = defineSource(async () => {
|
||||||
|
const apiUrl = `https://www.cls.cn/v3/depth/home/assembled/1000`
|
||||||
|
const res: Depthes = await $fetch(apiUrl, {
|
||||||
|
query: await getSearchParams(),
|
||||||
|
})
|
||||||
|
return res.data.depth_list.sort((m, n) => n.ctime - m.ctime).map((k) => {
|
||||||
|
return {
|
||||||
|
id: k.id,
|
||||||
|
title: k.title || k.brief,
|
||||||
|
mobileUrl: k.shareurl,
|
||||||
|
extra: {
|
||||||
|
date: k.ctime * 1000,
|
||||||
|
},
|
||||||
|
url: `https://www.cls.cn/detail/${k.id}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// hot 失效
|
||||||
|
|
||||||
|
const telegraph = defineSource(async () => {
|
||||||
|
const apiUrl = `https://www.cls.cn/nodeapi/updateTelegraphList`
|
||||||
|
const res: TelegraphRes = await $fetch(apiUrl, {
|
||||||
|
query: await getSearchParams({ }),
|
||||||
|
})
|
||||||
|
return res.data.roll_data.filter(k => !k.is_ad).map((k) => {
|
||||||
|
return {
|
||||||
|
id: k.id,
|
||||||
|
title: k.title || k.brief,
|
||||||
|
mobileUrl: k.shareurl,
|
||||||
|
extra: {
|
||||||
|
date: k.ctime * 1000,
|
||||||
|
},
|
||||||
|
url: `https://www.cls.cn/detail/${k.id}`,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineSource({
|
||||||
|
"cls": telegraph,
|
||||||
|
"cls-telegraph": telegraph,
|
||||||
|
"cls-depth": depth,
|
||||||
|
})
|
13
server/sources/cls/utils.ts
Normal file
13
server/sources/cls/utils.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
// https://github.com/DIYgod/RSSHub/blob/master/lib/routes/cls/utils.ts
|
||||||
|
const params = {
|
||||||
|
appName: "CailianpressWeb",
|
||||||
|
os: "web",
|
||||||
|
sv: "7.7.5",
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getSearchParams(moreParams?: any) {
|
||||||
|
const searchParams = new URLSearchParams({ ...params, ...moreParams })
|
||||||
|
searchParams.sort()
|
||||||
|
searchParams.append("sign", await md5(await myCrypto(searchParams.toString(), "SHA-1")))
|
||||||
|
return searchParams
|
||||||
|
}
|
@ -23,9 +23,9 @@ interface Res {
|
|||||||
export default defineSource(async () => {
|
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 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, {
|
const r: Res = await $fetch(url, {
|
||||||
headers: genHeaders(),
|
headers: await genHeaders(),
|
||||||
})
|
})
|
||||||
if (!r.data || r.data.length === 0) throw new Error("Failed to fetch")
|
if (!r.data.length) throw new Error("Failed to fetch")
|
||||||
return r.data.filter(k => k.id).map(i => ({
|
return r.data.filter(k => k.id).map(i => ({
|
||||||
id: i.id,
|
id: i.id,
|
||||||
title: i.editor_title || load(i.message).text().split("\n")[0],
|
title: i.editor_title || load(i.message).text().split("\n")[0],
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
// https://github.com/DIYgod/RSSHub/blob/master/lib/routes/coolapk/utils.ts
|
// https://github.com/DIYgod/RSSHub/blob/master/lib/routes/coolapk/utils.ts
|
||||||
import { Buffer } from "node:buffer"
|
import { Buffer } from "node:buffer"
|
||||||
import md5 from "md5"
|
|
||||||
|
|
||||||
function getRandomDEVICE_ID() {
|
function getRandomDEVICE_ID() {
|
||||||
const r = [10, 6, 6, 6, 14]
|
const r = [10, 6, 6, 6, 14]
|
||||||
@ -8,22 +7,22 @@ function getRandomDEVICE_ID() {
|
|||||||
return id.join("-")
|
return id.join("-")
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_app_token() {
|
async function get_app_token() {
|
||||||
const DEVICE_ID = getRandomDEVICE_ID()
|
const DEVICE_ID = getRandomDEVICE_ID()
|
||||||
const now = Math.round(Date.now() / 1000)
|
const now = Math.round(Date.now() / 1000)
|
||||||
const hex_now = `0x${now.toString(16)}`
|
const hex_now = `0x${now.toString(16)}`
|
||||||
const md5_now = md5(now.toString())
|
const md5_now = await md5(now.toString())
|
||||||
const s = `token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?${md5_now}$${DEVICE_ID}&com.coolapk.market`
|
const s = `token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?${md5_now}$${DEVICE_ID}&com.coolapk.market`
|
||||||
const md5_s = md5(Buffer.from(s).toString("base64"))
|
const md5_s = await md5(Buffer.from(s).toString("base64"))
|
||||||
const token = md5_s + DEVICE_ID + hex_now
|
const token = md5_s + DEVICE_ID + hex_now
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|
||||||
export function genHeaders() {
|
export async function genHeaders() {
|
||||||
return {
|
return {
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
"X-Requested-With": "XMLHttpRequest",
|
||||||
"X-App-Id": "com.coolapk.market",
|
"X-App-Id": "com.coolapk.market",
|
||||||
"X-App-Token": get_app_token(),
|
"X-App-Token": await get_app_token(),
|
||||||
"X-Sdk-Int": "29",
|
"X-Sdk-Int": "29",
|
||||||
"X-Sdk-Locale": "zh-CN",
|
"X-Sdk-Locale": "zh-CN",
|
||||||
"X-App-Version": "11.0",
|
"X-App-Version": "11.0",
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
interface Res {
|
interface Res {
|
||||||
data?: {
|
data: {
|
||||||
word_list?: {
|
word_list: {
|
||||||
sentence_id: string
|
sentence_id: string
|
||||||
word: string
|
word: string
|
||||||
event_time: string
|
event_time: string
|
||||||
@ -33,16 +33,11 @@ export default defineSource(async () => {
|
|||||||
Cookie: `passport_csrf_token=${cookie}`,
|
Cookie: `passport_csrf_token=${cookie}`,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
if (!res?.data?.word_list || res.data.word_list.length === 0) throw new Error("Cannot fetch data")
|
|
||||||
return res.data.word_list
|
return res.data.word_list
|
||||||
.slice(0, 30)
|
|
||||||
.map((k) => {
|
.map((k) => {
|
||||||
return {
|
return {
|
||||||
id: k.sentence_id,
|
id: k.sentence_id,
|
||||||
title: k.word,
|
title: k.word,
|
||||||
extra: {
|
|
||||||
info: k.hot_value,
|
|
||||||
},
|
|
||||||
url: `https://www.douyin.com/hot/${k.sentence_id}`,
|
url: `https://www.douyin.com/hot/${k.sentence_id}`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import type { SourceID } from "@shared/types"
|
import type { DisabledSourceID, SourceID } from "@shared/types"
|
||||||
import weibo from "./weibo"
|
import weibo from "./weibo"
|
||||||
import zaobao from "./zaobao"
|
import zaobao from "./zaobao"
|
||||||
import v2ex from "./v2ex"
|
import v2ex from "./v2ex"
|
||||||
@ -6,11 +6,12 @@ import ithome from "./ithome"
|
|||||||
import zhihu from "./zhihu"
|
import zhihu from "./zhihu"
|
||||||
import cankaoxiaoxi from "./cankaoxiaoxi"
|
import cankaoxiaoxi from "./cankaoxiaoxi"
|
||||||
import coolapk from "./coolapk"
|
import coolapk from "./coolapk"
|
||||||
import sputniknewscn from "./sputniknewscn"
|
|
||||||
import kr36 from "./36kr"
|
import kr36 from "./36kr"
|
||||||
import wallstreetcn from "./wallstreetcn"
|
import wallstreetcn from "./wallstreetcn"
|
||||||
import douyin from "./douyin"
|
import douyin from "./douyin"
|
||||||
import toutiao from "./toutiao"
|
import toutiao from "./toutiao"
|
||||||
|
import cls from "./cls"
|
||||||
|
import sputniknewscn from "./sputniknewscn"
|
||||||
import type { SourceGetter } from "#/types"
|
import type { SourceGetter } from "#/types"
|
||||||
|
|
||||||
export const sourcesGetters = {
|
export const sourcesGetters = {
|
||||||
@ -22,8 +23,9 @@ export const sourcesGetters = {
|
|||||||
coolapk,
|
coolapk,
|
||||||
cankaoxiaoxi,
|
cankaoxiaoxi,
|
||||||
sputniknewscn,
|
sputniknewscn,
|
||||||
wallstreetcn,
|
...wallstreetcn,
|
||||||
douyin,
|
douyin,
|
||||||
|
...cls,
|
||||||
toutiao,
|
toutiao,
|
||||||
...kr36,
|
...kr36,
|
||||||
} as Record<SourceID, SourceGetter>
|
} as Record<SourceID, SourceGetter> & Partial<Record<DisabledSourceID, SourceGetter>>
|
||||||
|
@ -28,5 +28,4 @@ export default defineSource(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
return news.sort((m, n) => n.extra!.date > m.extra!.date ? 1 : -1)
|
return news.sort((m, n) => n.extra!.date > m.extra!.date ? 1 : -1)
|
||||||
.slice(0, 30)
|
|
||||||
})
|
})
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import * as cheerio from "cheerio"
|
import * as cheerio from "cheerio"
|
||||||
import type { NewsItem } from "@shared/types"
|
import type { NewsItem } from "@shared/types"
|
||||||
import { $fetch } from "ofetch"
|
|
||||||
|
|
||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const response = await $fetch("https://sputniknews.cn/services/widget/lenta/")
|
const response: any = await $fetch("https://sputniknews.cn/services/widget/lenta/")
|
||||||
const $ = cheerio.load(response)
|
const $ = cheerio.load(response)
|
||||||
const $items = $(".lenta__item")
|
const $items = $(".lenta__item")
|
||||||
const news: NewsItem[] = []
|
const news: NewsItem[] = []
|
||||||
@ -24,5 +23,5 @@ export default defineSource(async () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
return news.slice(0, 30)
|
return news
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
interface Res {
|
interface Res {
|
||||||
data?: {
|
data: {
|
||||||
ClusterIdStr: string
|
ClusterIdStr: string
|
||||||
Title: string
|
Title: string
|
||||||
HotValue: string
|
HotValue: string
|
||||||
@ -12,16 +12,11 @@ interface Res {
|
|||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const url = "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc"
|
const url = "https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc"
|
||||||
const res: Res = await $fetch(url)
|
const res: Res = await $fetch(url)
|
||||||
if (!res.data || res.data.length === 0) throw new Error("Cannot fetch data")
|
|
||||||
return res.data
|
return res.data
|
||||||
.slice(0, 30)
|
|
||||||
.map((k) => {
|
.map((k) => {
|
||||||
return {
|
return {
|
||||||
id: k.ClusterIdStr,
|
id: k.ClusterIdStr,
|
||||||
title: k.Title,
|
title: k.Title,
|
||||||
extra: {
|
|
||||||
info: k.HotValue,
|
|
||||||
},
|
|
||||||
url: `https://www.toutiao.com/trending/${k.ClusterIdStr}/`,
|
url: `https://www.toutiao.com/trending/${k.ClusterIdStr}/`,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -18,7 +18,6 @@ interface Res {
|
|||||||
|
|
||||||
const share = defineSource(async () => {
|
const share = defineSource(async () => {
|
||||||
const res = await Promise.all(["create", "ideas", "programmer", "share"].map(k => $fetch(`https://www.v2ex.com/feed/${k}.json`) as Promise< Res>))
|
const res = await Promise.all(["create", "ideas", "programmer", "share"].map(k => $fetch(`https://www.v2ex.com/feed/${k}.json`) as Promise< Res>))
|
||||||
if (!res?.[0]?.items?.length) throw new Error("Cannot fetch data")
|
|
||||||
return res.map(k => k.items).flat().map(k => ({
|
return res.map(k => k.items).flat().map(k => ({
|
||||||
id: k.id,
|
id: k.id,
|
||||||
title: k.title,
|
title: k.title,
|
||||||
|
@ -1,33 +1,87 @@
|
|||||||
interface Res {
|
interface Item {
|
||||||
|
uri: string
|
||||||
|
id: number
|
||||||
|
title?: string
|
||||||
|
// ad
|
||||||
|
resource_type?: string
|
||||||
|
content_text: string
|
||||||
|
content_short: string
|
||||||
|
display_time: number
|
||||||
|
type?: string
|
||||||
|
}
|
||||||
|
interface LiveRes {
|
||||||
|
data: {
|
||||||
|
items: Item[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NewsRes {
|
||||||
data: {
|
data: {
|
||||||
items: {
|
items: {
|
||||||
uri: string
|
resource: Item
|
||||||
id: number
|
|
||||||
title?: string
|
|
||||||
content_text: string
|
|
||||||
display_time: number
|
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/DIYgod/RSSHub/blob/master/lib/routes/wallstreetcn/live.ts
|
interface HotRes {
|
||||||
export default defineSource(async () => {
|
data: {
|
||||||
const category = "global"
|
day_items: Item[]
|
||||||
const apiRootUrl = "https://api-one.wallstcn.com"
|
}
|
||||||
const apiUrl = `${apiRootUrl}/apiv1/content/lives?channel=${category}-channel&limit=30`
|
}
|
||||||
|
|
||||||
const res: Res = await $fetch(apiUrl)
|
// https://github.com/DIYgod/RSSHub/blob/master/lib/routes/wallstreetcn/live.ts
|
||||||
if (!res?.data?.items || res.data.items.length === 0) throw new Error("Cannot fetch data")
|
const live = defineSource(async () => {
|
||||||
|
const apiUrl = `https://api-one.wallstcn.com/apiv1/content/lives?channel=global-channel&limit=30`
|
||||||
|
|
||||||
|
const res: LiveRes = await $fetch(apiUrl)
|
||||||
return res.data.items
|
return res.data.items
|
||||||
.slice(0, 30)
|
|
||||||
.map((k) => {
|
.map((k) => {
|
||||||
return {
|
return {
|
||||||
id: k.id,
|
id: k.id,
|
||||||
title: k.title || k.content_text,
|
title: k.title || k.content_text,
|
||||||
extra: {
|
extra: {
|
||||||
date: new Date(k.display_time * 1000).getTime(),
|
date: k.display_time * 1000,
|
||||||
},
|
},
|
||||||
url: k.uri,
|
url: k.uri,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const news = defineSource(async () => {
|
||||||
|
const apiUrl = `https://api-one.wallstcn.com/apiv1/content/information-flow?channel=global-channel&accept=article&limit=30`
|
||||||
|
|
||||||
|
const res: NewsRes = await $fetch(apiUrl)
|
||||||
|
return res.data.items
|
||||||
|
.filter(k => k.resource.resource_type !== "ad" && k.resource.type !== "live")
|
||||||
|
.map(({ resource: h }) => {
|
||||||
|
return {
|
||||||
|
id: h.id,
|
||||||
|
title: h.title || h.content_short,
|
||||||
|
extra: {
|
||||||
|
date: h.display_time * 1000,
|
||||||
|
},
|
||||||
|
url: h.uri,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const hot = defineSource(async () => {
|
||||||
|
const apiUrl = `https://api-one.wallstcn.com/apiv1/content/articles/hot?period=all`
|
||||||
|
|
||||||
|
const res: HotRes = await $fetch(apiUrl)
|
||||||
|
return res.data.day_items
|
||||||
|
.map((h) => {
|
||||||
|
return {
|
||||||
|
id: h.id,
|
||||||
|
title: h.title!,
|
||||||
|
url: h.uri,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
export default defineSource({
|
||||||
|
"wallstreetcn": live,
|
||||||
|
"wallstreetcn-quick": live,
|
||||||
|
"wallstreetcn-news": news,
|
||||||
|
"wallstreetcn-hot": hot,
|
||||||
|
})
|
||||||
|
@ -28,10 +28,8 @@ interface Res {
|
|||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const url = "https://weibo.com/ajax/side/hotSearch"
|
const url = "https://weibo.com/ajax/side/hotSearch"
|
||||||
const res: Res = await $fetch(url)
|
const res: Res = await $fetch(url)
|
||||||
if (!res.ok || res.data.realtime.length === 0) throw new Error("Cannot fetch data")
|
|
||||||
return res.data.realtime
|
return res.data.realtime
|
||||||
.filter(k => !k.is_ad)
|
.filter(k => !k.is_ad)
|
||||||
.slice(0, 30)
|
|
||||||
.map((k) => {
|
.map((k) => {
|
||||||
const keyword = k.word_scheme ? k.word_scheme : `#${k.word}#`
|
const keyword = k.word_scheme ? k.word_scheme : `#${k.word}#`
|
||||||
return {
|
return {
|
||||||
|
@ -31,5 +31,4 @@ export default defineSource(async () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
return news.sort((m, n) => n.extra!.date > m.extra!.date ? 1 : -1)
|
return news.sort((m, n) => n.extra!.date > m.extra!.date ? 1 : -1)
|
||||||
.slice(0, 30)
|
|
||||||
})
|
})
|
||||||
|
@ -22,7 +22,6 @@ interface Res {
|
|||||||
export default defineSource(async () => {
|
export default defineSource(async () => {
|
||||||
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true"
|
const url = "https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=20&desktop=true"
|
||||||
const res: Res = await $fetch(url)
|
const res: Res = await $fetch(url)
|
||||||
if (!res.data || res.data.length === 0) throw new Error("Cannot fetch data")
|
|
||||||
return res.data
|
return res.data
|
||||||
.slice(0, 30)
|
.slice(0, 30)
|
||||||
.map((k) => {
|
.map((k) => {
|
||||||
|
24
server/utils/crypto.ts
Normal file
24
server/utils/crypto.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import _md5 from "md5"
|
||||||
|
import { subtle as _ } from "uncrypto"
|
||||||
|
|
||||||
|
type T = typeof crypto.subtle
|
||||||
|
const subtle: T = _
|
||||||
|
|
||||||
|
export async function md5(s: string) {
|
||||||
|
try {
|
||||||
|
// https://developers.cloudflare.com/workers/runtime-apis/web-crypto/
|
||||||
|
// cloudflare worker support md5
|
||||||
|
return await myCrypto(s, "MD5")
|
||||||
|
} catch {
|
||||||
|
return _md5(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Algorithm = "MD5" | "SHA-1" | "SHA-256" | "SHA-384" | "SHA-512"
|
||||||
|
export async function myCrypto(s: string, algorithm: Algorithm) {
|
||||||
|
const sUint8 = new TextEncoder().encode(s)
|
||||||
|
const hashBuffer = await subtle.digest(algorithm, sUint8)
|
||||||
|
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
||||||
|
const hashHex = hashArray.map(b => b.toString(16).padStart(2, "0")).join("")
|
||||||
|
return hashHex
|
||||||
|
}
|
@ -1,8 +1,8 @@
|
|||||||
import type { SourceID } from "@shared/types"
|
import type { AllSourceID, SourceID } from "@shared/types"
|
||||||
import defu from "defu"
|
import defu from "defu"
|
||||||
import type { FallbackResponse, RSSHubOption, RSSHubInfo as RSSHubResponse, SourceGetter, SourceOption } from "#/types"
|
import type { FallbackResponse, RSSHubOption, RSSHubInfo as RSSHubResponse, SourceGetter, SourceOption } from "#/types"
|
||||||
|
|
||||||
type X = SourceGetter | Partial<Record<SourceID, SourceGetter>>
|
type X = SourceGetter | Partial<Record<AllSourceID, SourceGetter>>
|
||||||
export function defineSource<T extends X>(source: T): T {
|
export function defineSource<T extends X>(source: T): T {
|
||||||
return source
|
return source
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ const originMetadata: Metadata = {
|
|||||||
},
|
},
|
||||||
finance: {
|
finance: {
|
||||||
name: "财经",
|
name: "财经",
|
||||||
sources: ["wallstreetcn", "36kr-quick"],
|
sources: ["cls-telegraph", "cls-depth", "wallstreetcn", "wallstreetcn-hot", "wallstreetcn-news"],
|
||||||
},
|
},
|
||||||
focus: {
|
focus: {
|
||||||
name: "关注",
|
name: "关注",
|
||||||
|
@ -52,16 +52,30 @@ export const originSources = {
|
|||||||
},
|
},
|
||||||
"wallstreetcn": {
|
"wallstreetcn": {
|
||||||
name: "华尔街见闻",
|
name: "华尔街见闻",
|
||||||
interval: Time.Fast,
|
|
||||||
type: "realtime",
|
|
||||||
color: "blue",
|
color: "blue",
|
||||||
home: "https://wallstreetcn.com/",
|
home: "https://wallstreetcn.com/",
|
||||||
title: "快讯",
|
sub: {
|
||||||
|
quick: {
|
||||||
|
type: "realtime",
|
||||||
|
interval: Time.Fast,
|
||||||
|
title: "实时快讯",
|
||||||
|
},
|
||||||
|
news: {
|
||||||
|
title: "最新资讯",
|
||||||
|
interval: Time.Common,
|
||||||
|
},
|
||||||
|
hot: {
|
||||||
|
title: "最热文章",
|
||||||
|
type: "hottest",
|
||||||
|
interval: Time.Common,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"36kr": {
|
"36kr": {
|
||||||
name: "36氪",
|
name: "36氪",
|
||||||
type: "realtime",
|
type: "realtime",
|
||||||
color: "blue",
|
color: "blue",
|
||||||
|
disable: true,
|
||||||
home: "https://36kr.com",
|
home: "https://36kr.com",
|
||||||
sub: {
|
sub: {
|
||||||
quick: {
|
quick: {
|
||||||
@ -115,6 +129,22 @@ export const originSources = {
|
|||||||
interval: Time.Common,
|
interval: Time.Common,
|
||||||
home: "https://china.cankaoxiaoxi.com",
|
home: "https://china.cankaoxiaoxi.com",
|
||||||
},
|
},
|
||||||
|
"cls": {
|
||||||
|
name: "财联社",
|
||||||
|
color: "red",
|
||||||
|
home: "https://www.cls.cn",
|
||||||
|
sub: {
|
||||||
|
telegraph: {
|
||||||
|
title: "电报",
|
||||||
|
interval: Time.Fast,
|
||||||
|
type: "realtime",
|
||||||
|
},
|
||||||
|
depth: {
|
||||||
|
title: "深度头条",
|
||||||
|
interval: Time.Common,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
} as const satisfies Record<string, OriginSource>
|
} as const satisfies Record<string, OriginSource>
|
||||||
|
|
||||||
export const sources = genSources()
|
export const sources = genSources()
|
||||||
|
@ -15,6 +15,15 @@ export type SourceID = {
|
|||||||
}[keyof SubSource] | Key : Key;
|
}[keyof SubSource] | Key : Key;
|
||||||
}[MainSourceID]
|
}[MainSourceID]
|
||||||
|
|
||||||
|
export type AllSourceID = {
|
||||||
|
[Key in MainSourceID]: ConstSources[Key] extends { sub?: infer SubSource } ? keyof {
|
||||||
|
// @ts-expect-error >_<
|
||||||
|
[SubKey in keyof SubSource as `${Key}-${SubKey}`]: never
|
||||||
|
} | Key : Key
|
||||||
|
}[MainSourceID]
|
||||||
|
|
||||||
|
export type DisabledSourceID = Exclude<SourceID, MainSourceID>
|
||||||
|
|
||||||
export type ColumnID = (typeof columnIds)[number]
|
export type ColumnID = (typeof columnIds)[number]
|
||||||
export type Metadata = Record<ColumnID, Column>
|
export type Metadata = Record<ColumnID, Column>
|
||||||
|
|
||||||
|
@ -74,6 +74,7 @@ export default defineConfig({
|
|||||||
experimental: {
|
experimental: {
|
||||||
database: true,
|
database: true,
|
||||||
},
|
},
|
||||||
|
sourceMap: false,
|
||||||
database: {
|
database: {
|
||||||
default: {
|
default: {
|
||||||
connector: isCF ? "cloudflare-d1" : "sqlite",
|
connector: isCF ? "cloudflare-d1" : "sqlite",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user