Initial commit
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
.git
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
__pycache__/
|
||||
*.pyc
|
||||
tmp/
|
||||
logs/
|
||||
newspaper/*.html
|
||||
newspaper/*.md
|
||||
newspaper/*.json
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# Copy this file to .env on the Debian server.
|
||||
# Do not put real credentials in committed files.
|
||||
|
||||
# DeepSeek API settings.
|
||||
DEEPSEEK_API_KEY=
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||
DEEPSEEK_MODEL=deepseek-v4-pro
|
||||
|
||||
# SMTP delivery settings.
|
||||
NEWSPAPER_SMTP_HOST=
|
||||
NEWSPAPER_SMTP_PORT=
|
||||
NEWSPAPER_SMTP_USERNAME=
|
||||
NEWSPAPER_SMTP_PASSWORD=
|
||||
NEWSPAPER_FROM_EMAIL=
|
||||
NEWSPAPER_FROM_NAME=Hongyu Daily
|
||||
NEWSPAPER_SMTP_SECURITY=ssl
|
||||
|
||||
# Scheduler settings for docker compose.
|
||||
NEWSPAPER_SCHEDULE_TIME=08:00
|
||||
TZ=Asia/Shanghai
|
||||
@@ -0,0 +1,9 @@
|
||||
.DS_Store
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
__pycache__/
|
||||
*.pyc
|
||||
tmp/
|
||||
logs/
|
||||
|
||||
@@ -0,0 +1,157 @@
|
||||
# Daily News for Markyan04 Editorial Constraints
|
||||
|
||||
本仓库用于生成和发送 **Daily News for Markyan04**,一份每天早上的中文个人报纸。
|
||||
|
||||
它不是通用科技 newsletter,也不是申请提醒。它应该像一份压缩后的早报:先说明当天真正发生了什么,再补充必要背景,最后把技术、金融和安全信息整理到可长期阅读的结构里。
|
||||
|
||||
## 输出契约
|
||||
|
||||
每天生成一封邮件。
|
||||
|
||||
标题格式:
|
||||
|
||||
```text
|
||||
Daily News for Markyan04 | YYYY - MM - DD
|
||||
```
|
||||
|
||||
正文必须使用中文。常见英文技术词保留英文,例如 `RAG`、`agent`、`vibe coding`、`LLMOps`、`MCP`、`eBPF`、`KYC / AML`。
|
||||
|
||||
邮件栏目顺序固定为:
|
||||
|
||||
1. 今日汇总
|
||||
2. 传统新闻
|
||||
3. AI领域
|
||||
4. 传统软工
|
||||
5. 安全与性能工程
|
||||
6. GitHub / 开源项目跟踪
|
||||
7. 金融领域
|
||||
|
||||
目标权重:
|
||||
|
||||
- 今日汇总:5%
|
||||
- 传统新闻:15%
|
||||
- AI领域:35%
|
||||
- 传统软工:15%
|
||||
- 安全与性能工程:12%
|
||||
- GitHub / 开源项目跟踪:10%
|
||||
- 金融领域:8%
|
||||
|
||||
最终邮件应当信息密度高、选择克制,适合 8-12 分钟读完。
|
||||
|
||||
## 写作原则
|
||||
|
||||
- 每次生成前必须重新采集来源,不得把模型记忆当成新闻来源。
|
||||
- 新闻型条目必须有来源 URL,并带有发布时间、更新时间或采集时间。
|
||||
- 不要重复过去几期已经写过的同一事件;除非出现重大新进展,才可以再次报道,并且要说明新增事实。
|
||||
- 不要使用“为何重要”“学习启示”“对你来说”“建议关注”这类模板化分段词。
|
||||
- 标准信息放在标题下方的 metadata 行,例如:`来源:BBC World | 时间:2026-07-05 | 链接:https://...`。
|
||||
- 正文以自然文段写作,优先概括事实本身:谁做了什么、何时发生、涉及哪些地区或机构、当前进展是什么、还有哪些不确定性。
|
||||
- 可以解释影响,但解释必须服务于理解事件,不要只说“这很重要”而不交代事件内容。
|
||||
- 不要直接对读者进行个人化说教,不要写“你应该学习”“对你而言”。
|
||||
- 不要加入每日行动清单、实习就业板块、英语写作练习或申请动态板块。
|
||||
- 重要论文、技术报告、benchmark 放进 `AI领域`,不单独设研究动态。
|
||||
- 不追踪用户自己的 GitHub 项目,除非用户明确要求。
|
||||
|
||||
## 栏目要求
|
||||
|
||||
### 1. 今日汇总
|
||||
|
||||
最多 5 条,覆盖当天最值得读的内容。可以使用短 bullet,但要总结具体事件,不要只写意义。
|
||||
|
||||
示例:
|
||||
|
||||
```text
|
||||
- 传统新闻:...
|
||||
- AI:...
|
||||
- 安全:...
|
||||
- GitHub:...
|
||||
- 金融:...
|
||||
```
|
||||
|
||||
### 2. 传统新闻
|
||||
|
||||
覆盖国际政治、地缘冲突、中国与香港政策、金融监管、宏观经济和亚太地区重要事件。
|
||||
|
||||
优先主题包括:
|
||||
|
||||
- 中美关系、特朗普政府相关政策、访华或外交动态。
|
||||
- 俄乌战争及其外交、军事、能源、制裁进展。
|
||||
- 中国内地与香港金融监管,例如证监会、港交所、香港证监会、金管局的重要动作。
|
||||
- 影响科技、金融和全球供应链的政策新闻。
|
||||
|
||||
使用成熟新闻机构和官方监管机构来源。二手媒体报道可以用于发现,但重大政策和监管内容应尽量回到官方来源核验。
|
||||
|
||||
### 3. AI领域
|
||||
|
||||
这是主栏目,覆盖前沿新闻和工程理解。
|
||||
|
||||
扫描范围包括:
|
||||
|
||||
- 大模型发布、能力更新和产品变化。
|
||||
- AI coding tools、coding agents、vibe coding 工作流。
|
||||
- agent 框架、工具调用、MCP、多 agent 系统、工作流编排。
|
||||
- AI engineering / LLMOps:评测、可观测性、路由、guardrails、推理、缓存、部署、成本、可靠性。
|
||||
- RAG、GraphRAG、长上下文、memory、retrieval evaluation。
|
||||
- 多模态 AI、UI agents、computer-use agents。
|
||||
- 开源模型与推理基础设施。
|
||||
- AI security:prompt injection、数据泄露、沙箱、agent 风险。
|
||||
- 进入工程实践的新词和新概念。
|
||||
- 高价值论文、benchmark 或技术报告。
|
||||
|
||||
每天选择约 5-8 条。优先最近 24-72 小时内的实质更新;可以保留少量背景解释,但必须由近期事件触发。
|
||||
|
||||
### 4. 传统软工
|
||||
|
||||
关注耐用的软件工程知识和工业级实践:
|
||||
|
||||
- Backend:Spring Boot、FastAPI、API 设计、auth、服务边界。
|
||||
- Frontend:Vue、TypeScript、构建系统、状态管理、测试。
|
||||
- Database:PostgreSQL、MySQL、索引、事务、查询计划、缓存。
|
||||
- DevOps:Docker、CI/CD、Nginx、Linux service、Sentry、observability。
|
||||
- Testing:E2E、contract test、load test、回归策略。
|
||||
- 事故复盘、生产故障分析、可维护性和技术债。
|
||||
|
||||
避免初级教程,除非它解释了常被误解的重要概念。
|
||||
|
||||
### 5. 安全与性能工程
|
||||
|
||||
覆盖安全漏洞、GFW / 网络测量和系统性能。
|
||||
|
||||
安全条目应包括漏洞编号或名称、受影响系统、利用状态、当前处置或缓解方向。不得提供攻击步骤、绕过审查教程或可操作规避指南。
|
||||
|
||||
性能条目优先 Linux performance、`perf`、`eBPF`、FlameGraph、scheduler、memory、IO、networking、Android Perfetto、数据库和后端延迟分析。
|
||||
|
||||
### 6. GitHub / 开源项目跟踪
|
||||
|
||||
只跟踪外部高价值项目。
|
||||
|
||||
优先类别:
|
||||
|
||||
- AI agents 和 coding agents。
|
||||
- LLM infrastructure。
|
||||
- RAG 与 evaluation tools。
|
||||
- Developer tools。
|
||||
- Security tools。
|
||||
- Performance engineering tools。
|
||||
- Fintech 相关基础设施。
|
||||
|
||||
项目摘要要说明它在做什么、为什么最近被关注、技术栈或架构特征、成熟度。避免低质量 prompt collection、壳包装、营销 demo 和长期无人维护项目。
|
||||
|
||||
### 7. 金融领域
|
||||
|
||||
这是金融系统和 fintech 素养栏目,不提供个性化投资建议。
|
||||
|
||||
优先主题:
|
||||
|
||||
- 利率、债券、收益率曲线、ETF、流动性、做市、清算与结算。
|
||||
- 支付、清算、风控、反欺诈、`KYC / AML`、open banking。
|
||||
- 稳定币、tokenization、CBDC 和金融基础设施。
|
||||
- AI in finance:风控模型、欺诈检测、研究助手、文档处理、合规。
|
||||
- HKMA、MAS、BIS、SEC、Fed、交易所等机构动态。
|
||||
|
||||
## 来源和归档
|
||||
|
||||
来源规则在 `config/sources.yaml`。投递和归档规则在 `config/delivery.yaml`。
|
||||
|
||||
发送前必须写入 `newspaper/` 下的 Markdown、HTML 和 metadata JSON。不要把 SMTP 授权码、DeepSeek API key、cookie 或 session token 写入仓库。
|
||||
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
FROM python:3.12-slim
|
||||
|
||||
ARG HTTP_PROXY
|
||||
ARG HTTPS_PROXY
|
||||
ARG ALL_PROXY
|
||||
ARG NO_PROXY
|
||||
ARG http_proxy
|
||||
ARG https_proxy
|
||||
ARG all_proxy
|
||||
ARG no_proxy
|
||||
|
||||
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||
PYTHONUNBUFFERED=1 \
|
||||
TZ=Asia/Shanghai \
|
||||
NEWSPAPER_ROOT=/app
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends ca-certificates tzdata \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY pyproject.toml ./
|
||||
COPY AGENTS.md ./
|
||||
COPY config ./config
|
||||
COPY src ./src
|
||||
COPY newspaper ./newspaper
|
||||
|
||||
RUN pip install --no-cache-dir .
|
||||
|
||||
VOLUME ["/app/newspaper"]
|
||||
|
||||
CMD ["hongyu-daily", "run"]
|
||||
@@ -0,0 +1,130 @@
|
||||
# Daily News for Markyan04
|
||||
|
||||
这是一个中文个人日报系统。它会每天早上生成一份报纸,归档到 `newspaper/` 目录,并通过邮件发送到:
|
||||
|
||||
```text
|
||||
markyan04@163.com
|
||||
```
|
||||
|
||||
生产环境目标是:**个人 Debian 服务器 + Docker Compose + DeepSeek API + SMTP 邮件发送**。
|
||||
|
||||
默认执行时间:
|
||||
|
||||
```text
|
||||
每天 08:00,Asia/Shanghai 时区
|
||||
```
|
||||
|
||||
## 目录说明
|
||||
|
||||
- `AGENTS.md`:日报编辑约束,主要给 agent / 生成器读取。
|
||||
- `config/newspaper.yaml`:日报结构、栏目顺序、栏目权重。
|
||||
- `config/sources.yaml`:新闻来源、来源分层、新闻质量规则。
|
||||
- `config/delivery.yaml`:收件人、归档、SMTP、定时任务配置。
|
||||
- `.env.example`:服务器上的 `.env` 配置模板。
|
||||
- `docker-compose.yml`:一次性运行和每日定时运行的 Docker 服务。
|
||||
- `docs/debian-docker-deployment.md`:完整 Debian 部署说明。
|
||||
- `newspaper/`:每天生成并发送的报纸归档目录。
|
||||
|
||||
## Debian 快速部署
|
||||
|
||||
把仓库复制到服务器,例如:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /srv/apps/newspaper
|
||||
sudo chown -R "$USER":"$USER" /srv/apps/newspaper
|
||||
rsync -av --exclude .env ./ /srv/apps/newspaper/
|
||||
cd /srv/apps/newspaper
|
||||
```
|
||||
|
||||
创建 `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
填写必要配置:
|
||||
|
||||
```text
|
||||
DEEPSEEK_API_KEY=
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||
DEEPSEEK_MODEL=deepseek-v4-pro
|
||||
GITHUB_TOKEN=
|
||||
|
||||
NEWSPAPER_SMTP_HOST=smtp.163.com
|
||||
NEWSPAPER_SMTP_PORT=465
|
||||
NEWSPAPER_SMTP_USERNAME=
|
||||
NEWSPAPER_SMTP_PASSWORD=
|
||||
NEWSPAPER_FROM_EMAIL=
|
||||
NEWSPAPER_FROM_NAME=Daily News for Markyan04
|
||||
NEWSPAPER_SMTP_SECURITY=ssl
|
||||
|
||||
NEWSPAPER_SCHEDULE_TIME=08:00
|
||||
TZ=Asia/Shanghai
|
||||
```
|
||||
|
||||
说明:
|
||||
|
||||
- `DEEPSEEK_API_KEY` 填你的 DeepSeek API Key。
|
||||
- `GITHUB_TOKEN` 可不填;填一个只读 token 可以提高 GitHub Search API 限额,让开源项目跟踪更稳定。
|
||||
- 如果用 163 邮箱发件,`NEWSPAPER_SMTP_PASSWORD` 应该填邮箱授权码,不是网页登录密码。
|
||||
- `.env` 里有密钥,不能提交到仓库。
|
||||
|
||||
## 构建和检查
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
docker compose run --rm newspaper hongyu-daily check-config
|
||||
```
|
||||
|
||||
## 只生成,不发送
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily generate
|
||||
```
|
||||
|
||||
生成后检查:
|
||||
|
||||
```bash
|
||||
ls -lh newspaper/
|
||||
```
|
||||
|
||||
应该看到类似:
|
||||
|
||||
```text
|
||||
YYYY-MM-DD-daily-news-for-markyan04.md
|
||||
YYYY-MM-DD-daily-news-for-markyan04.html
|
||||
YYYY-MM-DD-daily-news-for-markyan04.meta.json
|
||||
```
|
||||
|
||||
## 生成并立即发送一次
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily run
|
||||
```
|
||||
|
||||
## 启动每日定时任务
|
||||
|
||||
```bash
|
||||
docker compose up -d scheduler
|
||||
docker compose logs -f scheduler
|
||||
```
|
||||
|
||||
启动后,`scheduler` 服务会常驻运行,并在每天 08:00 生成和发送日报。
|
||||
|
||||
## 停止定时任务
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## 密钥注意事项
|
||||
|
||||
不要提交这些内容:
|
||||
|
||||
- `.env`
|
||||
- SMTP 授权码
|
||||
- DeepSeek API Key
|
||||
- Cookie
|
||||
- Session token
|
||||
- 任何邮箱密码或平台密钥
|
||||
@@ -0,0 +1,100 @@
|
||||
version: 1
|
||||
name: Daily News for Markyan04 Delivery Configuration
|
||||
timezone: Asia/Shanghai
|
||||
|
||||
recipient:
|
||||
email: markyan04@163.com
|
||||
display_name: Hongyu Yan
|
||||
|
||||
archive:
|
||||
directory: newspaper
|
||||
write_before_send: true
|
||||
mark_sent_after_success: true
|
||||
formats:
|
||||
- markdown
|
||||
- html
|
||||
- metadata_json
|
||||
naming:
|
||||
markdown: "{YYYY}-{MM}-{DD}-daily-news-for-markyan04.md"
|
||||
html: "{YYYY}-{MM}-{DD}-daily-news-for-markyan04.html"
|
||||
metadata_json: "{YYYY}-{MM}-{DD}-daily-news-for-markyan04.meta.json"
|
||||
metadata_fields:
|
||||
- date
|
||||
- subject
|
||||
- recipient
|
||||
- generated_at
|
||||
- sent_at
|
||||
- delivery_status
|
||||
- source_item_count
|
||||
- filtered_recent_source_item_count
|
||||
- source_error_count
|
||||
- source_errors
|
||||
- source_items
|
||||
- content_hash
|
||||
|
||||
email:
|
||||
transport: smtp
|
||||
subject_format_ref: config/newspaper.yaml:title_format
|
||||
body:
|
||||
primary_format: html
|
||||
include_plain_text_fallback: true
|
||||
smtp:
|
||||
host_env: NEWSPAPER_SMTP_HOST
|
||||
port_env: NEWSPAPER_SMTP_PORT
|
||||
username_env: NEWSPAPER_SMTP_USERNAME
|
||||
password_env: NEWSPAPER_SMTP_PASSWORD
|
||||
from_email_env: NEWSPAPER_FROM_EMAIL
|
||||
from_name_env: NEWSPAPER_FROM_NAME
|
||||
security_env: NEWSPAPER_SMTP_SECURITY
|
||||
supported_security:
|
||||
- ssl
|
||||
- starttls
|
||||
timeout_seconds: 30
|
||||
secrets_policy:
|
||||
never_commit_credentials: true
|
||||
require_env_vars: true
|
||||
allow_dotenv_for_local_dev: true
|
||||
dotenv_file: .env
|
||||
dotenv_should_be_gitignored: true
|
||||
|
||||
delivery_policy:
|
||||
send_only_after_successful_generation: true
|
||||
do_not_send_if_required_sections_missing: true
|
||||
do_not_send_if_news_items_lack_sources: true
|
||||
max_send_attempts: 3
|
||||
retry_backoff_seconds:
|
||||
- 60
|
||||
- 300
|
||||
failure_behavior:
|
||||
- keep_archived_draft
|
||||
- write_failure_metadata
|
||||
- do_not_mark_sent
|
||||
|
||||
automation:
|
||||
preferred_time_local: "08:00"
|
||||
preferred_runner: docker_compose_scheduler
|
||||
timezone: Asia/Shanghai
|
||||
docker_compose_scheduler:
|
||||
schedule_local: "08:00"
|
||||
note: "Run the scheduler service on the Debian server with docker compose. It loops and runs once per local day."
|
||||
required_secrets:
|
||||
- DEEPSEEK_API_KEY
|
||||
- DEEPSEEK_BASE_URL
|
||||
- DEEPSEEK_MODEL
|
||||
- NEWSPAPER_SMTP_HOST
|
||||
- NEWSPAPER_SMTP_PORT
|
||||
- NEWSPAPER_SMTP_USERNAME
|
||||
- NEWSPAPER_SMTP_PASSWORD
|
||||
- NEWSPAPER_FROM_EMAIL
|
||||
- NEWSPAPER_FROM_NAME
|
||||
- NEWSPAPER_SMTP_SECURITY
|
||||
debian_host_cron:
|
||||
cron_local: "0 8 * * *"
|
||||
command: "cd /srv/apps/newspaper && docker compose run --rm newspaper hongyu-daily run"
|
||||
note: "Use this instead of the scheduler service if you prefer host-level cron."
|
||||
|
||||
sender_notes:
|
||||
- "The recipient is fixed as markyan04@163.com."
|
||||
- "The sender account is intentionally not fixed here. Use any SMTP-capable mailbox or transactional email provider."
|
||||
- "If a 163 mailbox is used as the sender, enable SMTP/IMAP service in the mailbox settings and use an authorization code rather than the normal login password."
|
||||
- "Keep provider-specific SMTP host and port in environment variables, not in committed code."
|
||||
@@ -0,0 +1,211 @@
|
||||
title_format: "Daily News for Markyan04 | YYYY - MM - DD"
|
||||
language: zh-CN
|
||||
timezone: Asia/Shanghai
|
||||
target_reading_minutes: 8-12
|
||||
sources_config: config/sources.yaml
|
||||
delivery_config: config/delivery.yaml
|
||||
archive_dir: newspaper
|
||||
|
||||
output_language_policy:
|
||||
newspaper_body: Chinese
|
||||
preserve_common_english_terms: true
|
||||
examples:
|
||||
- RAG
|
||||
- agent
|
||||
- vibe coding
|
||||
- LLMOps
|
||||
- MCP
|
||||
- eBPF
|
||||
- KYC / AML
|
||||
|
||||
freshness_policy:
|
||||
daily_collection_required: true
|
||||
do_not_use_model_memory_as_news_source: true
|
||||
news_items_require_source_url: true
|
||||
news_items_require_date_or_collected_at: true
|
||||
default_recent_window_hours: 72
|
||||
prefer_recent_window_hours: 24
|
||||
allow_evergreen_background_when_labeled: true
|
||||
no_fresh_item_behavior: state_no_high_signal_update
|
||||
|
||||
sections:
|
||||
summary:
|
||||
weight: 0.05
|
||||
title: 今日汇总
|
||||
general_news:
|
||||
weight: 0.15
|
||||
title: 传统新闻
|
||||
ai:
|
||||
weight: 0.35
|
||||
title: AI领域
|
||||
software_engineering:
|
||||
weight: 0.15
|
||||
title: 传统软工
|
||||
security_performance:
|
||||
weight: 0.12
|
||||
title: 安全与性能工程
|
||||
github:
|
||||
weight: 0.10
|
||||
title: GitHub / 开源项目跟踪
|
||||
finance:
|
||||
weight: 0.08
|
||||
title: 金融领域
|
||||
|
||||
general_news:
|
||||
target_items_per_issue:
|
||||
min: 3
|
||||
max: 5
|
||||
priority_topics:
|
||||
- international_politics
|
||||
- us_china_relations
|
||||
- china_hong_kong_policy
|
||||
- russia_ukraine_war
|
||||
- asia_pacific
|
||||
- macroeconomy
|
||||
- market_regulation
|
||||
- financial_regulation
|
||||
style:
|
||||
- summarize_events_before_interpretation
|
||||
- prefer_primary_or_established_newsroom_sources
|
||||
- include_source_metadata_under_title
|
||||
|
||||
ai:
|
||||
discovery_map_not_daily_quota: true
|
||||
target_items_per_issue:
|
||||
min: 5
|
||||
max: 8
|
||||
freshness_rules:
|
||||
min_recent_items_when_available: 4
|
||||
recent_window_hours: 72
|
||||
max_evergreen_or_background_items: 2
|
||||
include_fresh_discovery_slot: true
|
||||
do_not_repackage_old_concepts_as_today_news: true
|
||||
priority_topics:
|
||||
- large_language_models
|
||||
- ai_coding_tools
|
||||
- coding_agents
|
||||
- vibe_coding
|
||||
- agent_frameworks
|
||||
- model_context_protocol
|
||||
- multi_agent_systems
|
||||
- llmops
|
||||
- evaluation
|
||||
- evaluation_harness
|
||||
- agent_harness
|
||||
- context_engineering
|
||||
- observability
|
||||
- guardrails
|
||||
- inference_optimization
|
||||
- rag
|
||||
- graphrag
|
||||
- long_context
|
||||
- memory_systems
|
||||
- multimodal_ai
|
||||
- open_source_models
|
||||
- ai_security
|
||||
- new_ai_terms
|
||||
- high_value_papers_and_technical_reports
|
||||
item_template:
|
||||
- what_happened
|
||||
- context
|
||||
- concrete_update
|
||||
- possible_impact
|
||||
- source
|
||||
- published_or_collected_at
|
||||
|
||||
software_engineering:
|
||||
priority_topics:
|
||||
- backend_architecture
|
||||
- spring_boot
|
||||
- fastapi
|
||||
- api_design
|
||||
- frontend_engineering
|
||||
- vue
|
||||
- typescript
|
||||
- databases
|
||||
- postgresql
|
||||
- mysql
|
||||
- caching
|
||||
- docker
|
||||
- ci_cd
|
||||
- observability
|
||||
- testing
|
||||
- incident_reviews
|
||||
- maintainability
|
||||
|
||||
security_performance:
|
||||
priority_topics:
|
||||
- cve
|
||||
- nvd
|
||||
- cisa_kev
|
||||
- vendor_advisories
|
||||
- exploited_in_the_wild
|
||||
- supply_chain_security
|
||||
- gfw_measurement
|
||||
- network_censorship_research
|
||||
- linux_performance
|
||||
- ebpf
|
||||
- perf
|
||||
- flamegraph
|
||||
- android_performance
|
||||
- systrace
|
||||
- perfetto
|
||||
- database_performance
|
||||
safety_constraints:
|
||||
- do_not_provide_censorship_evasion_tutorials
|
||||
- do_not_provide_operational_bypass_guidance
|
||||
|
||||
github:
|
||||
track_user_projects: false
|
||||
priority_topics:
|
||||
- ai_agents
|
||||
- coding_agents
|
||||
- llm_infrastructure
|
||||
- rag_tools
|
||||
- evaluation_tools
|
||||
- developer_tools
|
||||
- security_tools
|
||||
- performance_tools
|
||||
- fintech_infrastructure
|
||||
selection_criteria:
|
||||
- recent_star_growth
|
||||
- active_commits_or_releases
|
||||
- practical_use_case
|
||||
- technical_learning_value
|
||||
exclude:
|
||||
- low_quality_prompt_collections
|
||||
- thin_wrappers
|
||||
- marketing_demos
|
||||
- abandoned_repositories
|
||||
|
||||
finance:
|
||||
not_investment_advice: true
|
||||
priority_topics:
|
||||
- interest_rates
|
||||
- bonds
|
||||
- yield_curve
|
||||
- etf
|
||||
- liquidity
|
||||
- market_makers
|
||||
- clearing
|
||||
- settlement
|
||||
- payments
|
||||
- risk_control
|
||||
- anti_fraud
|
||||
- kyc_aml
|
||||
- open_banking
|
||||
- stablecoins
|
||||
- tokenization
|
||||
- ai_in_finance
|
||||
- financial_regulation
|
||||
|
||||
negative_constraints:
|
||||
- no_daily_action_checklist
|
||||
- no_internship_or_job_section
|
||||
- no_english_writing_section
|
||||
- no_separate_research_section
|
||||
- no_phd_or_mphil_content
|
||||
- no_supervisor_matching
|
||||
- no_cold_email_content
|
||||
- no_user_project_github_tracking
|
||||
- no_personalized_investment_advice
|
||||
@@ -0,0 +1,875 @@
|
||||
version: 1
|
||||
name: Daily News for Markyan04 Source Configuration
|
||||
language: zh-CN
|
||||
timezone: Asia/Shanghai
|
||||
|
||||
purpose:
|
||||
- Provide stable source rules for Daily News for Markyan04.
|
||||
- Separate timely news from evergreen explanation.
|
||||
- Make every newspaper item answer the basic journalistic questions when applicable.
|
||||
- Prefer primary sources and fresh collection over model memory.
|
||||
|
||||
journalism_contract:
|
||||
news_questions:
|
||||
who: "谁发布、谁受影响、谁参与"
|
||||
what: "发生了什么具体变化"
|
||||
when: "发布日期、更新时间、事件发生时间或采集时间"
|
||||
where: "发生在哪个机构、产品、地区、生态或仓库"
|
||||
why: "事件的背景、影响范围和后续观察点"
|
||||
how: "证据、机制、技术路径、影响方式或复现条件"
|
||||
required_fields_for_news_like_items:
|
||||
- title
|
||||
- content_class
|
||||
- section
|
||||
- source_name
|
||||
- source_url
|
||||
- source_tier
|
||||
- published_at_or_updated_at
|
||||
- collected_at
|
||||
- who
|
||||
- what
|
||||
- when
|
||||
- where
|
||||
- context_and_impact
|
||||
- how_or_evidence
|
||||
required_fields_for_background_items:
|
||||
- title
|
||||
- content_class
|
||||
- section
|
||||
- trigger_reason
|
||||
- source_name
|
||||
- source_url
|
||||
- explanation
|
||||
- relation_to_section_topic
|
||||
background_items:
|
||||
allowed: true
|
||||
max_per_issue: 3
|
||||
rule: "Background explanations are allowed only when they help understand a fresh item or a recurring important concept."
|
||||
label: "背景理解"
|
||||
|
||||
content_classes:
|
||||
breaking_news:
|
||||
time_sensitive: true
|
||||
freshness_window_hours: 24
|
||||
requires_news_questions: true
|
||||
official_release:
|
||||
time_sensitive: true
|
||||
freshness_window_hours: 72
|
||||
requires_news_questions: true
|
||||
technical_report:
|
||||
time_sensitive: true
|
||||
freshness_window_hours: 168
|
||||
requires_news_questions: true
|
||||
paper:
|
||||
time_sensitive: true
|
||||
freshness_window_hours: 168
|
||||
requires_news_questions: true
|
||||
belongs_under_section: ai
|
||||
vulnerability:
|
||||
time_sensitive: true
|
||||
freshness_window_hours: 72
|
||||
requires_news_questions: true
|
||||
github_project_signal:
|
||||
time_sensitive: true
|
||||
freshness_window_hours: 72
|
||||
requires_growth_signal: true
|
||||
requires_news_questions: true
|
||||
explainer:
|
||||
time_sensitive: false
|
||||
requires_trigger_reason: true
|
||||
requires_news_questions: false
|
||||
concept_note:
|
||||
time_sensitive: false
|
||||
requires_trigger_reason: true
|
||||
requires_news_questions: false
|
||||
|
||||
source_tiers:
|
||||
official_primary:
|
||||
description: "Official pages, official blogs, official docs, official APIs, university pages, regulator pages."
|
||||
default_weight: 1.0
|
||||
technical_primary:
|
||||
description: "Project repos, release notes, engineering blogs, model cards, arXiv papers, benchmark pages."
|
||||
default_weight: 0.9
|
||||
reputable_secondary:
|
||||
description: "High-quality technical newsletters, established security research, respected independent experts."
|
||||
default_weight: 0.75
|
||||
community_signal:
|
||||
description: "GitHub Trending, Hacker News, social discovery pages. Use for discovery, then verify with primary source."
|
||||
default_weight: 0.55
|
||||
background_reference:
|
||||
description: "Evergreen docs and explainers. Use only as background, not as today's news."
|
||||
default_weight: 0.4
|
||||
|
||||
collection_policy:
|
||||
daily_collection_required: true
|
||||
prefer_recent_window_hours: 24
|
||||
default_recent_window_hours: 72
|
||||
collect_before_generate: true
|
||||
no_model_memory_as_news_source: true
|
||||
store_raw_items: true
|
||||
store_normalized_items: true
|
||||
store_snapshots_for_change_only_sources: true
|
||||
deduplicate_by:
|
||||
- canonical_url
|
||||
- normalized_title
|
||||
- source_item_id
|
||||
if_source_unavailable:
|
||||
- retry_once
|
||||
- use_fallback_url_if_configured
|
||||
- mark_source_error
|
||||
- do_not_invent_item
|
||||
if_no_fresh_high_signal_item:
|
||||
behavior: "state_no_high_signal_update"
|
||||
text: "今日未发现高信号更新。"
|
||||
|
||||
ranking_policy:
|
||||
score_fields:
|
||||
source_tier: 0.25
|
||||
freshness: 0.20
|
||||
relevance_to_sections: 0.25
|
||||
novelty: 0.15
|
||||
technical_depth: 0.10
|
||||
practical_value: 0.05
|
||||
demote:
|
||||
- no_source_url
|
||||
- no_publication_or_collection_time
|
||||
- repost_without_primary_source
|
||||
- generic_commentary
|
||||
- marketing_without_technical_detail
|
||||
- low_quality_ai_listicle
|
||||
promote:
|
||||
- primary_source
|
||||
- official_release_with_technical_detail
|
||||
- useful_engineering_lessons
|
||||
- affects_ai_engineering_workflow
|
||||
- affects_general_news_or_financial_regulation
|
||||
- active_exploitation_or_urgent_patch
|
||||
- fast_github_growth_with_real_utility
|
||||
|
||||
sections:
|
||||
summary:
|
||||
source_strategy: "Use selected high-score items from all sections."
|
||||
max_items: 5
|
||||
|
||||
general_news:
|
||||
source_strategy: "Use established newsrooms plus official regulators for international politics, China/Hong Kong policy, war, and market regulation."
|
||||
recent_window_hours: 72
|
||||
target_items_per_issue:
|
||||
min: 3
|
||||
max: 5
|
||||
allowed_item_classes:
|
||||
- breaking_news
|
||||
- official_release
|
||||
- technical_report
|
||||
priority_topics:
|
||||
- us_china_relations
|
||||
- russia_ukraine_war
|
||||
- china_hong_kong_policy
|
||||
- financial_regulation
|
||||
- macroeconomy
|
||||
- asia_pacific
|
||||
sources:
|
||||
- id: bbc_world
|
||||
name: "BBC World"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.bbc.com/news/world"
|
||||
feed_url: "https://feeds.bbci.co.uk/news/world/rss.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, world, politics]
|
||||
- id: bbc_business
|
||||
name: "BBC Business"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.bbc.com/news/business"
|
||||
feed_url: "https://feeds.bbci.co.uk/news/business/rss.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, business, economy]
|
||||
- id: guardian_world
|
||||
name: "The Guardian World"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.theguardian.com/world"
|
||||
feed_url: "https://www.theguardian.com/world/rss"
|
||||
access_status: seed
|
||||
tags: [general_news, world, politics]
|
||||
- id: guardian_us_news
|
||||
name: "The Guardian US News"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.theguardian.com/us-news"
|
||||
feed_url: "https://www.theguardian.com/us-news/rss"
|
||||
access_status: seed
|
||||
tags: [general_news, US, politics, Trump]
|
||||
- id: aljazeera_all
|
||||
name: "Al Jazeera"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.aljazeera.com/"
|
||||
feed_url: "https://www.aljazeera.com/xml/rss/all.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, world, war, middle_east]
|
||||
- id: nytimes_world
|
||||
name: "The New York Times World"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.nytimes.com/section/world"
|
||||
feed_url: "https://rss.nytimes.com/services/xml/rss/nyt/World.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, world, politics]
|
||||
- id: nytimes_asia_pacific
|
||||
name: "The New York Times Asia Pacific"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.nytimes.com/section/world/asia"
|
||||
feed_url: "https://rss.nytimes.com/services/xml/rss/nyt/AsiaPacific.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, Asia, China, Hong_Kong]
|
||||
- id: npr_world
|
||||
name: "NPR World"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.npr.org/sections/world/"
|
||||
feed_url: "https://feeds.npr.org/1004/rss.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, world, politics]
|
||||
- id: un_news
|
||||
name: "UN News"
|
||||
kind: rss
|
||||
source_tier: official_primary
|
||||
url: "https://news.un.org/en/"
|
||||
feed_url: "https://news.un.org/feed/subscribe/en/news/all/rss.xml"
|
||||
access_status: seed
|
||||
tags: [general_news, UN, war, humanitarian]
|
||||
- id: google_news_trump_china_visit
|
||||
name: "Google News Search - Trump China Visit"
|
||||
kind: rss
|
||||
source_tier: community_signal
|
||||
url: "https://news.google.com/search?q=Trump%20China%20visit"
|
||||
feed_url: "https://news.google.com/rss/search?q=Trump%20China%20visit&hl=zh-CN&gl=US&ceid=US:zh-Hans"
|
||||
access_status: seed
|
||||
tags: [general_news, US_China, Trump, discovery]
|
||||
- id: google_news_russia_ukraine
|
||||
name: "Google News Search - Russia Ukraine War"
|
||||
kind: rss
|
||||
source_tier: community_signal
|
||||
url: "https://news.google.com/search?q=Russia%20Ukraine%20war"
|
||||
feed_url: "https://news.google.com/rss/search?q=Russia%20Ukraine%20war&hl=zh-CN&gl=US&ceid=US:zh-Hans"
|
||||
access_status: seed
|
||||
tags: [general_news, Russia, Ukraine, war, discovery]
|
||||
- id: google_news_csrc_hk_brokers
|
||||
name: "Google News Search - CSRC Hong Kong Brokers"
|
||||
kind: rss
|
||||
source_tier: community_signal
|
||||
url: "https://news.google.com/search?q=CSRC%20Hong%20Kong%20brokers"
|
||||
feed_url: "https://news.google.com/rss/search?q=CSRC%20Hong%20Kong%20brokers%20OR%20%E4%B8%AD%E5%9B%BD%E8%AF%81%E7%9B%91%E4%BC%9A%20%E9%A6%99%E6%B8%AF%E5%88%B8%E5%95%86&hl=zh-CN&gl=HK&ceid=HK:zh-Hans"
|
||||
access_status: seed
|
||||
tags: [general_news, China, Hong_Kong, securities, regulation, discovery]
|
||||
- id: china_csrc_news
|
||||
name: "China Securities Regulatory Commission"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "http://www.csrc.gov.cn/csrc/c100028/common_list.shtml"
|
||||
access_status: seed
|
||||
tags: [general_news, China, securities, regulation]
|
||||
- id: hong_kong_sfc_news
|
||||
name: "Hong Kong SFC News"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.sfc.hk/en/News-and-announcements/News"
|
||||
access_status: seed
|
||||
tags: [general_news, Hong_Kong, securities, regulation]
|
||||
- id: china_mfa_news
|
||||
name: "China MFA News"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.mfa.gov.cn/eng/xw/"
|
||||
access_status: seed
|
||||
tags: [general_news, China, diplomacy, US_China]
|
||||
|
||||
ai:
|
||||
source_strategy: "Broad fresh scan, then selective reporting. Topic list is a discovery map, not a quota."
|
||||
target_items_per_issue:
|
||||
min: 5
|
||||
max: 8
|
||||
min_recent_items_when_available: 4
|
||||
recent_window_hours: 72
|
||||
max_background_items: 2
|
||||
include_fresh_discovery_slot: true
|
||||
allowed_item_classes:
|
||||
- breaking_news
|
||||
- official_release
|
||||
- technical_report
|
||||
- paper
|
||||
- github_project_signal
|
||||
- explainer
|
||||
- concept_note
|
||||
sources:
|
||||
- id: openai_news
|
||||
name: "OpenAI News"
|
||||
kind: rss
|
||||
source_tier: official_primary
|
||||
url: "https://openai.com/news/"
|
||||
feed_url: "https://openai.com/news/rss.xml"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [AI, LLM, agents, models, product]
|
||||
- id: anthropic_news
|
||||
name: "Anthropic News"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.anthropic.com/news"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [AI, LLM, Claude, agents, safety]
|
||||
- id: google_deepmind_blog
|
||||
name: "Google DeepMind Blog"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://deepmind.google/discover/blog/"
|
||||
access_status: seed
|
||||
tags: [AI, LLM, multimodal, research, agents]
|
||||
- id: google_ai_blog
|
||||
name: "Google AI Blog"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://blog.google/technology/ai/"
|
||||
access_status: seed
|
||||
tags: [AI, LLM, Gemini, product, engineering]
|
||||
- id: meta_ai_blog
|
||||
name: "Meta AI Blog"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://ai.meta.com/blog/"
|
||||
access_status: seed
|
||||
tags: [AI, Llama, open_source_models, multimodal]
|
||||
- id: microsoft_ai_blog
|
||||
name: "Microsoft AI Blog"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://blogs.microsoft.com/ai/"
|
||||
access_status: seed
|
||||
tags: [AI, Copilot, agents, enterprise_ai]
|
||||
- id: microsoft_research_blog
|
||||
name: "Microsoft Research Blog"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://www.microsoft.com/en-us/research/blog/"
|
||||
feed_url: "https://www.microsoft.com/en-us/research/feed/"
|
||||
access_status: seed
|
||||
tags: [AI, research, agents, systems, engineering]
|
||||
- id: mistral_news
|
||||
name: "Mistral AI News"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://mistral.ai/news/"
|
||||
access_status: seed
|
||||
tags: [AI, LLM, open_models, agents]
|
||||
- id: cohere_blog
|
||||
name: "Cohere Blog"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://cohere.com/blog"
|
||||
access_status: seed
|
||||
tags: [AI, LLM, enterprise_ai, RAG]
|
||||
- id: qwen_blog
|
||||
name: "Qwen Blog"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://qwenlm.github.io/blog/"
|
||||
access_status: seed
|
||||
tags: [AI, LLM, open_source_models, multimodal]
|
||||
- id: huggingface_blog
|
||||
name: "Hugging Face Blog"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://huggingface.co/blog"
|
||||
feed_url: "https://huggingface.co/blog/feed.xml"
|
||||
access_status: slow_or_unverified_2026_07_04
|
||||
tags: [AI, open_source_models, datasets, inference, tooling]
|
||||
- id: aws_ml_blog
|
||||
name: "AWS Machine Learning Blog"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://aws.amazon.com/blogs/machine-learning/"
|
||||
feed_url: "https://aws.amazon.com/blogs/machine-learning/feed/"
|
||||
access_status: seed
|
||||
tags: [AI, LLMOps, deployment, inference, cloud]
|
||||
- id: nvidia_ai_blog
|
||||
name: "NVIDIA Technical Blog - Generative AI"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://developer.nvidia.com/blog/category/generative-ai/"
|
||||
feed_url: "https://developer.nvidia.com/blog/category/generative-ai/feed/"
|
||||
access_status: seed
|
||||
tags: [AI, inference, GPU, deployment, multimodal]
|
||||
- id: langchain_blog
|
||||
name: "LangChain Blog"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://blog.langchain.com/"
|
||||
feed_url: "https://blog.langchain.com/rss/"
|
||||
access_status: seed
|
||||
tags: [AI, agents, RAG, LLMOps, evaluation]
|
||||
- id: llamaindex_blog
|
||||
name: "LlamaIndex Blog"
|
||||
kind: official_html
|
||||
source_tier: technical_primary
|
||||
url: "https://www.llamaindex.ai/blog"
|
||||
access_status: seed
|
||||
tags: [AI, RAG, agents, data_frameworks]
|
||||
- id: vllm_blog
|
||||
name: "vLLM Blog"
|
||||
kind: official_html
|
||||
source_tier: technical_primary
|
||||
url: "https://blog.vllm.ai/"
|
||||
access_status: seed
|
||||
tags: [AI, inference, serving, open_source_models]
|
||||
- id: ollama_blog
|
||||
name: "Ollama Blog"
|
||||
kind: official_html
|
||||
source_tier: technical_primary
|
||||
url: "https://ollama.com/blog"
|
||||
access_status: seed
|
||||
tags: [AI, local_models, developer_tools, open_source_models]
|
||||
- id: vercel_ai_blog
|
||||
name: "Vercel AI Blog"
|
||||
kind: official_html
|
||||
source_tier: technical_primary
|
||||
url: "https://vercel.com/blog/category/ai"
|
||||
access_status: seed
|
||||
tags: [AI, frontend_ai, ai_sdk, agents, engineering]
|
||||
- id: cursor_changelog
|
||||
name: "Cursor Changelog"
|
||||
kind: official_html
|
||||
source_tier: technical_primary
|
||||
url: "https://cursor.com/changelog"
|
||||
access_status: seed
|
||||
tags: [AI, coding_agent, vibe_coding, developer_tools]
|
||||
- id: arxiv_cs_cl
|
||||
name: "arXiv cs.CL"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://arxiv.org/list/cs.CL/recent"
|
||||
feed_url: "https://export.arxiv.org/rss/cs.CL"
|
||||
access_status: seed
|
||||
tags: [AI, NLP, LLM, papers]
|
||||
- id: arxiv_cs_ai
|
||||
name: "arXiv cs.AI"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://arxiv.org/list/cs.AI/recent"
|
||||
feed_url: "https://export.arxiv.org/rss/cs.AI"
|
||||
access_status: seed
|
||||
tags: [AI, agents, reasoning, papers]
|
||||
- id: arxiv_cs_lg
|
||||
name: "arXiv cs.LG"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://arxiv.org/list/cs.LG/recent"
|
||||
feed_url: "https://export.arxiv.org/rss/cs.LG"
|
||||
access_status: seed
|
||||
tags: [AI, machine_learning, papers]
|
||||
- id: simon_willison
|
||||
name: "Simon Willison"
|
||||
kind: atom
|
||||
source_tier: reputable_secondary
|
||||
url: "https://simonwillison.net/"
|
||||
feed_url: "https://simonwillison.net/atom/everything/"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [AI, LLM, tools, agents, engineering]
|
||||
- id: latent_space
|
||||
name: "Latent Space"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.latent.space/"
|
||||
feed_url: "https://www.latent.space/feed"
|
||||
access_status: seed
|
||||
tags: [AI, LLM, agents, industry_analysis]
|
||||
- id: deeplearning_ai_the_batch
|
||||
name: "The Batch by DeepLearning.AI"
|
||||
kind: rss_or_html
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.deeplearning.ai/the-batch/"
|
||||
access_status: seed
|
||||
tags: [AI, weekly_news, explainers]
|
||||
|
||||
software_engineering:
|
||||
source_strategy: "Prefer engineering blogs, release notes, incident reviews, and deep technical writeups."
|
||||
recent_window_hours: 168
|
||||
allowed_item_classes:
|
||||
- official_release
|
||||
- technical_report
|
||||
- explainer
|
||||
- concept_note
|
||||
sources:
|
||||
- id: github_engineering
|
||||
name: "GitHub Engineering"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://github.blog/engineering/"
|
||||
feed_url: "https://github.blog/engineering/feed/"
|
||||
access_status: seed
|
||||
tags: [software_engineering, developer_tools, scaling, devops]
|
||||
- id: cloudflare_blog
|
||||
name: "Cloudflare Blog"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://blog.cloudflare.com/"
|
||||
feed_url: "https://blog.cloudflare.com/rss/"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [software_engineering, networking, security, performance]
|
||||
- id: netflix_techblog
|
||||
name: "Netflix TechBlog"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://netflixtechblog.com/"
|
||||
feed_url: "https://netflixtechblog.com/feed"
|
||||
access_status: seed
|
||||
tags: [software_engineering, distributed_systems, reliability]
|
||||
- id: uber_engineering
|
||||
name: "Uber Engineering"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://www.uber.com/blog/engineering/"
|
||||
access_status: seed
|
||||
tags: [software_engineering, backend, data, platform]
|
||||
- id: stripe_engineering
|
||||
name: "Stripe Engineering"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://stripe.com/blog/engineering"
|
||||
access_status: seed
|
||||
tags: [software_engineering, fintech, infrastructure, reliability]
|
||||
- id: martin_fowler
|
||||
name: "Martin Fowler"
|
||||
kind: atom
|
||||
source_tier: reputable_secondary
|
||||
url: "https://martinfowler.com/"
|
||||
feed_url: "https://martinfowler.com/feed.atom"
|
||||
access_status: seed
|
||||
tags: [software_design, architecture, refactoring]
|
||||
- id: acm_queue
|
||||
name: "ACM Queue"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://queue.acm.org/"
|
||||
feed_url: "https://queue.acm.org/rss/feeds/queuecontent.xml"
|
||||
access_status: seed
|
||||
tags: [software_engineering, systems, architecture]
|
||||
- id: spring_blog
|
||||
name: "Spring Blog"
|
||||
kind: atom
|
||||
source_tier: technical_primary
|
||||
url: "https://spring.io/blog"
|
||||
feed_url: "https://spring.io/blog.atom"
|
||||
access_status: seed
|
||||
tags: [software_engineering, Java, Spring_Boot, backend]
|
||||
- id: vue_blog
|
||||
name: "Vue Blog"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://blog.vuejs.org/"
|
||||
feed_url: "https://blog.vuejs.org/feed.rss"
|
||||
access_status: seed
|
||||
tags: [software_engineering, Vue, frontend, TypeScript]
|
||||
- id: postgresql_news
|
||||
name: "PostgreSQL News"
|
||||
kind: rss
|
||||
source_tier: official_primary
|
||||
url: "https://www.postgresql.org/about/news/"
|
||||
feed_url: "https://www.postgresql.org/about/news/rss/"
|
||||
access_status: seed
|
||||
tags: [software_engineering, database, PostgreSQL]
|
||||
|
||||
security_performance:
|
||||
source_strategy: "Prioritize official advisories for urgent risk and deep technical sources for learning."
|
||||
recent_window_hours: 72
|
||||
allowed_item_classes:
|
||||
- vulnerability
|
||||
- breaking_news
|
||||
- official_release
|
||||
- technical_report
|
||||
- explainer
|
||||
- concept_note
|
||||
safety_constraints:
|
||||
- do_not_provide_censorship_evasion_tutorials
|
||||
- do_not_provide_operational_bypass_guidance
|
||||
sources:
|
||||
- id: cisa_kev
|
||||
name: "CISA Known Exploited Vulnerabilities Catalog"
|
||||
kind: json
|
||||
source_tier: official_primary
|
||||
url: "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [security, CVE, exploited_in_the_wild, urgent_patch]
|
||||
- id: cisa_advisories
|
||||
name: "CISA Cybersecurity Advisories"
|
||||
kind: rss_or_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.cisa.gov/news-events/cybersecurity-advisories"
|
||||
feed_url: "https://www.cisa.gov/cybersecurity-advisories/all.xml"
|
||||
access_status: seed
|
||||
tags: [security, advisories, vulnerability]
|
||||
- id: nvd_cve_api
|
||||
name: "NVD CVE API"
|
||||
kind: api
|
||||
source_tier: official_primary
|
||||
url: "https://services.nvd.nist.gov/rest/json/cves/2.0"
|
||||
access_status: seed
|
||||
tags: [security, CVE, vulnerability]
|
||||
- id: github_advisory_api
|
||||
name: "GitHub Security Advisory API"
|
||||
kind: api
|
||||
source_tier: official_primary
|
||||
url: "https://api.github.com/advisories"
|
||||
access_status: seed
|
||||
tags: [security, supply_chain, dependencies]
|
||||
- id: google_project_zero
|
||||
name: "Google Project Zero"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://googleprojectzero.blogspot.com/"
|
||||
feed_url: "https://googleprojectzero.blogspot.com/feeds/posts/default"
|
||||
access_status: seed
|
||||
tags: [security, vulnerability_research, browser, OS]
|
||||
- id: trail_of_bits_blog
|
||||
name: "Trail of Bits Blog"
|
||||
kind: rss
|
||||
source_tier: technical_primary
|
||||
url: "https://blog.trailofbits.com/"
|
||||
feed_url: "https://blog.trailofbits.com/feed/"
|
||||
access_status: seed
|
||||
tags: [security, supply_chain, code_audit, vulnerability_research]
|
||||
- id: portswigger_research
|
||||
name: "PortSwigger Research"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://portswigger.net/research"
|
||||
feed_url: "https://portswigger.net/research/rss"
|
||||
access_status: seed
|
||||
tags: [security, web_security, vulnerability_research]
|
||||
- id: ooni_blog
|
||||
name: "OONI Blog"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://ooni.org/blog/"
|
||||
feed_url: "https://ooni.org/index.xml"
|
||||
access_status: seed
|
||||
tags: [GFW, network_measurement, censorship_research]
|
||||
- id: citizen_lab
|
||||
name: "Citizen Lab"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://citizenlab.ca/"
|
||||
feed_url: "https://citizenlab.ca/feed/"
|
||||
access_status: seed
|
||||
tags: [GFW, censorship_research, surveillance, security]
|
||||
- id: censored_planet
|
||||
name: "Censored Planet Publications"
|
||||
kind: official_html
|
||||
source_tier: technical_primary
|
||||
url: "https://censoredplanet.org/publications"
|
||||
access_status: seed
|
||||
tags: [GFW, network_measurement, censorship_research]
|
||||
- id: lwn_headlines
|
||||
name: "LWN Headlines"
|
||||
kind: rss
|
||||
source_tier: reputable_secondary
|
||||
url: "https://lwn.net/"
|
||||
feed_url: "https://lwn.net/headlines/rss"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [Linux, kernel, performance, systems]
|
||||
- id: brendan_gregg
|
||||
name: "Brendan Gregg"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://www.brendangregg.com/blog/"
|
||||
feed_url: "https://www.brendangregg.com/blog/rss.xml"
|
||||
access_status: seed
|
||||
tags: [Linux, performance, eBPF, FlameGraph, observability]
|
||||
- id: ebpf_blog
|
||||
name: "eBPF.io Blog"
|
||||
kind: rss_or_html
|
||||
source_tier: technical_primary
|
||||
url: "https://ebpf.io/blog/"
|
||||
access_status: seed
|
||||
tags: [Linux, eBPF, observability, performance]
|
||||
|
||||
github:
|
||||
source_strategy: "Use community signals for discovery, then verify project value on GitHub repo, releases, commits, and issues."
|
||||
recent_window_hours: 72
|
||||
track_user_projects: false
|
||||
allowed_item_classes:
|
||||
- github_project_signal
|
||||
- technical_report
|
||||
source_rules:
|
||||
require_repo_url: true
|
||||
require_growth_or_activity_signal: true
|
||||
require_project_summary: true
|
||||
do_not_select_prompt_collections_without_engineering_value: true
|
||||
sources:
|
||||
- id: github_trending_daily
|
||||
name: "GitHub Trending Daily"
|
||||
kind: html
|
||||
source_tier: community_signal
|
||||
url: "https://github.com/trending?since=daily"
|
||||
access_status: seed
|
||||
tags: [GitHub, open_source, discovery]
|
||||
- id: github_trending_weekly
|
||||
name: "GitHub Trending Weekly"
|
||||
kind: html
|
||||
source_tier: community_signal
|
||||
url: "https://github.com/trending?since=weekly"
|
||||
access_status: seed
|
||||
tags: [GitHub, open_source, discovery]
|
||||
- id: github_trending_python
|
||||
name: "GitHub Trending Python"
|
||||
kind: html
|
||||
source_tier: community_signal
|
||||
url: "https://github.com/trending/python?since=weekly"
|
||||
access_status: seed
|
||||
tags: [GitHub, Python, AI, tools]
|
||||
- id: github_trending_typescript
|
||||
name: "GitHub Trending TypeScript"
|
||||
kind: html
|
||||
source_tier: community_signal
|
||||
url: "https://github.com/trending/typescript?since=weekly"
|
||||
access_status: seed
|
||||
tags: [GitHub, TypeScript, AI, developer_tools]
|
||||
- id: github_search_api_ai_agents
|
||||
name: "GitHub Search API - AI Agents"
|
||||
kind: api
|
||||
source_tier: community_signal
|
||||
url: "https://api.github.com/search/repositories"
|
||||
query_template: "agents ai stars:>200 pushed:>{date_minus_30d}"
|
||||
access_status: seed
|
||||
tags: [GitHub, AI, agents, discovery]
|
||||
- id: github_search_api_llm_infra
|
||||
name: "GitHub Search API - LLM Infrastructure"
|
||||
kind: api
|
||||
source_tier: community_signal
|
||||
url: "https://api.github.com/search/repositories"
|
||||
query_template: "llm inference stars:>200 pushed:>{date_minus_30d}"
|
||||
access_status: seed
|
||||
tags: [GitHub, LLM, inference, infrastructure]
|
||||
- id: github_search_api_security_tools
|
||||
name: "GitHub Search API - Security Tools"
|
||||
kind: api
|
||||
source_tier: community_signal
|
||||
url: "https://api.github.com/search/repositories"
|
||||
query_template: "security tool stars:>500 pushed:>{date_minus_30d}"
|
||||
access_status: seed
|
||||
tags: [GitHub, security, tools]
|
||||
|
||||
finance:
|
||||
source_strategy: "Use official regulators and central banks first. Use fintech media only as discovery, not as investment advice."
|
||||
recent_window_hours: 168
|
||||
allowed_item_classes:
|
||||
- breaking_news
|
||||
- official_release
|
||||
- technical_report
|
||||
- explainer
|
||||
- concept_note
|
||||
not_investment_advice: true
|
||||
sources:
|
||||
- id: hkma_press_releases
|
||||
name: "Hong Kong Monetary Authority Press Releases"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.hkma.gov.hk/eng/news-and-media/press-releases/"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [finance, fintech, Hong_Kong, banking, regulation]
|
||||
- id: mas_news
|
||||
name: "Monetary Authority of Singapore News"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.mas.gov.sg/news"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [finance, fintech, Singapore, banking, regulation]
|
||||
- id: federal_reserve_press
|
||||
name: "Federal Reserve Press Releases"
|
||||
kind: rss
|
||||
source_tier: official_primary
|
||||
url: "https://www.federalreserve.gov/newsevents/pressreleases.htm"
|
||||
feed_url: "https://www.federalreserve.gov/feeds/press_all.xml"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [finance, rates, banking, regulation, macro]
|
||||
- id: bis_press
|
||||
name: "Bank for International Settlements Press Releases"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.bis.org/press/pressrels.htm"
|
||||
access_status: verified_2026_07_04
|
||||
tags: [finance, central_banks, payments, regulation]
|
||||
- id: bis_innovation_hub
|
||||
name: "BIS Innovation Hub"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.bis.org/about/bisih/"
|
||||
access_status: seed
|
||||
tags: [finance, fintech, CBDC, tokenization, payments]
|
||||
- id: sec_news
|
||||
name: "SEC Newsroom"
|
||||
kind: official_html
|
||||
source_tier: official_primary
|
||||
url: "https://www.sec.gov/newsroom"
|
||||
access_status: protected_or_dynamic_2026_07_04
|
||||
tags: [finance, securities, regulation, fintech]
|
||||
- id: finextra
|
||||
name: "Finextra"
|
||||
kind: rss_or_html
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.finextra.com/"
|
||||
access_status: seed
|
||||
tags: [finance, fintech, payments, banking]
|
||||
- id: chainalysis_blog
|
||||
name: "Chainalysis Blog"
|
||||
kind: rss_or_html
|
||||
source_tier: reputable_secondary
|
||||
url: "https://www.chainalysis.com/blog/"
|
||||
access_status: seed
|
||||
tags: [finance, crypto, compliance, AML, stablecoins]
|
||||
|
||||
global_search_queries:
|
||||
general_news:
|
||||
- "\"Trump\" \"China visit\" OR \"特朗普\" \"访华\""
|
||||
- "\"Russia Ukraine war\" \"latest\" OR \"俄乌战争\""
|
||||
- "\"CSRC\" \"Hong Kong brokers\" OR \"中国证监会\" \"香港券商\""
|
||||
- "\"US China\" \"tariff\" OR \"中美关系\" \"关税\""
|
||||
- "\"Hong Kong\" \"securities regulator\" OR \"香港\" \"证券监管\""
|
||||
ai:
|
||||
- "\"AI agent\" OR \"coding agent\" OR \"vibe coding\""
|
||||
- "\"agent harness\" OR \"evaluation harness\" OR \"context engineering\""
|
||||
- "\"LLMOps\" OR \"model routing\" OR \"prompt caching\" OR \"agent evaluation\""
|
||||
- "\"MCP\" \"agent\" \"release\""
|
||||
- "\"RAG\" \"evaluation\" OR \"GraphRAG\""
|
||||
- "\"computer use\" \"agent\" \"AI\""
|
||||
software_engineering:
|
||||
- "\"incident review\" \"engineering\""
|
||||
- "\"PostgreSQL\" \"performance\" \"release\""
|
||||
- "\"Spring Boot\" \"release\""
|
||||
- "\"Vue\" \"TypeScript\" \"release\""
|
||||
security_performance:
|
||||
- "\"CVE\" \"exploited in the wild\""
|
||||
- "\"CISA KEV\" \"added\""
|
||||
- "\"eBPF\" \"performance\""
|
||||
- "\"Linux perf\" \"FlameGraph\""
|
||||
- "\"network censorship measurement\" OR \"GFW measurement\""
|
||||
github:
|
||||
- "\"GitHub\" \"AI agent\" \"stars\""
|
||||
- "\"open source\" \"LLM infrastructure\""
|
||||
- "\"RAG\" \"GitHub\" \"release\""
|
||||
finance:
|
||||
- "\"fintech\" \"HKMA\""
|
||||
- "\"fintech\" \"MAS\""
|
||||
- "\"stablecoin\" \"regulation\""
|
||||
- "\"tokenization\" \"BIS\""
|
||||
|
||||
output_rules:
|
||||
cite_every_news_item: true
|
||||
cite_background_items: true
|
||||
show_dates_for_news_items: true
|
||||
source_label_format: "{source_name}, {published_at_or_updated_at}"
|
||||
stale_item_policy: "Do not include stale news unless explicitly labeled as background."
|
||||
primary_source_verification_for_secondary_items: "Required when item makes factual claims about releases, vulnerabilities, market regulation, or official policy."
|
||||
metadata_placement: "Put source, date, and link directly under each item title."
|
||||
prose_style: "Write paragraphs. Do not use formulaic labels such as 为何重要, 学习启示, 对你来说, 来源, 时间 as sectioned bullets inside the body."
|
||||
@@ -0,0 +1,49 @@
|
||||
services:
|
||||
newspaper:
|
||||
network_mode: host
|
||||
build:
|
||||
context: .
|
||||
network: host
|
||||
args:
|
||||
HTTP_PROXY: ${HTTP_PROXY:-}
|
||||
HTTPS_PROXY: ${HTTPS_PROXY:-}
|
||||
ALL_PROXY: ${ALL_PROXY:-}
|
||||
NO_PROXY: ${NO_PROXY:-localhost,127.0.0.1}
|
||||
http_proxy: ${HTTP_PROXY:-}
|
||||
https_proxy: ${HTTPS_PROXY:-}
|
||||
all_proxy: ${ALL_PROXY:-}
|
||||
no_proxy: ${NO_PROXY:-localhost,127.0.0.1}
|
||||
image: hongyu-daily-newspaper:latest
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
volumes:
|
||||
- ./newspaper:/app/newspaper
|
||||
command: ["hongyu-daily", "run"]
|
||||
restart: "no"
|
||||
|
||||
scheduler:
|
||||
network_mode: host
|
||||
build:
|
||||
context: .
|
||||
network: host
|
||||
args:
|
||||
HTTP_PROXY: ${HTTP_PROXY:-}
|
||||
HTTPS_PROXY: ${HTTPS_PROXY:-}
|
||||
ALL_PROXY: ${ALL_PROXY:-}
|
||||
NO_PROXY: ${NO_PROXY:-localhost,127.0.0.1}
|
||||
http_proxy: ${HTTP_PROXY:-}
|
||||
https_proxy: ${HTTPS_PROXY:-}
|
||||
all_proxy: ${ALL_PROXY:-}
|
||||
no_proxy: ${NO_PROXY:-localhost,127.0.0.1}
|
||||
image: hongyu-daily-newspaper:latest
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
TZ: ${TZ:-Asia/Shanghai}
|
||||
NEWSPAPER_SCHEDULE_TIME: ${NEWSPAPER_SCHEDULE_TIME:-08:00}
|
||||
volumes:
|
||||
- ./newspaper:/app/newspaper
|
||||
command: ["hongyu-daily", "schedule"]
|
||||
restart: unless-stopped
|
||||
@@ -0,0 +1,256 @@
|
||||
# Debian Docker 部署说明
|
||||
|
||||
这份文档说明如何把 Daily News for Markyan04 部署到你的个人 Debian 服务器上,通过 Docker Compose 每天早上自动生成并发送日报。
|
||||
|
||||
## 部署目标
|
||||
|
||||
每天早上 08:00,按 `Asia/Shanghai` 时区执行:
|
||||
|
||||
```text
|
||||
采集新闻 -> 生成中文日报 -> 归档到 newspaper/ -> 发送到 markyan04@163.com
|
||||
```
|
||||
|
||||
收件人已经在 `config/delivery.yaml` 中配置为:
|
||||
|
||||
```text
|
||||
markyan04@163.com
|
||||
```
|
||||
|
||||
## 服务器前置条件
|
||||
|
||||
服务器需要安装 Docker 和 Docker Compose plugin。
|
||||
|
||||
Debian 常见安装方式:
|
||||
|
||||
```bash
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y ca-certificates curl gnupg
|
||||
sudo install -m 0755 -d /etc/apt/keyrings
|
||||
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
|
||||
sudo chmod a+r /etc/apt/keyrings/docker.gpg
|
||||
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
|
||||
```
|
||||
|
||||
如果希望当前用户直接运行 Docker,可以执行:
|
||||
|
||||
```bash
|
||||
sudo usermod -aG docker "$USER"
|
||||
```
|
||||
|
||||
执行后需要退出 SSH 并重新登录,用户组才会生效。
|
||||
|
||||
## 复制仓库到服务器
|
||||
|
||||
建议放到 `/srv/apps/newspaper`:
|
||||
|
||||
```bash
|
||||
sudo mkdir -p /srv/apps/newspaper
|
||||
sudo chown -R "$USER":"$USER" /srv/apps/newspaper
|
||||
rsync -av --exclude .env /path/to/Newspaper/ /srv/apps/newspaper/
|
||||
cd /srv/apps/newspaper
|
||||
```
|
||||
|
||||
如果你用 `scp`、`git clone` 或 Synology 同步目录,也可以,只要最终目录结构完整即可。
|
||||
|
||||
## 创建 `.env`
|
||||
|
||||
复制模板:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
```
|
||||
|
||||
填写:
|
||||
|
||||
```text
|
||||
DEEPSEEK_API_KEY=你的 DeepSeek API Key
|
||||
DEEPSEEK_BASE_URL=https://api.deepseek.com
|
||||
DEEPSEEK_MODEL=deepseek-v4-pro
|
||||
GITHUB_TOKEN=可选,GitHub 只读 token,用于提高开源项目搜索限额
|
||||
|
||||
NEWSPAPER_SMTP_HOST=smtp.163.com
|
||||
NEWSPAPER_SMTP_PORT=465
|
||||
NEWSPAPER_SMTP_USERNAME=你的发件邮箱
|
||||
NEWSPAPER_SMTP_PASSWORD=你的邮箱授权码
|
||||
NEWSPAPER_FROM_EMAIL=你的发件邮箱
|
||||
NEWSPAPER_FROM_NAME=Daily News for Markyan04
|
||||
NEWSPAPER_SMTP_SECURITY=ssl
|
||||
|
||||
NEWSPAPER_SCHEDULE_TIME=08:00
|
||||
TZ=Asia/Shanghai
|
||||
```
|
||||
|
||||
注意:
|
||||
|
||||
- 如果使用 163 邮箱发件,需要在邮箱设置中开启 SMTP/IMAP 服务。
|
||||
- `GITHUB_TOKEN` 可以留空;如果 GitHub / 开源项目栏目经常空白,建议填一个只读 token。
|
||||
- `NEWSPAPER_SMTP_PASSWORD` 应填写邮箱授权码,不是网页登录密码。
|
||||
- 如果使用其他邮箱或邮件服务商,请按服务商文档替换 SMTP host、port 和 security。
|
||||
- 不要把 `.env` 提交到仓库,也不要把授权码发到聊天里。
|
||||
|
||||
## 构建镜像
|
||||
|
||||
```bash
|
||||
docker compose build
|
||||
```
|
||||
|
||||
## 检查配置
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily check-config
|
||||
```
|
||||
|
||||
期望输出:
|
||||
|
||||
```text
|
||||
config/newspaper.yaml: OK
|
||||
config/sources.yaml: OK
|
||||
config/delivery.yaml: OK
|
||||
newspaper archive: newspaper/
|
||||
```
|
||||
|
||||
## 只生成日报,不发送
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily generate
|
||||
```
|
||||
|
||||
检查归档目录:
|
||||
|
||||
```bash
|
||||
ls -lh newspaper/
|
||||
```
|
||||
|
||||
应该看到:
|
||||
|
||||
```text
|
||||
YYYY-MM-DD-daily-news-for-markyan04.md
|
||||
YYYY-MM-DD-daily-news-for-markyan04.html
|
||||
YYYY-MM-DD-daily-news-for-markyan04.meta.json
|
||||
```
|
||||
|
||||
建议第一次先打开 `.md` 或 `.html` 检查:
|
||||
|
||||
- 正文是否为中文。
|
||||
- 每条新闻是否有来源链接。
|
||||
- 新闻是否带有发布时间、更新时间或采集时间。
|
||||
- AI 栏目是否是新鲜内容,而不是旧概念拼贴。
|
||||
- 传统新闻是否覆盖了真实的新进展,而不是泛泛复述旧背景。
|
||||
|
||||
## 发送已生成的日报
|
||||
|
||||
如果你已经生成了某一天的日报,可以单独发送:
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily send --date YYYY-MM-DD
|
||||
```
|
||||
|
||||
例如:
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily send --date 2026-07-04
|
||||
```
|
||||
|
||||
## 生成并发送一次
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily run
|
||||
```
|
||||
|
||||
这个命令会完成:
|
||||
|
||||
```text
|
||||
采集 -> 生成 -> 归档 -> 发送
|
||||
```
|
||||
|
||||
## 启动每日定时任务
|
||||
|
||||
```bash
|
||||
docker compose up -d scheduler
|
||||
```
|
||||
|
||||
查看日志:
|
||||
|
||||
```bash
|
||||
docker compose logs -f scheduler
|
||||
```
|
||||
|
||||
`scheduler` 服务会常驻运行,每天本地时间 08:00 执行一次。
|
||||
|
||||
## 停止每日定时任务
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
```
|
||||
|
||||
## 可选方案:使用 Debian 主机 cron
|
||||
|
||||
如果你不想让 `scheduler` 容器常驻,也可以用 Debian 主机的 cron。
|
||||
|
||||
先创建日志目录:
|
||||
|
||||
```bash
|
||||
mkdir -p /srv/apps/newspaper/logs
|
||||
```
|
||||
|
||||
编辑 crontab:
|
||||
|
||||
```bash
|
||||
crontab -e
|
||||
```
|
||||
|
||||
加入:
|
||||
|
||||
```text
|
||||
0 8 * * * cd /srv/apps/newspaper && docker compose run --rm newspaper hongyu-daily run >> logs/cron.log 2>&1
|
||||
```
|
||||
|
||||
注意:不要同时启用主机 cron 和 `scheduler` 容器,否则每天可能会重复发送。
|
||||
|
||||
## 更新部署
|
||||
|
||||
当你把新代码复制到服务器后:
|
||||
|
||||
```bash
|
||||
cd /srv/apps/newspaper
|
||||
docker compose build
|
||||
docker compose up -d scheduler
|
||||
```
|
||||
|
||||
如果只想手动跑一次:
|
||||
|
||||
```bash
|
||||
docker compose run --rm newspaper hongyu-daily run
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
### Docker 构建失败
|
||||
|
||||
先确认服务器能访问 Python 包源和 Debian 软件源。
|
||||
|
||||
```bash
|
||||
docker compose build --no-cache
|
||||
```
|
||||
|
||||
### 邮件发送失败
|
||||
|
||||
优先检查:
|
||||
|
||||
- SMTP host 和 port 是否正确。
|
||||
- `NEWSPAPER_SMTP_SECURITY` 是否应为 `ssl` 或 `starttls`。
|
||||
- 邮箱是否开启 SMTP/IMAP。
|
||||
- `NEWSPAPER_SMTP_PASSWORD` 是否是授权码。
|
||||
- 服务器是否允许连接 SMTP 端口。
|
||||
|
||||
### 生成日报失败
|
||||
|
||||
优先检查:
|
||||
|
||||
- `DEEPSEEK_API_KEY` 是否正确。
|
||||
- DeepSeek API 账户是否有额度。
|
||||
- 服务器是否能访问 `https://api.deepseek.com`。
|
||||
- `config/sources.yaml` 中的来源是否大面积无法访问。
|
||||
@@ -0,0 +1,119 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Daily News for Markyan04 | 2026 - 07 - 05</title>
|
||||
<style>
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.65; color: #111; max-width: 860px; margin: 0 auto; padding: 24px; }
|
||||
a { color: #1756a9; }
|
||||
h1, h2, h3 { line-height: 1.25; }
|
||||
blockquote { border-left: 4px solid #ddd; padding-left: 12px; color: #555; }
|
||||
code { background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Daily News for Markyan04 | 2026 - 07 - 05</h1>
|
||||
<h2>1. 今日汇总</h2>
|
||||
<ul>
|
||||
<li><strong>传统新闻</strong>:特朗普与普京通长电话,在乌克兰问题上重新将自己定位为和平缔造者;德黑兰为在美以空袭中丧生的哈梅内伊举行国葬,特朗普承诺葬礼期间保持克制。</li>
|
||||
<li><strong>AI</strong>:Anthropic 于 6 月 30 日发布 Claude Sonnet 5,在编程、agent 和专业工作中达到前沿性能;同时重新部署 Fable 5,并联合 Amazon 提出 jailbreak 严重性评分行业框架。</li>
|
||||
<li><strong>传统软工</strong>:Cloudflare 发布 Monetization Gateway,允许开发者对任何 Web 资源收费并以稳定币通过 x402 协议结算,同时发布 AI 时代网络经济报告。</li>
|
||||
<li><strong>安全</strong>:Linux 稳定内核批量更新修复关键 IPv6 漏洞 CVE-2026-53362;GNU Guix 披露四个严重漏洞,包括远程提权和本地敏感文件泄露。</li>
|
||||
<li><strong>GitHub</strong>:<code>usestrix/strix</code> 开源 AI 渗透测试工具获 1,904 星;<code>DeusData/codebase-memory-mcp</code> 代码智能 MCP 服务器本周获 9,517 星。</li>
|
||||
</ul>
|
||||
<h2>2. 传统新闻</h2>
|
||||
<h3>特朗普与普京长通话,重新定位为乌克兰和平缔造者</h3>
|
||||
<p>来源:The Guardian | 时间:2026-07-05 | 链接:https://www.theguardian.com/us-news/2026/jul/04/trump-speech-dc-evacuation-thunderstorm</p>
|
||||
<p>根据 Google News 汇总报道,特朗普与普京进行了长时间通话,在乌克兰战争问题上重新调整立场,将自己定位为和平缔造者角色。通话发生在俄乌战争持续且国际社会对停火路径尚无共识的背景下,特朗普的具体建议或谈判框架尚未公开。目前尚不清楚此次通话是否会对美国对乌政策产生实质性转向。</p>
|
||||
<h3>伊朗为哈梅内伊举行国葬,特朗普承诺葬礼期间保持克制</h3>
|
||||
<p>来源:Al Jazeera | 时间:2026-07-05 | 链接:https://www.aljazeera.com/news/liveblog/2026/7/5/iran-war-live-huge-crowds-mourn-khamenei-trump-vows-calm-during-funeral</p>
|
||||
<p>伊朗最高领袖哈梅内伊及其家人于今年二月在美以联合空袭中丧生,大规模哀悼仪式于德黑兰启动,数万名伊朗民众走上街头。特朗普在葬礼期间公开承诺将“保持克制”。Al Jazeera 现场博客持续跟踪最新动态,地区安全局势在葬礼期间的稳定性仍存在高度不确定性。</p>
|
||||
<h3>菲律宾副总统弹劾审判即将开始</h3>
|
||||
<p>来源:The New York Times | 时间:2026-07-05 | 链接:https://www.nytimes.com/2026/07/05/world/asia/impeachment-trial-philippines-sara-duterte.html</p>
|
||||
<p>菲律宾副总统莎拉·杜特尔特面临腐败指控及对总统马科斯的死亡威胁指控,弹劾审判即将启动。若罪名成立,她可能被禁止从政,而她此前已表示将竞选总统。审判将对菲律宾国内政治格局产生深远影响,目前尚无明确时间表披露庭审具体进程。</p>
|
||||
<h3>习近平准备延长任期,蔡奇成为关键助手</h3>
|
||||
<p>来源:The New York Times | 时间:2026-07-05 | 链接:https://www.nytimes.com/2026/07/05/world/asia/china-xi-leadership-cai-qi.html</p>
|
||||
<p>习近平正在准备延长其任期,并计划向党内精英层注入新鲜血液。蔡奇被视为承担这一任务的核心人物。相关决策预计将在下一次重大党内会议上明朗化。</p>
|
||||
<h3>美国 250 周年独立日遭受恶劣天气冲击</h3>
|
||||
<p>来源:Al Jazeera | 时间:2026-07-05 | 链接:https://www.aljazeera.com/news/2026/7/5/severe-weather-disrupts-uss-250th-celebrations?traffic_source=rss</p>
|
||||
<p>美国 250 周年独立日庆祝活动遭恶劣天气严重干扰,华盛顿特区部分区域因雷暴紧急疏散。特朗普在深夜以竞选式演讲发表讲话,称这是美国历史上“最欢乐、最辉煌的时刻之一”。</p>
|
||||
<h2>3. AI领域</h2>
|
||||
<h3>Anthropic 发布 Claude Sonnet 5</h3>
|
||||
<p>来源:Anthropic News | 时间:2026-06-30 | 链接:https://www.anthropic.com/news/claude-sonnet-5</p>
|
||||
<p>Anthropic 于 6 月 30 日正式发布 Claude Sonnet 5。根据官方公告,Sonnet 5 在编程、agent 及大规模专业工作场景中实现了前沿性能,意味着其定位已不仅限于中端模型,而是开始在核心能力上逼近或匹配前代旗舰水平。目前尚未公开详细 benchmark 数据或架构调整说明,但发布指出这是面向规模化应用的重要迭代。</p>
|
||||
<h3>Anthropic 重新部署 Fable 5 并提出 Jailbreak 严重性框架</h3>
|
||||
<p>来源:Anthropic News | 时间:2026-06-30 | 链接:https://www.anthropic.com/news/redeploying-fable-5</p>
|
||||
<p>在 Claude Sonnet 5 发布同日,Anthropic 宣布 Fable 5 于 7 月 1 日全球重新上线,并与 Amazon 合作提出了一套行业范围的 jailbreak 严重性评分框架。这表明 Anthropic 正在试图将安全评估标准化为可跨模型和跨组织比较的公共度量,类似 CVSS 之于传统软件安全的思路。Fable 5 此前曾被部署后被短暂撤回,此次重新发布可能涉及安全机制的调整。</p>
|
||||
<h3>Meta 发布 Muse Spark:向个人超级智能扩展</h3>
|
||||
<p>来源:Meta AI Blog | 时间:2026-07-05 | 链接:https://ai.meta.com/blog/introducing-muse-spark-msl/</p>
|
||||
<p>Meta 发布 Muse Spark(MSL),方向定位为“向个人超级智能扩展”。目前公开信息有限,但从路线图来看,该产品线可能涉及个性化模型微调、本地推理能力提升和 agent 长期记忆,试图将超大规模模型的能力压缩到个人可用的尺度。这与 Google Gemini 系列的个人化方向构成直接竞争。</p>
|
||||
<h3>Meta 发布 Brain2Qwerty:非侵入式脑电波文本通信</h3>
|
||||
<p>来源:Meta AI Blog | 时间:2026-07-05 | 链接:https://ai.meta.com/blog/brain2qwerty-brain-ai-human-communication/</p>
|
||||
<p>Meta 发布了 Brain2Qwerty 研究,展示了一条不依赖手术植入的脑电波到文字的通信路径。该技术属于脑机接口领域中非侵入式分支,相较于 Neuralink 的手术方案,安全性更高但信号精度和速度存在固有瓶颈,目前大概率仍处于实验室验证阶段。</p>
|
||||
<h3>主流模型系统提示大规模泄露</h3>
|
||||
<p>来源:GitHub - asgeirtj/system_prompts_leaks | 时间:2026-07-05 | 链接:https://github.com/asgeirtj/system_prompts_leaks</p>
|
||||
<p>一个名为 <code>system_prompts_leaks</code> 的仓库公开了当前主流模型的系统提示,涵盖 Anthropic Claude Fable 5、Opus 4.8、Claude Code、OpenAI ChatGPT 5.5 Thinking、GPT 5.5 Instant、Codex、Google Gemini 3.5 Flash、3.1 Pro、xAI Grok、Cursor、Copilot、VS Code 和 Perplexity 等。这些提示的提取方法未公开,但其广泛的范围说明多数商业模型的系统提示仍缺乏有效的防提取保护。对于依赖系统提示进行安全约束的部署场景,这是一个直接影响。</p>
|
||||
<h3>编程 Agent 工具生态持续快速扩张</h3>
|
||||
<p>近期多个工具试图解决编程 agent 的实际问题。OpenAI 发布 <code>codex-plugin-cc</code>(718 星),让 Codex 可直接在 Claude Code 中被调用,说明不同 AI 编程工具之间的互操作性和竞争关系正在加速融合。<code>JuliusBrussee/caveman</code>(1,089 星)是一个 Claude Code 技能,通过将 prompt 压缩成“穴居人语”声称可节省 65% token,反映出企业在 token 成本上的压力。<code>alibaba/page-agent</code>(742 星)则是一个页面内 GUI agent,用自然语言控制网页界面,属于 computer-use agent 的轻量化实现路径。</p>
|
||||
<h3>AI 记忆与知识图谱基础设施出现多头竞争</h3>
|
||||
<p><code>topoteretes/cognee</code>(3,388 星本周)定位为开源 AI 记忆平台,向 agent 提供跨会话的持久长期记忆,底層使用自托管的图引擎。<code>Ontos-AI/knowhere</code>(1,854 星)则专注于将非结构化内容提取为结构化块以供 RAG 和 agent 使用。两者从不同方向切入同一个核心问题:如何让 LLM 在多次交互中保持对上下文的理解,而不依赖无限增长的长上下文窗口或重复嵌入检索。</p>
|
||||
<h3>多 Agent 系统和并行编排工具</h3>
|
||||
<p><code>stablyai/orca</code>(3,790 星本周)是一个支持多 agent 并行运行的编排环境,可让用户用自己的订阅运行任何编程 agent,支持桌面和移动端。<code>ohdearquant/lionagi</code>(399 星)自称“智能乐团”,暗示其多 agent 调度可能在模型路由和工作流编排方面进行了抽象。<code>kunchenguid/gnhf</code>(443 星本周)的 slogan 是“睡觉前告诉 agent:晚安,玩得开心”,反映了夜间自主 agent 运行场景正在从梗变成工程实践。</p>
|
||||
<h3>Harvard 发布机器学习系统教材</h3>
|
||||
<p>来源:GitHub - harvard-edge/cs249r_book | 时间:2026-07-05 | 链接:https://github.com/harvard-edge/cs249r_book</p>
|
||||
<p>Harvard Edge 计算实验室公开了 CS249r 课程教材《Machine Learning Systems》,涵盖生产级 ML 系统的完整生命周期,面向 AI engineering 方向的系统和工程化教育正在从散落博文走向结构化课程。</p>
|
||||
<h2>4. 传统软工</h2>
|
||||
<h3>Cloudflare 发布 Monetization Gateway:用稳定币计费任何 Web 资源</h3>
|
||||
<p>来源:Cloudflare Blog | 时间:2026-07-01 | 链接:https://blog.cloudflare.com/monetization-gateway/</p>
|
||||
<p>Cloudflare 开放 Monetization Gateway 等待列表,该产品允许开发者对 Cloudflare 后面的任何网页、数据集、API 或 MCP 工具进行收费。结算采用 x402 开放协议以稳定币完成,无需自建支付系统。x402 协议在此前主要用于 AI 内容付费和微支付场景,此次与 Cloudflare 的边缘基础设施结合后,实际降低了内容创作者直接通过缓存层收费的门槛。</p>
|
||||
<h3>Cloudflare 发布“Agentic Internet”一周年报告</h3>
|
||||
<p>来源:Cloudflare Blog | 时间:2026-07-01 | 链接:https://blog.cloudflare.com/agentic-internet-bot-report/</p>
|
||||
<p>在宣布“内容独立日”一周年之际,Cloudflare 发布报告分析自主 AI agent 如何颠覆传统搜索引擎推荐流量,并研究了支撑可持续网络经济所需的新基础设施堆栈。报告的核心论点是:随着 agent 成为网络内容的主要消费者,传统以广告和搜索导流为核心的网站盈利模式正在失效,需要新的计费和发现机制。</p>
|
||||
<p>配合这一判断,Cloudflare 同步推出了三项功能:让所有客户精细区分并管理 Search、Agent 和 Training bot 的流量选项;Attribution Business Insights 仪表盘用于理解爬虫行为和内容价值;以及“让 AI 搜索更聪明”的双项计划,旨在帮助创作者在 agent 时代保持可发现性和内容变现能力。</p>
|
||||
<h3>Netflix 发布 GenPage:端到端生成式首页构建</h3>
|
||||
<p>来源:Netflix TechBlog | 时间:2026-06-29 | 链接:https://netflixtechblog.com/genpage-towards-end-to-end-generative-homepage-construction-at-netflix-77146fba8a08?source=rss----2615bd06b42e---4</p>
|
||||
<p>Netflix 技术博客介绍了 GenPage,一种自回归首页生成方法。它将首页构建分解为逐行动态生成,每一步基于已生成内容和用户上下文决定下一个实体或行。这不是传统推荐系统上的 LLM 外衣,而是从页面结构层面用大模型直接生成组合决策,可能影响推荐系统的延迟、可解释性和冷启动策略。</p>
|
||||
<h3>Spring AI 与 Spring Framework 最新进展</h3>
|
||||
<p>来源:Spring Blog | 时间:2026-07-02 | 链接:https://spring.io/blog/2026/07/02/a-bootiful-podcast-sebastien-deleuze</p>
|
||||
<p>Spring 框架核心贡献者 Sébastien Deleuze 在播客中详述了 Spring AI 的最新进展以及 Spring Framework 在 Kotlin 支持上的持续投入。Spring AI 作为 Java 生态对接 LLM 的主要桥梁,其框架级设计(如向量存储抽象、工具调用和 agent 编排)对于已经在 Spring 基础设施上投入的团队具有直接的工程参考价值。</p>
|
||||
<h2>5. 安全与性能工程</h2>
|
||||
<h3>Linux 稳定内核批量更新修复关键 IPv6 漏洞</h3>
|
||||
<p>来源:LWN | 时间:2026-07-04 | 链接:https://lwn.net/Articles/1081230/</p>
|
||||
<p>Greg Kroah-Hartman 发布了七个 Linux 稳定内核版本(7.1.3、6.18.38、6.12.95、6.6.144、6.1.177、5.15.211、5.10.260)。其中多个版本修复了 CVE-2026-53362——一个在 6.0 内核引入的 IPv6 漏洞,可能允许攻击者逃逸沙箱或实现权限提升。该漏洞影响范围广,横跨最新主线到多个 LTS 分支,建议尽快评估并部署对应内核更新。</p>
|
||||
<h3>GNU Guix 披露四个严重漏洞</h3>
|
||||
<p>来源:LWN | 时间:2026-07-03 | 链接:https://lwn.net/Articles/1081199/</p>
|
||||
<p>GNU Guix 项目公布四个漏洞:三个存在于 <code>guix substitute</code> 工具中,另一个影响 <code>guix pull</code> 和 <code>guix time-machine</code> 命令。漏洞严重度从远程提权到本地敏感文件泄露。由于 Guix 是用于可复现构建和科学计算环境的基础设施,这些漏洞可能影响 CI 管道和开发环境的安全性,远程可利用性尤为值得警惕。</p>
|
||||
<h3>Linux 负向 Dentry 限制的存储峰会讨论</h3>
|
||||
<p>来源:LWN | 时间:2026-07-03 | 链接:https://lwn.net/Articles/1079407/</p>
|
||||
<p>在 2026 年 Linux 存储、文件系统、内存管理和 BPF 峰会上,负向目录条目(negative dentries)引发专题讨论。负向 dentry 用于标记“某名称的文件在目录中不存在”,在频繁查询不存在文件的场景下会不受限制地积累,导致内存占用激增。讨论聚焦于内核是否应引入全局限制机制,但方案共识尚未形成,这可能影响高并发 Web 服务和容器化环境下的内存预估。</p>
|
||||
<h3>CalyxOS 隐私导向 Android 发行版正式回归</h3>
|
||||
<p>来源:LWN | 时间:2026-07-02 | 链接:https://lwn.net/Articles/1081038/</p>
|
||||
<p>CalyxOS 在去年 8 月因核心开发者离开暂停发布后,宣布“正式回归”。项目已完成发布流程重做、安全协议升级和签名密钥更换。对于使用该系统的用户,升级路径和密钥信任链的迁移方案将是接下来需要关注的实际问题。</p>
|
||||
<h2>6. GitHub / 开源项目跟踪</h2>
|
||||
<h3>usestrix/strix:开源 AI 渗透测试工具</h3>
|
||||
<p>来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/usestrix/strix</p>
|
||||
<p>一个 Python 项目的 AI 渗透测试工具,定位为自动发现和修复应用漏洞,本周获 1,904 星。技术栈未完全披露,但从功能描述来看,该工具可能结合了 agent 驱动的安全扫描和自动漏洞利用验证。作为开源安全测试工具,其成熟度尚需通过实际安全审计来验证。</p>
|
||||
<h3>DeusData/codebase-memory-mcp:高性能代码智能 MCP 服务器</h3>
|
||||
<p>来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/DeusData/codebase-memory-mcp</p>
|
||||
<p>用 C 编写的 MCP 服务器,将代码库索引到持久化知识图中,声称“平均代码库毫秒级索引”、支持 158 种语言、亚毫秒级查询并节省 99% token。以单一静态二进制分发,零依赖。本周 9,517 星,反映开发者在 LLM 编程场景中对低成本代码上下文检索的迫切需求。</p>
|
||||
<h3>ChromeDevTools/chrome-devtools-mcp:面向编程 Agent 的浏览器控制协议</h3>
|
||||
<p>来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/ChromeDevTools/chrome-devtools-mcp</p>
|
||||
<p>Chrome DevTools 团队官方发布的 MCP 服务器,让编程 agent 能够以结构化方式与浏览器交互,而不是通过原始 DOM 或截图驱动。TypeScript 实现,日增 304 星。此前第三方 browser-use 工具流行度爆炸,此官方方案的出现可能推动 browser-agent 从实验进入标准化。</p>
|
||||
<h3>ddalcu/mlx-serve:Apple Silicon 原生 LLM 推理服务器</h3>
|
||||
<p>来源:GitHub Search API | 时间:2026-07-05 | 链接:https://github.com/ddalcu/mlx-serve</p>
|
||||
<p>专为 Apple Silicon 设计的 LLM 推理服务器,与 OpenAI 和 Anthropic API 兼容,使用 Zig 编写,不依赖 Python。包含 macOS 原生应用,支持聊天、agent 模式和工具调用。232 星,代表了在本地推理效率上绕开 Python 解释器开销的工程尝试。</p>
|
||||
<h3>topoteretes/cognee:开源 AI Agent 长期记忆平台</h3>
|
||||
<p>来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/topoteretes/cognee</p>
|
||||
<p>Python 编写的 AI 记忆平台,为 agent 提供跨会话持久长期记忆,使用自托管知识图引擎。本周 3,388 星。在多个 agent 复用上下文、避免重复推理和降低 token 成本的场景中有直接工程价值。</p>
|
||||
<h3>calesthio/OpenMontage:开源智能视频制作系统</h3>
|
||||
<p>来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/calesthio/OpenMontage</p>
|
||||
<p>号称“世界首个开源智能视频制作系统”,包含 12 条流水线、52 个工具和 500+ 项 agent 技能。本周 8,447 星,是目前 agent 在创意工具领域最高关注度的项目之一。Python 实现,具体生产线流程和输出质量尚无第三方评估。</p>
|
||||
<h2>7. 金融领域</h2>
|
||||
<h3>美联储对 Small Business Bank 采取执法行动,终止对法巴的执法</h3>
|
||||
<p>来源:Federal Reserve | 时间:2026-07-02 | 链接:https://www.federalreserve.gov/newsevents/pressreleases/enforcement20260702a.htm</p>
|
||||
<p>美联储宣布对 Small Business Bank 启动新的执法行动,同时终止对法国巴黎银行及其美国子公司、法巴证券公司和 Community Bankshares 的执法行动。终止执法通常意味着相关银行已满足整改要求,法巴这类全球系统重要性银行的合规问题一度涉及反洗钱和制裁合规领域。具体执法原因和被终止的整改措施细节尚未一并公开。</p>
|
||||
<h3>支付领域:稳定币结算进入基础设施层</h3>
|
||||
<p>Cloudflare 发布的 Monetization Gateway(详见传统软工栏目)允许用稳定币结算网络资源费用,这意味着稳定币正在从投资和跨境转账场景渗透到微支付和 API 计费等基础设施层面。x402 协议的作用类似于 HTTP 状态码 402“Payment Required”的工程实现,配合 Cloudflare 的边缘网络,可能催生更广泛的机器间支付场景。该趋势对 KYC/AML 的影响在于,微支付级稳定币流如何在监管框架下被归类和处理,目前仍是开放问题。</p>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,195 @@
|
||||
# Daily News for Markyan04 | 2026 - 07 - 05
|
||||
|
||||
## 1. 今日汇总
|
||||
|
||||
- **传统新闻**:特朗普与普京通长电话,在乌克兰问题上重新将自己定位为和平缔造者;德黑兰为在美以空袭中丧生的哈梅内伊举行国葬,特朗普承诺葬礼期间保持克制。
|
||||
- **AI**:Anthropic 于 6 月 30 日发布 Claude Sonnet 5,在编程、agent 和专业工作中达到前沿性能;同时重新部署 Fable 5,并联合 Amazon 提出 jailbreak 严重性评分行业框架。
|
||||
- **传统软工**:Cloudflare 发布 Monetization Gateway,允许开发者对任何 Web 资源收费并以稳定币通过 x402 协议结算,同时发布 AI 时代网络经济报告。
|
||||
- **安全**:Linux 稳定内核批量更新修复关键 IPv6 漏洞 CVE-2026-53362;GNU Guix 披露四个严重漏洞,包括远程提权和本地敏感文件泄露。
|
||||
- **GitHub**:`usestrix/strix` 开源 AI 渗透测试工具获 1,904 星;`DeusData/codebase-memory-mcp` 代码智能 MCP 服务器本周获 9,517 星。
|
||||
|
||||
## 2. 传统新闻
|
||||
|
||||
### 特朗普与普京长通话,重新定位为乌克兰和平缔造者
|
||||
|
||||
来源:The Guardian | 时间:2026-07-05 | 链接:https://www.theguardian.com/us-news/2026/jul/04/trump-speech-dc-evacuation-thunderstorm
|
||||
|
||||
根据 Google News 汇总报道,特朗普与普京进行了长时间通话,在乌克兰战争问题上重新调整立场,将自己定位为和平缔造者角色。通话发生在俄乌战争持续且国际社会对停火路径尚无共识的背景下,特朗普的具体建议或谈判框架尚未公开。目前尚不清楚此次通话是否会对美国对乌政策产生实质性转向。
|
||||
|
||||
### 伊朗为哈梅内伊举行国葬,特朗普承诺葬礼期间保持克制
|
||||
|
||||
来源:Al Jazeera | 时间:2026-07-05 | 链接:https://www.aljazeera.com/news/liveblog/2026/7/5/iran-war-live-huge-crowds-mourn-khamenei-trump-vows-calm-during-funeral
|
||||
|
||||
伊朗最高领袖哈梅内伊及其家人于今年二月在美以联合空袭中丧生,大规模哀悼仪式于德黑兰启动,数万名伊朗民众走上街头。特朗普在葬礼期间公开承诺将“保持克制”。Al Jazeera 现场博客持续跟踪最新动态,地区安全局势在葬礼期间的稳定性仍存在高度不确定性。
|
||||
|
||||
### 菲律宾副总统弹劾审判即将开始
|
||||
|
||||
来源:The New York Times | 时间:2026-07-05 | 链接:https://www.nytimes.com/2026/07/05/world/asia/impeachment-trial-philippines-sara-duterte.html
|
||||
|
||||
菲律宾副总统莎拉·杜特尔特面临腐败指控及对总统马科斯的死亡威胁指控,弹劾审判即将启动。若罪名成立,她可能被禁止从政,而她此前已表示将竞选总统。审判将对菲律宾国内政治格局产生深远影响,目前尚无明确时间表披露庭审具体进程。
|
||||
|
||||
### 习近平准备延长任期,蔡奇成为关键助手
|
||||
|
||||
来源:The New York Times | 时间:2026-07-05 | 链接:https://www.nytimes.com/2026/07/05/world/asia/china-xi-leadership-cai-qi.html
|
||||
|
||||
习近平正在准备延长其任期,并计划向党内精英层注入新鲜血液。蔡奇被视为承担这一任务的核心人物。相关决策预计将在下一次重大党内会议上明朗化。
|
||||
|
||||
### 美国 250 周年独立日遭受恶劣天气冲击
|
||||
|
||||
来源:Al Jazeera | 时间:2026-07-05 | 链接:https://www.aljazeera.com/news/2026/7/5/severe-weather-disrupts-uss-250th-celebrations?traffic_source=rss
|
||||
|
||||
美国 250 周年独立日庆祝活动遭恶劣天气严重干扰,华盛顿特区部分区域因雷暴紧急疏散。特朗普在深夜以竞选式演讲发表讲话,称这是美国历史上“最欢乐、最辉煌的时刻之一”。
|
||||
|
||||
## 3. AI领域
|
||||
|
||||
### Anthropic 发布 Claude Sonnet 5
|
||||
|
||||
来源:Anthropic News | 时间:2026-06-30 | 链接:https://www.anthropic.com/news/claude-sonnet-5
|
||||
|
||||
Anthropic 于 6 月 30 日正式发布 Claude Sonnet 5。根据官方公告,Sonnet 5 在编程、agent 及大规模专业工作场景中实现了前沿性能,意味着其定位已不仅限于中端模型,而是开始在核心能力上逼近或匹配前代旗舰水平。目前尚未公开详细 benchmark 数据或架构调整说明,但发布指出这是面向规模化应用的重要迭代。
|
||||
|
||||
### Anthropic 重新部署 Fable 5 并提出 Jailbreak 严重性框架
|
||||
|
||||
来源:Anthropic News | 时间:2026-06-30 | 链接:https://www.anthropic.com/news/redeploying-fable-5
|
||||
|
||||
在 Claude Sonnet 5 发布同日,Anthropic 宣布 Fable 5 于 7 月 1 日全球重新上线,并与 Amazon 合作提出了一套行业范围的 jailbreak 严重性评分框架。这表明 Anthropic 正在试图将安全评估标准化为可跨模型和跨组织比较的公共度量,类似 CVSS 之于传统软件安全的思路。Fable 5 此前曾被部署后被短暂撤回,此次重新发布可能涉及安全机制的调整。
|
||||
|
||||
### Meta 发布 Muse Spark:向个人超级智能扩展
|
||||
|
||||
来源:Meta AI Blog | 时间:2026-07-05 | 链接:https://ai.meta.com/blog/introducing-muse-spark-msl/
|
||||
|
||||
Meta 发布 Muse Spark(MSL),方向定位为“向个人超级智能扩展”。目前公开信息有限,但从路线图来看,该产品线可能涉及个性化模型微调、本地推理能力提升和 agent 长期记忆,试图将超大规模模型的能力压缩到个人可用的尺度。这与 Google Gemini 系列的个人化方向构成直接竞争。
|
||||
|
||||
### Meta 发布 Brain2Qwerty:非侵入式脑电波文本通信
|
||||
|
||||
来源:Meta AI Blog | 时间:2026-07-05 | 链接:https://ai.meta.com/blog/brain2qwerty-brain-ai-human-communication/
|
||||
|
||||
Meta 发布了 Brain2Qwerty 研究,展示了一条不依赖手术植入的脑电波到文字的通信路径。该技术属于脑机接口领域中非侵入式分支,相较于 Neuralink 的手术方案,安全性更高但信号精度和速度存在固有瓶颈,目前大概率仍处于实验室验证阶段。
|
||||
|
||||
### 主流模型系统提示大规模泄露
|
||||
|
||||
来源:GitHub - asgeirtj/system_prompts_leaks | 时间:2026-07-05 | 链接:https://github.com/asgeirtj/system_prompts_leaks
|
||||
|
||||
一个名为 `system_prompts_leaks` 的仓库公开了当前主流模型的系统提示,涵盖 Anthropic Claude Fable 5、Opus 4.8、Claude Code、OpenAI ChatGPT 5.5 Thinking、GPT 5.5 Instant、Codex、Google Gemini 3.5 Flash、3.1 Pro、xAI Grok、Cursor、Copilot、VS Code 和 Perplexity 等。这些提示的提取方法未公开,但其广泛的范围说明多数商业模型的系统提示仍缺乏有效的防提取保护。对于依赖系统提示进行安全约束的部署场景,这是一个直接影响。
|
||||
|
||||
### 编程 Agent 工具生态持续快速扩张
|
||||
|
||||
近期多个工具试图解决编程 agent 的实际问题。OpenAI 发布 `codex-plugin-cc`(718 星),让 Codex 可直接在 Claude Code 中被调用,说明不同 AI 编程工具之间的互操作性和竞争关系正在加速融合。`JuliusBrussee/caveman`(1,089 星)是一个 Claude Code 技能,通过将 prompt 压缩成“穴居人语”声称可节省 65% token,反映出企业在 token 成本上的压力。`alibaba/page-agent`(742 星)则是一个页面内 GUI agent,用自然语言控制网页界面,属于 computer-use agent 的轻量化实现路径。
|
||||
|
||||
### AI 记忆与知识图谱基础设施出现多头竞争
|
||||
|
||||
`topoteretes/cognee`(3,388 星本周)定位为开源 AI 记忆平台,向 agent 提供跨会话的持久长期记忆,底層使用自托管的图引擎。`Ontos-AI/knowhere`(1,854 星)则专注于将非结构化内容提取为结构化块以供 RAG 和 agent 使用。两者从不同方向切入同一个核心问题:如何让 LLM 在多次交互中保持对上下文的理解,而不依赖无限增长的长上下文窗口或重复嵌入检索。
|
||||
|
||||
### 多 Agent 系统和并行编排工具
|
||||
|
||||
`stablyai/orca`(3,790 星本周)是一个支持多 agent 并行运行的编排环境,可让用户用自己的订阅运行任何编程 agent,支持桌面和移动端。`ohdearquant/lionagi`(399 星)自称“智能乐团”,暗示其多 agent 调度可能在模型路由和工作流编排方面进行了抽象。`kunchenguid/gnhf`(443 星本周)的 slogan 是“睡觉前告诉 agent:晚安,玩得开心”,反映了夜间自主 agent 运行场景正在从梗变成工程实践。
|
||||
|
||||
### Harvard 发布机器学习系统教材
|
||||
|
||||
来源:GitHub - harvard-edge/cs249r_book | 时间:2026-07-05 | 链接:https://github.com/harvard-edge/cs249r_book
|
||||
|
||||
Harvard Edge 计算实验室公开了 CS249r 课程教材《Machine Learning Systems》,涵盖生产级 ML 系统的完整生命周期,面向 AI engineering 方向的系统和工程化教育正在从散落博文走向结构化课程。
|
||||
|
||||
## 4. 传统软工
|
||||
|
||||
### Cloudflare 发布 Monetization Gateway:用稳定币计费任何 Web 资源
|
||||
|
||||
来源:Cloudflare Blog | 时间:2026-07-01 | 链接:https://blog.cloudflare.com/monetization-gateway/
|
||||
|
||||
Cloudflare 开放 Monetization Gateway 等待列表,该产品允许开发者对 Cloudflare 后面的任何网页、数据集、API 或 MCP 工具进行收费。结算采用 x402 开放协议以稳定币完成,无需自建支付系统。x402 协议在此前主要用于 AI 内容付费和微支付场景,此次与 Cloudflare 的边缘基础设施结合后,实际降低了内容创作者直接通过缓存层收费的门槛。
|
||||
|
||||
### Cloudflare 发布“Agentic Internet”一周年报告
|
||||
|
||||
来源:Cloudflare Blog | 时间:2026-07-01 | 链接:https://blog.cloudflare.com/agentic-internet-bot-report/
|
||||
|
||||
在宣布“内容独立日”一周年之际,Cloudflare 发布报告分析自主 AI agent 如何颠覆传统搜索引擎推荐流量,并研究了支撑可持续网络经济所需的新基础设施堆栈。报告的核心论点是:随着 agent 成为网络内容的主要消费者,传统以广告和搜索导流为核心的网站盈利模式正在失效,需要新的计费和发现机制。
|
||||
|
||||
配合这一判断,Cloudflare 同步推出了三项功能:让所有客户精细区分并管理 Search、Agent 和 Training bot 的流量选项;Attribution Business Insights 仪表盘用于理解爬虫行为和内容价值;以及“让 AI 搜索更聪明”的双项计划,旨在帮助创作者在 agent 时代保持可发现性和内容变现能力。
|
||||
|
||||
### Netflix 发布 GenPage:端到端生成式首页构建
|
||||
|
||||
来源:Netflix TechBlog | 时间:2026-06-29 | 链接:https://netflixtechblog.com/genpage-towards-end-to-end-generative-homepage-construction-at-netflix-77146fba8a08?source=rss----2615bd06b42e---4
|
||||
|
||||
Netflix 技术博客介绍了 GenPage,一种自回归首页生成方法。它将首页构建分解为逐行动态生成,每一步基于已生成内容和用户上下文决定下一个实体或行。这不是传统推荐系统上的 LLM 外衣,而是从页面结构层面用大模型直接生成组合决策,可能影响推荐系统的延迟、可解释性和冷启动策略。
|
||||
|
||||
### Spring AI 与 Spring Framework 最新进展
|
||||
|
||||
来源:Spring Blog | 时间:2026-07-02 | 链接:https://spring.io/blog/2026/07/02/a-bootiful-podcast-sebastien-deleuze
|
||||
|
||||
Spring 框架核心贡献者 Sébastien Deleuze 在播客中详述了 Spring AI 的最新进展以及 Spring Framework 在 Kotlin 支持上的持续投入。Spring AI 作为 Java 生态对接 LLM 的主要桥梁,其框架级设计(如向量存储抽象、工具调用和 agent 编排)对于已经在 Spring 基础设施上投入的团队具有直接的工程参考价值。
|
||||
|
||||
## 5. 安全与性能工程
|
||||
|
||||
### Linux 稳定内核批量更新修复关键 IPv6 漏洞
|
||||
|
||||
来源:LWN | 时间:2026-07-04 | 链接:https://lwn.net/Articles/1081230/
|
||||
|
||||
Greg Kroah-Hartman 发布了七个 Linux 稳定内核版本(7.1.3、6.18.38、6.12.95、6.6.144、6.1.177、5.15.211、5.10.260)。其中多个版本修复了 CVE-2026-53362——一个在 6.0 内核引入的 IPv6 漏洞,可能允许攻击者逃逸沙箱或实现权限提升。该漏洞影响范围广,横跨最新主线到多个 LTS 分支,建议尽快评估并部署对应内核更新。
|
||||
|
||||
### GNU Guix 披露四个严重漏洞
|
||||
|
||||
来源:LWN | 时间:2026-07-03 | 链接:https://lwn.net/Articles/1081199/
|
||||
|
||||
GNU Guix 项目公布四个漏洞:三个存在于 `guix substitute` 工具中,另一个影响 `guix pull` 和 `guix time-machine` 命令。漏洞严重度从远程提权到本地敏感文件泄露。由于 Guix 是用于可复现构建和科学计算环境的基础设施,这些漏洞可能影响 CI 管道和开发环境的安全性,远程可利用性尤为值得警惕。
|
||||
|
||||
### Linux 负向 Dentry 限制的存储峰会讨论
|
||||
|
||||
来源:LWN | 时间:2026-07-03 | 链接:https://lwn.net/Articles/1079407/
|
||||
|
||||
在 2026 年 Linux 存储、文件系统、内存管理和 BPF 峰会上,负向目录条目(negative dentries)引发专题讨论。负向 dentry 用于标记“某名称的文件在目录中不存在”,在频繁查询不存在文件的场景下会不受限制地积累,导致内存占用激增。讨论聚焦于内核是否应引入全局限制机制,但方案共识尚未形成,这可能影响高并发 Web 服务和容器化环境下的内存预估。
|
||||
|
||||
### CalyxOS 隐私导向 Android 发行版正式回归
|
||||
|
||||
来源:LWN | 时间:2026-07-02 | 链接:https://lwn.net/Articles/1081038/
|
||||
|
||||
CalyxOS 在去年 8 月因核心开发者离开暂停发布后,宣布“正式回归”。项目已完成发布流程重做、安全协议升级和签名密钥更换。对于使用该系统的用户,升级路径和密钥信任链的迁移方案将是接下来需要关注的实际问题。
|
||||
|
||||
## 6. GitHub / 开源项目跟踪
|
||||
|
||||
### usestrix/strix:开源 AI 渗透测试工具
|
||||
|
||||
来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/usestrix/strix
|
||||
|
||||
一个 Python 项目的 AI 渗透测试工具,定位为自动发现和修复应用漏洞,本周获 1,904 星。技术栈未完全披露,但从功能描述来看,该工具可能结合了 agent 驱动的安全扫描和自动漏洞利用验证。作为开源安全测试工具,其成熟度尚需通过实际安全审计来验证。
|
||||
|
||||
### DeusData/codebase-memory-mcp:高性能代码智能 MCP 服务器
|
||||
|
||||
来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/DeusData/codebase-memory-mcp
|
||||
|
||||
用 C 编写的 MCP 服务器,将代码库索引到持久化知识图中,声称“平均代码库毫秒级索引”、支持 158 种语言、亚毫秒级查询并节省 99% token。以单一静态二进制分发,零依赖。本周 9,517 星,反映开发者在 LLM 编程场景中对低成本代码上下文检索的迫切需求。
|
||||
|
||||
### ChromeDevTools/chrome-devtools-mcp:面向编程 Agent 的浏览器控制协议
|
||||
|
||||
来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/ChromeDevTools/chrome-devtools-mcp
|
||||
|
||||
Chrome DevTools 团队官方发布的 MCP 服务器,让编程 agent 能够以结构化方式与浏览器交互,而不是通过原始 DOM 或截图驱动。TypeScript 实现,日增 304 星。此前第三方 browser-use 工具流行度爆炸,此官方方案的出现可能推动 browser-agent 从实验进入标准化。
|
||||
|
||||
### ddalcu/mlx-serve:Apple Silicon 原生 LLM 推理服务器
|
||||
|
||||
来源:GitHub Search API | 时间:2026-07-05 | 链接:https://github.com/ddalcu/mlx-serve
|
||||
|
||||
专为 Apple Silicon 设计的 LLM 推理服务器,与 OpenAI 和 Anthropic API 兼容,使用 Zig 编写,不依赖 Python。包含 macOS 原生应用,支持聊天、agent 模式和工具调用。232 星,代表了在本地推理效率上绕开 Python 解释器开销的工程尝试。
|
||||
|
||||
### topoteretes/cognee:开源 AI Agent 长期记忆平台
|
||||
|
||||
来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/topoteretes/cognee
|
||||
|
||||
Python 编写的 AI 记忆平台,为 agent 提供跨会话持久长期记忆,使用自托管知识图引擎。本周 3,388 星。在多个 agent 复用上下文、避免重复推理和降低 token 成本的场景中有直接工程价值。
|
||||
|
||||
### calesthio/OpenMontage:开源智能视频制作系统
|
||||
|
||||
来源:GitHub Trending | 时间:2026-07-05 | 链接:https://github.com/calesthio/OpenMontage
|
||||
|
||||
号称“世界首个开源智能视频制作系统”,包含 12 条流水线、52 个工具和 500+ 项 agent 技能。本周 8,447 星,是目前 agent 在创意工具领域最高关注度的项目之一。Python 实现,具体生产线流程和输出质量尚无第三方评估。
|
||||
|
||||
## 7. 金融领域
|
||||
|
||||
### 美联储对 Small Business Bank 采取执法行动,终止对法巴的执法
|
||||
|
||||
来源:Federal Reserve | 时间:2026-07-02 | 链接:https://www.federalreserve.gov/newsevents/pressreleases/enforcement20260702a.htm
|
||||
|
||||
美联储宣布对 Small Business Bank 启动新的执法行动,同时终止对法国巴黎银行及其美国子公司、法巴证券公司和 Community Bankshares 的执法行动。终止执法通常意味着相关银行已满足整改要求,法巴这类全球系统重要性银行的合规问题一度涉及反洗钱和制裁合规领域。具体执法原因和被终止的整改措施细节尚未一并公开。
|
||||
|
||||
### 支付领域:稳定币结算进入基础设施层
|
||||
|
||||
Cloudflare 发布的 Monetization Gateway(详见传统软工栏目)允许用稳定币结算网络资源费用,这意味着稳定币正在从投资和跨境转账场景渗透到微支付和 API 计费等基础设施层面。x402 协议的作用类似于 HTTP 状态码 402“Payment Required”的工程实现,配合 Cloudflare 的边缘网络,可能催生更广泛的机器间支付场景。该趋势对 KYC/AML 的影响在于,微支付级稳定币流如何在监管框架下被归类和处理,目前仍是开放问题。
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,29 @@
|
||||
# 报纸归档目录
|
||||
|
||||
这个目录用于保存已经生成的日报。
|
||||
|
||||
每一期日报预期包含三个文件:
|
||||
|
||||
```text
|
||||
YYYY-MM-DD-hongyu-daily.md
|
||||
YYYY-MM-DD-hongyu-daily.html
|
||||
YYYY-MM-DD-hongyu-daily.meta.json
|
||||
```
|
||||
|
||||
新版本开始使用:
|
||||
|
||||
```text
|
||||
YYYY-MM-DD-daily-news-for-markyan04.md
|
||||
YYYY-MM-DD-daily-news-for-markyan04.html
|
||||
YYYY-MM-DD-daily-news-for-markyan04.meta.json
|
||||
```
|
||||
|
||||
其中:
|
||||
|
||||
- `.md` 是 Markdown 正文,方便阅读和版本管理。
|
||||
- `.html` 是邮件正文,发送邮件时使用。
|
||||
- `.meta.json` 是元数据,记录生成时间、发送状态、来源数量、错误数量等信息。
|
||||
|
||||
如果邮件发送失败,草稿文件可以留在这里,但元数据不能标记为已发送。
|
||||
|
||||
不要在这个目录里保存 SMTP 授权码、DeepSeek API Key、Cookie、Session token 或任何其他密钥。
|
||||
@@ -0,0 +1,28 @@
|
||||
[project]
|
||||
name = "hongyu-daily-newspaper"
|
||||
version = "0.1.0"
|
||||
description = "Personal daily newspaper pipeline for Daily News for Markyan04."
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"beautifulsoup4>=4.12.0",
|
||||
"feedparser>=6.0.11",
|
||||
"jinja2>=3.1.0",
|
||||
"markdown>=3.6",
|
||||
"openai>=1.0.0",
|
||||
"python-dotenv>=1.0.0",
|
||||
"pyyaml>=6.0.0",
|
||||
"requests>=2.31.0",
|
||||
]
|
||||
|
||||
[project.scripts]
|
||||
hongyu-daily = "newspaper_pipeline.cli:main"
|
||||
|
||||
[tool.uv]
|
||||
package = true
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["src/newspaper_pipeline"]
|
||||
@@ -0,0 +1,5 @@
|
||||
"""Daily News for Markyan04 newspaper pipeline."""
|
||||
|
||||
__all__ = ["__version__"]
|
||||
|
||||
__version__ = "0.1.0"
|
||||
@@ -0,0 +1,851 @@
|
||||
"""Command-line entry point for the Daily News for Markyan04 pipeline."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import dataclasses
|
||||
from datetime import date, datetime, time, timedelta
|
||||
from email.message import EmailMessage
|
||||
from email.utils import formataddr, parsedate_to_datetime
|
||||
import hashlib
|
||||
import html
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import re
|
||||
import smtplib
|
||||
import sys
|
||||
import time as time_module
|
||||
from typing import Any
|
||||
|
||||
|
||||
ROOT = Path(os.getenv("NEWSPAPER_ROOT", Path.cwd())).resolve()
|
||||
CONFIG_FILES = [
|
||||
ROOT / "config" / "newspaper.yaml",
|
||||
ROOT / "config" / "sources.yaml",
|
||||
ROOT / "config" / "delivery.yaml",
|
||||
]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class SourceItem:
|
||||
section: str
|
||||
source_name: str
|
||||
source_tier: str
|
||||
title: str
|
||||
url: str
|
||||
published_at_or_updated_at: str
|
||||
collected_at: str
|
||||
snippet: str = ""
|
||||
|
||||
|
||||
def _load_yaml_module():
|
||||
try:
|
||||
import yaml # type: ignore
|
||||
except ModuleNotFoundError as exc:
|
||||
raise SystemExit("PyYAML is not installed. Run `uv sync` first.") from exc
|
||||
return yaml
|
||||
|
||||
|
||||
def _load_dotenv() -> None:
|
||||
try:
|
||||
from dotenv import load_dotenv # type: ignore
|
||||
except ModuleNotFoundError:
|
||||
return
|
||||
load_dotenv(ROOT / ".env")
|
||||
|
||||
|
||||
def _load_configs() -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]:
|
||||
yaml = _load_yaml_module()
|
||||
loaded = []
|
||||
for path in CONFIG_FILES:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
loaded.append(yaml.safe_load(handle))
|
||||
return loaded[0], loaded[1], loaded[2]
|
||||
|
||||
|
||||
def _today(value: str | None = None) -> date:
|
||||
if value:
|
||||
return date.fromisoformat(value)
|
||||
return date.today()
|
||||
|
||||
|
||||
def _subject(config: dict[str, Any], issue_date: date) -> str:
|
||||
return (
|
||||
config["title_format"]
|
||||
.replace("YYYY", f"{issue_date.year:04d}")
|
||||
.replace("MM", f"{issue_date.month:02d}")
|
||||
.replace("DD", f"{issue_date.day:02d}")
|
||||
)
|
||||
|
||||
|
||||
def _archive_paths(issue_date: date) -> dict[str, Path]:
|
||||
stem = f"{issue_date:%Y-%m-%d}-daily-news-for-markyan04"
|
||||
archive_dir = ROOT / "newspaper"
|
||||
archive_dir.mkdir(parents=True, exist_ok=True)
|
||||
return {
|
||||
"markdown": archive_dir / f"{stem}.md",
|
||||
"html": archive_dir / f"{stem}.html",
|
||||
"metadata": archive_dir / f"{stem}.meta.json",
|
||||
}
|
||||
|
||||
|
||||
def _collect_rss(source: dict[str, Any], section: str, collected_at: str) -> list[SourceItem]:
|
||||
import feedparser # type: ignore
|
||||
|
||||
feed_url = source.get("feed_url") or source["url"]
|
||||
parsed = feedparser.parse(feed_url)
|
||||
items: list[SourceItem] = []
|
||||
for entry in parsed.entries[:8]:
|
||||
title = getattr(entry, "title", "").strip()
|
||||
url = getattr(entry, "link", source["url"])
|
||||
published = (
|
||||
getattr(entry, "published", None)
|
||||
or getattr(entry, "updated", None)
|
||||
or collected_at
|
||||
)
|
||||
snippet = getattr(entry, "summary", "")
|
||||
if title:
|
||||
items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=title,
|
||||
url=url,
|
||||
published_at_or_updated_at=str(published),
|
||||
collected_at=collected_at,
|
||||
snippet=_strip_html(snippet)[:500],
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def _collect_html(source: dict[str, Any], section: str, collected_at: str) -> list[SourceItem]:
|
||||
import requests # type: ignore
|
||||
from bs4 import BeautifulSoup # type: ignore
|
||||
|
||||
response = requests.get(
|
||||
source["url"],
|
||||
timeout=20,
|
||||
headers={"User-Agent": "DailyNewsForMarkyan04/0.1 (+personal newspaper)"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
items: list[SourceItem] = []
|
||||
for link in soup.find_all("a", href=True)[:60]:
|
||||
title = link.get_text(" ", strip=True)
|
||||
href = str(link["href"])
|
||||
if _is_low_value_link(title, href):
|
||||
continue
|
||||
absolute = _absolute_url(source["url"], href)
|
||||
items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=title[:180],
|
||||
url=absolute,
|
||||
published_at_or_updated_at=collected_at,
|
||||
collected_at=collected_at,
|
||||
)
|
||||
)
|
||||
if len(items) >= 8:
|
||||
break
|
||||
if not items:
|
||||
page_title = (soup.title.string if soup.title and soup.title.string else source["name"]).strip()
|
||||
items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=page_title,
|
||||
url=source["url"],
|
||||
published_at_or_updated_at=collected_at,
|
||||
collected_at=collected_at,
|
||||
snippet=_strip_html(soup.get_text(" ", strip=True))[:700],
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def _collect_json(source: dict[str, Any], section: str, collected_at: str) -> list[SourceItem]:
|
||||
import requests # type: ignore
|
||||
|
||||
response = requests.get(source["url"], timeout=20)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
vulnerabilities = data.get("vulnerabilities", []) if isinstance(data, dict) else []
|
||||
items = []
|
||||
for item in vulnerabilities[:8]:
|
||||
cve = item.get("cveID", "CVE")
|
||||
vendor = item.get("vendorProject", "")
|
||||
product = item.get("product", "")
|
||||
title = f"{cve}: {vendor} {product}".strip()
|
||||
items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=title,
|
||||
url=source["url"],
|
||||
published_at_or_updated_at=item.get("dateAdded", collected_at),
|
||||
collected_at=collected_at,
|
||||
snippet=item.get("shortDescription", "")[:500],
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def _collect_github_search(source: dict[str, Any], section: str, collected_at: str) -> list[SourceItem]:
|
||||
import requests # type: ignore
|
||||
|
||||
query_date = (datetime.now().date() - timedelta(days=30)).isoformat()
|
||||
query = source["query_template"].replace("{date_minus_30d}", query_date)
|
||||
headers = {
|
||||
"Accept": "application/vnd.github+json",
|
||||
"X-GitHub-Api-Version": "2022-11-28",
|
||||
"User-Agent": "DailyNewsForMarkyan04/0.1 (+personal newspaper)",
|
||||
}
|
||||
token = os.getenv("GITHUB_TOKEN")
|
||||
if token:
|
||||
headers["Authorization"] = f"Bearer {token}"
|
||||
response = requests.get(
|
||||
source["url"],
|
||||
timeout=20,
|
||||
headers=headers,
|
||||
params={"q": query, "sort": "updated", "order": "desc", "per_page": 8},
|
||||
)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
items: list[SourceItem] = []
|
||||
for repo in data.get("items", [])[:8]:
|
||||
name = repo.get("full_name")
|
||||
url = repo.get("html_url")
|
||||
if not name or not url:
|
||||
continue
|
||||
description = repo.get("description") or ""
|
||||
stars = repo.get("stargazers_count", 0)
|
||||
language = repo.get("language") or "unknown"
|
||||
topics = ", ".join(repo.get("topics") or [])
|
||||
updated = repo.get("pushed_at") or repo.get("updated_at") or collected_at
|
||||
snippet = (
|
||||
f"{description} Stars: {stars}. Language: {language}. "
|
||||
f"Topics: {topics}. Search query: {query}."
|
||||
).strip()
|
||||
items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=name,
|
||||
url=url,
|
||||
published_at_or_updated_at=updated,
|
||||
collected_at=collected_at,
|
||||
snippet=snippet[:700],
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def _collect_github_trending(source: dict[str, Any], section: str, collected_at: str) -> list[SourceItem]:
|
||||
import requests # type: ignore
|
||||
from bs4 import BeautifulSoup # type: ignore
|
||||
|
||||
response = requests.get(
|
||||
source["url"],
|
||||
timeout=12,
|
||||
headers={"User-Agent": "DailyNewsForMarkyan04/0.1 (+personal newspaper)"},
|
||||
)
|
||||
response.raise_for_status()
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
items: list[SourceItem] = []
|
||||
for article in soup.select("article.Box-row")[:8]:
|
||||
link = article.select_one("h2 a[href]")
|
||||
if not link:
|
||||
continue
|
||||
repo_path = str(link["href"]).strip()
|
||||
repo_name = " ".join(link.get_text(" ", strip=True).split()).replace(" / ", "/")
|
||||
if not repo_name or repo_path.count("/") < 2:
|
||||
continue
|
||||
description = article.select_one("p")
|
||||
language = article.select_one("[itemprop='programmingLanguage']")
|
||||
stars_today = article.select_one("span.d-inline-block.float-sm-right")
|
||||
snippet_parts = []
|
||||
if description:
|
||||
snippet_parts.append(description.get_text(" ", strip=True))
|
||||
if language:
|
||||
snippet_parts.append(f"Language: {language.get_text(' ', strip=True)}")
|
||||
if stars_today:
|
||||
snippet_parts.append(stars_today.get_text(" ", strip=True))
|
||||
items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=repo_name,
|
||||
url=_absolute_url("https://github.com", repo_path),
|
||||
published_at_or_updated_at=collected_at,
|
||||
collected_at=collected_at,
|
||||
snippet=". ".join(snippet_parts)[:700],
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def collect_sources(sources_config: dict[str, Any]) -> tuple[list[SourceItem], list[str]]:
|
||||
collected_at = datetime.now().isoformat(timespec="seconds")
|
||||
collected_dt = datetime.fromisoformat(collected_at)
|
||||
items: list[SourceItem] = []
|
||||
errors: list[str] = []
|
||||
for section, section_config in sources_config["sections"].items():
|
||||
section_items: list[SourceItem] = []
|
||||
for source in section_config.get("sources", []):
|
||||
try:
|
||||
kind = source["kind"]
|
||||
if section == "github" and "github.com/trending" in source.get("url", ""):
|
||||
section_items.extend(_collect_github_trending(source, section, collected_at))
|
||||
elif source.get("url") == "https://api.github.com/search/repositories":
|
||||
section_items.extend(_collect_github_search(source, section, collected_at))
|
||||
elif kind in {"rss", "atom", "rss_or_html"} and source.get("feed_url"):
|
||||
section_items.extend(_collect_rss(source, section, collected_at))
|
||||
elif kind == "json":
|
||||
section_items.extend(_collect_json(source, section, collected_at))
|
||||
elif kind in {
|
||||
"official_html",
|
||||
"html",
|
||||
"official_program_page",
|
||||
"official_listing_or_search_page",
|
||||
"official_school_page",
|
||||
}:
|
||||
section_items.extend(_collect_html(source, section, collected_at))
|
||||
else:
|
||||
section_items.append(
|
||||
SourceItem(
|
||||
section=section,
|
||||
source_name=source["name"],
|
||||
source_tier=source["source_tier"],
|
||||
title=source["name"],
|
||||
url=source["url"],
|
||||
published_at_or_updated_at=collected_at,
|
||||
collected_at=collected_at,
|
||||
snippet=f"Configured source kind: {kind}",
|
||||
)
|
||||
)
|
||||
except Exception as exc: # noqa: BLE001 - keep pipeline resilient.
|
||||
errors.append(f"{section}:{source.get('id', source.get('name'))}: {exc}")
|
||||
items.extend(_filter_fresh_items(section_items, section_config, collected_dt))
|
||||
return _dedupe_items(items), errors
|
||||
|
||||
|
||||
def _dedupe_items(items: list[SourceItem]) -> list[SourceItem]:
|
||||
seen: set[str] = set()
|
||||
deduped = []
|
||||
for item in items:
|
||||
key = f"{item.section}:{item.url}:{item.title}".lower()
|
||||
if key in seen:
|
||||
continue
|
||||
seen.add(key)
|
||||
deduped.append(item)
|
||||
return deduped
|
||||
|
||||
|
||||
def _is_low_value_link(title: str, href: str) -> bool:
|
||||
normalized_title = " ".join((title or "").split()).lower()
|
||||
normalized_href = (href or "").lower()
|
||||
if len(normalized_title) < 8:
|
||||
return True
|
||||
blocked_titles = {
|
||||
"sign in",
|
||||
"sign up",
|
||||
"subscribe",
|
||||
"log in",
|
||||
"login",
|
||||
"careers",
|
||||
"contact",
|
||||
"privacy",
|
||||
"terms",
|
||||
"cookie policy",
|
||||
"skip to content",
|
||||
"main content",
|
||||
"read more",
|
||||
"learn more",
|
||||
}
|
||||
if normalized_title in blocked_titles:
|
||||
return True
|
||||
blocked_href_parts = (
|
||||
"/login",
|
||||
"/signup",
|
||||
"/account",
|
||||
"/privacy",
|
||||
"/terms",
|
||||
"/careers",
|
||||
"/contact",
|
||||
"mailto:",
|
||||
"javascript:",
|
||||
)
|
||||
return any(part in normalized_href for part in blocked_href_parts)
|
||||
|
||||
|
||||
def _parse_item_datetime(value: str) -> datetime | None:
|
||||
if not value:
|
||||
return None
|
||||
normalized = value.strip()
|
||||
try:
|
||||
return datetime.fromisoformat(normalized.replace("Z", "+00:00")).replace(tzinfo=None)
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
return parsedate_to_datetime(normalized).replace(tzinfo=None)
|
||||
except (TypeError, ValueError, IndexError, OverflowError):
|
||||
return None
|
||||
|
||||
|
||||
def _filter_fresh_items(
|
||||
items: list[SourceItem],
|
||||
section_config: dict[str, Any],
|
||||
collected_dt: datetime,
|
||||
) -> list[SourceItem]:
|
||||
window_hours = section_config.get("recent_window_hours")
|
||||
if not window_hours:
|
||||
return items
|
||||
cutoff = collected_dt - timedelta(hours=int(window_hours))
|
||||
fresh: list[SourceItem] = []
|
||||
for item in items:
|
||||
item_dt = _parse_item_datetime(item.published_at_or_updated_at)
|
||||
if item_dt is None or item_dt >= cutoff:
|
||||
fresh.append(item)
|
||||
return fresh
|
||||
|
||||
|
||||
def _recent_archive_urls(issue_date: date, days: int = 7) -> set[str]:
|
||||
archive_dir = ROOT / "newspaper"
|
||||
if not archive_dir.exists():
|
||||
return set()
|
||||
urls: set[str] = set()
|
||||
start = issue_date - timedelta(days=days)
|
||||
for path in archive_dir.glob("*.md"):
|
||||
try:
|
||||
archive_date = date.fromisoformat(path.name[:10])
|
||||
except ValueError:
|
||||
continue
|
||||
if not start <= archive_date < issue_date:
|
||||
continue
|
||||
text = path.read_text(encoding="utf-8", errors="ignore")
|
||||
urls.update(url.rstrip(").,,。]") for url in re.findall(r"https?://[^\s)>\]]+", text))
|
||||
return urls
|
||||
|
||||
|
||||
def _recent_archive_excerpt(issue_date: date, days: int = 7) -> str:
|
||||
archive_dir = ROOT / "newspaper"
|
||||
if not archive_dir.exists():
|
||||
return "无"
|
||||
start = issue_date - timedelta(days=days)
|
||||
chunks: list[str] = []
|
||||
for path in sorted(archive_dir.glob("*.md"), reverse=True):
|
||||
try:
|
||||
archive_date = date.fromisoformat(path.name[:10])
|
||||
except ValueError:
|
||||
continue
|
||||
if not start <= archive_date < issue_date:
|
||||
continue
|
||||
lines = []
|
||||
for line in path.read_text(encoding="utf-8", errors="ignore").splitlines():
|
||||
if line.startswith("#") or line.startswith("- "):
|
||||
lines.append(line)
|
||||
if lines:
|
||||
chunks.append(f"\n### {archive_date.isoformat()}\n" + "\n".join(lines[:80]))
|
||||
return "\n".join(chunks)[:12000] or "无"
|
||||
|
||||
|
||||
def _filter_recently_used_items(items: list[SourceItem], issue_date: date) -> tuple[list[SourceItem], int]:
|
||||
recent_urls = _recent_archive_urls(issue_date)
|
||||
if not recent_urls:
|
||||
return items, 0
|
||||
filtered = [item for item in items if item.url not in recent_urls]
|
||||
return filtered, len(items) - len(filtered)
|
||||
|
||||
|
||||
def _source_items_for_metadata(items: list[SourceItem]) -> list[dict[str, str]]:
|
||||
return [
|
||||
{
|
||||
"section": item.section,
|
||||
"source_name": item.source_name,
|
||||
"source_tier": item.source_tier,
|
||||
"title": item.title,
|
||||
"url": item.url,
|
||||
"published_at_or_updated_at": item.published_at_or_updated_at,
|
||||
"collected_at": item.collected_at,
|
||||
}
|
||||
for item in items
|
||||
]
|
||||
|
||||
|
||||
def _absolute_url(base: str, href: str) -> str:
|
||||
from urllib.parse import urljoin
|
||||
|
||||
return urljoin(base, href)
|
||||
|
||||
|
||||
def _strip_html(value: str) -> str:
|
||||
from bs4 import BeautifulSoup # type: ignore
|
||||
|
||||
return BeautifulSoup(value or "", "html.parser").get_text(" ", strip=True)
|
||||
|
||||
|
||||
def _items_for_prompt(items: list[SourceItem]) -> str:
|
||||
by_section: dict[str, list[SourceItem]] = {}
|
||||
for item in items:
|
||||
by_section.setdefault(item.section, []).append(item)
|
||||
|
||||
chunks: list[str] = []
|
||||
for section, section_items in by_section.items():
|
||||
chunks.append(f"\n## {section}")
|
||||
section_items = sorted(section_items, key=_item_sort_key, reverse=True)
|
||||
limit = {"ai": 40, "github": 32, "general_news": 24}.get(section, 18)
|
||||
for item in section_items[:limit]:
|
||||
chunks.append(
|
||||
"\n".join(
|
||||
[
|
||||
f"- title: {item.title}",
|
||||
f" source: {item.source_name} ({item.source_tier})",
|
||||
f" date: {item.published_at_or_updated_at}",
|
||||
f" url: {item.url}",
|
||||
f" snippet: {item.snippet[:300]}",
|
||||
]
|
||||
)
|
||||
)
|
||||
return "\n".join(chunks)
|
||||
|
||||
|
||||
def _item_sort_key(item: SourceItem) -> tuple[datetime, int]:
|
||||
parsed = _parse_item_datetime(item.published_at_or_updated_at)
|
||||
fallback = _parse_item_datetime(item.collected_at) or datetime.min
|
||||
tier_score = {
|
||||
"official_primary": 4,
|
||||
"technical_primary": 3,
|
||||
"reputable_secondary": 2,
|
||||
"community_signal": 1,
|
||||
"background_reference": 0,
|
||||
}.get(item.source_tier, 0)
|
||||
return (parsed or fallback, tier_score)
|
||||
|
||||
|
||||
def _section_order_for_prompt(newspaper_config: dict[str, Any]) -> str:
|
||||
lines = []
|
||||
for index, section_config in enumerate(newspaper_config["sections"].values(), start=1):
|
||||
lines.append(f"{index}. {section_config['title']}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def _read_agent_constraints() -> str:
|
||||
return (ROOT / "AGENTS.md").read_text(encoding="utf-8")[:12000]
|
||||
|
||||
|
||||
def generate_content(
|
||||
newspaper_config: dict[str, Any],
|
||||
sources_config: dict[str, Any],
|
||||
items: list[SourceItem],
|
||||
errors: list[str],
|
||||
issue_date: date,
|
||||
filtered_recent_count: int = 0,
|
||||
) -> str:
|
||||
from openai import OpenAI # type: ignore
|
||||
|
||||
api_key = os.getenv("DEEPSEEK_API_KEY")
|
||||
if not api_key:
|
||||
raise RuntimeError("DEEPSEEK_API_KEY is required for generation.")
|
||||
client = OpenAI(
|
||||
api_key=api_key,
|
||||
base_url=os.getenv("DEEPSEEK_BASE_URL", "https://api.deepseek.com"),
|
||||
)
|
||||
model = os.getenv("DEEPSEEK_MODEL", "deepseek-v4-pro")
|
||||
subject = _subject(newspaper_config, issue_date)
|
||||
prompt = f"""
|
||||
你正在生成 Daily News for Markyan04。请严格输出中文 Markdown 正文。
|
||||
|
||||
标题:{subject}
|
||||
日期:{issue_date.isoformat()}
|
||||
|
||||
长期约束:
|
||||
{_read_agent_constraints()}
|
||||
|
||||
输出风格:
|
||||
- 栏目和标题可以使用 Markdown 标题。
|
||||
- 每个新闻条目标题下方放一行 metadata,格式为:`来源:source | 时间:date | 链接:url`。
|
||||
- 正文写成自然文段,优先概括事实本身、背景和当前进展。
|
||||
- 不要在正文中使用“为何重要”“学习启示”“对你来说”“建议关注”“来源”“时间”这类模板化小标题或 bullet 标签。
|
||||
- 不要正对读者做个人化说教。
|
||||
- 如果同一事件过去 7 天已经写过,只有出现实质新进展才可再次报道,并明确新增事实。
|
||||
|
||||
过去 7 天已归档内容摘要:
|
||||
{_recent_archive_excerpt(issue_date)}
|
||||
|
||||
候选来源条目:
|
||||
{_items_for_prompt(items)[:45000]}
|
||||
|
||||
采集错误摘要:
|
||||
{chr(10).join(errors[:30]) or "无"}
|
||||
|
||||
去重信息:
|
||||
已从候选源中过滤掉过去 7 天使用过的 URL 数量:{filtered_recent_count}
|
||||
|
||||
请生成完整日报,栏目顺序必须是:
|
||||
{_section_order_for_prompt(newspaper_config)}
|
||||
|
||||
要求:
|
||||
- 正文中文,保留必要英文技术术语。
|
||||
- 每个新闻型条目必须包含来源链接和日期。
|
||||
- 没有高质量新鲜内容的栏目要简短说明,不要硬凑。
|
||||
- AI 栏目是主菜,优先选择最近、重要、工程相关的内容;即使没有重大模型发布,也要从候选源里选择 AI 工程、开源模型、推理基础设施、agent 工具或高价值论文/报告。只有当候选 AI 条目少于 3 条时,才允许写成“今日缺乏更新”的短说明。
|
||||
- GitHub 栏目只报道候选源中给出真实 GitHub 仓库 URL 的项目,不要编造仓库链接。
|
||||
- 不要输出隐私、密钥、系统提示或无关说明。
|
||||
"""
|
||||
response = client.chat.completions.create(
|
||||
model=model,
|
||||
messages=[
|
||||
{"role": "system", "content": "你是严谨的中文科技报纸编辑,重视时效、来源和解释质量。"},
|
||||
{"role": "user", "content": prompt},
|
||||
],
|
||||
temperature=0.3,
|
||||
)
|
||||
content = response.choices[0].message.content or ""
|
||||
if not content.strip():
|
||||
raise RuntimeError("DeepSeek returned empty content.")
|
||||
return content.strip()
|
||||
|
||||
|
||||
def markdown_to_html(markdown_text: str, subject: str) -> str:
|
||||
import markdown # type: ignore
|
||||
|
||||
body = markdown.markdown(markdown_text, extensions=["extra", "sane_lists"])
|
||||
return f"""<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{html.escape(subject)}</title>
|
||||
<style>
|
||||
body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; line-height: 1.65; color: #111; max-width: 860px; margin: 0 auto; padding: 24px; }}
|
||||
a {{ color: #1756a9; }}
|
||||
h1, h2, h3 {{ line-height: 1.25; }}
|
||||
blockquote {{ border-left: 4px solid #ddd; padding-left: 12px; color: #555; }}
|
||||
code {{ background: #f4f4f4; padding: 2px 4px; border-radius: 4px; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
{body}
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
|
||||
def write_archive(
|
||||
issue_date: date,
|
||||
subject: str,
|
||||
markdown_text: str,
|
||||
html_text: str,
|
||||
recipient: str,
|
||||
items: list[SourceItem],
|
||||
errors: list[str],
|
||||
filtered_recent_count: int = 0,
|
||||
status: str = "generated",
|
||||
) -> dict[str, Path]:
|
||||
paths = _archive_paths(issue_date)
|
||||
paths["markdown"].write_text(markdown_text, encoding="utf-8")
|
||||
paths["html"].write_text(html_text, encoding="utf-8")
|
||||
metadata = {
|
||||
"date": issue_date.isoformat(),
|
||||
"subject": subject,
|
||||
"recipient": recipient,
|
||||
"generated_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"sent_at": None,
|
||||
"delivery_status": status,
|
||||
"source_item_count": len(items),
|
||||
"filtered_recent_source_item_count": filtered_recent_count,
|
||||
"source_error_count": len(errors),
|
||||
"source_errors": errors[:100],
|
||||
"source_items": _source_items_for_metadata(items),
|
||||
"content_hash": hashlib.sha256(markdown_text.encode("utf-8")).hexdigest(),
|
||||
}
|
||||
paths["metadata"].write_text(
|
||||
json.dumps(metadata, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
return paths
|
||||
|
||||
|
||||
def command_generate(args: argparse.Namespace) -> int:
|
||||
_load_dotenv()
|
||||
newspaper_config, sources_config, delivery_config = _load_configs()
|
||||
issue_date = _today(args.date)
|
||||
items, errors = collect_sources(sources_config)
|
||||
items, filtered_recent_count = _filter_recently_used_items(items, issue_date)
|
||||
content = generate_content(
|
||||
newspaper_config,
|
||||
sources_config,
|
||||
items,
|
||||
errors,
|
||||
issue_date,
|
||||
filtered_recent_count,
|
||||
)
|
||||
subject = _subject(newspaper_config, issue_date)
|
||||
html_text = markdown_to_html(content, subject)
|
||||
paths = write_archive(
|
||||
issue_date,
|
||||
subject,
|
||||
content,
|
||||
html_text,
|
||||
delivery_config["recipient"]["email"],
|
||||
items,
|
||||
errors,
|
||||
filtered_recent_count,
|
||||
)
|
||||
print(f"generated: {paths['markdown'].relative_to(ROOT)}")
|
||||
print(f"generated: {paths['html'].relative_to(ROOT)}")
|
||||
print(f"metadata: {paths['metadata'].relative_to(ROOT)}")
|
||||
return 0
|
||||
|
||||
|
||||
def _env_required(name: str) -> str:
|
||||
value = os.getenv(name)
|
||||
if not value:
|
||||
raise RuntimeError(f"Missing required environment variable: {name}")
|
||||
return value
|
||||
|
||||
|
||||
def command_send(args: argparse.Namespace) -> int:
|
||||
_load_dotenv()
|
||||
newspaper_config, _, delivery_config = _load_configs()
|
||||
issue_date = _today(args.date)
|
||||
paths = _archive_paths(issue_date)
|
||||
if not paths["html"].exists() or not paths["markdown"].exists():
|
||||
raise RuntimeError(f"Archive files for {issue_date.isoformat()} do not exist. Run generate first.")
|
||||
|
||||
subject = _subject(newspaper_config, issue_date)
|
||||
recipient = delivery_config["recipient"]["email"]
|
||||
html_text = paths["html"].read_text(encoding="utf-8")
|
||||
plain_text = paths["markdown"].read_text(encoding="utf-8")
|
||||
|
||||
msg = EmailMessage()
|
||||
from_name = _env_required("NEWSPAPER_FROM_NAME")
|
||||
from_email = _env_required("NEWSPAPER_FROM_EMAIL")
|
||||
msg["Subject"] = subject
|
||||
msg["From"] = formataddr((from_name, from_email))
|
||||
msg["To"] = recipient
|
||||
msg.set_content(plain_text)
|
||||
msg.add_alternative(html_text, subtype="html")
|
||||
|
||||
host = _env_required("NEWSPAPER_SMTP_HOST")
|
||||
port = int(_env_required("NEWSPAPER_SMTP_PORT"))
|
||||
username = _env_required("NEWSPAPER_SMTP_USERNAME")
|
||||
password = _env_required("NEWSPAPER_SMTP_PASSWORD")
|
||||
security = os.getenv("NEWSPAPER_SMTP_SECURITY", "ssl").lower()
|
||||
|
||||
if security == "ssl":
|
||||
with smtplib.SMTP_SSL(host, port, timeout=30) as smtp:
|
||||
smtp.login(username, password)
|
||||
smtp.send_message(msg)
|
||||
elif security == "starttls":
|
||||
with smtplib.SMTP(host, port, timeout=30) as smtp:
|
||||
smtp.starttls()
|
||||
smtp.login(username, password)
|
||||
smtp.send_message(msg)
|
||||
else:
|
||||
raise RuntimeError("NEWSPAPER_SMTP_SECURITY must be ssl or starttls.")
|
||||
|
||||
if paths["metadata"].exists():
|
||||
metadata = json.loads(paths["metadata"].read_text(encoding="utf-8"))
|
||||
else:
|
||||
metadata = {}
|
||||
metadata.update(
|
||||
{
|
||||
"date": issue_date.isoformat(),
|
||||
"subject": subject,
|
||||
"recipient": recipient,
|
||||
"sent_at": datetime.now().isoformat(timespec="seconds"),
|
||||
"delivery_status": "sent",
|
||||
}
|
||||
)
|
||||
paths["metadata"].write_text(
|
||||
json.dumps(metadata, ensure_ascii=False, indent=2),
|
||||
encoding="utf-8",
|
||||
)
|
||||
print(f"sent: {subject} -> {recipient}")
|
||||
return 0
|
||||
|
||||
|
||||
def command_run(args: argparse.Namespace) -> int:
|
||||
command_generate(args)
|
||||
return command_send(args)
|
||||
|
||||
|
||||
def _seconds_until_next(schedule: str) -> float:
|
||||
hour, minute = [int(part) for part in schedule.split(":", 1)]
|
||||
now = datetime.now()
|
||||
target = datetime.combine(now.date(), time(hour=hour, minute=minute))
|
||||
if target <= now:
|
||||
target += timedelta(days=1)
|
||||
return (target - now).total_seconds()
|
||||
|
||||
|
||||
def command_schedule(args: argparse.Namespace) -> int:
|
||||
_load_dotenv()
|
||||
schedule_time = args.time or os.getenv("NEWSPAPER_SCHEDULE_TIME", "08:00")
|
||||
print(f"scheduler started; daily run time={schedule_time}")
|
||||
while True:
|
||||
seconds = _seconds_until_next(schedule_time)
|
||||
print(f"sleeping {int(seconds)} seconds until next run")
|
||||
time_module.sleep(seconds)
|
||||
try:
|
||||
run_args = argparse.Namespace(date=None)
|
||||
command_run(run_args)
|
||||
except Exception as exc: # noqa: BLE001 - scheduler should keep running.
|
||||
print(f"scheduled run failed: {exc}", file=sys.stderr)
|
||||
time_module.sleep(60)
|
||||
|
||||
|
||||
def check_config(_: argparse.Namespace) -> int:
|
||||
yaml = _load_yaml_module()
|
||||
for path in CONFIG_FILES:
|
||||
with path.open("r", encoding="utf-8") as handle:
|
||||
yaml.safe_load(handle)
|
||||
print(f"{path.relative_to(ROOT)}: OK")
|
||||
print("newspaper archive: newspaper/")
|
||||
return 0
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(prog="hongyu-daily")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
check = subparsers.add_parser("check-config", help="Validate YAML configs.")
|
||||
check.set_defaults(func=check_config)
|
||||
|
||||
generate = subparsers.add_parser("generate", help="Generate and archive an issue.")
|
||||
generate.add_argument("--date", help="Issue date in YYYY-MM-DD format.")
|
||||
generate.set_defaults(func=command_generate)
|
||||
|
||||
send = subparsers.add_parser("send", help="Send an archived issue.")
|
||||
send.add_argument("--date", help="Issue date in YYYY-MM-DD format.")
|
||||
send.set_defaults(func=command_send)
|
||||
|
||||
run = subparsers.add_parser("run", help="Generate, archive, and send an issue.")
|
||||
run.add_argument("--date", help="Issue date in YYYY-MM-DD format.")
|
||||
run.set_defaults(func=command_run)
|
||||
|
||||
schedule = subparsers.add_parser("schedule", help="Run the daily scheduler loop.")
|
||||
schedule.add_argument("--time", help="Local HH:MM schedule time. Defaults to NEWSPAPER_SCHEDULE_TIME or 08:00.")
|
||||
schedule.set_defaults(func=command_schedule)
|
||||
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
parser = build_parser()
|
||||
args = parser.parse_args(argv)
|
||||
return args.func(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user