newsnow 1.1.0 → 1.1.1

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 CHANGED
@@ -36,8 +36,39 @@ newsnow hackernews
36
36
  # Output as JSON (pipeable to jq, etc.)
37
37
  newsnow hackernews --json
38
38
 
39
+ # Pretty-print JSON
40
+ newsnow hackernews --json --pretty
41
+
42
+ # Limit number of items
43
+ newsnow hackernews --limit 5
44
+
45
+ # Select specific fields (JSON mode)
46
+ newsnow hackernews --json --fields title,url
47
+
39
48
  # List sources as JSON
40
49
  newsnow list --json
50
+
51
+ # Print machine-readable JSON Schema
52
+ newsnow schema
53
+ ```
54
+
55
+ ### JSON Envelopes
56
+
57
+ All `--json` output uses structured envelopes:
58
+
59
+ **Fetch** (`newsnow <source> --json`):
60
+ ```json
61
+ { "source": "hackernews", "count": 30, "items": [...] }
62
+ ```
63
+
64
+ **List** (`newsnow list --json`):
65
+ ```json
66
+ { "count": 66, "sources": [{ "name": "hackernews", "category": "hackernews", "envVars": [] }, ...] }
67
+ ```
68
+
69
+ **Errors** (written to stderr):
70
+ ```json
71
+ { "error": "Unknown source \"foo\"", "code": "UNKNOWN_SOURCE", "suggestions": ["foobar"] }
41
72
  ```
42
73
 
43
74
  ## Sources
@@ -0,0 +1,57 @@
1
+ import { load } from "cheerio";
2
+ import { myFetch } from "../fetch.js";
3
+ import { parseRelativeDate } from "../utils.js";
4
+ const quick = async () => {
5
+ const baseURL = "https://www.36kr.com";
6
+ const url = `${baseURL}/newsflashes`;
7
+ const response = await myFetch(url);
8
+ const $ = load(response);
9
+ const news = [];
10
+ const $items = $(".newsflash-item");
11
+ $items.each((_, el) => {
12
+ const $el = $(el);
13
+ const $a = $el.find("a.item-title");
14
+ const url = $a.attr("href");
15
+ const title = $a.text();
16
+ const relativeDate = $el.find(".time").text();
17
+ if (url && title && relativeDate) {
18
+ news.push({
19
+ url: `${baseURL}${url}`,
20
+ title,
21
+ id: url,
22
+ extra: { date: parseRelativeDate(relativeDate, "Asia/Shanghai").valueOf() },
23
+ });
24
+ }
25
+ });
26
+ return news;
27
+ };
28
+ const renqi = async () => {
29
+ const url = "https://gateway.36kr.com/api/mis/nav/home/nav/rank/hot";
30
+ const response = await myFetch(url, {
31
+ method: "POST",
32
+ headers: {
33
+ "Content-Type": "application/json",
34
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
35
+ },
36
+ body: {
37
+ partner_id: "wap",
38
+ param: { siteId: 1, platformId: 2 },
39
+ timestamp: Date.now(),
40
+ },
41
+ });
42
+ const items = response?.data?.hotRankList ?? [];
43
+ return items.map((item) => {
44
+ const m = item.templateMaterial ?? {};
45
+ return {
46
+ url: `https://36kr.com/p/${item.itemId}`,
47
+ title: m.widgetTitle ?? "",
48
+ id: String(item.itemId),
49
+ extra: { info: m.authorName },
50
+ };
51
+ }).filter((item) => item.title);
52
+ };
53
+ export default {
54
+ "36kr": quick,
55
+ "36kr-quick": quick,
56
+ "36kr-renqi": renqi,
57
+ };
@@ -16,7 +16,14 @@ const hotSearch = async () => {
16
16
  };
17
17
  const hotVideo = async () => {
18
18
  const url = "https://api.bilibili.com/x/web-interface/popular";
19
- const res = await myFetch(url);
19
+ const res = await myFetch(url, {
20
+ headers: {
21
+ "Referer": "https://www.bilibili.com",
22
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
23
+ },
24
+ });
25
+ if (!res?.data?.list)
26
+ throw new Error(`Bilibili popular API returned unexpected response: ${JSON.stringify(res).slice(0, 200)}`);
20
27
  return res.data.list.map(video => ({
21
28
  id: video.bvid,
22
29
  title: video.title,
@@ -31,7 +38,14 @@ const hotVideo = async () => {
31
38
  };
32
39
  const ranking = async () => {
33
40
  const url = "https://api.bilibili.com/x/web-interface/ranking/v2";
34
- const res = await myFetch(url);
41
+ const res = await myFetch(url, {
42
+ headers: {
43
+ "Referer": "https://www.bilibili.com/ranking/all",
44
+ "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36",
45
+ },
46
+ });
47
+ if (!res?.data?.list)
48
+ throw new Error(`Bilibili ranking API returned unexpected response: ${JSON.stringify(res).slice(0, 200)}`);
35
49
  return res.data.list.map(video => ({
36
50
  id: video.bvid,
37
51
  title: video.title,
@@ -7,11 +7,11 @@ const express = async () => {
7
7
  const $main = $(".news-list");
8
8
  const news = [];
9
9
  $main.each((_, el) => {
10
- const a = $(el).find(".title_name");
11
- const url = a.attr("href");
12
- const titleText = a.text();
10
+ const $el = $(el);
11
+ const titleText = $el.find(".title_name").text();
13
12
  const title = titleText.match(/【(.+)】/)?.[1] ?? titleText;
14
- const date = $(el).attr("data-date");
13
+ const url = $el.find(".shear_box").attr("data-href");
14
+ const date = $el.attr("data-date");
15
15
  if (url && title && date) {
16
16
  news.push({
17
17
  url: baseURL + url,
@@ -1,4 +1,4 @@
1
- import _36kr from "./_36kr.js";
1
+ import _36kr from "./36kr.js";
2
2
  import baidu from "./baidu.js";
3
3
  import bilibili from "./bilibili.js";
4
4
  import cankaoxiaoxi from "./cankaoxiaoxi.js";
@@ -1,28 +1,34 @@
1
1
  import { myFetch } from "../fetch.js";
2
- const hot = async () => {
3
- const res = await myFetch("https://linux.do/top/daily.json", {
4
- headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" },
2
+ import * as cheerio from "cheerio";
3
+ function parseDiscourseRSS(xml) {
4
+ const $ = cheerio.load(xml, { xmlMode: true });
5
+ const items = [];
6
+ $("item").each((_, el) => {
7
+ const pinned = $(el).find("discourse\\:topicPinned").text();
8
+ const archived = $(el).find("discourse\\:topicArchived").text();
9
+ if (pinned === "Yes" || archived === "Yes")
10
+ return;
11
+ const title = $(el).find("title").text();
12
+ const link = $(el).find("link").text();
13
+ const pubDate = $(el).find("pubDate").text();
14
+ if (title && link) {
15
+ items.push({
16
+ id: link,
17
+ title,
18
+ url: link,
19
+ pubDate: pubDate ? new Date(pubDate).getTime() : undefined,
20
+ });
21
+ }
5
22
  });
6
- return res.topic_list.topics
7
- .filter(k => k.visible && !k.archived && !k.pinned)
8
- .map(k => ({
9
- id: k.id,
10
- title: k.title,
11
- url: `https://linux.do/t/topic/${k.id}`,
12
- }));
23
+ return items;
24
+ }
25
+ const hot = async () => {
26
+ const xml = await myFetch("https://linux.do/top/daily.rss");
27
+ return parseDiscourseRSS(xml);
13
28
  };
14
29
  const latest = async () => {
15
- const res = await myFetch("https://linux.do/latest.json?order=created", {
16
- headers: { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" },
17
- });
18
- return res.topic_list.topics
19
- .filter(k => k.visible && !k.archived && !k.pinned)
20
- .map(k => ({
21
- id: k.id,
22
- title: k.title,
23
- pubDate: new Date(k.created_at).valueOf(),
24
- url: `https://linux.do/t/topic/${k.id}`,
25
- }));
30
+ const xml = await myFetch("https://linux.do/latest.rss");
31
+ return parseDiscourseRSS(xml);
26
32
  };
27
33
  export default {
28
34
  "linuxdo": latest,
@@ -1,5 +1,5 @@
1
1
  import { fetchRSS } from "../rss.js";
2
2
  export default {
3
3
  "pcbeta-windows11": () => fetchRSS("https://bbs.pcbeta.com/forum.php?mod=rss&fid=563&auth=0"),
4
- "pcbeta-windows": () => fetchRSS("https://bbs.pcbeta.com/forum.php?mod=rss&fid=521&auth=0"),
4
+ "pcbeta-windows": () => fetchRSS("https://bbs.pcbeta.com/forum.php?mod=rss&fid=548&auth=0"),
5
5
  };
@@ -31,10 +31,30 @@ const hotSearch = async () => {
31
31
  module_strategy_id: {},
32
32
  sub_module_id: "20251106065177",
33
33
  flip_params: {
34
+ folding_screen_show_num: "",
35
+ is_mvl: "1",
36
+ mvl_strategy_info: JSON.stringify({
37
+ default_strategy_id: "06755800b45b49238582a6fa1ad0f5c5",
38
+ default_version: "3836",
39
+ hit_page_uuid: "b5080d97dc694a5fb50eb9e7c99326ac",
40
+ hit_tab_info: null,
41
+ gray_status_info: null,
42
+ bypass_to_un_exp_id: "",
43
+ }),
44
+ mvl_sub_mod_id: "20251106065177",
45
+ pad_post_show_num: "",
46
+ pad_pro_post_show_num: "",
47
+ pad_pro_small_hor_pic_display_num: "",
48
+ pad_small_hor_pic_display_num: "",
34
49
  page_id: "scms_shake",
35
50
  page_num: "0",
36
51
  page_type: "scms_shake",
52
+ post_show_num: "",
53
+ shake_size: "",
54
+ small_hor_pic_display_num: "",
37
55
  source_key: "100113",
56
+ un_policy_id: "06755800b45b49238582a6fa1ad0f5c5",
57
+ un_strategy_id: "06755800b45b49238582a6fa1ad0f5c5",
38
58
  },
39
59
  relace_children_key: [],
40
60
  },
@@ -1,17 +1,4 @@
1
- import * as cheerio from "cheerio";
2
- import { myFetch } from "../fetch.js";
3
- async function handler() {
4
- const baseURL = "https://post.smzdm.com/hot_1/";
5
- const html = await myFetch(baseURL);
6
- const $ = cheerio.load(html);
7
- const $main = $("#feed-main-list .z-feed-title");
8
- const news = [];
9
- $main.each((_, el) => {
10
- const a = $(el).find("a");
11
- const url = a.attr("href");
12
- const title = a.text();
13
- news.push({ url, title, id: url });
14
- });
15
- return news;
16
- }
17
- export default { smzdm: handler };
1
+ import { fetchRSS } from "../rss.js";
2
+ export default {
3
+ smzdm: () => fetchRSS("https://post.smzdm.com/feed/"),
4
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "newsnow",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "newsnow": "./dist/src/cli.js"