mirror of
https://github.com/ourongxing/newsnow.git
synced 2025-01-19 03:09:14 +08:00
chore(server): clean up
This commit is contained in:
parent
f8303382ef
commit
63db52fb9a
@ -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))
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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>
|
||||||
|
@ -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,
|
||||||
})
|
})
|
||||||
|
@ -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[]>
|
||||||
|
@ -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,
|
||||||
|
@ -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: "财经",
|
||||||
|
@ -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: "知乎",
|
||||||
|
@ -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])
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user