Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions plugins/newsnow/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Changelog

## 2.0.0

### Added

- 风格样式切换:支持风格1(侧栏)、风格2(卡片)、风格3(占位,即将推出)
- **风格1** 侧栏布局:左侧频道 Tab + 右侧资讯列表
- 风格1 搜索:支持模糊搜索频道名称与当前频道内资讯
- 风格1 关注:频道标题关注按钮、左侧「关注」分组展开已关注频道
- 风格1 交互:右下角刷新 / 回到顶部悬浮按钮,资讯悬停蓝色高亮动效
- 风格1 收藏:热搜与实时新闻列表均支持收藏
- 设置项 `uiStyle`、`lastStyle1Source` 持久化

### Changed

- 默认风格由风格2(卡片)调整为 **风格1(侧栏)**
- 共享逻辑集中于 `lib/newsnow/`(设置、元数据、收藏、栏目配置等)

### Fixed

- 风格1 搜索框输入时失焦(改为局部 DOM 更新)
- 侧栏无法滚动、设置入口被遮挡(底部固定悬浮设置栏)
- GitHub、百度、Freebuf 等频道热度/标题显示异常(修正 `extra` 字段解析与 HTML 转义)
- 模块初始化时 `style1SelectedSource` 暂时性死区导致页面空白

## 1.0.0

### Added

- NewsNow 插件首版:多平台热搜与实时新闻订阅
- 风格2 卡片网格布局,支持关注、收藏、搜索、数据源配置
- ZTools preload 代理请求与本地调试(Vite HMR)
1 change: 1 addition & 0 deletions plugins/newsnow/assets/main-BZwbOKIC.css

Large diffs are not rendered by default.

251 changes: 251 additions & 0 deletions plugins/newsnow/assets/main-D8qa8V4m.js

Large diffs are not rendered by default.

14 changes: 14 additions & 0 deletions plugins/newsnow/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="zh-CN" class="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=860, initial-scale=1" />
<title>NewsNow</title>
<script type="module" crossorigin src="./assets/main-D8qa8V4m.js"></script>
<link rel="stylesheet" crossorigin href="./assets/main-BZwbOKIC.css">
</head>
<body>
<div id="app"></div>
<div id="overlay-root"></div>
</body>
</html>
Binary file added plugins/newsnow/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions plugins/newsnow/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"type": "commonjs",
"private": true
}
37 changes: 37 additions & 0 deletions plugins/newsnow/plugin.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "newsnow",
"title": "NewsNow",
"description": "实时新闻快讯与多平台热搜订阅",
"version": "2.0.0",
"author": "crisy",
"logo": "logo.png",
"main": "index.html",
"preload": "preload.js",
"pluginSetting": {
"single": true,
"height": 640
},
"features": [
{
"code": "newsnow-news",
"explain": "NewsNow - 实时新闻快讯",
"cmds": [
"新闻"
]
},
{
"code": "newsnow-hot",
"explain": "NewsNow - 多平台热搜热榜",
"cmds": [
"热搜"
]
},
{
"code": "newsnow-browse",
"explain": "NewsNow - 看一看订阅频道",
"cmds": [
"看一看"
]
}
]
}
91 changes: 91 additions & 0 deletions plugins/newsnow/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* NewsNow preload — 远程 API 请求(Node.js https)
*/
const https = require("node:https");
const http = require("node:http");

function buildHeaders(origin) {
return {
Accept: "application/json, text/plain, */*",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
"User-Agent":
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36",
Referer: `${origin}/`,
};
}

function parseErrorMessage(body, statusCode) {
try {
const json = JSON.parse(body);
if (json.message) return String(json.message);
if (json.statusMessage) return String(json.statusMessage);
} catch {
// ignore
}
if (statusCode === 500) return "源站暂不可用";
return `HTTP ${statusCode}`;
}

function requestJson(urlString) {
return new Promise((resolve, reject) => {
let url;
try {
url = new URL(urlString);
} catch (error) {
reject(error);
return;
}

const origin = `${url.protocol}//${url.host}`;
const lib = url.protocol === "https:" ? https : http;
const options = {
hostname: url.hostname,
port: url.port || (url.protocol === "https:" ? 443 : 80),
path: `${url.pathname}${url.search}`,
method: "GET",
headers: buildHeaders(origin),
};

const req = lib.request(options, (res) => {
if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
requestJson(new URL(res.headers.location, urlString).toString()).then(resolve).catch(reject);
return;
}
let body = "";
res.setEncoding("utf8");
res.on("data", (chunk) => {
body += chunk;
});
res.on("end", () => {
if (res.statusCode && res.statusCode >= 400) {
reject(new Error(parseErrorMessage(body, res.statusCode)));
return;
}
try {
const json = JSON.parse(body);
if (json.error) {
reject(new Error(json.message || json.statusMessage || "请求失败"));
return;
}
resolve(json);
} catch (error) {
reject(new Error("响应不是有效 JSON"));
}
});
});

req.on("error", reject);
req.on("timeout", () => {
req.destroy();
reject(new Error("请求超时"));
});
req.setTimeout(20000);
req.end();
});
}
Comment on lines +29 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

在处理 HTTP 重定向时,缺少对重定向次数的限制,这可能会导致无限重定向循环(例如 A -> B -> A),从而引发栈溢出或请求挂起(拒绝服务)。此外,也应当限制重定向的目标协议仅为 http:https:,以防止潜在的协议走私或非预期行为。

建议引入一个重定向计数器(例如最大允许 5 次重定向),并对 URL 协议进行校验。

function requestJson(urlString, redirectCount = 0) {
  return new Promise((resolve, reject) => {
    if (redirectCount > 5) {
      reject(new Error("重定向次数过多"));
      return;
    }
    let url;
    try {
      url = new URL(urlString);
    } catch (error) {
      reject(error);
      return;
    }

    if (url.protocol !== "http:" && url.protocol !== "https:") {
      reject(new Error("不支持的协议类型"));
      return;
    }

    const origin = `${url.protocol}//${url.host}`;
    const lib = url.protocol === "https:" ? https : http;
    const options = {
      hostname: url.hostname,
      port: url.port || (url.protocol === "https:" ? 443 : 80),
      path: `${url.pathname}${url.search}`,
      method: "GET",
      headers: buildHeaders(origin),
    };

    const req = lib.request(options, (res) => {
      if (res.statusCode && res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
        const redirectUrl = new URL(res.headers.location, urlString).toString();
        requestJson(redirectUrl, redirectCount + 1).then(resolve).catch(reject);
        return;
      }
      let body = "";
      res.setEncoding("utf8");
      res.on("data", (chunk) => {
        body += chunk;
      });
      res.on("end", () => {
        if (res.statusCode && res.statusCode >= 400) {
          reject(new Error(parseErrorMessage(body, res.statusCode)));
          return;
        }
        try {
          const json = JSON.parse(body);
          if (json.error) {
            reject(new Error(json.message || json.statusMessage || "请求失败"));
            return;
          }
          resolve(json);
        } catch (error) {
          reject(new Error("响应不是有效 JSON"));
        }
      });
    });

    req.on("error", reject);
    req.on("timeout", () => {
      req.destroy();
      reject(new Error("请求超时"));
    });
    req.setTimeout(20000);
    req.end();
  });
}


window.newsnowServices = {
fetchSource(apiUrl) {
return requestJson(apiUrl);
},
};
43 changes: 43 additions & 0 deletions plugins/newsnow/viewer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>加载中…</title>
<style>
body {
margin: 0;
font-family: system-ui, sans-serif;
display: flex;
align-items: center;
justify-content: center;
height: 100vh;
color: #71717a;
background: #f4f4f5;
}
</style>
</head>
<body>
<script>
(function () {
const raw = new URLSearchParams(location.search).get("url");
if (!raw) {
document.body.textContent = "缺少链接地址";
return;
}
let target;
try {
target = new URL(raw);
} catch {
document.body.textContent = "链接无效";
return;
}
if (target.protocol !== "http:" && target.protocol !== "https:") {
document.body.textContent = "不支持的链接类型";
return;
}
location.replace(target.toString());
})();
</script>
</body>
</html>
Loading