feat(site): 页头加上 GitHub 仓库与 Telegram 频道链接

做成配置项 LDR_HEADER_LINKS,格式 "名称|地址" 逗号分隔,
不硬编码 —— 以后换地址改环境变量即可,不用动代码。

默认值:
  GitHub          https://github.com/lowseekai/let-deals-radar
  Telegram 频道   https://t.me/LowSeekNews

细节
- 解析时跳过非法项(缺分隔符、地址不是 http 开头)
- 未配置则整段不输出,不留空占位
- 外链带 target=_blank + rel=noopener

验证:235 测试通过(新增 4 个)。
This commit is contained in:
gungun88 committed 2026-10-18 22:30:03 +08:00
1 parent c70361796c
commit 1f0554acb7
3 files changed
+83

No files matched your search

+26
View File
@@ -63,6 +63,32 @@ SITE_DESC = _env(
SITE_URL = _env("LDR_SITE_URL", "").rstrip("/")
SITE_FOOTER = _env("LDR_SITE_FOOTER", "")
# 页头的附加链接,格式 "名称|地址",多项用逗号分隔。
# 例:LDR_HEADER_LINKS="GitHub|https://github.com/x/y, Telegram|https://t.me/z"
# 留空则不显示 —— 不影响现有布局。
HEADER_LINKS_RAW = _env(
"LDR_HEADER_LINKS",
"GitHub|https://github.com/lowseekai/let-deals-radar,"
" Telegram 频道|https://t.me/LowSeekNews",
)
def _parse_header_links(raw: str) -> list[tuple[str, str]]:
"""把 "名称|地址,..." 解析成 [(名称, 地址)],非法项跳过。"""
out: list[tuple[str, str]] = []
for chunk in (raw or "").split(","):
chunk = chunk.strip()
if not chunk or "|" not in chunk:
continue
name, url = chunk.split("|", 1)
name, url = name.strip(), url.strip()
if name and url.startswith(("http://", "https://")):
out.append((name, url))
return out
HEADER_LINKS = _parse_header_links(HEADER_LINKS_RAW)
# --------------------------------------------------------------------------
# 数据源
+15
View File
@@ -153,6 +153,19 @@ def _json_for_script(obj) -> str:
.replace("&", "\\u0026"))
def _header_links_html() -> str:
"""渲染页头附加链接。未配置时返回空串(不影响布局)。"""
links = getattr(config, "HEADER_LINKS", None) or []
if not links:
return ""
parts = []
for name, url in links:
parts.append('<span><a href="%s" target="_blank" rel="noopener">%s</a>'
'</span>' % (html.escape(url, quote=True),
html.escape(name)))
return "\n ".join(parts)
SITE_TEMPLATE = r"""<!DOCTYPE html>
<html lang="zh-CN">
<head>
@@ -301,6 +314,7 @@ footer{margin-top:28px;color:var(--muted);font-size:12px;line-height:1.9}
<span>共 <b id="m-total">@@COUNT@@</b> 条</span>
<span>生成于 <b>@@GENERATED@@</b></span>
<span><a href="data/deals.json">原始 JSON</a></span>
@@HEADER_LINKS@@
</div>
</header>
@@ -668,6 +682,7 @@ def build_site(payload: dict, out_dir=None, verbose: bool = True) -> str:
.replace("@@EVENT_CN@@", json.dumps(EVENT_CN, ensure_ascii=False))
.replace("@@AVAIL_CN@@", json.dumps(AVAIL_CN, ensure_ascii=False))
.replace("@@FXNOTE@@", html.escape(config.FX_NOTE))
.replace("@@HEADER_LINKS@@", _header_links_html())
.replace("@@FOOTER@@",
html.escape(config.SITE_FOOTER) if config.SITE_FOOTER
else ""))
+42
View File
@@ -289,6 +289,48 @@ def test_forum_links_corrupt_file_is_safe(tmp_path):
mp.undo()
def test_header_links_rendered(tmp_path):
"""页头应渲染配置的附加链接(GitHub / Telegram)。"""
render.build_site(_sample_payload(), str(tmp_path), verbose=False)
html = open(os.path.join(tmp_path, "index.html"), encoding="utf-8").read()
links = render.config.HEADER_LINKS
assert links, "默认应配置页头链接"
for name, url in links:
assert name in html, "缺少链接名:%s" % name
assert url in html, "缺少链接地址:%s" % url
assert 'rel="noopener"' in html
def test_header_links_empty_is_safe(tmp_path):
"""未配置页头链接时不报错、不留占位符。"""
import pytest as _pytest
mp = _pytest.MonkeyPatch()
mp.setattr(render.config, "HEADER_LINKS", [])
try:
render.build_site(_sample_payload(), str(tmp_path), verbose=False)
finally:
mp.undo()
html = open(os.path.join(tmp_path, "index.html"), encoding="utf-8").read()
assert "@@HEADER_LINKS@@" not in html
def test_parse_header_links():
"""解析 "名称|地址,...",非法项跳过。"""
from letdeals.config import _parse_header_links
got = _parse_header_links(
"GitHub|https://github.com/x, Bad|not-a-url, ,"
" TG|https://t.me/y")
assert got == [("GitHub", "https://github.com/x"),
("TG", "https://t.me/y")]
def test_parse_header_links_empty():
from letdeals.config import _parse_header_links
assert _parse_header_links("") == []
assert _parse_header_links("没有分隔符") == []
def test_link_rendering_both_cases(tmp_path):
"""链接区两种形态:有论坛帖=主+次两个;无=只有原帖。"""
render.build_site(_sample_payload(), str(tmp_path), verbose=False)