newsnow 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +130 -0
- package/dist/src/cli.js +104 -0
- package/dist/src/crypto.js +11 -0
- package/dist/src/fetch.js +36 -0
- package/dist/src/rss.js +25 -0
- package/dist/src/sources/_36kr.js +66 -0
- package/dist/src/sources/baidu.js +13 -0
- package/dist/src/sources/bilibili.js +52 -0
- package/dist/src/sources/cankaoxiaoxi.js +12 -0
- package/dist/src/sources/chongbuluo.js +28 -0
- package/dist/src/sources/cls/index.js +46 -0
- package/dist/src/sources/cls/utils.js +12 -0
- package/dist/src/sources/coolapk/index.js +44 -0
- package/dist/src/sources/douban.js +20 -0
- package/dist/src/sources/douyin.js +14 -0
- package/dist/src/sources/fastbull.js +52 -0
- package/dist/src/sources/freebuf.js +44 -0
- package/dist/src/sources/gelonghui.js +30 -0
- package/dist/src/sources/ghxi.js +48 -0
- package/dist/src/sources/github.js +29 -0
- package/dist/src/sources/hackernews.js +21 -0
- package/dist/src/sources/hupu.js +17 -0
- package/dist/src/sources/ifeng.js +21 -0
- package/dist/src/sources/index.js +90 -0
- package/dist/src/sources/iqiyi.js +17 -0
- package/dist/src/sources/ithome.js +29 -0
- package/dist/src/sources/jin10.js +24 -0
- package/dist/src/sources/juejin.js +11 -0
- package/dist/src/sources/kaopu.js +12 -0
- package/dist/src/sources/kuaishou.js +23 -0
- package/dist/src/sources/linuxdo.js +31 -0
- package/dist/src/sources/mktnews.js +20 -0
- package/dist/src/sources/nowcoder.js +19 -0
- package/dist/src/sources/pcbeta.js +5 -0
- package/dist/src/sources/producthunt.js +37 -0
- package/dist/src/sources/qqvideo.js +51 -0
- package/dist/src/sources/smzdm.js +17 -0
- package/dist/src/sources/solidot.js +27 -0
- package/dist/src/sources/sputniknewscn.js +25 -0
- package/dist/src/sources/sspai.js +13 -0
- package/dist/src/sources/steam.js +26 -0
- package/dist/src/sources/tencent.js +14 -0
- package/dist/src/sources/thepaper.js +12 -0
- package/dist/src/sources/tieba.js +11 -0
- package/dist/src/sources/toutiao.js +12 -0
- package/dist/src/sources/v2ex.js +14 -0
- package/dist/src/sources/wallstreetcn.js +38 -0
- package/dist/src/sources/weibo.js +46 -0
- package/dist/src/sources/xueqiu.js +18 -0
- package/dist/src/sources/zaobao.js +31 -0
- package/dist/src/sources/zhihu.js +15 -0
- package/dist/src/types.js +1 -0
- package/dist/src/utils.js +46 -0
- package/dist/test/cli.test.js +61 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# newsnow
|
|
2
|
+
|
|
3
|
+
A command-line tool to fetch trending news and hot topics from 66 sources across 44 platforms. Built with TypeScript, runs on Bun.
|
|
4
|
+
|
|
5
|
+
Ported from [ourongxing/newsnow](https://github.com/ourongxing/newsnow) server sources.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
bun install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# Show help
|
|
17
|
+
bun src/cli.ts --help
|
|
18
|
+
|
|
19
|
+
# List all available sources
|
|
20
|
+
bun src/cli.ts list
|
|
21
|
+
|
|
22
|
+
# Fetch news from a source
|
|
23
|
+
bun src/cli.ts hackernews
|
|
24
|
+
|
|
25
|
+
# Output as JSON (pipeable to jq, etc.)
|
|
26
|
+
bun src/cli.ts hackernews --json
|
|
27
|
+
|
|
28
|
+
# List sources as JSON
|
|
29
|
+
bun src/cli.ts list --json
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Sources
|
|
33
|
+
|
|
34
|
+
66 source endpoints across 44 platforms:
|
|
35
|
+
|
|
36
|
+
| Platform | Sources |
|
|
37
|
+
|---|---|
|
|
38
|
+
| 36kr | `36kr`, `36kr-quick`, `36kr-renqi` |
|
|
39
|
+
| Baidu | `baidu` |
|
|
40
|
+
| Bilibili | `bilibili`, `bilibili-hot-search`, `bilibili-hot-video`, `bilibili-ranking` |
|
|
41
|
+
| Cankaoxiaoxi | `cankaoxiaoxi` |
|
|
42
|
+
| Chongbuluo | `chongbuluo`, `chongbuluo-hot`, `chongbuluo-latest` |
|
|
43
|
+
| CLS | `cls`, `cls-telegraph`, `cls-depth`, `cls-hot` |
|
|
44
|
+
| Coolapk | `coolapk` |
|
|
45
|
+
| Douban | `douban` |
|
|
46
|
+
| Douyin | `douyin` |
|
|
47
|
+
| Fastbull | `fastbull`, `fastbull-express`, `fastbull-news` |
|
|
48
|
+
| FreeBuf | `freebuf` |
|
|
49
|
+
| Gelonghui | `gelonghui` |
|
|
50
|
+
| Ghxi | `ghxi` |
|
|
51
|
+
| GitHub | `github`, `github-trending-today` |
|
|
52
|
+
| Hacker News | `hackernews` |
|
|
53
|
+
| Hupu | `hupu` |
|
|
54
|
+
| iFeng | `ifeng` |
|
|
55
|
+
| iQIYI | `iqiyi-hot-ranklist` |
|
|
56
|
+
| ITHome | `ithome` |
|
|
57
|
+
| Jin10 | `jin10` |
|
|
58
|
+
| Juejin | `juejin` |
|
|
59
|
+
| Kaopu | `kaopu` |
|
|
60
|
+
| Kuaishou | `kuaishou` |
|
|
61
|
+
| LinuxDo | `linuxdo`, `linuxdo-latest`, `linuxdo-hot` |
|
|
62
|
+
| MktNews | `mktnews`, `mktnews-flash` |
|
|
63
|
+
| Nowcoder | `nowcoder` |
|
|
64
|
+
| PCBeta | `pcbeta-windows`, `pcbeta-windows11` |
|
|
65
|
+
| Product Hunt | `producthunt` |
|
|
66
|
+
| QQ Video | `qqvideo-tv-hotsearch` |
|
|
67
|
+
| SMZDM | `smzdm` |
|
|
68
|
+
| Solidot | `solidot` |
|
|
69
|
+
| Sputnik News CN | `sputniknewscn` |
|
|
70
|
+
| SSPai | `sspai` |
|
|
71
|
+
| Steam | `steam` |
|
|
72
|
+
| Tencent | `tencent-hot` |
|
|
73
|
+
| The Paper | `thepaper` |
|
|
74
|
+
| Tieba | `tieba` |
|
|
75
|
+
| Toutiao | `toutiao` |
|
|
76
|
+
| V2EX | `v2ex`, `v2ex-share` |
|
|
77
|
+
| Wall Street CN | `wallstreetcn`, `wallstreetcn-quick`, `wallstreetcn-news`, `wallstreetcn-hot` |
|
|
78
|
+
| Weibo | `weibo` |
|
|
79
|
+
| Xueqiu | `xueqiu`, `xueqiu-hotstock` |
|
|
80
|
+
| Zaobao | `zaobao` |
|
|
81
|
+
| Zhihu | `zhihu` |
|
|
82
|
+
|
|
83
|
+
Some sources require environment variables:
|
|
84
|
+
|
|
85
|
+
- `producthunt` requires `PRODUCTHUNT_API_TOKEN`
|
|
86
|
+
|
|
87
|
+
Some sources may be blocked by Cloudflare or require authentication:
|
|
88
|
+
|
|
89
|
+
- `linuxdo`, `linuxdo-latest`, `linuxdo-hot` - May return 403 Forbidden
|
|
90
|
+
|
|
91
|
+
## Testing
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
bun test
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## Project Structure
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
src/
|
|
101
|
+
cli.ts # CLI entry point (raw process.argv)
|
|
102
|
+
types.ts # NewsItem type definitions
|
|
103
|
+
fetch.ts # HTTP client (ofetch wrapper)
|
|
104
|
+
crypto.ts # md5, SHA-1, base64 helpers
|
|
105
|
+
utils.ts # Date parsing utilities
|
|
106
|
+
rss.ts # RSS feed parser
|
|
107
|
+
sources/
|
|
108
|
+
index.ts # Source registry (all sources merged)
|
|
109
|
+
baidu.ts # One file per platform
|
|
110
|
+
bilibili.ts
|
|
111
|
+
cls/ # Multi-file sources
|
|
112
|
+
index.ts
|
|
113
|
+
utils.ts
|
|
114
|
+
coolapk/
|
|
115
|
+
index.ts
|
|
116
|
+
...
|
|
117
|
+
test/
|
|
118
|
+
cli.test.ts
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Dependencies
|
|
122
|
+
|
|
123
|
+
- [cheerio](https://github.com/cheeriojs/cheerio) - HTML parsing
|
|
124
|
+
- [ofetch](https://github.com/unjs/ofetch) - HTTP client
|
|
125
|
+
- [dayjs](https://github.com/iamkun/dayjs) - Date formatting
|
|
126
|
+
- [iconv-lite](https://github.com/ashtuchkin/iconv-lite) - Character encoding
|
|
127
|
+
|
|
128
|
+
## License
|
|
129
|
+
|
|
130
|
+
ISC
|
package/dist/src/cli.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { sources } from "./sources/index.js";
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const jsonFlag = args.includes("--json");
|
|
5
|
+
const filteredArgs = args.filter(a => a !== "--json");
|
|
6
|
+
const command = filteredArgs[0];
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`Usage: newsnow <source> [--json]
|
|
9
|
+
newsnow list [--json]
|
|
10
|
+
|
|
11
|
+
Commands:
|
|
12
|
+
list List all available sources
|
|
13
|
+
<source> Fetch news from the given source
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--json Output as JSON
|
|
17
|
+
|
|
18
|
+
Sources: ${Object.keys(sources).length} available. Run "newsnow list" to see all.`);
|
|
19
|
+
}
|
|
20
|
+
function printList() {
|
|
21
|
+
const names = Object.keys(sources).sort();
|
|
22
|
+
if (jsonFlag) {
|
|
23
|
+
console.log(JSON.stringify(names, null, 2));
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
console.log(`Available sources (${names.length}):\n`);
|
|
27
|
+
const groups = {};
|
|
28
|
+
for (const name of names) {
|
|
29
|
+
const base = name.split("-")[0];
|
|
30
|
+
if (!groups[base])
|
|
31
|
+
groups[base] = [];
|
|
32
|
+
groups[base].push(name);
|
|
33
|
+
}
|
|
34
|
+
for (const [base, items] of Object.entries(groups).sort(([a], [b]) => a.localeCompare(b))) {
|
|
35
|
+
if (items.length === 1) {
|
|
36
|
+
console.log(` ${items[0]}`);
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
console.log(` ${base}: ${items.join(", ")}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function suggestSimilar(input) {
|
|
45
|
+
const names = Object.keys(sources);
|
|
46
|
+
return names.filter(n => n.includes(input) || input.includes(n) || levenshtein(n, input) <= 3).slice(0, 5);
|
|
47
|
+
}
|
|
48
|
+
function levenshtein(a, b) {
|
|
49
|
+
const m = a.length, n = b.length;
|
|
50
|
+
const dp = Array.from({ length: m + 1 }, () => Array(n + 1).fill(0));
|
|
51
|
+
for (let i = 0; i <= m; i++)
|
|
52
|
+
dp[i][0] = i;
|
|
53
|
+
for (let j = 0; j <= n; j++)
|
|
54
|
+
dp[0][j] = j;
|
|
55
|
+
for (let i = 1; i <= m; i++)
|
|
56
|
+
for (let j = 1; j <= n; j++)
|
|
57
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1, dp[i - 1][j - 1] + (a[i - 1] !== b[j - 1] ? 1 : 0));
|
|
58
|
+
return dp[m][n];
|
|
59
|
+
}
|
|
60
|
+
async function fetchSource(name) {
|
|
61
|
+
const handler = sources[name];
|
|
62
|
+
if (!handler) {
|
|
63
|
+
const similar = suggestSimilar(name);
|
|
64
|
+
console.error(`Error: Unknown source "${name}"`);
|
|
65
|
+
if (similar.length) {
|
|
66
|
+
console.error(`Did you mean: ${similar.join(", ")}?`);
|
|
67
|
+
}
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const items = await handler();
|
|
72
|
+
if (jsonFlag) {
|
|
73
|
+
console.log(JSON.stringify(items, null, 2));
|
|
74
|
+
}
|
|
75
|
+
else {
|
|
76
|
+
if (!items.length) {
|
|
77
|
+
console.log("No items found.");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
console.log(`\n${name} (${items.length} items)\n${"─".repeat(60)}`);
|
|
81
|
+
for (const [i, item] of items.entries()) {
|
|
82
|
+
const parts = [`${String(i + 1).padStart(3)}. ${item.title}`];
|
|
83
|
+
if (item.url)
|
|
84
|
+
parts.push(` ${item.url}`);
|
|
85
|
+
if (item.extra?.info && typeof item.extra.info === "string")
|
|
86
|
+
parts.push(` ${item.extra.info}`);
|
|
87
|
+
console.log(parts.join("\n"));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
catch (err) {
|
|
92
|
+
console.error(`Error fetching "${name}": ${err.message}`);
|
|
93
|
+
process.exit(1);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
97
|
+
printHelp();
|
|
98
|
+
}
|
|
99
|
+
else if (command === "list") {
|
|
100
|
+
printList();
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
fetchSource(command);
|
|
104
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
export async function md5(input) {
|
|
3
|
+
return createHash("md5").update(input).digest("hex");
|
|
4
|
+
}
|
|
5
|
+
export async function myCrypto(data, algorithm) {
|
|
6
|
+
const alg = algorithm.toLowerCase().replace("-", "");
|
|
7
|
+
return createHash(alg === "sha1" ? "sha1" : alg).update(data).digest("hex");
|
|
8
|
+
}
|
|
9
|
+
export function encodeBase64(input) {
|
|
10
|
+
return Buffer.from(input).toString("base64");
|
|
11
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { ofetch } from "ofetch";
|
|
2
|
+
export async function myFetch(url, opts) {
|
|
3
|
+
const fetchOpts = {};
|
|
4
|
+
if (opts?.method)
|
|
5
|
+
fetchOpts.method = opts.method;
|
|
6
|
+
if (opts?.headers)
|
|
7
|
+
fetchOpts.headers = opts.headers;
|
|
8
|
+
if (opts?.query)
|
|
9
|
+
fetchOpts.query = opts.query;
|
|
10
|
+
if (opts?.body)
|
|
11
|
+
fetchOpts.body = opts.body;
|
|
12
|
+
if (opts?.responseType === "arrayBuffer") {
|
|
13
|
+
fetchOpts.responseType = "arrayBuffer";
|
|
14
|
+
}
|
|
15
|
+
else {
|
|
16
|
+
const contentNegotiation = !opts?.responseType;
|
|
17
|
+
if (contentNegotiation) {
|
|
18
|
+
fetchOpts.responseType = "text";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
const res = await ofetch(url, fetchOpts);
|
|
22
|
+
if (typeof res === "string") {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(res);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return res;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return res;
|
|
31
|
+
}
|
|
32
|
+
export const $fetch = Object.assign(ofetch, {
|
|
33
|
+
raw: async (url, opts) => {
|
|
34
|
+
return fetch(url, { redirect: "manual", ...opts });
|
|
35
|
+
},
|
|
36
|
+
});
|
package/dist/src/rss.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { myFetch } from "./fetch.js";
|
|
2
|
+
import * as cheerio from "cheerio";
|
|
3
|
+
export async function fetchRSS(url) {
|
|
4
|
+
const xml = await myFetch(url);
|
|
5
|
+
const $ = cheerio.load(xml, { xmlMode: true });
|
|
6
|
+
const items = [];
|
|
7
|
+
$("item").each((_, el) => {
|
|
8
|
+
const title = $(el).find("title").text();
|
|
9
|
+
const link = $(el).find("link").text();
|
|
10
|
+
const pubDate = $(el).find("pubDate").text();
|
|
11
|
+
const description = $(el).find("description").text();
|
|
12
|
+
if (title && link) {
|
|
13
|
+
items.push({
|
|
14
|
+
id: link,
|
|
15
|
+
title,
|
|
16
|
+
url: link,
|
|
17
|
+
pubDate: pubDate ? new Date(pubDate).getTime() : undefined,
|
|
18
|
+
extra: {
|
|
19
|
+
hover: description,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return items;
|
|
25
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { load } from "cheerio";
|
|
2
|
+
import dayjs from "dayjs";
|
|
3
|
+
import { myFetch } from "../fetch.js";
|
|
4
|
+
import { parseRelativeDate } from "../utils.js";
|
|
5
|
+
const quick = async () => {
|
|
6
|
+
const baseURL = "https://www.36kr.com";
|
|
7
|
+
const url = `${baseURL}/newsflashes`;
|
|
8
|
+
const response = await myFetch(url);
|
|
9
|
+
const $ = load(response);
|
|
10
|
+
const news = [];
|
|
11
|
+
const $items = $(".newsflash-item");
|
|
12
|
+
$items.each((_, el) => {
|
|
13
|
+
const $el = $(el);
|
|
14
|
+
const $a = $el.find("a.item-title");
|
|
15
|
+
const url = $a.attr("href");
|
|
16
|
+
const title = $a.text();
|
|
17
|
+
const relativeDate = $el.find(".time").text();
|
|
18
|
+
if (url && title && relativeDate) {
|
|
19
|
+
news.push({
|
|
20
|
+
url: `${baseURL}${url}`,
|
|
21
|
+
title,
|
|
22
|
+
id: url,
|
|
23
|
+
extra: { date: parseRelativeDate(relativeDate, "Asia/Shanghai").valueOf() },
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
return news;
|
|
28
|
+
};
|
|
29
|
+
const renqi = async () => {
|
|
30
|
+
const baseURL = "https://36kr.com";
|
|
31
|
+
const formatted = dayjs().format("YYYY-MM-DD");
|
|
32
|
+
const url = `${baseURL}/hot-list/renqi/${formatted}/1`;
|
|
33
|
+
const response = await myFetch(url, {
|
|
34
|
+
headers: {
|
|
35
|
+
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
36
|
+
"Referer": "https://www.freebuf.com/",
|
|
37
|
+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8",
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const $ = load(response);
|
|
41
|
+
const articles = [];
|
|
42
|
+
const $items = $(".article-item-info");
|
|
43
|
+
$items.each((_, el) => {
|
|
44
|
+
const $el = $(el);
|
|
45
|
+
const $a = $el.find("a.article-item-title.weight-bold");
|
|
46
|
+
const href = $a.attr("href") || "";
|
|
47
|
+
const title = $a.text().trim();
|
|
48
|
+
const description = $el.find("a.article-item-description.ellipsis-2").text().trim();
|
|
49
|
+
const author = $el.find(".kr-flow-bar-author").text().trim();
|
|
50
|
+
const hotText = $el.find(".kr-flow-bar-hot span").text().trim();
|
|
51
|
+
if (href && title) {
|
|
52
|
+
articles.push({
|
|
53
|
+
url: href.startsWith("http") ? href : `${baseURL}${href}`,
|
|
54
|
+
title,
|
|
55
|
+
id: href.slice(3),
|
|
56
|
+
extra: { info: `${author} | ${hotText}`, hover: description },
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
return articles;
|
|
61
|
+
};
|
|
62
|
+
export default {
|
|
63
|
+
"36kr": quick,
|
|
64
|
+
"36kr-quick": quick,
|
|
65
|
+
"36kr-renqi": renqi,
|
|
66
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { myFetch } from "../fetch.js";
|
|
2
|
+
async function handler() {
|
|
3
|
+
const rawData = await myFetch(`https://top.baidu.com/board?tab=realtime`);
|
|
4
|
+
const jsonStr = rawData.match(/<!--s-data:(.*?)-->/s);
|
|
5
|
+
const data = JSON.parse(jsonStr[1]);
|
|
6
|
+
return data.data.cards[0].content.filter(k => !k.isTop).map((k) => ({
|
|
7
|
+
id: k.rawUrl,
|
|
8
|
+
title: k.word,
|
|
9
|
+
url: k.rawUrl,
|
|
10
|
+
extra: { hover: k.desc },
|
|
11
|
+
}));
|
|
12
|
+
}
|
|
13
|
+
export default { baidu: handler };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { myFetch } from "../fetch.js";
|
|
2
|
+
function formatNumber(num) {
|
|
3
|
+
if (num >= 10000)
|
|
4
|
+
return `${Math.floor(num / 10000)}w+`;
|
|
5
|
+
return num.toString();
|
|
6
|
+
}
|
|
7
|
+
const hotSearch = async () => {
|
|
8
|
+
const url = "https://s.search.bilibili.com/main/hotword?limit=30";
|
|
9
|
+
const res = await myFetch(url);
|
|
10
|
+
return res.list.map(k => ({
|
|
11
|
+
id: k.keyword,
|
|
12
|
+
title: k.show_name,
|
|
13
|
+
url: `https://search.bilibili.com/all?keyword=${encodeURIComponent(k.keyword)}`,
|
|
14
|
+
extra: { icon: k.icon },
|
|
15
|
+
}));
|
|
16
|
+
};
|
|
17
|
+
const hotVideo = async () => {
|
|
18
|
+
const url = "https://api.bilibili.com/x/web-interface/popular";
|
|
19
|
+
const res = await myFetch(url);
|
|
20
|
+
return res.data.list.map(video => ({
|
|
21
|
+
id: video.bvid,
|
|
22
|
+
title: video.title,
|
|
23
|
+
url: `https://www.bilibili.com/video/${video.bvid}`,
|
|
24
|
+
pubDate: video.pubdate * 1000,
|
|
25
|
+
extra: {
|
|
26
|
+
info: `${video.owner.name} · ${formatNumber(video.stat.view)}观看 · ${formatNumber(video.stat.like)}点赞`,
|
|
27
|
+
hover: video.desc,
|
|
28
|
+
icon: video.pic,
|
|
29
|
+
},
|
|
30
|
+
}));
|
|
31
|
+
};
|
|
32
|
+
const ranking = async () => {
|
|
33
|
+
const url = "https://api.bilibili.com/x/web-interface/ranking/v2";
|
|
34
|
+
const res = await myFetch(url);
|
|
35
|
+
return res.data.list.map(video => ({
|
|
36
|
+
id: video.bvid,
|
|
37
|
+
title: video.title,
|
|
38
|
+
url: `https://www.bilibili.com/video/${video.bvid}`,
|
|
39
|
+
pubDate: video.pubdate * 1000,
|
|
40
|
+
extra: {
|
|
41
|
+
info: `${video.owner.name} · ${formatNumber(video.stat.view)}观看 · ${formatNumber(video.stat.like)}点赞`,
|
|
42
|
+
hover: video.desc,
|
|
43
|
+
icon: video.pic,
|
|
44
|
+
},
|
|
45
|
+
}));
|
|
46
|
+
};
|
|
47
|
+
export default {
|
|
48
|
+
"bilibili": hotSearch,
|
|
49
|
+
"bilibili-hot-search": hotSearch,
|
|
50
|
+
"bilibili-hot-video": hotVideo,
|
|
51
|
+
"bilibili-ranking": ranking,
|
|
52
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { myFetch } from "../fetch.js";
|
|
2
|
+
import { tranformToUTC } from "../utils.js";
|
|
3
|
+
async function handler() {
|
|
4
|
+
const res = await Promise.all(["zhongguo", "guandian", "gj"].map(k => myFetch(`https://china.cankaoxiaoxi.com/json/channel/${k}/list.json`)));
|
|
5
|
+
return res.map(k => k.list).flat().map(k => ({
|
|
6
|
+
id: k.data.id,
|
|
7
|
+
title: k.data.title,
|
|
8
|
+
extra: { date: tranformToUTC(k.data.publishTime) },
|
|
9
|
+
url: k.data.url,
|
|
10
|
+
})).sort((m, n) => m.extra.date < n.extra.date ? 1 : -1);
|
|
11
|
+
}
|
|
12
|
+
export default { cankaoxiaoxi: handler };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import * as cheerio from "cheerio";
|
|
2
|
+
import { myFetch } from "../fetch.js";
|
|
3
|
+
import { fetchRSS } from "../rss.js";
|
|
4
|
+
const hot = async () => {
|
|
5
|
+
const baseUrl = "https://www.chongbuluo.com/";
|
|
6
|
+
const html = await myFetch(`${baseUrl}forum.php?mod=guide&view=hot`);
|
|
7
|
+
const $ = cheerio.load(html);
|
|
8
|
+
const news = [];
|
|
9
|
+
$(".bmw table tr").each((_, elem) => {
|
|
10
|
+
const xst = $(elem).find(".common .xst").text();
|
|
11
|
+
const url = $(elem).find(".common a").attr("href");
|
|
12
|
+
news.push({
|
|
13
|
+
id: baseUrl + url,
|
|
14
|
+
url: baseUrl + url,
|
|
15
|
+
title: xst,
|
|
16
|
+
extra: { hover: xst },
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
return news;
|
|
20
|
+
};
|
|
21
|
+
const latest = async () => {
|
|
22
|
+
return fetchRSS("https://www.chongbuluo.com/forum.php?mod=rss&view=newthread");
|
|
23
|
+
};
|
|
24
|
+
export default {
|
|
25
|
+
"chongbuluo": hot,
|
|
26
|
+
"chongbuluo-hot": hot,
|
|
27
|
+
"chongbuluo-latest": latest,
|
|
28
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { myFetch } from "../../fetch.js";
|
|
2
|
+
import { getSearchParams } from "./utils.js";
|
|
3
|
+
const depth = async () => {
|
|
4
|
+
const apiUrl = `https://www.cls.cn/v3/depth/home/assembled/1000`;
|
|
5
|
+
const res = await myFetch(apiUrl, {
|
|
6
|
+
query: Object.fromEntries(await getSearchParams()),
|
|
7
|
+
});
|
|
8
|
+
return res.data.depth_list.sort((m, n) => n.ctime - m.ctime).map((k) => ({
|
|
9
|
+
id: k.id,
|
|
10
|
+
title: k.title || k.brief,
|
|
11
|
+
mobileUrl: k.shareurl,
|
|
12
|
+
pubDate: k.ctime * 1000,
|
|
13
|
+
url: `https://www.cls.cn/detail/${k.id}`,
|
|
14
|
+
}));
|
|
15
|
+
};
|
|
16
|
+
const hot = async () => {
|
|
17
|
+
const apiUrl = `https://www.cls.cn/v2/article/hot/list`;
|
|
18
|
+
const res = await myFetch(apiUrl, {
|
|
19
|
+
query: Object.fromEntries(await getSearchParams()),
|
|
20
|
+
});
|
|
21
|
+
return res.data.map((k) => ({
|
|
22
|
+
id: k.id,
|
|
23
|
+
title: k.title || k.brief,
|
|
24
|
+
mobileUrl: k.shareurl,
|
|
25
|
+
url: `https://www.cls.cn/detail/${k.id}`,
|
|
26
|
+
}));
|
|
27
|
+
};
|
|
28
|
+
const telegraph = async () => {
|
|
29
|
+
const apiUrl = `https://www.cls.cn/nodeapi/updateTelegraphList`;
|
|
30
|
+
const res = await myFetch(apiUrl, {
|
|
31
|
+
query: Object.fromEntries(await getSearchParams()),
|
|
32
|
+
});
|
|
33
|
+
return res.data.roll_data.filter((k) => !k.is_ad).map((k) => ({
|
|
34
|
+
id: k.id,
|
|
35
|
+
title: k.title || k.brief,
|
|
36
|
+
mobileUrl: k.shareurl,
|
|
37
|
+
pubDate: k.ctime * 1000,
|
|
38
|
+
url: `https://www.cls.cn/detail/${k.id}`,
|
|
39
|
+
}));
|
|
40
|
+
};
|
|
41
|
+
export default {
|
|
42
|
+
"cls": telegraph,
|
|
43
|
+
"cls-telegraph": telegraph,
|
|
44
|
+
"cls-depth": depth,
|
|
45
|
+
"cls-hot": hot,
|
|
46
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { md5, myCrypto } from "../../crypto.js";
|
|
2
|
+
const params = {
|
|
3
|
+
appName: "CailianpressWeb",
|
|
4
|
+
os: "web",
|
|
5
|
+
sv: "7.7.5",
|
|
6
|
+
};
|
|
7
|
+
export async function getSearchParams(moreParams) {
|
|
8
|
+
const searchParams = new URLSearchParams({ ...params, ...moreParams });
|
|
9
|
+
searchParams.sort();
|
|
10
|
+
searchParams.append("sign", await md5(await myCrypto(searchParams.toString(), "SHA-1")));
|
|
11
|
+
return searchParams;
|
|
12
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { myFetch } from "../../fetch.js";
|
|
2
|
+
import { md5, encodeBase64 } from "../../crypto.js";
|
|
3
|
+
import { load } from "cheerio";
|
|
4
|
+
function getRandomDEVICE_ID() {
|
|
5
|
+
const r = [10, 6, 6, 6, 14];
|
|
6
|
+
const id = r.map(i => Math.random().toString(36).substring(2, i));
|
|
7
|
+
return id.join("-");
|
|
8
|
+
}
|
|
9
|
+
async function get_app_token() {
|
|
10
|
+
const DEVICE_ID = getRandomDEVICE_ID();
|
|
11
|
+
const now = Math.round(Date.now() / 1000);
|
|
12
|
+
const hex_now = `0x${now.toString(16)}`;
|
|
13
|
+
const md5_now = await md5(now.toString());
|
|
14
|
+
const s = `token://com.coolapk.market/c67ef5943784d09750dcfbb31020f0ab?${md5_now}$${DEVICE_ID}&com.coolapk.market`;
|
|
15
|
+
const md5_s = await md5(encodeBase64(s));
|
|
16
|
+
const token = md5_s + DEVICE_ID + hex_now;
|
|
17
|
+
return token;
|
|
18
|
+
}
|
|
19
|
+
async function genHeaders() {
|
|
20
|
+
return {
|
|
21
|
+
"X-Requested-With": "XMLHttpRequest",
|
|
22
|
+
"X-App-Id": "com.coolapk.market",
|
|
23
|
+
"X-App-Token": await get_app_token(),
|
|
24
|
+
"X-Sdk-Int": "29",
|
|
25
|
+
"X-Sdk-Locale": "zh-CN",
|
|
26
|
+
"X-App-Version": "11.0",
|
|
27
|
+
"X-Api-Version": "11",
|
|
28
|
+
"X-App-Code": "2101202",
|
|
29
|
+
"User-Agent": "Dalvik/2.1.0 (Linux; U; Android 10; Redmi K30 5G MIUI/V12.0.3.0.QGICMXM) (#Build; Redmi; Redmi K30 5G; QKQ1.191222.002 test-keys; 10) +CoolMarket/11.0-2101202",
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const handler = async () => {
|
|
33
|
+
const url = "https://api.coolapk.com/v6/page/dataList?url=%2Ffeed%2FstatList%3FcacheExpires%3D300%26statType%3Dday%26sortField%3Ddetailnum%26title%3D%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&title=%E4%BB%8A%E6%97%A5%E7%83%AD%E9%97%A8&subTitle=&page=1";
|
|
34
|
+
const r = await myFetch(url, { headers: await genHeaders() });
|
|
35
|
+
if (!r.data.length)
|
|
36
|
+
throw new Error("Failed to fetch");
|
|
37
|
+
return r.data.filter(k => k.id).map(i => ({
|
|
38
|
+
id: i.id,
|
|
39
|
+
title: i.editor_title || load(i.message).text().split("\n")[0],
|
|
40
|
+
url: `https://www.coolapk.com${i.url}`,
|
|
41
|
+
extra: { info: i.targetRow?.subTitle },
|
|
42
|
+
}));
|
|
43
|
+
};
|
|
44
|
+
export default { coolapk: handler };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { myFetch } from "../fetch.js";
|
|
2
|
+
async function handler() {
|
|
3
|
+
const baseURL = "https://m.douban.com/rexxar/api/v2/subject/recent_hot/movie";
|
|
4
|
+
const res = await myFetch(baseURL, {
|
|
5
|
+
headers: {
|
|
6
|
+
Referer: "https://movie.douban.com/",
|
|
7
|
+
Accept: "application/json, text/plain, */*",
|
|
8
|
+
},
|
|
9
|
+
});
|
|
10
|
+
return res.items.map(movie => ({
|
|
11
|
+
id: movie.id,
|
|
12
|
+
title: movie.title,
|
|
13
|
+
url: `https://movie.douban.com/subject/${movie.id}`,
|
|
14
|
+
extra: {
|
|
15
|
+
info: movie.card_subtitle.split(" / ").slice(0, 3).join(" / "),
|
|
16
|
+
hover: movie.card_subtitle,
|
|
17
|
+
},
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
export default { douban: handler };
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { myFetch, $fetch } from "../fetch.js";
|
|
2
|
+
async function handler() {
|
|
3
|
+
const url = "https://www.douyin.com/aweme/v1/web/hot/search/list/?device_platform=webapp&aid=6383&channel=channel_pc_web&detail_list=1";
|
|
4
|
+
const cookie = (await $fetch.raw("https://login.douyin.com/")).headers.getSetCookie();
|
|
5
|
+
const res = await myFetch(url, {
|
|
6
|
+
headers: { cookie: cookie.join("; ") },
|
|
7
|
+
});
|
|
8
|
+
return res.data.word_list.map((k) => ({
|
|
9
|
+
id: k.sentence_id,
|
|
10
|
+
title: k.word,
|
|
11
|
+
url: `https://www.douyin.com/hot/${k.sentence_id}`,
|
|
12
|
+
}));
|
|
13
|
+
}
|
|
14
|
+
export default { douyin: handler };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as cheerio from "cheerio";
|
|
2
|
+
import { myFetch } from "../fetch.js";
|
|
3
|
+
const express = async () => {
|
|
4
|
+
const baseURL = "https://www.fastbull.com";
|
|
5
|
+
const html = await myFetch(`${baseURL}/cn/express-news`);
|
|
6
|
+
const $ = cheerio.load(html);
|
|
7
|
+
const $main = $(".news-list");
|
|
8
|
+
const news = [];
|
|
9
|
+
$main.each((_, el) => {
|
|
10
|
+
const a = $(el).find(".title_name");
|
|
11
|
+
const url = a.attr("href");
|
|
12
|
+
const titleText = a.text();
|
|
13
|
+
const title = titleText.match(/【(.+)】/)?.[1] ?? titleText;
|
|
14
|
+
const date = $(el).attr("data-date");
|
|
15
|
+
if (url && title && date) {
|
|
16
|
+
news.push({
|
|
17
|
+
url: baseURL + url,
|
|
18
|
+
title: title.length < 4 ? titleText : title,
|
|
19
|
+
id: url,
|
|
20
|
+
pubDate: Number(date),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
return news;
|
|
25
|
+
};
|
|
26
|
+
const newsHandler = async () => {
|
|
27
|
+
const baseURL = "https://www.fastbull.com";
|
|
28
|
+
const html = await myFetch(`${baseURL}/cn/news`);
|
|
29
|
+
const $ = cheerio.load(html);
|
|
30
|
+
const $main = $(".trending_type");
|
|
31
|
+
const news = [];
|
|
32
|
+
$main.each((_, el) => {
|
|
33
|
+
const a = $(el);
|
|
34
|
+
const url = a.attr("href");
|
|
35
|
+
const title = a.find(".title").text();
|
|
36
|
+
const date = a.find("[data-date]").attr("data-date");
|
|
37
|
+
if (url && title && date) {
|
|
38
|
+
news.push({
|
|
39
|
+
url: baseURL + url,
|
|
40
|
+
title,
|
|
41
|
+
id: url,
|
|
42
|
+
pubDate: Number(date),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
return news;
|
|
47
|
+
};
|
|
48
|
+
export default {
|
|
49
|
+
"fastbull": express,
|
|
50
|
+
"fastbull-express": express,
|
|
51
|
+
"fastbull-news": newsHandler,
|
|
52
|
+
};
|