This commit is contained in:
xiongxiao
2026-03-26 00:00:19 +08:00
committed by cnb
commit 4cd5c7ac22
97 changed files with 2804 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
node_modules

27
AGENTS.md Normal file
View File

@@ -0,0 +1,27 @@
# AGENTS.md
使用 fetch 获取热榜数据,定义了一个 `HotListItem` 类型来描述热榜子项的结构。每个热榜子项包含一个唯一的 `id`、标题、可选的描述、封面图、热度信息、提示信息、URL 地址、移动端 URL 地址、标签以及原始数据。
```ts
/**
* @description: 热榜子项
*/
export type HotListItem = {
id: string | number; // 唯一 key
title: string; // 标题
description?: string; // 描述
cover?: string; // 封面图
hot?: number | string; // 热度
tip?: string; // 如果不显示热度,显示其他信息
url: string; // 地址
mobileUrl?: string; // 移动端地址
label?: string; // 标签(微博)
originData?: any; // 原始数据
};
```
每一个模块有一个 label代表这个模块的功能。同时每一个模块会导出一个 main 函数main 函数会返回一个 PromisePromise resolve 的值是一个 HotListItem 数组,代表这个模块获取到的热榜数据。
## hotnow 模块
单独的 api 的模块,纯粹负责获取数据,不涉及任何 UI 相关的东西。这样做的好处是可以让数据获取和 UI 展示解耦,方便后续维护和扩展。

10
README.md Normal file
View File

@@ -0,0 +1,10 @@
# 通用当前热点信息汇集
### 参考
```
git clone https://github.com/ourongxing/newsnow.git
git clone https://github.com/baiwumm/next-daily-hot
```

20
package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "hotnow",
"version": "0.0.1",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "abearxiong <xiongxiao@xiongxiao.me> (https://www.xiongxiao.me)",
"license": "MIT",
"packageManager": "pnpm@10.32.1",
"type": "module",
"devDependencies": {
"cheerio": "^1.2.0",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.20",
"vitest": "^4.1.1"
}
}

22
src/hotnow/36kr/README.md Normal file
View File

@@ -0,0 +1,22 @@
# 36kr-24小时热榜
调用 36kr 官方 API 获取 24 小时热榜数据。
## API
- **URL**: `https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot`
- **Method**: POST
- **Headers**: Content-Type: application/json
## 数据结构
```typescript
{
id: string | number; // 文章ID
title: string; // 文章标题
cover?: string; // 封面图
hot: number | string; // 阅读数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("36kr", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

51
src/hotnow/36kr/api.ts Normal file
View File

@@ -0,0 +1,51 @@
import { HotListItem } from "../type";
export const label = {
name: '36kr-24小时热榜',
icon: 'https://36kr.com/favicon.ico',
color: '#3296CC',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot';
try {
const response = await fetch(url, {
method: 'POST',
headers: {
"Content-Type": "application/json; charset=utf-8",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
},
body: JSON.stringify({
partner_id: "wap",
param: {
siteId: 1,
platformId: 2,
},
timestamp: new Date().getTime(),
}),
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.code === 0) {
const result: HotListItem[] = responseBody.data?.hotRankList.map((v) => {
return {
id: v.itemId,
title: v?.templateMaterial?.widgetTitle,
cover: v?.templateMaterial.widgetImage,
hot: v?.templateMaterial.statRead,
url: `https://www.36kr.com/p/${v.itemId}`,
mobileUrl: `https://m.36kr.com/p/${v.itemId}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,20 @@
# 百度-热搜榜
调用百度热搜榜官方 API 获取实时热搜数据。
## API
- **URL**: `https://top.baidu.com/api/board?platform=wise&tab=realtime`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 排名索引
title: string; // 热搜词
label?: string; // 新增热词标签
url: string; // PC 端搜索链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("baidu", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

36
src/hotnow/baidu/api.ts Normal file
View File

@@ -0,0 +1,36 @@
import { HotListItem } from "../type";
export const label = {
name: '百度-热搜榜',
icon: 'https://www.baidu.com/favicon.ico',
color: '#2932E1',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://top.baidu.com/api/board?platform=wise&tab=realtime';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.success) {
const result: HotListItem[] = responseBody.data.cards[0]?.content[0]?.content.map((v) => {
return {
id: v.index,
title: v.word,
label: v.newHotName,
url: `https://www.baidu.com/s?wd=${encodeURIComponent(v.word)}`,
mobileUrl: v.url,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,22 @@
# 百度贴吧-热议榜
调用百度贴吧官方 API 获取热议榜数据。
## API
- **URL**: `https://tieba.baidu.com/hottopic/browse/topicList`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // topic_id
title: string; // 话题名称
description?: string; // 话题描述
cover?: string; // 封面图
hot: number | string; // 讨论数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("baidutieba", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,38 @@
import { HotListItem } from "../type";
export const label = {
name: '百度贴吧-热议榜',
icon: 'https://tieba.baidu.com/favicon.ico',
color: '#4676D8',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://tieba.baidu.com/hottopic/browse/topicList';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.errmsg === 'success') {
const result: HotListItem[] = responseBody.data.bang_topic.topic_list.map((v) => {
return {
id: v.topic_id.toString(),
title: v.topic_name,
description: v.topic_desc,
cover: v.topic_pic,
hot: v.discuss_num,
url: v.topic_url,
mobileUrl: v.topic_url,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,43 @@
# 哔哩哔哩-热门榜
调用哔哩哔哩官方 API 获取热门视频排行榜数据。
## API
- **URL**: `https://api.bilibili.com/x/web-interface/ranking/v2`
- **Method**: GET
- **Headers**: 需要 Referer 头信息
## 数据结构
```typescript
{
id: string | number; // 唯一标识 (bvid)
title: string; // 视频标题
description?: string; // 视频描述
cover?: string; // 封面图
hot: number | string; // 播放量
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```
## 响应示例
```json
{
"data": {
"list": [
{
"bvid": "BV1234567890",
"title": "视频标题",
"desc": "视频描述",
"pic": "封面URL",
"stat": {
"view": 1000000
}
}
]
}
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("bilibili", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,45 @@
import { HotListItem } from "../type";
export const label = {
name: '哔哩哔哩-热门榜',
icon: 'https://www.bilibili.com/favicon.ico',
color: '#FB7299',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://api.bilibili.com/x/web-interface/ranking/v2';
try {
const response = await fetch(url, {
headers: {
Referer: `https://www.bilibili.com/ranking/all`,
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
const data = responseBody?.data?.realtime || responseBody?.data?.list;
if (!data) {
return [];
}
const result: HotListItem[] = data.map((v) => {
return {
id: v.bvid,
title: v.title,
description: v.desc,
cover: v.pic.replace(/http:/, 'https:'),
hot: v.stat.view,
url: v.short_link_v2 || `https://b23.tv/${v.bvid}`,
mobileUrl: `https://m.bilibili.com/video/${v.bvid}`,
originData: v,
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

20
src/hotnow/csdn/README.md Normal file
View File

@@ -0,0 +1,20 @@
# CSDN-热榜
调用 CSDN 官方 API 获取热榜数据。
## API
- **URL**: `https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=100`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // articleDetailUrl
title: string; // 文章标题
tip?: string; // 热度分数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("csdn", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

42
src/hotnow/csdn/api.ts Normal file
View File

@@ -0,0 +1,42 @@
import { HotListItem } from "../type";
export const label = {
name: 'CSDN-热榜',
icon: 'https://blog.csdn.net/favicon.ico',
color: '#FA7040',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://blog.csdn.net/phoenix/web/blog/hot-rank?page=0&pageSize=100';
try {
const response = await fetch(url, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
cache: 'no-store',
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.code === 200) {
const result: HotListItem[] = responseBody.data.map((v) => {
return {
id: v.articleDetailUrl,
title: v.articleTitle,
tip: v.pcHotRankScore,
url: v.articleDetailUrl,
mobileUrl: v.articleDetailUrl,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,20 @@
# 懂车帝-热搜榜
通过解析懂车帝网页获取热搜榜数据。
## API
- **URL**: `https://www.dongchedi.com/news`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 索引
title: string; // 标题
hot: number | string; // 热度分数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("dongchedi", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,37 @@
import * as cheerio from 'cheerio';
import { HotListItem } from "../type";
export const label = {
name: '懂车帝-热搜榜',
icon: 'https://www.dongchedi.com/favicon.ico',
color: '#2B5D8A',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://www.dongchedi.com/news';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.text();
const $ = cheerio.load(responseBody);
const json = $('script#__NEXT_DATA__', responseBody).contents().text();
const data = JSON.parse(json);
const result: HotListItem[] = (data?.props?.pageProps?.hotSearchList || []).map((v, idx) => {
return {
id: idx + 1,
title: v.title,
hot: v.score,
url: `https://www.dongchedi.com/search?keyword=${encodeURIComponent(v.title)}`,
mobileUrl: `https://www.dongchedi.com/search?keyword=${encodeURIComponent(v.title)}`,
originData: v,
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 豆瓣电影-新片榜
通过解析豆瓣电影网页获取新片榜数据。
## API
- **URL**: `https://movie.douban.com/chart/`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 电影ID
title: string; // 电影标题
description?: string; // 电影描述/演员
hot: number | string; // 评价人数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("douban-movic", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,50 @@
import * as cheerio from 'cheerio';
import { HotListItem } from "../type";
export const label = {
name: '豆瓣电影-新片榜',
icon: 'https://movie.douban.com/favicon.ico',
color: '#23B000',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://movie.douban.com/chart/';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.text();
const getNumbers = (text: string | undefined) => {
if (!text) return 10000000;
const regex = /\d+/;
const match = text.match(regex);
if (match) {
return Number(match[0]);
} else {
return 10000000;
}
};
const $ = cheerio.load(responseBody);
const listDom = $('.article tr.item');
const result: HotListItem[] = listDom.toArray().map((item) => {
const dom = $(item);
const url = dom.find('a').attr('href') || '';
const score = dom.find('.rating_nums').text() ?? '0.0';
return {
id: String(getNumbers(url)),
title: `${dom.find('.pl2 a').text().replace(/\s+/g, ' ').trim().replace(/\n/g, '')}`,
description: dom.find('p.pl').text(),
hot: getNumbers(dom.find('span.pl').text()),
url,
mobileUrl: `https://m.douban.com/movie/subject/${getNumbers(url)}/`,
originData: dom.html(),
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 抖音-热点榜
调用抖音官方 API 获取热点榜数据。
## API
- **URL**: `https://aweme.snssdk.com/aweme/v1/hot/search/list/`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // group_id
title: string; // 热点词
cover?: string; // 封面图
hot: number | string; // 热度值
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("douyin", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

37
src/hotnow/douyin/api.ts Normal file
View File

@@ -0,0 +1,37 @@
import { HotListItem } from "../type";
export const label = {
name: '抖音-热点榜',
icon: 'https://www.douyin.com/favicon.ico',
color: '#00F2EA',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://aweme.snssdk.com/aweme/v1/hot/search/list/';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.status_code === 0) {
const result: HotListItem[] = responseBody.data.word_list.map((v) => {
return {
id: v.group_id,
title: v.word,
cover: `${v.word_cover.url_list[0]}`,
hot: Number(v.hot_value),
url: `https://www.douyin.com/hot/${encodeURIComponent(v.sentence_id)}`,
mobileUrl: `https://www.douyin.com/hot/${encodeURIComponent(v.sentence_id)}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# Github-热门仓库
通过解析 Github Trending 页面获取热门仓库数据。
## API
- **URL**: `https://github.com/trending`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 仓库路径
title: string; // 仓库名称 (owner/repo)
description?: string; // 仓库描述
tip?: string; // star 数量
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("github-trending", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,62 @@
import * as cheerio from 'cheerio';
import { HotListItem } from "../type";
export const label = {
name: 'Github-热门仓库',
icon: 'https://github.com/favicon.ico',
color: '#24292E',
};
export const main = async function(): Promise<HotListItem[]> {
const baseUrl = 'https://github.com';
const url = `${baseUrl}/trending`;
try {
const response = await fetch(`${url}`, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
cache: 'no-store',
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const formatStars = (count: number): string => {
if (count < 1000) return count.toString();
if (count < 1_000_000) {
return `${(count / 1000).toFixed(1).replace(/\.0$/, '')}K`;
}
return `${(count / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`;
};
const responseBody = await response.text();
const $ = cheerio.load(responseBody);
const listDom = $('.Box article.Box-row');
const result: HotListItem[] = listDom.get().map((repo, index) => {
const $repo = $(repo);
const relativeUrl = $repo.find('.h3').find('a').attr('href');
return {
id: relativeUrl || String(index),
title: (relativeUrl || '').replace(/^\//, ''),
description: $repo.find('p.my-1').text().trim() || '',
tip: formatStars(parseInt(
$repo
.find(".mr-3 svg[aria-label='star']")
.first()
.parent()
.text()
.trim()
.replace(',', '') || '0',
10
)),
url: `${baseUrl}${relativeUrl}`,
mobileUrl: `${baseUrl}${relativeUrl}`,
originData: $repo.html(),
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# HelloGithub-精选
调用 HelloGithub 官方 API 获取精选仓库数据。
## API
- **URL**: `https://api.hellogithub.com/v1/?sort_by=featured&page=1&rank_by=newest&tid=all`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // item_id
title: string; // 仓库名称-标题
description?: string; // 仓库描述
hot: number | string; // 总点击数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("hello-github", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,43 @@
import { HotListItem } from "../type";
export const label = {
name: 'HelloGithub-精选',
icon: 'https://hellogithub.com/favicon.ico',
color: '#FF6A00',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://api.hellogithub.com/v1/?sort_by=featured&page=1&rank_by=newest&tid=all';
try {
const response = await fetch(url, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
cache: 'no-store',
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.success) {
const result: HotListItem[] = responseBody.data.map((v) => {
return {
id: v.item_id,
title: `${v.name}-${v.title}`,
description: v.summary,
hot: v.clicks_total,
url: `https://hellogithub.com/repository/${v.full_name}`,
mobileUrl: `https://hellogithub.com/repository/${v.full_name}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 百度百科-历史上的今天
调用百度百科官方 API 获取历史上的今天数据。
## API
- **URL**: `https://baike.baidu.com/cms/home/eventsOnHistory/{month}.json`
- **Method**: GET
- **Note**: month 格式为 MM如 01, 02 等
## 数据结构
```typescript
{
id: string | number; // 索引
title: string; // 事件标题
tip?: string; // 年份
url: string; // 详情链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("history-today", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,35 @@
import { HotListItem } from "../type";
export const label = {
name: '百度百科-历史上的今天',
icon: 'https://baike.baidu.com/favicon.ico',
color: '#2932E1',
};
export const main = async function(): Promise<HotListItem[]> {
const month = (new Date().getMonth() + 1).toString().padStart(2, '0');
const day = new Date().getDate().toString().padStart(2, '0');
const url = `https://baike.baidu.com/cms/home/eventsOnHistory/${month}.json`;
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
const result: HotListItem[] = responseBody[month][month + day].map((v, index: number) => {
return {
id: index,
title: v.title.replace(/<[^>]+>/g, ''),
tip: v.year,
url: v.link,
mobileUrl: v.link,
originData: v,
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

22
src/hotnow/hupu/README.md Normal file
View File

@@ -0,0 +1,22 @@
# 虎扑-步行街热帖
通过解析虎扑网页获取步行街热帖数据。
## API
- **URL**: `https://bbs.hupu.com/all-gambia`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // tid
title: string; // 帖子标题
description?: string; // 帖子描述
cover?: string; // 封面图
tip?: string; // 亮帖数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("hupu", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

41
src/hotnow/hupu/api.ts Normal file
View File

@@ -0,0 +1,41 @@
import * as cheerio from 'cheerio';
import { HotListItem } from "../type";
export const label = {
name: '虎扑-步行街热帖',
icon: 'https://bbs.hupu.com/favicon.ico',
color: '#1E8E3E',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://bbs.hupu.com/all-gambia';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.text();
const $ = cheerio.load(responseBody);
const json = $("script").first();
const data = JSON.parse(json.text().split("window.$$data=")[1])
.pageData
.threads;
const result: HotListItem[] = data.map((v) => {
return {
id: v.tid,
title: v.title,
description: v.desc,
cover: v.cover,
tip: v.lights,
url: `https://bbs.hupu.com${v.url}`,
mobileUrl: `https://bbs.hupu.com${v.url}`,
originData: v,
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,22 @@
# 虎嗅-最新资讯
调用虎嗅官方 API 获取最新资讯数据。
## API
- **URL**: `https://moment-api.huxiu.com/web-v3/moment/feed?platform=www`
- **Method**: GET
- **Headers**: 需要 User-Agent 和 Referer 头信息
## 数据结构
```typescript
{
id: string | number; // object_id
title: string; // 标题
description?: string; // 内容简介
tip?: string; // 发布时间
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("huxiu", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

50
src/hotnow/huxiu/api.ts Normal file
View File

@@ -0,0 +1,50 @@
import { HotListItem } from "../type";
export const label = {
name: '虎嗅-最新资讯',
icon: 'https://www.huxiu.com/favicon.ico',
color: '#FF6600',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://moment-api.huxiu.com/web-v3/moment/feed?platform=www';
try {
const response = await fetch(url, {
headers: {
"User-Agent": "Mozilla/5.0",
Referer: "https://www.huxiu.com/moment/",
},
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.success) {
const result: HotListItem[] = responseBody?.data?.moment_list?.datalist.map((v) => {
const content = (v.content || "").replace(/<br\s*\/?>/gi, "\n");
const [titleLine, ...rest] = content
.split("\n")
.map((s) => s.trim())
.filter(Boolean);
const title = titleLine?.replace(/。$/, "") || "";
const intro = rest.join("\n");
const id = v.object_id;
return {
id,
title,
description: intro,
tip: v.format_time,
url: `https://www.huxiu.com/moment/${id}.html`,
mobileUrl: `https://m.huxiu.com/moment/${id}.html`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,19 @@
# 爱范儿-快讯
调用爱范儿官方 API 获取快讯数据。
## API
- **URL**: `https://sso.ifanr.com/api/v5/wp/buzz/?limit=50&offset=0`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // post_id
title: string; // 快讯标题
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("ifanr", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

36
src/hotnow/ifanr/api.ts Normal file
View File

@@ -0,0 +1,36 @@
import { HotListItem } from "../type";
export const label = {
name: '爱范儿-快讯',
icon: 'https://www.ifanr.com/favicon.ico',
color: '#007AFF',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://sso.ifanr.com/api/v5/wp/buzz/?limit=50&offset=0';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
const data = responseBody?.objects;
if (!data) {
return [];
}
const result: HotListItem[] = data.map((v) => {
return {
id: v.post_id,
title: v.post_title,
url: v.buzz_original_url || `https://www.ifanr.com/${v.post_id}`,
mobileUrl: v.buzz_original_url || `https://www.ifanr.com/digest/${v.post_id}`,
originData: v,
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# IT之家-热榜
通过解析 IT之家移动版网页获取热榜数据。
## API
- **URL**: `https://m.ithome.com/rankm`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 索引
title: string; // 标题
cover?: string; // 封面图
hot: number | string; // 评论数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("ithome", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

48
src/hotnow/ithome/api.ts Normal file
View File

@@ -0,0 +1,48 @@
import * as cheerio from 'cheerio';
import { HotListItem } from "../type";
export const label = {
name: 'IT之家-热榜',
icon: 'https://www.ithome.com/favicon.ico',
color: '#EE4344',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://m.ithome.com/rankm';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.text();
const replaceLink = (url: string, getId: boolean = false) => {
const match = url.match(/[html|live]\/(\d+)\.htm/);
if (match && match[1]) {
return getId
? match[1]
: `https://www.ithome.com/0/${match[1].slice(0, 3)}/${match[1].slice(3)}.htm`;
}
return url;
};
const $ = cheerio.load(responseBody);
const listDom = $(".rank-box .placeholder");
const result: HotListItem[] = listDom.toArray().map((item, index) => {
const dom = $(item);
const href = dom.find("a").attr("href");
return {
id: index,
title: dom.find(".plc-title").text().trim(),
cover: dom.find("img").attr("data-original"),
hot: Number(dom.find(".review-num").text().replace(/\D/g, "")),
url: href ? replaceLink(href) : "",
mobileUrl: href ? replaceLink(href) : "",
originData: dom.html(),
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,20 @@
# 稀土掘金-热榜
调用掘金官方 API 获取热榜数据。
## API
- **URL**: `https://api.juejin.cn/content_api/v1/content/article_rank?category_id=1&type=hot`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // content_id
title: string; // 文章标题
hot: number | string; // 热度排名
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("juejin", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

36
src/hotnow/juejin/api.ts Normal file
View File

@@ -0,0 +1,36 @@
import { HotListItem } from "../type";
export const label = {
name: '稀土掘金-热榜',
icon: 'https://juejin.cn/favicon.ico',
color: '#007AFF',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://api.juejin.cn/content_api/v1/content/article_rank?category_id=1&type=hot';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.err_msg === 'success') {
const result: HotListItem[] = responseBody.data.map((v) => {
return {
id: v.content.content_id,
title: v.content.title,
hot: v.content_counter.hot_rank,
url: `https://juejin.cn/post/${v.content.content_id}`,
mobileUrl: `https://juejin.cn/post/${v.content.content_id}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,20 @@
# 快手-热榜
通过解析快手网页获取热榜数据。
## API
- **URL**: `https://www.kuaishou.com/?isHome=1`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 视频ID
title: string; // 视频名称
hot: number | string; // 热度值
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("kuaishou", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,42 @@
import { HotListItem } from "../type";
export const label = {
name: '快手-热榜',
icon: 'https://www.kuaishou.com/favicon.ico',
color: '#FF0000',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://www.kuaishou.com/?isHome=1';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.text();
const result: HotListItem[] = [];
const pattern = /window.__APOLLO_STATE__=(.*);\(function\(\)/s;
const idPattern = /clientCacheKey=([A-Za-z0-9]+)/s;
const matchResult = responseBody.match(pattern);
const jsonObject = matchResult ? JSON.parse(matchResult[1])['defaultClient'] : [];
const allItems = jsonObject['$ROOT_QUERY.visionHotRank({"page":"home"})']['items'];
allItems.forEach((v) => {
const image = jsonObject[v.id]['poster'];
const id = image.match(idPattern)[1];
result.push({
id,
title: jsonObject[v.id]['name'],
hot: jsonObject[v.id]['hotValue']?.replace('万', '') * 10000,
url: `https://www.kuaishou.com/short-video/${id}`,
mobileUrl: `https://www.kuaishou.com/short-video/${id}`,
originData: jsonObject[v.id],
});
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

22
src/hotnow/lol/README.md Normal file
View File

@@ -0,0 +1,22 @@
# 英雄联盟-更新公告
调用英雄联盟官方 API 获取更新公告数据。
## API
- **URL**: `https://apps.game.qq.com/cmc/zmMcnTargetContentList?page=1&num=50&target=24&source=web_pc`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // iDocID
title: string; // 公告标题
description?: string; // 作者
cover?: string; // 封面图
hot: number | string; // 播放量
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("lol", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

38
src/hotnow/lol/api.ts Normal file
View File

@@ -0,0 +1,38 @@
import { HotListItem } from "../type";
export const label = {
name: '英雄联盟-更新公告',
icon: 'https://lol.qq.com/favicon.ico',
color: '#C89B3C',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://apps.game.qq.com/cmc/zmMcnTargetContentList?page=1&num=50&target=24&source=web_pc';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.status === 1) {
const result: HotListItem[] = responseBody.data.result.map((v) => {
return {
id: v.iDocID,
title: v.sTitle,
description: v.sAuthor,
cover: v.sIMG,
hot: Number(v.iTotalPlay),
url: `https://lol.qq.com/news/detail.shtml?docid=${encodeURIComponent(v.iDocID)}`,
mobileUrl: `https://lol.qq.com/news/detail.shtml?docid=${encodeURIComponent(v.iDocID)}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,22 @@
# 网易云音乐-新歌榜
调用网易云音乐官方 API 获取新歌榜数据。
## API
- **URL**: `https://music.163.com/api/playlist/detail?id=3778678`
- **Method**: GET
- **Headers**: 需要 authority 和 referer 头信息
## 数据结构
```typescript
{
id: string | number; // 歌曲ID
title: string; // 歌曲名称
cover?: string; // 封面图
tip?: string; // 时长 (mm:ss)
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("netease-music", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,50 @@
import { HotListItem } from "../type";
export const label = {
name: '网易云音乐-新歌榜',
icon: 'https://music.163.com/favicon.ico',
color: '#C20C0C',
};
const convertMillisecondsToTime = (milliseconds: number): string => {
const seconds = Math.floor((milliseconds / 1000) % 60);
const minutes = Math.floor(milliseconds / (1000 * 60));
const formattedSeconds = seconds < 10 ? '0' + seconds : seconds.toString();
const formattedMinutes = minutes < 10 ? '0' + minutes : minutes.toString();
return `${formattedMinutes}:${formattedSeconds}`;
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://music.163.com/api/playlist/detail?id=3778678';
try {
const response = await fetch(url, {
headers: {
authority: 'music.163.com',
referer: 'https://music.163.com/',
},
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.code === 200) {
const result: HotListItem[] = responseBody.result.tracks.map((v) => {
return {
id: v.id,
title: v.name,
tip: convertMillisecondsToTime(v.duration),
cover: v.album.picUrl,
url: `https://music.163.com/#/song?id=${v.id}`,
mobileUrl: `https://music.163.com/m/song?id=${v.id}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 网易新闻-热榜
调用网易新闻官方 API 获取热榜数据。
## API
- **URL**: `https://m.163.com/fe/api/hot/news/flow`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // skipID
title: string; // 标题
description?: string; // 描述
cover?: string; // 封面图
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("netease", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

37
src/hotnow/netease/api.ts Normal file
View File

@@ -0,0 +1,37 @@
import { HotListItem } from "../type";
export const label = {
name: '网易新闻-热榜',
icon: 'https://news.163.com/favicon.ico',
color: '#C20C0C',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://m.163.com/fe/api/hot/news/flow';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.msg === 'success') {
const result: HotListItem[] = responseBody.data.list.map((v) => {
return {
id: v.skipID,
title: v.title,
description: v._keyword,
cover: v.imgsrc,
url: `https://www.163.com/dy/article/${v.skipID}.html`,
mobileUrl: v.url,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

22
src/hotnow/qq/README.md Normal file
View File

@@ -0,0 +1,22 @@
# 腾讯新闻-热点榜
调用腾讯新闻官方 API 获取热点榜数据。
## API
- **URL**: `https://r.inews.qq.com/gw/event/hot_ranking_list`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 新闻ID
title: string; // 新闻标题
description?: string; // 新闻摘要
cover?: string; // 封面图
hot: number | string; // 阅读数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

19
src/hotnow/qq/api.test.ts Normal file
View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("qq", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

38
src/hotnow/qq/api.ts Normal file
View File

@@ -0,0 +1,38 @@
import { HotListItem } from "../type";
export const label = {
name: '腾讯新闻-热点榜',
icon: 'https://www.qq.com/favicon.ico',
color: '#FF6600',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://r.inews.qq.com/gw/event/hot_ranking_list';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.ret === 0) {
const result: HotListItem[] = responseBody.idlist[0].newslist.slice(1).map((v) => {
return {
id: v.id,
title: v.title,
description: v.abstract,
cover: v.miniProShareImage,
hot: v.readCount,
url: `https://new.qq.com/rain/a/${v.id}`,
mobileUrl: `https://view.inews.qq.com/a/${v.id}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,20 @@
# 夸克-今日热点
调用夸克官方 API 获取今日热点数据。
## API
- **URL**: `https://iflow.quark.cn/iflow/api/v1/article/aggregation?aggregation_id=16665090098771297825&count=50&bottom_pos=0`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 文章ID
title: string; // 标题
tip?: string; // 发布时间 (HH:mm)
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("quark", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

37
src/hotnow/quark/api.ts Normal file
View File

@@ -0,0 +1,37 @@
import dayjs from 'dayjs';
import { HotListItem } from "../type";
export const label = {
name: '夸克-今日热点',
icon: 'https://quark.com/favicon.ico',
color: '#4B9EFF',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://iflow.quark.cn/iflow/api/v1/article/aggregation?aggregation_id=16665090098771297825&count=50&bottom_pos=0';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.status === 0) {
const result: HotListItem[] = responseBody.data.articles.map((v) => {
return {
id: v.id,
title: v.title,
tip: dayjs(v.publish_time).format('HH:mm'),
url: `https://123.quark.cn/detail?item_id=${v.id}`,
mobileUrl: `https://123.quark.cn/detail?item_id=${v.id}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 澎湃新闻-热榜
调用澎湃新闻官方 API 获取热榜数据。
## API
- **URL**: `https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // contId
title: string; // 新闻标题
cover?: string; // 封面图
hot: number | string; // 点赞数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("thepaper", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,37 @@
import { HotListItem } from "../type";
export const label = {
name: '澎湃新闻-热榜',
icon: 'https://www.thepaper.cn/favicon.ico',
color: '#C20C0C',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://cache.thepaper.cn/contentapi/wwwIndex/rightSidebar';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.resultCode === 1) {
const result: HotListItem[] = responseBody.data.hotNews.map((v) => {
return {
id: v.contId,
title: v.name,
cover: v.pic,
hot: v.praiseTimes,
url: `https://www.thepaper.cn/newsDetail_forward_${v.contId}`,
mobileUrl: `https://m.thepaper.cn/newsDetail_forward_${v.contId}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 今日头条-热榜
调用今日头条官方 API 获取热榜数据。
## API
- **URL**: `https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // ClusterId
title: string; // 标题
cover?: string; // 封面图
hot: number | string; // 热度值
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("toutiao", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

37
src/hotnow/toutiao/api.ts Normal file
View File

@@ -0,0 +1,37 @@
import { HotListItem } from "../type";
export const label = {
name: '今日头条-热榜',
icon: 'https://www.toutiao.com/favicon.ico',
color: '#F85959',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://www.toutiao.com/hot-event/hot-board/?origin=toutiao_pc';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.status === 'success') {
const result: HotListItem[] = responseBody.data.map((v) => {
return {
id: v.ClusterId,
title: v.Title,
cover: v.Image.url,
hot: v.HotValue,
url: `https://www.toutiao.com/trending/${v.ClusterIdStr}/`,
mobileUrl: `https://api.toutiaoapi.com/feoffline/amos_land/new/html/main/index.html?topic_id=${v.ClusterIdStr}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

15
src/hotnow/type.ts Normal file
View File

@@ -0,0 +1,15 @@
/**
* @description: 热榜子项
*/
export type HotListItem = {
id: string | number; // 唯一 key
title: string; // 标题
description?: string; // 描述
cover?: string; // 封面图
hot?: number | string; // 热度
tip?: string; // 如果不显示热度,显示其他信息
url: string; // 地址
mobileUrl?: string; // 移动端地址
label?: string; // 标签(微博)
originData?: any; // 原始数据
};

122
src/hotnow/utils.ts Normal file
View File

@@ -0,0 +1,122 @@
export const hotnows = [
{
title: '小红书',
path: './xiaohongshu/api.ts',
},
{
title: '微博',
path: './weibo/api.ts',
},
{
title: '知乎',
path: './zhihu/api.ts',
},
{
title: '哔哩哔哩',
path: './bilibili/api.ts',
},
{
title: '36kr',
path: './36kr/api.ts',
},
{
title: '百度',
path: './baidu/api.ts',
},
{
title: '抖音',
path: './douyin/api.ts',
},
{
title: '今日头条',
path: './toutiao/api.ts',
},
{
title: '网易新闻',
path: './netease/api.ts',
},
{
title: 'IT之家',
path: './ithome/api.ts',
},
{
title: '虎嗅',
path: './huxiu/api.ts',
},
{
title: '稀土掘金',
path: './juejin/api.ts',
},
{
title: 'CSDN',
path: './csdn/api.ts',
},
{
title: '百度贴吧',
path: './baidutieba/api.ts',
},
{
title: '懂车帝',
path: './dongchedi/api.ts',
},
{
title: '快手',
path: './kuaishou/api.ts',
},
{
title: '英雄联盟',
path: './lol/api.ts',
},
{
title: '豆瓣电影',
path: './douban-movic/api.ts',
},
{
title: 'Github',
path: './github-trending/api.ts',
},
{
title: '虎扑',
path: './hupu/api.ts',
},
{
title: '爱范儿',
path: './ifanr/api.ts',
},
{
title: '微信读书',
path: './weread/api.ts',
},
{
title: '人人都是产品经理',
path: './woshipm/api.ts',
},
{
title: '网易云音乐',
path: './netease-music/api.ts',
},
{
title: '夸克',
path: './quark/api.ts',
},
{
title: '腾讯新闻',
path: './qq/api.ts',
},
{
title: '澎湃新闻',
path: './thepaper/api.ts',
},
{
title: '历史上的今天',
path: './history-today/api.ts',
},
{
title: 'HelloGithub',
path: './hello-github/api.ts',
},
{
title: '知乎日报',
path: './zhihu-daily/api.ts',
},
];

View File

@@ -0,0 +1,41 @@
# 微博-热搜榜
调用微博官方 API 获取实时热搜榜数据。
## API
- **URL**: `https://weibo.com/ajax/side/hotSearch`
- **Method**: GET
- **Headers**: 需要 User-Agent, Referer, Accept 头信息
## 数据结构
```typescript
{
id: string | number; // 唯一标识
title: string; // 热搜词
description?: string; // 描述
hot: number | string; // 热度值
label?: string; // 标签(热、沸、新、暖、爆)
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```
## 响应示例
```json
{
"ok": 1,
"data": {
"realtime": [
{
"mid": "123456",
"word": "热搜词",
"num": 1000000,
"label_name": "热"
}
]
}
}
```

View File

@@ -0,0 +1,24 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("weibo", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
it("should handle errors gracefully", async () => {
const result = await main();
expect(result).toBeDefined();
});
});

46
src/hotnow/weibo/api.ts Normal file
View File

@@ -0,0 +1,46 @@
import { HotListItem } from "../type";
export const label = {
name: '微博-热搜榜',
icon: 'https://weibo.com/favicon.ico',
color: '#FF8200',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://weibo.com/ajax/side/hotSearch';
try {
const response = await fetch(url, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
Referer: 'https://weibo.com/',
Accept: 'application/json',
},
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.ok === 1) {
const result: HotListItem[] = responseBody.data.realtime.map((v) => {
const key = v.word_scheme ? v.word_scheme : `#${v.word}`;
return {
id: v.mid,
title: v.word,
description: key,
hot: v.num,
label: v.label_name,
url: `https://s.weibo.com/weibo?q=${encodeURIComponent(key)}&t=31&band_rank=1&Refer=top`,
mobileUrl: `https://s.weibo.com/weibo?q=${encodeURIComponent(key)}&t=31&band_rank=1&Refer=top`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,21 @@
# 微信读书-飙升榜
调用微信读书官方 API 获取飙升榜数据。
## API
- **URL**: `https://weread.qq.com/web/bookListInCategory/rising?rank=1`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // bookId
title: string; // 书籍标题
cover?: string; // 封面图
hot: number | string; // 阅读数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("weread", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

81
src/hotnow/weread/api.ts Normal file
View File

@@ -0,0 +1,81 @@
import CryptoJS from 'crypto-js';
import { HotListItem } from "../type";
export const label = {
name: '微信读书-飙升榜',
icon: 'https://weread.qq.com/favicon.ico',
color: '#E9432B',
};
const getWereadID = (bookId: string): string | null => {
try {
const str = CryptoJS.MD5(bookId).toString();
let strSub = str.substring(0, 3);
let fa;
if (/^\d*$/.test(bookId)) {
const chunks = [];
for (let i = 0; i < bookId.length; i += 9) {
const chunk = bookId.substring(i, i + 9);
chunks.push(parseInt(chunk).toString(16));
}
fa = ['3', chunks];
} else {
let hexStr = '';
for (let i = 0; i < bookId.length; i++) {
hexStr += bookId.charCodeAt(i).toString(16);
}
fa = ['4', [hexStr]];
}
strSub += fa[0];
strSub += '2' + str.substring(str.length - 2);
for (let i = 0; i < fa[1].length; i++) {
const sub = fa[1][i];
const subLength = sub.length.toString(16);
const subLengthPadded = subLength.length === 1 ? '0' + subLength : subLength;
strSub += subLengthPadded + sub;
if (i < fa[1].length - 1) {
strSub += 'g';
}
}
if (strSub.length < 20) {
strSub += str.substring(0, 20 - strSub.length);
}
const finalStr = CryptoJS.MD5(strSub).toString();
strSub += finalStr.substring(0, 3);
return strSub;
} catch (error) {
console.error('处理微信读书 ID 时出现错误:' + error);
return null;
}
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://weread.qq.com/web/bookListInCategory/rising?rank=1';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.books) {
const result: HotListItem[] = responseBody.books.map((v) => {
const info = v.bookInfo;
return {
id: info.bookId,
title: info.title,
hot: v.readingCount,
cover: info.cover.replace('s_', 't9_'),
url: `https://weread.qq.com/web/bookDetail/${getWereadID(info.bookId)}`,
mobileUrl: `https://weread.qq.com/web/bookDetail/${getWereadID(info.bookId)}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,22 @@
# 人人都是产品经理-热榜
调用人人都是产品经理官方 API 获取热榜数据。
## API
- **URL**: `https://www.woshipm.com/api2/app/article/popular/daily`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 文章ID
title: string; // 文章标题
description?: string; // 文章摘要
cover?: string; // 封面图
hot: number | string; // 热度分数
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("woshipm", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

45
src/hotnow/woshipm/api.ts Normal file
View File

@@ -0,0 +1,45 @@
import { HotListItem } from "../type";
export const label = {
name: '人人都是产品经理-热榜',
icon: 'https://www.woshipm.com/favicon.ico',
color: '#F7B500',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://www.woshipm.com/api2/app/article/popular/daily';
try {
const response = await fetch(url, {
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
cache: 'no-store',
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.CODE === 200) {
const result: HotListItem[] = responseBody.RESULT.map((v) => {
const articleUrl = `https://www.woshipm.com/${v.data.type}/${v.data.id}.html`;
return {
id: v.data.id,
title: v.data.articleTitle,
description: v.data.articleSummary,
hot: v.scores,
cover: v.data.imageUrl,
url: articleUrl,
mobileUrl: articleUrl,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,40 @@
# 小红书-实时热榜
调用小红书官方 API 获取实时热榜数据。
## API
- **URL**: `https://edith.xiaohongshu.com/api/sns/v1/search/hot_list`
- **Method**: GET
- **Headers**: 需要特定的 User-Agent 和 Referer 头信息
## 数据结构
```typescript
{
id: string; // 唯一标识
title: string; // 热搜词
hot: number; // 热度值
label?: string; // 标签(热、沸、新等)
url: string; // PC 端链接
mobileUrl: string; // 移动端链接
}
```
## 响应示例
```json
{
"success": true,
"data": {
"items": [
{
"id": "dora_2178732",
"title": "用万能旅行拍照姿势美美出片",
"score": "934.9w",
"word_type": "热"
}
]
}
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("xiaohongshu", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,55 @@
import { HotListItem } from "../type";
export const label = {
name: '小红书-实时热榜',
icon: 'https://www.xiaohongshu.com/favicon.ico',
color: '#FF0000',
}
export const main = async function(): Promise<HotListItem[]> {
// 官方 url
const url = 'https://edith.xiaohongshu.com/api/sns/v1/search/hot_list';
const xhsHeaders = {
'User-Agent':
'Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.7(0x18000733) NetType/WIFI Language/zh_CN',
referer: 'https://app.xhs.cn/',
'xy-direction': '22',
shield:
'XYAAAAAQAAAAEAAABTAAAAUzUWEe4xG1IYD9/c+qCLOlKGmTtFa+lG434Oe+FTRagxxoaz6rUWSZ3+juJYz8RZqct+oNMyZQxLEBaBEL+H3i0RhOBVGrauzVSARchIWFYwbwkV',
'xy-platform-info':
'platform=iOS&version=8.7&build=8070515&deviceId=C323D3A5-6A27-4CE6-AA0E-51C9D4C26A24&bundle=com.xingin.discover',
'xy-common-params':
'app_id=ECFAAF02&build=8070515&channel=AppStore&deviceId=C323D3A5-6A27-4CE6-AA0E-51C9D4C26A24&device_fingerprint=20230920120211bd7b71a80778509cf4211099ea911000010d2f20f6050264&device_fingerprint1=20230920120211bd7b71a80778509cf4211099ea911000010d2f20f6050264&device_model=phone&fid=1695182528-0-0-63b29d709954a1bb8c8733eb2fb58f29&gid=7dc4f3d168c355f1a886c54a898c6ef21fe7b9a847359afc77fc24ad&identifier_flag=0&lang=zh-Hans&launch_id=716882697&platform=iOS&project_id=ECFAAF&sid=session.1695189743787849952190&t=1695190591&teenager=0&tz=Asia/Shanghai&uis=light&version=8.7',
}
try {
// 请求数据
const response = await fetch(url, {
headers: xhsHeaders,
});
if (!response.ok) {
// 如果请求失败,抛出错误,不进行缓存
throw new Error(`请求错误 ${label.name}`);
}
// 得到请求体
const responseBody = await response.json();
// 处理数据
if (responseBody.success) {
const result: HotListItem[] = responseBody.data?.items.map((v: any): HotListItem => {
return {
id: v.id,
title: v.title,
hot: v.score,
label: (!v.word_type || v.word_type === '无') ? undefined : v.word_type,
url: `https://www.xiaohongshu.com/search_result?keyword=${encodeURIComponent(v.title)}`,
mobileUrl: `https://www.xiaohongshu.com/search_result?keyword=${encodeURIComponent(v.title)}`,
originData: v,
};
});
return result;
}
return [];
} catch {
// 请求失败,返回空数据
return [];
}
}
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,20 @@
# 知乎日报-推荐榜
调用知乎日报官方 API 获取最新推荐数据。
## API
- **URL**: `https://daily.zhihu.com/api/4/news/latest`
- **Method**: GET
- **Headers**: 需要 Referer 和 Host 头信息
## 数据结构
```typescript
{
id: string | number; // 新闻ID
title: string; // 新闻标题
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("zhihu-daily", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

View File

@@ -0,0 +1,43 @@
import { HotListItem } from "../type";
export const label = {
name: '知乎日报-推荐榜',
icon: 'https://daily.zhihu.com/favicon.ico',
color: '#0084FF',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://daily.zhihu.com/api/4/news/latest';
try {
const response = await fetch(url, {
headers: {
Referer: "https://daily.zhihu.com/api/4/news/latest",
Host: "daily.zhihu.com",
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
},
});
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
const data = responseBody?.stories;
if (!data) {
return [];
}
const result: HotListItem[] = data.map((v) => {
return {
id: v.id,
title: v.title,
url: v.url,
mobileUrl: v.url,
originData: v,
};
});
return result;
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

View File

@@ -0,0 +1,37 @@
# 知乎-热榜
调用知乎官方 API 获取热榜数据。
## API
- **URL**: `https://api.zhihu.com/topstory/hot-list`
- **Method**: GET
## 数据结构
```typescript
{
id: string | number; // 唯一标识
title: string; // 标题
cover?: string; // 封面图
hot: number | string; // 热度值
url: string; // PC 端链接
mobileUrl?: string; // 移动端链接
}
```
## 响应示例
```json
{
"data": [
{
"id": "123456",
"target": {
"title": "问题标题"
},
"detail_text": "1000万"
}
]
}
```

View File

@@ -0,0 +1,19 @@
import { describe, expect, it } from "vitest";
import { main } from "./api";
describe("zhihu", () => {
it("should return an array", async () => {
const result = await main();
expect(Array.isArray(result)).toBe(true);
});
it("should return items with required fields", async () => {
const result = await main();
if (result.length > 0) {
const item = result[0];
expect(item).toHaveProperty("id");
expect(item).toHaveProperty("title");
expect(item).toHaveProperty("url");
}
});
});

37
src/hotnow/zhihu/api.ts Normal file
View File

@@ -0,0 +1,37 @@
import { HotListItem } from "../type";
export const label = {
name: '知乎-热榜',
icon: 'https://static.zhihu.com/heifetz/favicon.ico',
color: '#0084FF',
};
export const main = async function(): Promise<HotListItem[]> {
const url = 'https://api.zhihu.com/topstory/hot-list';
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`请求错误 ${label.name}`);
}
const responseBody = await response.json();
if (responseBody.data) {
const result: HotListItem[] = responseBody.data.map((v) => {
return {
id: v.id,
title: v.target.title,
cover: v.children[0].thumbnail,
hot: parseInt(v.detail_text.replace(/[^\d]/g, '')) * 10000,
url: `https://www.zhihu.com/question/${v.card_id.replace('Q_', '')}`,
mobileUrl: `https://www.zhihu.com/question/${v.card_id.replace('Q_', '')}`,
originData: v,
};
});
return result;
}
return [];
} catch {
return [];
}
};
main().then(console.log).catch(console.error);

18
vitest.config.ts Normal file
View File

@@ -0,0 +1,18 @@
import { defineConfig } from "vitest/config";
import { fileURLToPath } from "url";
import { resolve } from "path";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
export default defineConfig({
test: {
globals: true,
environment: "node",
include: ["src/**/*.test.ts"],
},
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
});