Skip to content

Commit 70b94dd

Browse files
committed
Merge branch 'dev'
# Conflicts: # README.md # docs/README.md
2 parents fa8dd65 + 8bf9f49 commit 70b94dd

20 files changed

Lines changed: 70117 additions & 7842 deletions

AGENTS.md

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,7 @@ docker compose -f docker-compose.dev.yml up --build
3232
http://127.0.0.1:25173/reader?pw=banana
3333
```
3434

35-
开发 compose 默认 `SCHEDULER_ENABLED=false`,避免启动时自动全量拉取;如需验证自动调度:
36-
37-
```bash
38-
SCHEDULER_ENABLED=true docker compose -f docker-compose.dev.yml up --build
39-
```
35+
开发 compose 默认会按设置页时间间隔自动触发拉取。
4036

4137
生产环境:
4238

EditREADME.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,6 @@
267267
| C017 | 博客园 | 博客园精华区 | {{latest_content}} | [订阅地址](http://feed.cnblogs.com/blog/picked/rss) |
268268
| C018 | 博客园 | 博客园首页 | {{latest_content}} | [订阅地址](http://feed.cnblogs.com/blog/sitehome/rss) |
269269
| C019 | PTT(台湾论坛) | PTT电影专题 | {{latest_content}} | [订阅地址](https://www.ptt.cc/atom/movie.xml) |
270-
| C020 | PTT(台湾论坛) | PTT正妹专题 | {{latest_content}} | [订阅地址](https://www.ptt.cc/atom/beauty.xml) |
271270
| <div id="C021" style="text-align: center;"><img src="./_media/favicon/C021.png" width="30px" style="width:30px;height: auto;"/><br><span>C021</span></div> | 吾爱破解 | 吾爱破解精品软件区 | {{latest_content}} | [订阅地址](http://rsshub:1200/discuz/x/https%3a%2f%2fwww.52pojie.cn%2fforum-16-1.html) |
272271
| <div id="C022" style="text-align: center;"><img src="./_media/favicon/C022.png" width="30px" style="width:30px;height: auto;"/><br><span>C022</span></div> | cnBeta.COM 精彩优秀评论 | 从cnBeta每天数千评论中精选出来的优秀评论 | {{latest_content}} | [订阅地址](https://www.cnbeta.com/commentrss.php) |
273272
| <div id="C023" style="text-align: center;"><img src="./_media/favicon/C023.png" width="30px" style="width:30px;height: auto;"/><br><span>C023</span></div> | 比特客栈的文艺复兴 | We do not choose who we are, but we do choose who we become. | {{latest_content}} | [订阅地址](https://bitinn.net/feed/) |
@@ -283,6 +282,7 @@
283282
| C033 | 恩山无线论坛 | 无线路由器爱好者的乐园 | {{latest_content}} | [订阅地址](http://rsshub:1200/right/forum/31) |
284283
| C034 | xLog | An open-source creative community written on the blockchain. | {{latest_content}} | [订阅地址](https://xlog.app/feed/hottest?interval=1) |
285284
| C035 | NodeSeek | 一个面向Web开发、主机托管、VPS/服务器等技术话题的极客社区 | {{latest_content}} | [订阅地址](https://rss.nodeseek.com/) |
285+
| N013 | 一亩三分地 - 分区-世界公民 | 分区 分区 id 留学申请 257 世界公民 379 投资理财 400 生活干货 31 职场达人 345 人际关系 391 海外求职 38 签证移民 265 分类 热门帖子 最新帖子 hot new 排序方式 最新回复 最新发布 post &#124; 示例:/1point3acres/section/345 &#124; 参数:id: 分区 id,见下表,默认为全部;type: 帖子分类, 见下表,默认为 hot,即热门帖子;order: 排序方式,见下表,默认为空,即最新回复 &#124; 源码:section.ts | {{latest_content}} | [订阅地址](http://rsshub:1200/1point3acres/section/379/hot) |
286286
| <h2 id="影视资源">影视资源</h2> | | | |
287287
| <div id="M001" style="text-align: center;"><img src="./_media/favicon/M001.png" width="30px" style="width:30px;height: auto;"/><br><span>M001</span></div> | VIP影院 | 666影院 - 全网VIP电影免费看! | {{latest_content}} | [订阅地址](https://bukaivip.com/rss) |
288288
| M002 | LimeTorrents | Latest Torrents RSS | {{latest_content}} | [订阅地址](https://www.limetorrents.pro/rss/) |
@@ -313,6 +313,18 @@
313313
| TG009 | 树莓派家用云服务器 | 树莓派家用云服务器交流 | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/zhaoolee_pi) |
314314
| TG010 | 快乐星球 | 美女图片 | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/botmzt) |
315315
| TG011 | Newlearnerの自留地 | 不定期推送 IT 相关资讯 | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/NewlearnerChannel) |
316+
| N008 | 小破不入渠 | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/forwardlikehell) |
317+
| N009 | 小声读书 | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/weekly_books) |
318+
| N010 | 小道消息 | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/WebNoteslah) |
319+
| N011 | 每日消费电子观察 | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/CE_Observe) |
320+
| N012 | Alan的小纸箱 | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/alansbox) |
321+
| <h2 id="摄影">摄影</h2> | | | |
322+
| C020 | PTT(台湾论坛) | PTT正妹专题 | {{latest_content}} | [订阅地址](https://www.ptt.cc/atom/beauty.xml) |
323+
| N003 | 热点影像模特约拍频道 | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/hotspotimage88) |
324+
| N004 | R21JP | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/m81plus) |
325+
| N005 | Analog Photography | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/anallog) |
326+
| N006 | photography | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/photography) |
327+
| N007 | Photography Poses | | {{latest_content}} | [订阅地址](http://rsshub:1200/telegram/channel/photography_poses) |
316328
| <h2 id="未分类">未分类</h2> | | | |
317329
| N001 | 共产党员网 - 最新发布 | 最新发布 : : zxfb &#124; 示例:/12371/zxfb &#124; 参数:category: 新闻分类名,预设 zxfb &#124; 来源:www.12371.cn &#124; 源码:zxfb.ts | {{latest_content}} | [订阅地址](http://rsshub:1200/12371/zxfb) |
318330

garss-studio/.env.example

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
ACCESS_CODE=banana
22
ACCESS_TOKEN_SECRET=replace-this-secret
33
SESSION_TTL_HOURS=168
4-
SCHEDULER_ENABLED=true

garss-studio/README.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@ cp .env.example .env
3838
- `ACCESS_CODE`:前端登录使用的提取码
3939
- `ACCESS_TOKEN_SECRET`:后端签名密钥
4040
- `SESSION_TTL_HOURS`:登录有效期
41-
- `SCHEDULER_ENABLED`:是否启用后端自动调度,生产默认 `true`,开发 compose 默认 `false`
4241

4342
内部端口和服务地址由 Docker Compose 注入,不需要写进 `.env`。例如后端容器内部监听 `3001`,前端代理到 `backend:3001`,RSSHub 指向 `rsshub:1200`;这些都不需要客户直接配置或访问。
4443

@@ -64,18 +63,12 @@ docker compose -f docker-compose.dev.yml up --build
6463

6564
开发环境下,浏览器只访问 `25173`。前端通过 Vite 代理 `/api``/socket.io` 到后端;后端和 RSSHub 不再暴露宿主机端口。Vite HMR WebSocket 也复用这个入口端口,因此修改 `src/` 后应直接热更新,不需要重建镜像。
6665

67-
`docker-compose.dev.yml` 默认会注入 `SCHEDULER_ENABLED=false`,避免开发容器启动时立刻触发后端全量自动拉取导致内存暴涨。这个默认值只影响自动调度
66+
后端默认会按设置页里的时间间隔自动调度拉取任务
6867

6968
- 阅读页和非强制读取仍然只读取 `storage/reader-cache.json`
7069
- 手动刷新当前订阅源仍然会真实抓取并更新缓存
7170
- 浏览器访问方式不变,仍然只通过 `25173` 和 Vite 代理访问 `/api``/socket.io`
7271

73-
如果要在开发环境验证自动调度,再显式打开:
74-
75-
```bash
76-
SCHEDULER_ENABLED=true docker compose -f docker-compose.dev.yml up --build
77-
```
78-
7972
如果不走 Docker,而是在宿主机上单独运行 Vite/后端,也仍然应该只通过前端入口访问接口;需要为 Vite 显式设置代理目标,例如:
8073

8174
```bash

garss-studio/docker-compose.dev.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ services:
2828
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET:-garss-studio-dev-secret}
2929
SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-168}
3030
RSSHUB_BASE_URL: http://rsshub:1200
31-
SCHEDULER_ENABLED: ${SCHEDULER_ENABLED:-false}
3231
NODE_OPTIONS: --max-old-space-size=6144
3332
volumes:
3433
- .:/app

garss-studio/docker-compose.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ services:
2020
ACCESS_TOKEN_SECRET: ${ACCESS_TOKEN_SECRET:-garss-studio-prod-secret}
2121
SESSION_TTL_HOURS: ${SESSION_TTL_HOURS:-168}
2222
RSSHUB_BASE_URL: http://rsshub:1200
23-
SCHEDULER_ENABLED: ${SCHEDULER_ENABLED:-true}
2423
volumes:
2524
- ./storage:/app/storage
2625
depends_on:

garss-studio/docs/backend-maintenance.md

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,7 @@
2121
## Scheduler And Cache Safety
2222

2323
- 调度入口是 `refreshAllSubscriptionsIntoCache()`.
24-
- 调度总开关是环境变量 `SCHEDULER_ENABLED`,默认按后端环境取值;开发 compose 默认注入 `false`
25-
-`SCHEDULER_ENABLED=false` 时,只能禁用自动调度,不要影响手动刷新接口和缓存读取路径。
24+
- 自动调度默认开启,按设置页里的时间间隔触发。
2625
- 调度刷新必须按订阅源逐个持久化,不能先把整批抓取结果累计到内存里再统一写盘。
2726
- 调度写缓存必须走 `writeReaderCache()` -> `updateReaderCacheCollection()` -> `runWithReaderCacheWriteLock()` 这条串行写路径。
2827
- 不要绕开 `readerCacheWriteChain` 直接并发写 `storage/reader-cache.json`
@@ -31,9 +30,8 @@
3130

3231
开发环境约定:
3332

34-
- `docker-compose.dev.yml` 默认关闭自动调度,避免后端启动时立即做全量抓取。
35-
- 需要验证调度时,用 `SCHEDULER_ENABLED=true docker compose -f docker-compose.dev.yml up --build` 显式开启。
36-
- 即使调度关闭,浏览器仍然只通过 `25173` 访问,前端继续经由 Vite 代理访问 `/api``/socket.io`
33+
- `docker-compose.dev.yml` 默认开启自动调度,后端会按设置页里的时间间隔触发全量拉取。
34+
- 浏览器仍然只通过 `25173` 访问,前端继续经由 Vite 代理访问 `/api``/socket.io`
3735

3836
## Dev Proxy Rules
3937

garss-studio/scripts/lib/subscription-import.mjs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,25 +46,96 @@ export function mergeGeneratedSubscriptions({
4646
defaultEnabled = false,
4747
}) {
4848
const now = new Date().toISOString();
49-
const existingById = new Map(existingSubscriptions.map((subscription) => [subscription.id, subscription]));
49+
const existingById = new Map();
50+
51+
for (const subscription of existingSubscriptions) {
52+
const currentList = existingById.get(subscription.id) || [];
53+
currentList.push(subscription);
54+
existingById.set(subscription.id, currentList);
55+
}
56+
57+
const existingByRoutePath = new Map();
58+
59+
for (const subscription of existingSubscriptions) {
60+
if (!subscription.routePath) {
61+
continue;
62+
}
63+
64+
const currentList = existingByRoutePath.get(subscription.routePath) || [];
65+
currentList.push(subscription);
66+
existingByRoutePath.set(subscription.routePath, currentList);
67+
}
68+
69+
const generatedRoutePaths = new Set(
70+
generatedSubscriptions.map((subscription) => normalizeText(subscription.routePath)).filter(Boolean),
71+
);
5072
const manualSubscriptions = existingSubscriptions.filter(
51-
(subscription) => !subscription.id.startsWith(`${generatedIdPrefix}-`),
73+
(subscription) =>
74+
!subscription.id.startsWith(`${generatedIdPrefix}-`) &&
75+
!generatedRoutePaths.has(normalizeText(subscription.routePath)),
5276
);
5377

78+
function findExistingSubscription(subscription) {
79+
const candidateIds = [subscription.id, ...(subscription.previousIds || [])];
80+
81+
for (const candidateId of candidateIds) {
82+
const candidates = existingById.get(candidateId) || [];
83+
const matchedByRoute = candidates.find((candidate) => candidate.routePath === subscription.routePath);
84+
85+
if (matchedByRoute) {
86+
return matchedByRoute;
87+
}
88+
}
89+
90+
for (const candidateId of candidateIds) {
91+
const candidates = existingById.get(candidateId) || [];
92+
const matchedByName = candidates.find((candidate) => candidate.name === subscription.name);
93+
94+
if (matchedByName) {
95+
return matchedByName;
96+
}
97+
}
98+
99+
for (const candidateId of candidateIds) {
100+
const candidates = existingById.get(candidateId) || [];
101+
102+
if (candidates[0]) {
103+
return candidates[0];
104+
}
105+
}
106+
107+
const routeCandidates = existingByRoutePath.get(subscription.routePath) || [];
108+
const matchedByRouteAndName = routeCandidates.find((candidate) => candidate.name === subscription.name);
109+
110+
if (matchedByRouteAndName) {
111+
return matchedByRouteAndName;
112+
}
113+
114+
if (routeCandidates[0]) {
115+
return routeCandidates[0];
116+
}
117+
118+
return null;
119+
}
120+
54121
const mergedGeneratedSubscriptions = generatedSubscriptions.map((subscription) => {
55-
const current = existingById.get(subscription.id);
122+
const current = findExistingSubscription(subscription);
56123
const nextCore = {
57124
category: subscription.category,
125+
categories: subscription.categories?.length ? subscription.categories : current?.categories,
58126
name: subscription.name,
59127
routePath: subscription.routePath,
128+
routeTemplate: subscription.routeTemplate ?? current?.routeTemplate,
60129
description: subscription.description,
61130
};
62131

63132
const isUnchanged =
64133
current &&
65134
current.category === nextCore.category &&
135+
JSON.stringify(current.categories || []) === JSON.stringify(nextCore.categories || []) &&
66136
current.name === nextCore.name &&
67137
current.routePath === nextCore.routePath &&
138+
current.routeTemplate === nextCore.routeTemplate &&
68139
current.description === nextCore.description;
69140

70141
return {

garss-studio/scripts/sync-editreadme.mjs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
22
import path from "node:path";
33
import { fileURLToPath } from "node:url";
44
import {
5+
buildStableId,
56
mergeGeneratedSubscriptions,
67
normalizeText,
78
readStudioSubscriptions,
@@ -74,11 +75,40 @@ function parseEditReadme(markdown) {
7475
}
7576

7677
function buildStudioSubscriptions(parsedSources, existingSubscriptions) {
77-
const generatedSubscriptions = parsedSources.map((source) => ({
78-
id: `editreadme-${source.sourceId.toLowerCase()}`,
78+
const sourceByUrl = new Map();
79+
80+
for (const source of parsedSources) {
81+
const current = sourceByUrl.get(source.xmlUrl);
82+
83+
if (!current) {
84+
sourceByUrl.set(source.xmlUrl, {
85+
...source,
86+
categories: [source.category],
87+
sourceIds: [source.sourceId],
88+
});
89+
continue;
90+
}
91+
92+
if (!current.categories.includes(source.category)) {
93+
current.categories.push(source.category);
94+
}
95+
96+
if (!current.sourceIds.includes(source.sourceId)) {
97+
current.sourceIds.push(source.sourceId);
98+
}
99+
}
100+
101+
const generatedSubscriptions = Array.from(sourceByUrl.values()).map((source) => ({
102+
id: buildStableId("editreadme", source.xmlUrl),
103+
previousIds: [
104+
buildStableId("editreadme", source.category, source.sourceId, source.xmlUrl),
105+
...source.sourceIds.map((sourceId) => `editreadme-${sourceId.toLowerCase()}`),
106+
],
79107
category: source.category,
108+
categories: source.categories,
80109
name: source.title,
81110
routePath: source.xmlUrl,
111+
routeTemplate: source.xmlUrl,
82112
description: source.description,
83113
enabled: true,
84114
}));

0 commit comments

Comments
 (0)