Update secrets
Update RSS / update (push) Successful in 5m14s

This commit is contained in:
2026-08-29 15:11:13 +08:00
parent 530a356468
commit e19f4bf49c
4 changed files with 78 additions and 9 deletions
+44 -4
View File
@@ -7,6 +7,7 @@ import argparse
import calendar
import hashlib
import logging
import os
import re
import sys
import tempfile
@@ -15,6 +16,7 @@ from datetime import datetime, timezone
from email.utils import format_datetime
from pathlib import Path
from typing import Any, Iterable
from urllib.parse import parse_qsl, quote, quote_plus, urlencode, urlsplit, urlunsplit
import xml.etree.ElementTree as ET
import feedparser
@@ -27,6 +29,8 @@ from urllib3.util.retry import Retry
LOG = logging.getLogger("rss-merge")
ATOM_NS = "http://www.w3.org/2005/Atom"
CONTENT_NS = "http://purl.org/rss/1.0/modules/content/"
RSSHUB_HOST = "rsshub.yuzhen.pub"
RSSHUB_SECRET_ENV = "RSSHUB_ACCESS_KEY"
ET.register_namespace("atom", ATOM_NS)
ET.register_namespace("content", CONTENT_NS)
@@ -113,13 +117,42 @@ def make_session(user_agent: str, retries: int) -> requests.Session:
return session
def is_protected_rsshub_url(url: str) -> bool:
try:
return (urlsplit(url).hostname or "").casefold() == RSSHUB_HOST
except ValueError:
return False
def add_rsshub_access_key(url: str, access_key: str) -> str:
"""Add or replace the key query parameter without changing the configured URL."""
if not is_protected_rsshub_url(url):
return url
parsed = urlsplit(url)
query = [(name, value) for name, value in parse_qsl(parsed.query, keep_blank_values=True) if name != "key"]
query.append(("key", access_key))
# Never transmit the access key over plaintext HTTP.
return urlunsplit(("https", parsed.netloc, parsed.path, urlencode(query), parsed.fragment))
def redact_secret(message: str, secret: str) -> str:
if not secret:
return message
for value in {secret, quote(secret, safe=""), quote_plus(secret)}:
if value:
message = message.replace(value, "***")
return message
def fetch_feed(
session: requests.Session,
source: Source,
timeout: float,
max_bytes: int,
rsshub_access_key: str,
) -> list[FeedItem]:
with session.get(source.url, timeout=timeout, stream=True) as response:
request_url = add_rsshub_access_key(source.url, rsshub_access_key)
with session.get(request_url, timeout=timeout, stream=True) as response:
response.raise_for_status()
chunks: list[bytes] = []
size = 0
@@ -297,6 +330,7 @@ def run(config_path: Path) -> int:
)
groups: dict[str, dict[str, Any]] = config["groups"]
rsshub_access_key = os.environ.get(RSSHUB_SECRET_ENV, "")
repo_root = config_path.resolve().parent
all_sources: dict[str, Source] = {}
group_urls: dict[str, list[str]] = {}
@@ -313,16 +347,22 @@ def run(config_path: Path) -> int:
for source in sources:
all_sources.setdefault(source.url, source)
if any(is_protected_rsshub_url(url) for url in all_sources) and not rsshub_access_key:
raise ConfigError(f"配置包含 {RSSHUB_HOST},但环境变量 {RSSHUB_SECRET_ENV} 未设置")
session = make_session(user_agent, retries)
# urllib3 的重试日志可能包含完整查询参数;避免将 access key 写入 Actions 日志。
logging.getLogger("urllib3").setLevel(logging.CRITICAL)
fetched: dict[str, list[FeedItem]] = {}
errors: dict[str, str] = {}
for url, source in all_sources.items():
try:
fetched[url] = fetch_feed(session, source, timeout, max_bytes)
fetched[url] = fetch_feed(session, source, timeout, max_bytes, rsshub_access_key)
LOG.info("已获取 %s(%d 条)", url, len(fetched[url]))
except Exception as exc: # One source should produce a useful aggregated error.
errors[url] = str(exc)
LOG.error("获取失败 %s: %s", url, exc)
safe_error = redact_secret(str(exc), rsshub_access_key)
errors[url] = safe_error
LOG.error("获取失败 %s: %s", url, safe_error)
if errors and not allow_partial:
LOG.error("有 %d 个源失败;为避免临时丢失条目,本次不改写输出。可设置 settings.allow_partial: true 改变此行为。", len(errors))