chore(server): clean up

This commit is contained in:
Ou 2024-10-20 20:28:49 +08:00
parent f8303382ef
commit 63db52fb9a
9 changed files with 80 additions and 62 deletions

View File

@ -2,7 +2,7 @@ import process from "node:process"
import { TTL } from "@shared/consts" import { TTL } from "@shared/consts"
import type { SourceID, SourceResponse } from "@shared/types" import type { SourceID, SourceResponse } from "@shared/types"
import { sources } from "@shared/sources" import { sources } from "@shared/sources"
import { sourcesFn } from "#/sources" import { sourcesGetters } from "#/sources"
import { useCache } from "#/hooks/useCache" import { useCache } from "#/hooks/useCache"
export default defineEventHandler(async (event): Promise<SourceResponse> => { export default defineEventHandler(async (event): Promise<SourceResponse> => {
@ -10,7 +10,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
let id = getRouterParam(event, "id") as SourceID let id = getRouterParam(event, "id") as SourceID
const query = getQuery(event) const query = getQuery(event)
const latest = query.latest !== undefined && query.latest !== "false" const latest = query.latest !== undefined && query.latest !== "false"
const isValid = (id: SourceID) => !id || !sources[id] || !sourcesFn[id] const isValid = (id: SourceID) => !id || !sources[id] || !sourcesGetters[id]
if (isValid(id)) { if (isValid(id)) {
const redirectID = sources?.[id].redirect const redirectID = sources?.[id].redirect
@ -54,7 +54,7 @@ export default defineEventHandler(async (event): Promise<SourceResponse> => {
} }
} }
const data = await sourcesFn[id]() const data = (await sourcesGetters[id]()).slice(0, 30)
logger.success(`fetch ${id} latest`) logger.success(`fetch ${id} latest`)
if (cacheTable) { if (cacheTable) {
if (event.context.waitUntil) event.context.waitUntil(cacheTable.set(id, data)) if (event.context.waitUntil) event.context.waitUntil(cacheTable.set(id, data))

View File

@ -1,7 +1,7 @@
import type { NewsItem } from "@shared/types" import type { NewsItem } from "@shared/types"
import { load } from "cheerio" import { load } from "cheerio"
export default defineSource(async () => { const quick = defineSource(async () => {
const url = "https://www.36kr.com/newsflashes" const url = "https://www.36kr.com/newsflashes"
const response = await $fetch(url) as any const response = await $fetch(url) as any
const $ = load(response) const $ = load(response)
@ -25,5 +25,10 @@ export default defineSource(async () => {
} }
}) })
return news.slice(0, 30) return news
})
export default defineSource({
"36kr": quick,
"36kr-quick": quick,
}) })

View File

@ -1,4 +1,4 @@
import type { NewsItem, SourceID } from "@shared/types" import type { 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"
@ -11,11 +11,12 @@ 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 type { SourceGetter } from "#/types"
export const sourcesFn = { export const sourcesGetters = {
weibo, weibo,
zaobao, zaobao,
v2ex, ...v2ex,
ithome, ithome,
zhihu, zhihu,
coolapk, coolapk,
@ -24,5 +25,5 @@ export const sourcesFn = {
wallstreetcn, wallstreetcn,
douyin, douyin,
toutiao, toutiao,
"36kr-quick": kr36, ...kr36,
} as Record<SourceID, () => Promise<NewsItem[]>> } as Record<SourceID, SourceGetter>

View File

@ -16,7 +16,7 @@ interface Res {
}[] }[]
} }
export default 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") 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 => ({
@ -26,5 +26,10 @@ export default defineSource(async () => {
date: k.date_modified ?? k.date_published, date: k.date_modified ?? k.date_published,
}, },
url: k.url, url: k.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)
})
export default defineSource({
"v2ex": share,
"v2ex-share": share,
}) })

View File

@ -44,3 +44,34 @@ export interface UserInfo {
created: number created: number
updated: number updated: number
} }
export interface RSSHubOption {
// default: true
sorted?: boolean
// default: 20
limit?: number
}
export interface SourceOption {
// default: false
hiddenDate?: boolean
}
export interface FallbackResponse {
code: number
message: string
name: string
title: string
subtitle: string
total: number
updateTime: string
data: {
title: string
desc: string
time?: string
url: string
mobileUrl: string
}[]
}
export type SourceGetter = () => Promise<NewsItem[]>

View File

@ -1,36 +1,18 @@
import type { NewsItem, RSSHubInfo, SourceID } from "@shared/types" import type { SourceID } from "@shared/types"
import defu from "defu"
import type { FallbackResponse, RSSHubOption, RSSHubInfo as RSSHubResponse, SourceGetter, SourceOption } from "#/types"
export function defineSource(source: () => Promise<NewsItem[]>): () => Promise<NewsItem[]> { type X = SourceGetter | Partial<Record<SourceID, SourceGetter>>
export function defineSource<T extends X>(source: T): T {
return source return source
} }
interface SourceOption { export function defineFallbackSource(id: SourceID, option?: SourceOption): SourceGetter {
// default: false
hiddenDate?: boolean
}
interface FallbackRes {
code: number
message: string
name: string
title: string
subtitle: string
total: number
updateTime: string
data: {
title: string
desc: string
time?: string
url: string
mobileUrl: string
}[]
}
export function defineFallbackSource(id: SourceID, option?: SourceOption): () => Promise<NewsItem[]> {
return async () => { return async () => {
const url = `https://smzdk.top/api/${id}/new` const url = `https://smzdk.top/api/${id}/new`
const res: FallbackRes = await $fetch(url) const res: FallbackResponse = await $fetch(url)
if (res.code !== 200 || !res.data) throw new Error(res.message) if (res.code !== 200 || !res.data) throw new Error(res.message)
return res.data.slice(0, 30).map(item => ({ return res.data.map(item => ({
extra: { extra: {
date: !option?.hiddenDate && item.time, date: !option?.hiddenDate && item.time,
}, },
@ -42,11 +24,11 @@ export function defineFallbackSource(id: SourceID, option?: SourceOption): () =>
} }
} }
export function defineRSSSource(url: string, option?: SourceOption): () => Promise<NewsItem[]> { export function defineRSSSource(url: string, option?: SourceOption): SourceGetter {
return async () => { return async () => {
const data = await rss2json(url) const data = await rss2json(url)
if (!data?.items.length) throw new Error("Cannot fetch data") if (!data?.items.length) throw new Error("Cannot fetch data")
return data.items.slice(0, 30).map(item => ({ return data.items.map(item => ({
title: item.title, title: item.title,
url: item.link, url: item.link,
id: item.link, id: item.link,
@ -57,28 +39,21 @@ export function defineRSSSource(url: string, option?: SourceOption): () => Promi
} }
} }
interface RSSHubOption { export function defineRSSHubSource(route: string, RSSHubOptions?: RSSHubOption, sourceOption?: SourceOption): SourceGetter {
// default: true
sorted?: boolean
// default: 20
limit?: number
}
export function defineRSSHubSource(route: string, RSSHubOptions?: RSSHubOption, sourceOption?: SourceOption): () => Promise<NewsItem[]> {
return async () => { return async () => {
// "https://rsshub.pseudoyu.com" // "https://rsshub.pseudoyu.com"
const RSSHubBase = "https://rsshub.rssforever.com" const RSSHubBase = "https://rsshub.rssforever.com"
const url = new URL(route, RSSHubBase) const url = new URL(route, RSSHubBase)
url.searchParams.set("format", "json") url.searchParams.set("format", "json")
const defaultRSSHubOption: RSSHubOption = { RSSHubOptions = defu<RSSHubOption, RSSHubOption[]>(RSSHubOptions, {
sorted: true, sorted: true,
limit: 20, })
}
Object.assign(defaultRSSHubOption, RSSHubOptions) Object.entries(RSSHubOptions).forEach(([key, value]) => {
Object.entries(defaultRSSHubOption).forEach(([key, value]) => {
url.searchParams.set(key, value.toString()) url.searchParams.set(key, value.toString())
}) })
const data: RSSHubInfo = await $fetch(url) const data: RSSHubResponse = await $fetch(url)
return data.items.slice(0, 30).map(item => ({ return data.items.map(item => ({
title: item.title, title: item.title,
url: item.url, url: item.url,
id: item.id ?? item.url, id: item.id ?? item.url,

View File

@ -15,7 +15,7 @@ const originMetadata: Metadata = {
}, },
tech: { tech: {
name: "科技", name: "科技",
sources: ["ithome", "v2ex", "coolapk", "36kr-quick"], sources: ["ithome", "v2ex", "coolapk"],
}, },
finance: { finance: {
name: "财经", name: "财经",

View File

@ -16,6 +16,11 @@ export const originSources = {
name: "V2EX", name: "V2EX",
color: "slate", color: "slate",
home: "https://v2ex.com/", home: "https://v2ex.com/",
sub: {
share: {
title: "最新分享",
},
},
}, },
"zhihu": { "zhihu": {
name: "知乎", name: "知乎",

View File

@ -4,25 +4,21 @@ import { useRegisterSW } from "virtual:pwa-register/react"
export function usePWA() { export function usePWA() {
const { const {
offlineReady: [offlineReady, setOfflineReady],
needRefresh: [needRefresh, setNeedRefresh], needRefresh: [needRefresh, setNeedRefresh],
updateServiceWorker, updateServiceWorker,
} = useRegisterSW() } = useRegisterSW()
useEffect(() => { useEffect(() => {
if (offlineReady) { if (needRefresh) {
toast.info("PWA 准备好了") toast("网站有更新,点击更新", {
} else if (needRefresh) {
toast("有更新,点击更新", {
action: { action: {
label: "更新", label: "更新",
onClick: () => updateServiceWorker(true), onClick: () => updateServiceWorker(true),
}, },
onDismiss: () => { onDismiss: () => {
setOfflineReady(false)
setNeedRefresh(false) setNeedRefresh(false)
}, },
}) })
} }
}, [offlineReady, needRefresh, updateServiceWorker, setOfflineReady, setNeedRefresh]) }, [needRefresh, updateServiceWorker, setNeedRefresh])
} }