Engineering 14 min read

How TrendRadar Builds a Personal Trend Radar

MMNMNOTE
githubtrendradarpythonrssmcpself-hostednews-aggregationgithub-actions
Updated June 8, 2026

Reference: sansan0/TrendRadar — GPL-3.0 · Python

TrendRadar is a self-hosted "personal trend radar." It pulls hot-lists from eleven public platforms plus your RSS feeds, filters them down to the topics you actually track — by keyword rules or by an AI describing your interests — and pushes a short digest to your phone. It runs on a schedule, often for free on GitHub Actions.

The project has drawn 58,948 stars and 24,540 forks as of 2026-06-05 1, unusually high for a tool maintained by three contributors 1. Its pitch is restrained: "This project is designed to be lightweight and easy to deploy" 2. What makes it worth reading is not the popularity but the architecture — a clean six-stage pipeline that turns the firehose of trending news into a filtered, scheduled push. This post walks that pipeline from source code.

What is TrendRadar?

TrendRadar is a Python tool that aggregates public trending-topic lists and RSS feeds, filters them against your keywords or AI-defined interests, optionally writes an AI briefing, and delivers the result through eight notification channels. Its own tagline is "Say goodbye to endless scrolling, only see the news you truly care about" 3. You configure it once and let it run unattended.

The codebase is 33,724 lines of Python across two packages — trendradar (the pipeline) and mcp_server (an AI-query layer) 1. The dependency list is deliberately short: requests, feedparser, boto3, litellm, PyYAML, and a handful more 4. There is no web app to host, no database to provision, no account. The entry point is a single command, python -m trendradar, run on a timer.

Where does TrendRadar get its news?

TrendRadar does not scrape platforms directly. Its crawler calls the NewsNow API — a community project that already normalizes hot-lists from many sites — and reads RSS/Atom feeds you add. The README is explicit and gracious about the dependency: "This project uses the API from newsnow to fetch multi-platform data" 5. By default it monitors eleven platforms 6, and you can add custom ones.

Two details show care. The fetcher (crawler/fetcher.py) defaults to one shared endpoint and validates every returned link with _check_domain_safety — it requires HTTPS and matches the hostname against an expected domain, parsing with the standard library so a userinfo trick like https://[email protected] cannot slip a foreign host past the check. The RSS side (crawler/rss/fetcher.py) applies a freshness filter, dropping articles older than a configurable window — default_max_age_days = 3 — so the same stories do not get re-pushed for a week 7.

The architecture, end to end

The pipeline is six stages, orchestrated by a 2,327-line __main__.py 1. Sources flow into storage, get filtered and ranked, optionally summarized by an AI, then dispatched to your channels — and the whole thing is wrapped in a scheduler so each stage runs only when the timeline says it should.

flowchart TD
    A[NewsNow API<br/>11 platforms] --> C[Storage<br/>local / S3 / auto]
    B[RSS / Atom feeds<br/>freshness filter] --> C
    C --> D{Filter mode}
    D -->|keyword| E[frequency_words.txt<br/>groups + rules]
    D -->|AI| F[ai_interests.txt<br/>two-stage tag + score]
    E --> G[Ranked matches]
    F --> G
    G --> H[Optional AI briefing<br/>LiteLLM]
    H --> I[Dispatcher]
    I --> J[Feishu / DingTalk / WeCom /<br/>Telegram / Email / ntfy / Bark / Slack]
    K[Scheduler<br/>cron + timeline] -.gates each stage.-> C
    K -.-> D
    K -.-> I

The storage layer (storage/manager.py) resolves a backend from configuration — local, remote, or auto — detecting whether it is running inside GitHub Actions or Docker. Remote storage is S3-compatible through boto3, so collected history can live in your own R2 or S3 bucket. Data stays self-hosted either way.

How keyword filtering actually works

The default filter is a small rule language in frequency_words.txt, parsed in core/frequency.py. You write word-groups; the matcher decides whether a headline survives. The evaluation order in matches_word_groups() is precise: a global filter list removes unwanted headlines first, then each group can exclude with its own filter words, require words that must all appear, and accept on any normal word.

Concretely, a single group separates three kinds of token. Required words (prefixed +) are an AND — every one must be in the title. Normal words are an OR — any match accepts. Filter words (prefixed !) are a veto. The parser also supports regex and display aliases:

# title must contain "policy" AND match the EV regex,
# but is dropped if it mentions "rumor"
+policy
/byd|tesla|electric vehicle/ => EV
!rumor

Under the hood, _parse_word recognizes a /.../ pattern as a compiled, case-insensitive regex and a => as a display name 8. The matching itself is plain substring or pattern.search — fast, dependency-free, and easy to reason about. If you leave the file empty, everything passes and you get the full hot-list.

The AI filter: describe interests in plain language

The second filter mode replaces rule syntax with natural language. Instead of writing keyword groups, you describe what you care about in ai_interests.txt. As the README puts it: "Describe your interests in natural language and let AI automatically classify news" 9. The implementation in ai/filter.py is a two-stage design — "AI first extracts structured tags from interest descriptions, then batch-classifies and scores news against those tags" 10.

Stage A turns your prose into structured tags. Stage B scores each headline against those tags in batches and keeps only items above min_score. The AI client (ai/client.py) routes through LiteLLM, so any provider works; the default model is deepseek/deepseek-chat, the key is bring-your-own, and json-repair salvages malformed model output 11. The most important line is the safety net: if AI filtering fails, the pipeline falls back to keyword matching automatically, so a flaky model never silently stops your pushes.

Run-on-a-schedule, often for free

TrendRadar's defining design choice is that it is built to run unattended on a cron, not as a long-lived server. The shipped GitHub Actions workflow (crawler.yml) sets cron: "33 * * * *" — hourly — plus a manual trigger, and its run step is exactly uv run python -m trendradar 5. Fork the repo, add a webhook secret, and your radar runs on GitHub's free runners.

A richer scheduler sits above the cron. core/scheduler.py resolves a timeline of periods, day-plans, and a week-map: different time windows can use different push modes, different filter methods, even different interest files. It handles cross-midnight periods, once-only dedup, and overlap conflict detection, and ships five presets — always_on, morning_evening, office_hours, night_owl, and custom. Three push modes shape the volume: daily (all matches), current (current rankings), and incremental"Push only new content, zero duplication" 12.

Talking to your trend data over MCP

Once TrendRadar has collected history, an mcp_server package exposes it to AI clients. It is a FastMCP 2.0 server named trendradar-news with 27 tools 1 spanning data queries, analytics, search, article reading, configuration, and notification. That makes the collected trend data conversational — you can ask an assistant to summarize the week or chart a keyword's rise.

The relevant standard, by the protocol's own definition, is this: "MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems" 13. TrendRadar treats your accumulated hot-news archive as exactly such an external system. The same aggregate-then-query shape appears in heavier tools — see how Taranis AI builds an OSINT intel pipeline for the team-scale version, and how Firecrawl's web-agent returns structured research for the on-demand version. TrendRadar is the lightweight, personal end of that family.

What it teaches

The reusable idea is the shape, not the platform list. A trend radar is four moves: aggregate from sources you do not own, filter against an intent you define, summarize only the survivors, and deliver on a schedule. TrendRadar keeps each move in its own module with a clear contract, which is why the same pipeline serves a day-trader on incremental and a manager on daily without forking the code.

Two specific choices are worth stealing. First, the filter is layered and cheap by default — substring and regex rules run with no API call, and AI is an opt-in upgrade that degrades back to keywords on failure. Second, the tool assumes it will be run, not hosted — the GitHub Actions and Docker paths are first-class, and storage adapts to wherever it finds itself. Designing for the cron, not the server, is what makes "deploy in thirty seconds" plausible.

Verdict

TrendRadar is worth studying as a model self-hosted aggregation pipeline, and worth using if you want a personal news monitor you control end to end. Be a good citizen about the upstream NewsNow API — the README asks you to keep request frequency reasonable — and remember the data comes from that third-party service, not from TrendRadar scraping sites itself. As architecture, it is a clean example of turning a firehose into a filtered feed.

Frequently Asked Questions

What is TrendRadar? TrendRadar is a self-hosted Python tool that aggregates public trending-topic lists and RSS feeds, filters them against your keywords or AI-defined interests, optionally writes an AI briefing, and pushes the result to channels like Telegram or email. It runs on a schedule, often for free on GitHub Actions 2.

Where does TrendRadar get its news from? It calls the third-party NewsNow API for multi-platform hot-lists — "This project uses the API from newsnow to fetch multi-platform data" 5 — and reads any RSS/Atom feeds you add. It does not scrape platforms directly, and it asks users to keep request frequency reasonable out of courtesy to the upstream service.

How do I filter news by keyword in TrendRadar? You edit frequency_words.txt with word-groups. Words can be required (AND), normal (OR), or filter words that veto a match, plus global filters, regex /.../, and display aliases =>. The matcher evaluates filters first, then required, then normal words 8. An empty file passes everything.

Does TrendRadar need an AI or LLM API key? No. Keyword filtering is the default and runs with no model call. AI filtering and AI briefings are optional; they use LiteLLM with a bring-your-own key (default deepseek/deepseek-chat) and fall back to keyword matching automatically if the model fails 11.

Can TrendRadar run for free on GitHub Actions? Yes. The shipped workflow runs hourly via cron: "33 * * * *" and executes python -m trendradar on GitHub's runners 5. Fork the repo, add your notification secret, and the radar runs unattended. Docker and a local cron are also supported.

What notification channels does TrendRadar support? Eight, dispatched through notification/dispatcher.py: Feishu, DingTalk, WeCom/WeWork, Telegram, email, ntfy, Bark, and Slack, plus a generic webhook. Each channel supports multiple accounts and automatic content batching to respect per-channel size limits.

What are the daily, current, and incremental push modes? daily pushes all matched news of the day, current pushes whatever currently ranks, and incremental pushes only new content for "zero duplication" 12. Choose incremental to avoid repeats, current for live ranking trends, and daily for a summary report.

Where does TrendRadar store my collected data? Its StorageManager resolves a backend of local, remote, or auto, detecting GitHub Actions or Docker. Remote storage is S3-compatible through boto3, so history can live in your own R2 or S3 bucket. The data stays self-hosted under your control.


A personal trend radar is just four honest moves — aggregate, filter, summarize, deliver — kept in separate, swappable boxes.


If owning the pipeline appeals to you, owning the notes it produces does too: mnmnote.com keeps your writing local-first on your own device.

Footnotes

  1. "sansan0/TrendRadar," GitHub, https://github.com/sansan0/TrendRadar — stars 58,948, forks 24,540, 3 contributors, GPL-3.0, Python, created 2025-04-28 (GitHub REST API); LOC 33,724 and 27 MCP tools re-derived from a shallow clone (find … | xargs wc -l; grep -rn "@mcp.tool"). Accessed 2026-06-05. 2 3 4 5

  2. "README-EN — This project is designed to be lightweight and easy to deploy," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/README-EN.md. Accessed 2026-06-05. 2

  3. "README-EN — Say goodbye to endless scrolling, only see the news you truly care about," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/README-EN.md. Accessed 2026-06-05.

  4. "pyproject.toml," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/pyproject.toml — dependencies: requests, pytz, PyYAML, fastmcp, websockets, feedparser, boto3, litellm, json-repair, tenacity. Accessed 2026-06-05.

  5. "README-EN (data-source attribution) + crawler.yml," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar — "This project uses the API from newsnow to fetch multi-platform data"; workflow cron: "33 * * * *", run step uv run python -m trendradar. Accessed 2026-06-05. 2 3 4

  6. "README-EN — Default monitoring of 11 mainstream platforms," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/README-EN.md. Accessed 2026-06-05.

  7. "crawler/rss/fetcher.py — default_max_age_days," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/trendradar/crawler/rss/fetcher.py. Accessed 2026-06-05.

  8. "core/frequency.py — _parse_word / matches_word_groups," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/trendradar/core/frequency.py. Accessed 2026-06-05. 2

  9. "README-EN — Describe your interests in natural language and let AI automatically classify news," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/README-EN.md. Accessed 2026-06-05.

  10. "README-EN — Two-Stage Smart Processing," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/README-EN.md. Accessed 2026-06-05.

  11. "ai/client.py — LiteLLM client, default deepseek/deepseek-chat," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/trendradar/ai/client.py. Accessed 2026-06-05. 2

  12. "README-EN — Incremental Monitor: Push only new content, zero duplication," sansan0/TrendRadar, https://github.com/sansan0/TrendRadar/blob/master/README-EN.md. Accessed 2026-06-05. 2

  13. "What is the Model Context Protocol (MCP)?," Anthropic / modelcontextprotocol.io, https://modelcontextprotocol.io/docs/getting-started/intro — "MCP (Model Context Protocol) is an open-source standard for connecting AI applications to external systems." Accessed 2026-06-05.