28 Commits
Author SHA1 Message Date
ZSCGR 819640c683 解决与原作者代码的冲突 2025-11-23 00:51:46 +08:00
ourongxing fcdf837435 chore: release v0.0.37 2025-11-20 10:14:46 +00:00
ourongxing fa9f552d73 fix: kaopu source 2025-11-20 10:06:54 +00:00
ourongxing 7e88e142b2 fix: weibo 2025-11-20 09:59:51 +00:00
likanugandGitHub b60bb219ec feat: Add new source: 腾讯新闻-综合早报 (#255) 2025-11-20 00:28:55 +08:00
ourongxing 3e870e1e37 chore: remove autorefresh script 2025-11-01 10:09:07 +00:00
ourongxing 3721fd383a feat(source): add steam 2025-10-27 16:58:16 +00:00
ourongxing 8142074b3c chore: release v0.0.36 2025-10-27 16:25:24 +00:00
ourongxing 5a2e2a5e9c chore: minor changes 2025-10-27 16:20:57 +00:00
ourongxing cb9b320996 feat(docker): add PRODUCTHUNT_API_TOKEN environment variable 2025-10-27 16:18:30 +00:00
ourongxing 3b845ce6b5 fix(source): mktnews 2025-10-27 16:14:01 +00:00
ourongxing a0941eff63 fix(source): producthunt by offical api 2025-10-27 16:10:04 +00:00
ourongxing 7e6805ca87 chore(source): opt display style 2025-10-27 15:51:58 +00:00
likanugandGitHub 4b89a57889 feat: Add new source: 豆瓣热门电影 (#237)
* feat: Add new source 豆瓣热门电影

* refactor: Simplify the if condition
2025-10-27 23:38:59 +08:00
5627cbef60 fix: kaopu source (#238)
Co-authored-by: kanson <[email protected]>
2025-10-27 23:38:39 +08:00
ZSCGRandGitHub 57dc2c319c Update sources.json 2025-09-28 18:47:55 +08:00
ZSCGRandGitHub 84543b12b6 Update idcflare.ts 2025-09-22 20:38:02 +08:00
ZSCGRandGitHub 3584efb09b Update idcflare.ts 2025-09-21 21:49:31 +08:00
ZSCGRandGitHub f357e920b7 Update weibo.ts 2025-09-21 21:43:09 +08:00
ZSCGRandGitHub dca03c4f0c Update refresh.ts 2025-09-21 21:42:34 +08:00
ZSCGRandGitHub 576137a62f Update glob.d.ts 2025-09-21 21:31:28 +08:00
ZSCGRandGitHub a896f15c44 Update pinyin.json 2025-09-21 21:11:19 +08:00
ZSCGRandGitHub f78eead5fd Create idcflare.ts 2025-09-21 21:07:18 +08:00
ZSCGRandGitHub 6fe8f03f16 Update sources.json 2025-09-21 21:04:38 +08:00
ZSCGRandGitHub 8e7b35385b Update pre-sources.ts 2025-09-21 21:03:29 +08:00
ourongxing f2698ba2f0 chore: release v0.0.35 2025-09-17 14:01:08 +08:00
ourongxing 5a07c02ad0 fix: adjust layout in NewsListHot component for better alignment 2025-09-17 14:00:40 +08:00
ourongxing ce0baddc78 fix: weibo source 2025-09-17 13:44:19 +08:00
21 changed files with 420 additions and 201 deletions
-23
View File
@@ -1,23 +0,0 @@
name: Auto Refresh
on:
schedule:
- cron: '*/20 0-17,22,23 * * *'
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Refresh
env:
JWT_TOKEN: ${{ secrets.JWT_TOKEN }}
run: npx tsx ./scripts/refresh.ts
+1
View File
@@ -15,6 +15,7 @@ services:
- JWT_SECRET=
- INIT_TABLE=true
- ENABLE_CACHE=true
- PRODUCTHUNT_API_TOKEN=
volumes:
newsnow_data:
+2 -1
View File
@@ -2,4 +2,5 @@ G_CLIENT_ID=
G_CLIENT_SECRET=
JWT_SECRET=
INIT_TABLE=true
ENABLE_CACHE=true
ENABLE_CACHE=true
PRODUCTHUNT_API_TOKEN=
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "newsnow",
"type": "module",
"version": "0.0.34",
"version": "0.0.37",
"private": true,
"packageManager": "[email protected]+sha512.ad27a79641b49c3e481a16a805baa71817a04bbe06a38d17e60e2eaee83f6a146c6a688125f5792e48dd5ba30e7da52a5cda4c3992b9ccf333f9ce223af84748",
"author": {
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

-5
View File
@@ -1,5 +0,0 @@
import sources from "../shared/sources.json"
Promise.all(Object.keys(sources).map(id =>
fetch(`https://newsnow.busiyi.world/api/s?id=${id}`),
)).catch(console.error)
+4
View File
@@ -8,6 +8,7 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
export const chongbuluo: typeof import('./sources/chongbuluo')
export const cls: typeof import('./sources/cls/index')
export const coolapk: typeof import('./sources/coolapk/index')
export const douban: typeof import('./sources/douban')
export const douyin: typeof import('./sources/douyin')
export const fastbull: typeof import('./sources/fastbull')
export const gelonghui: typeof import('./sources/gelonghui')
@@ -22,6 +23,7 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
export const kaopu: typeof import('./sources/kaopu')
export const kuaishou: typeof import('./sources/kuaishou')
export const linuxdo: typeof import('./sources/linuxdo')
export const idcflare: typeof import('./sources/idcflare')
export const mktnews: typeof import('./sources/mktnews')
export const nowcoder: typeof import('./sources/nowcoder')
export const pcbeta: typeof import('./sources/pcbeta')
@@ -30,6 +32,8 @@ declare module 'glob:./sources/{*.ts,**/index.ts}' {
export const solidot: typeof import('./sources/solidot')
export const sputniknewscn: typeof import('./sources/sputniknewscn')
export const sspai: typeof import('./sources/sspai')
export const steam: typeof import('./sources/steam')
export const tencent: typeof import('./sources/tencent')
export const thepaper: typeof import('./sources/thepaper')
export const tieba: typeof import('./sources/tieba')
export const toutiao: typeof import('./sources/toutiao')
+47
View File
@@ -0,0 +1,47 @@
interface HotMoviesRes {
category: string
tags: []
items: MovieItem[]
recommend_tags: []
total: number
type: string
}
interface MovieItem {
rating: {
count: number
max: number
star_count: number
value: number
}
title: string
pic: {
large: string
normal: string
}
is_new: boolean
uri: string
episodes_info: string
card_subtitle: string
type: string
id: string
}
export default defineSource(async () => {
const baseURL = "https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie"
const res: HotMoviesRes = await myFetch(baseURL, {
headers: {
Referer: "https://movie.douban.com/",
Accept: "application/json, text/plain, */*",
},
})
return res.items.map(movie => ({
id: movie.id,
title: movie.title,
url: `https://movie.douban.com/subject/${movie.id}`,
extra: {
info: movie.card_subtitle.split(" / ").slice(0, 3).join(" / "),
hover: movie.card_subtitle,
},
}))
})
+61
View File
@@ -0,0 +1,61 @@
interface Res {
topic_list: {
can_create_topic: boolean
more_topics_url: string
per_page: number
top_tags: string[]
topics: {
id: number
title: string
fancy_title: string
posts_count: number
reply_count: number
highest_post_number: number
image_url: null | string
created_at: Date
last_posted_at: Date
bumped: boolean
bumped_at: Date
unseen: boolean
pinned: boolean
excerpt?: string
visible: boolean
closed: boolean
archived: boolean
like_count: number
has_summary: boolean
last_poster_username: string
category_id: number
pinned_globally: boolean
}[]
}
}
const hot = defineSource(async () => {
const res = await myFetch<Res>("https://n8n.chgr.cc/webhook/api?name=idcflare")
return res.topic_list.topics
.filter(k => k.visible && !k.archived && !k.pinned)
.map(k => ({
id: k.id,
title: k.title,
url: `https://idcflare.com/t/topic/${k.id}`,
}))
})
const latest = defineSource(async () => {
const res = await myFetch<Res>("https://idcflare.com/latest.json?order=created")
return res.topic_list.topics
.filter(k => k.visible && !k.archived && !k.pinned)
.map(k => ({
id: k.id,
title: k.title,
pubDate: new Date(k.created_at).valueOf(),
url: `https://idcflare.com/t/topic/${k.id}`,
}))
})
export default defineSource({
"idcflare": latest,
"idcflare-latest": latest,
"idcflare-hot": hot,
})
+4 -4
View File
@@ -2,17 +2,17 @@ type Res = {
description: string
link: string
// Date
pubDate: string
pub_date: string
publisher: string
title: string
}[]
export default defineSource(async () => {
const res = await Promise.all(["https://kaopucdn.azureedge.net/jsondata/news_list_beta_hans_0.json", "https://kaopucdn.azureedge.net/jsondata/news_list_beta_hans_1.json"].map(url => myFetch(url) as Promise<Res>))
return res.flat().filter(k => ["财新", "公视"].every(h => k.publisher !== h)).map((k) => {
const res: Res = await $fetch("https://kaopustorage.blob.core.windows.net/news-prod/news_list_hans_0.json")
return res.filter(k => ["财新", "公视"].every(h => k.publisher !== h)).map((k) => {
return {
id: k.link,
title: k.title,
pubDate: k.pubDate,
pubDate: k.pub_date,
extra: {
hover: k.description,
info: k.publisher,
+8 -4
View File
@@ -41,17 +41,21 @@ const flash = defineSource(async () => {
const typeMap = { policy: "Policy", AI: "AI", financial: "Financial" } as const
const allReports = categories.flatMap((category) => {
const flash_list = res.data.find(item => item.name === category)?.child[0]?.flash_list || []
return flash_list.map(item => ({ ...item, type: typeMap[category] }))
const categoryData = res.data.find(item => item.name === category)
if (!categoryData?.child) return []
return categoryData.child.flatMap(subCategory =>
(subCategory.flash_list || []).map(item => ({ ...item, type: typeMap[category] })),
)
})
return allReports
.sort((a, b) => b.time.localeCompare(a.time))
.map(item => ({
id: item.id,
title: item.data.title || item.data.content,
title: item.data.title || item.data.content.match(/^【([^】]*)】(.*)$/)?.[1] || item.data.content,
pubDate: item.time,
extra: { info: item.type },
extra: { info: item.type, hover: item.data.content },
url: `https://mktnews.net/flashDetail.html?id=${item.id}`,
}))
})
+45 -17
View File
@@ -1,28 +1,56 @@
import * as cheerio from "cheerio"
import process from "node:process"
import type { NewsItem } from "@shared/types"
export default defineSource(async () => {
const baseURL = "https://www.producthunt.com"
const html: any = await myFetch(baseURL)
const $ = cheerio.load(html)
const $main = $("[data-test=homepage-section-0] [data-test^=post-item]")
const apiToken = process.env.PRODUCTHUNT_API_TOKEN
const token = `Bearer ${apiToken}`
if (!apiToken) {
throw new Error("PRODUCTHUNT_API_TOKEN is not set")
}
const query = `
query {
posts(first: 30, order: VOTES) {
edges {
node {
id
name
tagline
votesCount
url
slug
}
}
}
}
`
const response: any = await myFetch("https://api.producthunt.com/v2/api/graphql", {
method: "POST",
headers: {
"Authorization": token,
"Content-Type": "application/json",
"Accept": "application/json",
},
body: JSON.stringify({ query }),
})
const news: NewsItem[] = []
$main.each((_, el) => {
const a = $(el).find("a").first()
const url = a.attr("href")
const title = $(el).find("a[data-test^=post-name]").text().replace(/^\d+\.\s*/, "")
const id = $(el).attr("data-test")?.replace("post-item-", "")
const vote = $(el).find("[data-test=vote-button]").text()
if (url && id && title) {
const posts = response?.data?.posts?.edges || []
for (const edge of posts) {
const post = edge.node
if (post.id && post.name) {
news.push({
url: `${baseURL}${url}`,
title,
id,
id: post.id,
title: post.name,
url: post.url || `https://www.producthunt.com/posts/${post.slug}`,
extra: {
info: `△︎ ${vote}`,
info: ` △︎ ${post.votesCount || 0}`,
hover: post.tagline,
},
})
}
})
}
return news
})
+32
View File
@@ -0,0 +1,32 @@
import * as cheerio from "cheerio"
import type { NewsItem } from "@shared/types"
export default defineSource(async () => {
const response: any = await myFetch("https://store.steampowered.com/stats/stats/")
const $ = cheerio.load(response)
const $rows = $("#detailStats tr.player_count_row")
const news: NewsItem[] = []
$rows.each((_, el) => {
const $el = $(el)
const $a = $el.find("a.gameLink")
const url = $a.attr("href")
const gameName = $a.text().trim()
const currentPlayers = $el.find("td:first-child .currentServers").text().trim()
if (url && gameName && currentPlayers) {
const title = gameName
news.push({
url,
title,
id: url,
pubDate: Date.now(),
extra: {
info: currentPlayers,
},
})
}
})
return news
})
+63
View File
@@ -0,0 +1,63 @@
import { myFetch } from "#/utils/fetch"
import { defineSource } from "#/utils/source"
interface WapRes {
ret: number
msg: string
data: {
id: number
name: string
lead: string
cover?: string
shareTitle: string
shareAbstract: string
sharePic: string
is724: boolean
is724Paper: boolean
head_cmsid: string
feed_style: number
head_article: {
live_info: string
title: string
img: string
pub_time: string
media_name: string
}
paperInfo: any
tabs: {
id: string
channel_id: string
name: string
source: string
type: string
articleList: any[]
article_count: number
sub_tab: string
}[]
banner: string
}
}
/**
* 综合早报
*/
const comprehensiveNews = defineSource(async () => {
const url = "https://i.news.qq.com/web_backend/v2/getTagInfo?tagId=aEWqxLtdgmQ%3D"
const res = await myFetch<WapRes>(url, {
headers: {
Referer: "https://news.qq.com/",
},
})
return res.data.tabs[0].articleList.map(news => ({
id: news.id,
title: news.title,
url: news.link_info.url,
extra: {
hover: news.desc,
},
}))
})
export default defineSource({
"tencent-hot": comprehensiveNews,
})
+47 -139
View File
@@ -1,144 +1,52 @@
export interface Root {
ok: number
data: Data
}
export interface Data {
mapi: any[]
cards: Card[]
cardlistInfo: CardlistInfo
scheme: string
showAppTips: number
}
export interface Card {
card_group: CardGroup[]
card_type: number
show_type: number
itemid?: string
title?: string
}
export interface CardGroup {
actionlog?: Actionlog
card_type: number
pic?: string
itemid?: string
icon?: string
icon_width?: number
icon_height?: number
desc?: string
scheme: string
desc_extr: any
promotion?: Promotion
display_arrow?: number
show_type?: number
title?: string
is_show_arrow?: number
left_tag_img?: string
sub_title?: string
}
export interface Actionlog {
luicode: string
lfid: string
uicode: string
act_code: number
act_type: number
fid: string
ext: string
}
export interface Promotion {
monitor_url: MonitorUrl[]
}
export interface MonitorUrl {
third_party_click: string
third_party_show: string
type: string
monitor_name?: string
}
export interface CardlistInfo {
select_id: string
title_top: string
show_style: number
starttime: number
can_shared: number
v_p: string
containerid: string
page_type: string
nick: string
cardlist_head_cards: CardlistHeadCard[]
enable_load_imge_scrolling: number
page_title: string
search_request_id: string
load_more_threshold: number
pagesize: number
page_common_ext: string
cardlist_menus: any[]
total: number
headbg_animation: string
page_size: number
page: any
}
export interface CardlistHeadCard {
title_top?: string
show_menu: boolean
head_type_name: string
head_type: number
head_data?: HeadData
channel_list?: ChannelList[]
}
export interface HeadData {
cover_url: string
show_navi_mask: boolean
show_title: boolean
data_type: number
scheme: string
}
export interface ChannelList {
actionlog: Actionlog2
scheme: string
containerid: string
name: string
default_add: number
id: number
must_show: number
replaced_name?: string
}
export interface Actionlog2 {
luicode: string
lfid: string
uicode: string
act_code: number
act_type: number
fid: string
ext: string
}
import * as cheerio from "cheerio"
export default defineSource(async () => {
const url = "https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot"
const res: Root = await myFetch(url)
return res.data.cards[0].card_group
.filter((k, i) => i !== 0 && k.desc && !k.actionlog?.ext.includes("ads_word"))
.map((k) => {
return {
id: k.desc!,
title: k.desc!,
extra: {
icon: k.icon && {
url: proxyPicture(k.icon),
scale: 1.5,
const baseurl = "https://s.weibo.com"
const url = `${baseurl}/top/summary?cate=realtimehot`
const html = await myFetch(url, {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36",
// https://github.com/v5tech/weibo-trending-hot-search
"Cookie": "SUB=_2AkMWIuNSf8NxqwJRmP8dy2rhaoV2ygrEieKgfhKJJRMxHRl-yT9jqk86tRB6PaLNvQZR6zYUcYVT1zSjoSreQHidcUq7",
"referer": url,
},
})
const $ = cheerio.load(html)
const rows = $("#pl_top_realtimehot table tbody tr").slice(1)
const hotNews: NewsItem[] = []
rows.each((_, row) => {
const $row = $(row)
const $link = $row.find("td.td-02 a").filter((_, el) => {
const href = $(el).attr("href")
return !!(href && !href.includes("javascript:void(0);"))
}).first()
if ($link.length) {
const title = $link.text().trim()
const href = $link.attr("href")
if (title && href) {
const $flag = $row.find("td.td-03").text().trim()
const flagUrl = {
: "https://simg.s.weibo.com/moter/flags/1_0.png",
: "https://simg.s.weibo.com/moter/flags/2_0.png",
}[$flag]
hotNews.push({
id: title,
title,
url: `${baseurl}${href}`,
mobileUrl: `${baseurl}${href}`,
extra: {
icon: flagUrl ? { url: proxyPicture(flagUrl), scale: 1.5 } : undefined,
},
},
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(`#${k.desc}#`)}`,
mobileUrl: k.scheme,
})
}
})
}
})
return hotNews
})
+7 -1
View File
@@ -38,10 +38,16 @@
"baidu": "baiduresou",
"linuxdo-latest": "LINUX DO-zuixin",
"linuxdo-hot": "LINUX DO-jinrizuire",
"idcflare-latest": "IDC Flare-zuixin",
"idcflare-hot": "IDC Flare-jinrizuire",
"nowcoder": "niuke",
"sspai": "shaoshupai",
"juejin": "xitujuejin",
"ifeng": "fenghuangwang-redianzixun",
"chongbuluo-latest": "chongbuluo-zuixin",
"chongbuluo-hot": "chongbuluo-zuire"
"chongbuluo-hot": "chongbuluo-zuire",
"douban": "douban-remendianying",
"steam": "Steam-zaixianrenshu",
"tencent-hot": "tengxunxinwen-zonghezaobao"
}
+49 -3
View File
@@ -348,6 +348,24 @@ export const originSources = {
home: "https://linux.do/hot",
},
},
},
"idcflare": {
name: "IDC Flare",
column: "tech",
color: "slate",
home: "https://idcflare.com/",
sub: {
latest: {
title: "最新",
home: "https://idcflare.com/latest",
},
hot: {
title: "今日最热",
type: "hottest",
interval: Time.Common,
home: "https://idcflare.com/hot",
},
},
},
"ghxi": {
name: "果核剥壳",
@@ -412,6 +430,36 @@ export const originSources = {
},
},
},
"douban": {
name: "豆瓣",
column: "china",
title: "热门电影",
color: "green",
type: "hottest",
home: "https://www.douban.com",
},
"steam": {
name: "Steam",
column: "world",
title: "在线人数",
color: "blue",
type: "hottest",
home: "https://store.steampowered.com",
},
"tencent": {
name: "腾讯新闻",
column: "china",
color: "blue",
home: "https://news.qq.com",
sub: {
hot: {
title: "综合早报",
type: "hottest",
interval: Time.Common,
home: "https://news.qq.com/tag/aEWqxLtdgmQ=",
},
},
},
} as const satisfies Record<string, OriginSource>
export function genSources() {
@@ -460,10 +508,8 @@ export function genSources() {
_.filter(([_, v]) => {
if (v.disable === "cf" && process.env.CF_PAGES) {
return false
} else if (v.disable === true) {
return false
} else {
return true
return v.disable !== true
}
}),
)
+47 -1
View File
@@ -420,7 +420,16 @@
"column": "tech",
"home": "https://linux.do",
"color": "gray",
"interval": 1800000,
"interval": 600000,
"title": "今日最热"
},
"idcflare-hot": {
"name": "IDC Flare",
"type": "hottest",
"column": "tech",
"home": "https://idcflare.com",
"color": "gray",
"interval": 600000,
"title": "今日最热"
},
"nowcoder": {
@@ -481,5 +490,42 @@
"color": "green",
"interval": 1800000,
"title": "最热"
},
"douban": {
"title": "热门电影",
"name": "豆瓣",
"type": "hottest",
"column": "china",
"home": "https://www.douban.com",
"color": "green",
"interval": 600000
},
"steam": {
"title": "在线人数",
"name": "Steam",
"type": "hottest",
"column": "world",
"home": "https://store.steampowered.com",
"color": "blue",
"interval": 600000
},
"tencent": {
"redirect": "tencent-hot",
"name": "腾讯新闻",
"type": "hottest",
"column": "china",
"home": "https://news.qq.com/tag/aEWqxLtdgmQ=",
"color": "blue",
"interval": 1800000,
"title": "综合早报"
},
"tencent-hot": {
"name": "腾讯新闻",
"type": "hottest",
"column": "china",
"home": "https://news.qq.com/tag/aEWqxLtdgmQ=",
"color": "blue",
"interval": 1800000,
"title": "综合早报"
}
}
+2 -2
View File
@@ -236,11 +236,11 @@ function NewsListHot({ items }: { items: NewsItem[] }) {
key={item.id}
title={item.extra?.hover}
className={$(
"flex gap-2 items-start items-stretch relative cursor-pointer [&_*]:cursor-pointer transition-all",
"flex gap-2 items-center items-stretch relative cursor-pointer [&_*]:cursor-pointer transition-all",
"hover:bg-neutral-400/10 rounded-md pr-1 visited:(text-neutral-400)",
)}
>
<span className={$("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm h-6 flex-shrink-0")}>
<span className={$("bg-neutral-400/10 min-w-6 flex justify-center items-center rounded-md text-sm")}>
{i + 1}
</span>
{!!item.extra?.diff && <DiffNumber diff={item.extra.diff} />}