From 0191121c66a4b71aed320cd9a7a11919a5ae3c3b Mon Sep 17 00:00:00 2001
From: fishwww-ww <2470335462@qq.com>
Date: Wed, 15 Oct 2025 09:22:13 +0800
Subject: [PATCH 1/2] feat: daily hot news plugin
---
modules/tool/packages/dailyHot/config.ts | 50 ++++
modules/tool/packages/dailyHot/index.ts | 10 +
modules/tool/packages/dailyHot/logo.svg | 20 ++
modules/tool/packages/dailyHot/package.json | 17 ++
modules/tool/packages/dailyHot/src/index.ts | 243 ++++++++++++++++++
.../tool/packages/dailyHot/test/index.test.ts | 8 +
6 files changed, 348 insertions(+)
create mode 100644 modules/tool/packages/dailyHot/config.ts
create mode 100644 modules/tool/packages/dailyHot/index.ts
create mode 100644 modules/tool/packages/dailyHot/logo.svg
create mode 100644 modules/tool/packages/dailyHot/package.json
create mode 100644 modules/tool/packages/dailyHot/src/index.ts
create mode 100644 modules/tool/packages/dailyHot/test/index.test.ts
diff --git a/modules/tool/packages/dailyHot/config.ts b/modules/tool/packages/dailyHot/config.ts
new file mode 100644
index 00000000..18489b25
--- /dev/null
+++ b/modules/tool/packages/dailyHot/config.ts
@@ -0,0 +1,50 @@
+import { defineTool } from '@tool/type';
+import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt';
+import { ToolTypeEnum } from '@tool/type/tool';
+
+export default defineTool({
+ name: {
+ 'zh-CN': '热榜工具',
+ en: 'Hot List Tool'
+ },
+ type: ToolTypeEnum.tools,
+ description: {
+ 'zh-CN': '获取热榜信息,支持36氪、知乎、微博、掘金、头条等多个平台',
+ en: 'Get hot list information from multiple platforms including 36kr, Zhihu, Weibo, Juejin, and Toutiao'
+ },
+ toolDescription:
+ 'Get hot trending content from multiple platforms including 36kr, Zhihu, Weibo, Juejin, and Toutiao with accurate publish times',
+ versionList: [
+ {
+ value: '0.1.0',
+ description: 'Default version',
+ inputs: [
+ {
+ key: 'sources',
+ label: '热榜来源',
+ renderTypeList: [FlowNodeInputTypeEnum.multipleSelect],
+ valueType: WorkflowIOValueTypeEnum.arrayString,
+ required: true,
+ description: '选择热榜来源网站(可多选)',
+ toolDescription: '选择热榜来源网站',
+ defaultValue: ['36kr'],
+ list: [
+ { label: '36氪', value: '36kr' },
+ { label: '知乎', value: 'zhihu' },
+ { label: '微博', value: 'weibo' },
+ { label: '掘金', value: 'juejin' },
+ { label: '头条', value: 'toutiao' }
+ ]
+ }
+ ],
+ outputs: [
+ {
+ valueType: WorkflowIOValueTypeEnum.arrayObject,
+ key: 'hotNewsList',
+ label: '新闻热榜列表',
+ description: '新闻热榜数据列表'
+ }
+ ]
+ }
+ ]
+});
diff --git a/modules/tool/packages/dailyHot/index.ts b/modules/tool/packages/dailyHot/index.ts
new file mode 100644
index 00000000..d698ed48
--- /dev/null
+++ b/modules/tool/packages/dailyHot/index.ts
@@ -0,0 +1,10 @@
+import config from './config';
+import { InputType, OutputType, tool as toolCb } from './src';
+import { exportTool } from '@tool/utils/tool';
+
+export default exportTool({
+ toolCb,
+ InputType,
+ OutputType,
+ config
+});
diff --git a/modules/tool/packages/dailyHot/logo.svg b/modules/tool/packages/dailyHot/logo.svg
new file mode 100644
index 00000000..1e4b9a3d
--- /dev/null
+++ b/modules/tool/packages/dailyHot/logo.svg
@@ -0,0 +1,20 @@
+
diff --git a/modules/tool/packages/dailyHot/package.json b/modules/tool/packages/dailyHot/package.json
new file mode 100644
index 00000000..f98f7091
--- /dev/null
+++ b/modules/tool/packages/dailyHot/package.json
@@ -0,0 +1,17 @@
+{
+ "name": "@fastgpt-plugins/tool-daily-hot",
+ "module": "index.ts",
+ "type": "module",
+ "scripts": {
+ "build": "bun ../../../../scripts/build.ts"
+ },
+ "devDependencies": {
+ "@types/bun": "^1.2.2"
+ },
+ "peerDependencies": {
+ "typescript": "^5.0.0"
+ },
+ "dependencies": {
+ "zod": "^3.24.3"
+ }
+}
diff --git a/modules/tool/packages/dailyHot/src/index.ts b/modules/tool/packages/dailyHot/src/index.ts
new file mode 100644
index 00000000..7c16328b
--- /dev/null
+++ b/modules/tool/packages/dailyHot/src/index.ts
@@ -0,0 +1,243 @@
+import { z } from 'zod';
+import { POST, GET } from '@tool/utils/request';
+
+export const InputType = z.object({
+ sources: z.array(z.enum(['36kr', 'zhihu', 'weibo', 'juejin', 'toutiao'])).default(['36kr'])
+});
+
+export const OutputType = z.object({
+ hotNewsList: z.array(
+ z.object({
+ title: z.string().describe('hot news title').optional(),
+ description: z.string().describe('hot news description').optional(),
+ source: z.string().describe('hot news source website'),
+ time: z.string().describe('hot news publish time').optional(),
+ error: z.string().describe('failed to get hot news, error message').optional()
+ })
+ )
+});
+
+type Response = {
+ title?: string;
+ description?: string;
+ source: string;
+ time?: string;
+ error?: string;
+};
+
+type Kr36Response = {
+ data: {
+ hotRankList: {
+ templateMaterial: {
+ widgetTitle: string;
+ };
+ publishTime: string;
+ }[];
+ };
+};
+
+type ZhihuResponse = {
+ data: {
+ target: {
+ title: string;
+ created: number;
+ excerpt: string;
+ };
+ }[];
+};
+
+type WeiboResponse = {
+ data: {
+ cards: {
+ card_group?: {
+ desc: string;
+ }[];
+ }[];
+ };
+};
+
+type JuejinResponse = {
+ data: {
+ content: {
+ content_id: string;
+ title: string;
+ created_at: number;
+ ctime: number;
+ };
+ author: {
+ name: string;
+ };
+ content_counter: {
+ hot_rank: number;
+ };
+ }[];
+};
+
+type ToutiaoResponse = {
+ data: {
+ Title: string;
+ }[];
+};
+
+export async function tool({
+ sources
+}: z.infer): Promise> {
+ const promises = sources.map(async (source) => {
+ switch (source) {
+ case '36kr':
+ return await get36krList();
+ case 'zhihu':
+ return await getZhihuList();
+ case 'weibo':
+ return await getWeiboList();
+ case 'juejin':
+ return await getJuejinList();
+ case 'toutiao':
+ return await getToutiaoList();
+ default:
+ return [];
+ }
+ });
+ const results = await Promise.allSettled(promises);
+ const allHotNewsList: Response[] = [];
+ results.forEach((result, index) => {
+ if (result.status === 'fulfilled') {
+ allHotNewsList.push(...result.value);
+ } else {
+ allHotNewsList.push({
+ error: `Failed to get hot news from ${sources[index]}`,
+ source: sources[index]
+ });
+ }
+ });
+
+ if (!allHotNewsList.length) {
+ return Promise.reject({
+ error: 'Failed to get hot news list'
+ });
+ }
+
+ return {
+ hotNewsList: allHotNewsList
+ };
+}
+
+async function get36krList(): Promise {
+ const url = `https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot`;
+
+ const { data: Kr36Response } = await POST(url, {
+ partner_id: 'wap',
+ param: {
+ siteId: 1,
+ platformId: 2
+ },
+ timestamp: new Date().getTime()
+ });
+ const kr36List = Kr36Response?.data?.hotRankList;
+ if (!kr36List) {
+ return Promise.reject({
+ error: 'Failed to get kr36 list'
+ });
+ }
+
+ const hotNewsList: Response[] = kr36List.map((item) => {
+ return {
+ title: item.templateMaterial.widgetTitle,
+ source: '36氪',
+ time: new Date(item.publishTime).toLocaleString('zh-CN')
+ };
+ });
+
+ return hotNewsList;
+}
+
+async function getZhihuList(): Promise {
+ const url = `https://api.zhihu.com/topstory/hot-lists/total?limit=50`;
+
+ const { data: ZhihuResponse } = await GET(url);
+ const zhihuList = ZhihuResponse?.data;
+ if (!zhihuList) {
+ return Promise.reject({
+ error: 'Failed to get zhihu list'
+ });
+ }
+
+ const hotNewsList: Response[] = zhihuList.map((item) => {
+ const data = item.target;
+ return {
+ title: data.title,
+ description: data.excerpt,
+ source: '知乎',
+ time: new Date(data.created * 1000).toLocaleString('zh-CN')
+ };
+ });
+
+ return hotNewsList;
+}
+
+async function getWeiboList(): Promise {
+ const url =
+ 'https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot&title=%E5%BE%AE%E5%8D%9A%E7%83%AD%E6%90%9C&extparam=filter_type%3Drealtimehot%26mi_cid%3D100103%26pos%3D0_0%26c_type%3D30%26display_time%3D1540538388&luicode=10000011&lfid=231583';
+
+ const { data: WeiboResponse } = await GET(url, {
+ headers: {
+ 'X-Requested-With': 'XMLHttpRequest'
+ }
+ });
+ const weiboList = WeiboResponse.data.cards[0].card_group;
+ if (!weiboList) {
+ return Promise.reject({
+ error: 'Failed to get weibo list'
+ });
+ }
+
+ const hotNewsList: Response[] = weiboList.map((item) => {
+ return {
+ title: item.desc,
+ source: '微博'
+ };
+ });
+
+ return hotNewsList;
+}
+
+async function getJuejinList(): Promise {
+ const url = 'https://api.juejin.cn/content_api/v1/content/article_rank?category_id=1&type=hot';
+
+ const { data: JuejinResponse } = await GET(url);
+ const juejinList = JuejinResponse?.data;
+ if (!juejinList) {
+ return Promise.reject({
+ error: 'Failed to get juejin list'
+ });
+ }
+
+ const hotNewsList: Response[] = juejinList.map((item) => {
+ return {
+ title: item.content.title,
+ source: '掘金'
+ };
+ });
+
+ return hotNewsList;
+}
+
+async function getToutiaoList(): Promise {
+ const url = `https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc`;
+
+ const { data: ToutiaoResponse } = await GET(url);
+ const toutiaoList = ToutiaoResponse?.data;
+ if (!toutiaoList) {
+ return Promise.reject({
+ error: 'Failed to get toutiao list'
+ });
+ }
+ const hotNewsList: Response[] = toutiaoList.map((item) => {
+ return {
+ title: item.Title,
+ source: '头条'
+ };
+ });
+
+ return hotNewsList;
+}
diff --git a/modules/tool/packages/dailyHot/test/index.test.ts b/modules/tool/packages/dailyHot/test/index.test.ts
new file mode 100644
index 00000000..b70e289f
--- /dev/null
+++ b/modules/tool/packages/dailyHot/test/index.test.ts
@@ -0,0 +1,8 @@
+import { expect, test } from 'vitest';
+import tool from '..';
+
+test(async () => {
+ expect(tool.name).toBeDefined();
+ expect(tool.description).toBeDefined();
+ expect(tool.cb).toBeDefined();
+});
From 77b7a314b4452fa8d9ec9e2ab84f855c5369e592 Mon Sep 17 00:00:00 2001
From: archer <545436317@qq.com>
Date: Thu, 16 Oct 2025 21:59:27 +0800
Subject: [PATCH 2/2] perf: code
---
modules/tool/packages/dailyHot/src/index.ts | 191 +++++++++-----------
1 file changed, 90 insertions(+), 101 deletions(-)
diff --git a/modules/tool/packages/dailyHot/src/index.ts b/modules/tool/packages/dailyHot/src/index.ts
index 7c16328b..c8f95c61 100644
--- a/modules/tool/packages/dailyHot/src/index.ts
+++ b/modules/tool/packages/dailyHot/src/index.ts
@@ -7,23 +7,22 @@ export const InputType = z.object({
export const OutputType = z.object({
hotNewsList: z.array(
- z.object({
- title: z.string().describe('hot news title').optional(),
- description: z.string().describe('hot news description').optional(),
- source: z.string().describe('hot news source website'),
- time: z.string().describe('hot news publish time').optional(),
- error: z.string().describe('failed to get hot news, error message').optional()
- })
+ z.union([
+ z.object({
+ title: z.string().describe('hot news title').optional(),
+ description: z.string().describe('hot news description').optional(),
+ source: z.string().describe('hot news source website'),
+ time: z.string().describe('hot news publish time').optional()
+ }),
+ z.object({
+ source: z.string().describe('hot news source website'),
+ error: z.string().describe('failed to get hot news, error message').optional()
+ })
+ ])
)
});
-type Response = {
- title?: string;
- description?: string;
- source: string;
- time?: string;
- error?: string;
-};
+type Response = z.infer['hotNewsList'][number];
type Kr36Response = {
data: {
@@ -35,93 +34,6 @@ type Kr36Response = {
}[];
};
};
-
-type ZhihuResponse = {
- data: {
- target: {
- title: string;
- created: number;
- excerpt: string;
- };
- }[];
-};
-
-type WeiboResponse = {
- data: {
- cards: {
- card_group?: {
- desc: string;
- }[];
- }[];
- };
-};
-
-type JuejinResponse = {
- data: {
- content: {
- content_id: string;
- title: string;
- created_at: number;
- ctime: number;
- };
- author: {
- name: string;
- };
- content_counter: {
- hot_rank: number;
- };
- }[];
-};
-
-type ToutiaoResponse = {
- data: {
- Title: string;
- }[];
-};
-
-export async function tool({
- sources
-}: z.infer): Promise> {
- const promises = sources.map(async (source) => {
- switch (source) {
- case '36kr':
- return await get36krList();
- case 'zhihu':
- return await getZhihuList();
- case 'weibo':
- return await getWeiboList();
- case 'juejin':
- return await getJuejinList();
- case 'toutiao':
- return await getToutiaoList();
- default:
- return [];
- }
- });
- const results = await Promise.allSettled(promises);
- const allHotNewsList: Response[] = [];
- results.forEach((result, index) => {
- if (result.status === 'fulfilled') {
- allHotNewsList.push(...result.value);
- } else {
- allHotNewsList.push({
- error: `Failed to get hot news from ${sources[index]}`,
- source: sources[index]
- });
- }
- });
-
- if (!allHotNewsList.length) {
- return Promise.reject({
- error: 'Failed to get hot news list'
- });
- }
-
- return {
- hotNewsList: allHotNewsList
- };
-}
-
async function get36krList(): Promise {
const url = `https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot`;
@@ -151,6 +63,15 @@ async function get36krList(): Promise {
return hotNewsList;
}
+type ZhihuResponse = {
+ data: {
+ target: {
+ title: string;
+ created: number;
+ excerpt: string;
+ };
+ }[];
+};
async function getZhihuList(): Promise {
const url = `https://api.zhihu.com/topstory/hot-lists/total?limit=50`;
@@ -175,6 +96,15 @@ async function getZhihuList(): Promise {
return hotNewsList;
}
+type WeiboResponse = {
+ data: {
+ cards: {
+ card_group?: {
+ desc: string;
+ }[];
+ }[];
+ };
+};
async function getWeiboList(): Promise {
const url =
'https://m.weibo.cn/api/container/getIndex?containerid=106003type%3D25%26t%3D3%26disable_hot%3D1%26filter_type%3Drealtimehot&title=%E5%BE%AE%E5%8D%9A%E7%83%AD%E6%90%9C&extparam=filter_type%3Drealtimehot%26mi_cid%3D100103%26pos%3D0_0%26c_type%3D30%26display_time%3D1540538388&luicode=10000011&lfid=231583';
@@ -201,6 +131,22 @@ async function getWeiboList(): Promise {
return hotNewsList;
}
+type JuejinResponse = {
+ data: {
+ content: {
+ content_id: string;
+ title: string;
+ created_at: number;
+ ctime: number;
+ };
+ author: {
+ name: string;
+ };
+ content_counter: {
+ hot_rank: number;
+ };
+ }[];
+};
async function getJuejinList(): Promise {
const url = 'https://api.juejin.cn/content_api/v1/content/article_rank?category_id=1&type=hot';
@@ -222,6 +168,11 @@ async function getJuejinList(): Promise {
return hotNewsList;
}
+type ToutiaoResponse = {
+ data: {
+ Title: string;
+ }[];
+};
async function getToutiaoList(): Promise {
const url = `https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc`;
@@ -241,3 +192,41 @@ async function getToutiaoList(): Promise {
return hotNewsList;
}
+
+export async function tool({
+ sources
+}: z.infer): Promise> {
+ const map = {
+ '36kr': get36krList,
+ zhihu: getZhihuList,
+ weibo: getWeiboList,
+ juejin: getJuejinList,
+ toutiao: getToutiaoList
+ };
+ const promises = sources.map(async (source) => {
+ return await map[source]();
+ });
+
+ const results = await Promise.allSettled(promises);
+ const allHotNewsList: Response[] = [];
+ results.forEach((result, index) => {
+ if (result.status === 'fulfilled') {
+ allHotNewsList.push(...result.value);
+ } else {
+ allHotNewsList.push({
+ error: `Failed to get hot news from ${sources[index]}`,
+ source: sources[index]
+ });
+ }
+ });
+
+ if (!allHotNewsList.length) {
+ return Promise.reject({
+ error: 'Failed to get hot news list'
+ });
+ }
+
+ return {
+ hotNewsList: allHotNewsList
+ };
+}