Thanks for your interest in contributing! TopicEye is built for content creators, and the fastest way to help is to add a source connector — each one lets the radar cover more ground.
git clone https://github.com/fxbin/TopicEye.git
cd TopicEye/backend
python -m venv venv && source venv/bin/activate
pip install -r requirements.txt
cp .env.example .env
# SQLite is the default database — no extra setup needed.
python -m uvicorn app.main:app --host 127.0.0.1 --port 8102 --reloadIn another terminal:
cd TopicEye/frontend
npm install
npm run devOpen http://localhost:3000. The first run will seed default categories and (if you enable it) an admin account.
backend/app/
├── api/v1/ # FastAPI routers (one file per domain)
├── models/ # SQLAlchemy ORM models
├── schemas/ # Pydantic request/response schemas
├── services/ # Business logic
│ ├── scrapers/ # RSS / RSSHub / YouTube / Podcast / Newsletter
│ └── trending_scrapers/ # Trending boards (Reddit, Zhihu, ...)
├── repositories/ # DB access layer
└── core/ # config, database, auth primitives
frontend/src/
├── app/ # Next.js App Router pages
├── components/ # Shared UI (Button, Panel, Badge in ui.tsx)
└── lib/ # API client, navigation, helpers
This repo uses Conventional Commits with Chinese summaries. The convention is enforced in AGENTS.md — read it before your first PR.
Shape: <type>(<scope>): <中文说明>
- types:
feat,fix,chore,test,docs,refactor - scopes:
auth,cache,trending,backend,frontend,config,db,docs,test
Examples:
feat(auth): 后端新增 OAuth 登录(Google/GitHub)
fix(cache): 重试统计工作台启动预热
docs: 补充 agent 提交规范
Stage explicit paths (git add backend/app/...), avoid git add -A. Keep each commit focused on one behavior or risk boundary — don't mix backend and frontend in the same commit unless they're the same feature.
Run the smallest relevant verification:
# Backend — syntax + tests
cd backend
python -m py_compile $(git diff --name-only --cached | grep '\.py$')
python -m pytest tests/path/to/your_test.py -q
# Frontend — type check (don't run `npm run lint`, it's broken under current Next.js)
cd frontend && npx tsc --noEmit
# Shell scripts
bash -n path/to/script.shInspect your staged diff:
git diff --cached --stat
git diff --cached --summaryKeep local-only files out of commits — especially backend/.env, *.db, venv/, node_modules/, and screenshots.
This is the most valuable contribution and the most beginner-friendly. A connector lives in backend/app/services/scrapers/ (for content sources) or backend/app/services/trending_scrapers/ (for ranking boards).
Every scraper subclasses BaseScraper and registers itself with @register_scraper("<SourceType>"). The framework instantiates it with (source_url, source_config) and calls async fetch(client) to get items.
# backend/app/services/scrapers/my_source.py
from __future__ import annotations
from typing import Any
import httpx
from . import BaseScraper, register_scraper
@register_scraper("MySource") # must match a SourceType enum value (backend/app/models/source.py)
class MySourceScraper(BaseScraper):
def __init__(self, source_url: str, source_config: dict[str, Any] | None = None):
super().__init__(source_url, source_config)
# pre-parse anything you need from source_config (e.g. channel id)
async def fetch(self, client: httpx.AsyncClient) -> list[dict[str, Any]]:
"""Fetch items. Each dict MUST have these keys:
title, url, author, summary, raw_content, tags,
published_at, cover_url
Missing keys should be "" / None / [] — the framework normalizes.
"""
resp = await client.get(self.url)
resp.raise_for_status()
# parse resp.text / resp.json() into entries...
return [{"title": ..., "url": ..., "summary": ..., "published_at": ...}]- Write the scraper in
backend/app/services/scrapers/my_source.py(userss.py/youtube.py/podcast.pyas templates — start with the simplest one matching your source type). - Register the import in
backend/app/services/scrapers/__init__.py(thefrom . import xxxblock near the bottom). Without this, the decorator never runs. - Add the type to
SourceTypeenum inbackend/app/models/source.pyif it's genuinely new. - Add host patterns to
backend/app/services/scrapers/recognizer.pyso the "paste URL" auto-detect recognizes your source.
Note: we're working on collapsing steps 2 + 4 into the registry so contributors only write the scraper. For now, copy an existing scraper end-to-end.
The best template is backend/tests/test_youtube_scraper.py — it shows the three things to verify:
- Registration:
get_scraper_cls("YouTube") is YouTubeScraper - URL parsing helpers (pure functions, no network)
- Full
fetch()with aFakeClientthat returns canned responses (no real HTTP)
Suggested flow:
- Open an issue with
good first issue+ the source you want to add. - Copy the closest existing connector as a template.
- Add a test under
backend/tests/(copytest_youtube_scraper.pystructure). - Submit a PR — include a sample URL you tested against.
If you want to add a provider beyond Google / GitHub (e.g. Microsoft, Apple), the OAuth layer is in backend/app/core/oauth.py and backend/app/api/v1/oauth.py. Follow the existing Google/GitHub registration pattern, add config vars, and document the redirect URI in backend/.env.example.
- Backend: Follow the existing async SQLAlchemy 2.0 style (
Mapped/mapped_column). Reuseretry_sqlite_lockedfor DB writes. Keep routers thin — business logic goes inservices/. - Frontend: Self-built UI components (
components/ui.tsxexportsButton,Panel,Badge,cx). No third-party UI library. Tailwind v4.lucide-reactfor icons. - No new heavy dependencies without discussion. The project is deliberately lean.
Use the bug report template (.github/ISSUE_TEMPLATE/bug_report.yml). Include:
- What you expected vs. what happened
- Backend logs (the relevant
ERROR/WARNIlines) backend/.envwith secrets redacted- Browser console errors if frontend
Open a discussion or an issue with the question label. We're friendly.
By contributing, you agree that your contributions will be licensed under the Apache License 2.0.