# Contributing to AniKoKo Thanks for wanting to contribute. This is a small project with a narrow scope — a Hindi-dub anime aggregator — so a few ground rules keep it maintainable. ## What this project is - **Backend** (`backend/`) — FastAPI service. Scrapes anime metadata and stream sources from source sites, normalizes them into one API, proxies media. - **Frontend** (`frontend/`) — Zero-build single-page client. Plain HTML, CSS, and vanilla JavaScript. Talks to the backend over JSON. No frameworks on the frontend. No ORM, no database. Everything is in-memory or ephemeral. Keep it that way unless there's a strong reason. ## Getting set up ### Backend ```bash cd backend python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install -r requirements.txt cp .env.example .env python3 main.py ``` ### Frontend ```bash cd frontend python3 -m http.server 5500 ``` Open `http://localhost:5500`. ## Reporting bugs Open an issue with: - what you did - what you expected - what actually happened - browser / OS / Python version - relevant console output (browser DevTools or server terminal) If a scraper broke because a source site changed its HTML, include the exact series or episode URL and — if you can — a `curl` output of the page. ## Suggesting features Open an issue first. Small, focused suggestions are easier to accept than broad rewrites. If your idea touches more than one subsystem, describe the shape before writing code. ## Adding a provider Providers are the main extension point. To add one: 1. **Probe the source site.** Figure out: - How to search (`?s=` or a REST endpoint) - How to list latest / popular - How a series page links to its episodes - Where the stream sources live (m3u8? iframe? base64 blob?) 2. **Create `backend/scrapers/.py`.** Subclass `BaseScraper`: ```python from scrapers.base import BaseScraper, AnimeCard, Episode, StreamSource from config import PROVIDERS class MyProviderScraper(BaseScraper): slug = "myprovider" base = PROVIDERS[slug]["base"] async def search(self, query: str) -> list[AnimeCard]: ... async def latest(self, page: int = 1) -> list[AnimeCard]: ... async def anime_detail(self, url: str) -> dict: ... async def episode_sources(self, url: str) -> list[StreamSource]: ... ``` 3. **Register it** in `backend/scrapers/registry.py` and `backend/config.py`: ```python # registry.py _SCRAPERS = { "desidubanime": DesiDubAnimeScraper(), "myprovider": MyProviderScraper(), } # config.py PROVIDERS = { "desidubanime": {"name": "DesiDubAnime", "base": "...", "lang": "Hindi"}, "myprovider": {"name": "MyProvider", "base": "...", "lang": "Hindi"}, } ``` 4. **Test it** against real URLs before opening a PR. Confirm: - `GET /api/latest?provider=myprovider` - `GET /api/search?q=some%20title&provider=myprovider` - `GET /api/anime?url=&provider=myprovider` → has episodes - `GET /api/sources?url=&provider=myprovider` → has at least one source ## Adding an embed resolver If a source site uses a third-party player you want to turn into a direct m3u8: 1. Fetch an example embed page and inspect what it returns (raw HTML, JSON, base64 blob, packed JS). 2. Create `backend/resolvers/.py` subclassing `BaseResolver`. 3. Register it in `backend/resolvers/__init__.py`. 4. Test with `GET /api/resolve?url=` — it should return `{"ok": true, "resolved": "https://.../something.m3u8"}`. If the resolver needs to execute JavaScript (obfuscated crypto, token derivation), **don't guess**. Document what you found and leave it as a plain embed — a working iframe is better than a broken native player. ## Code style ### Python - 4-space indent - Type hints on public functions - Prefer `async`/`await` throughout — this is an I/O-bound service - No blocking calls in request paths - Keep imports flat; no lazy imports unless avoiding a cycle ### JavaScript - 2-space indent - `const` and `let`, never `var` - Prefer template literals over concatenation - Every user-facing string goes through the escape helper before HTML insertion - No frameworks, no build step — plain DOM APIs ### CSS - 2-space indent - CSS custom properties for anything reused - Mobile-first — the base styles should work at 360px; widen upward - No `!important` except for the global `[hidden]` rule ## Commit style Conventional-ish, short, imperative: ``` scraper: handle new episode URL shape on desidubanime frontend: add keyboard shortcut for search focus resolver: fix vidmoly m3u8 extraction when folder changes api: return 404 instead of 500 for unknown provider ``` Prefixes in use: `scraper:`, `resolver:`, `api:`, `frontend:`, `style:`, `docs:`, `fix:`, `chore:`. One change per commit. Don't mix a scraper fix with a CSS tweak. ## Pull requests - Keep PRs focused — one feature or one fix - Reference the issue it closes: `Closes #12` - Include a short "what changed" and "how to test" section - If you touched a scraper, say which URLs you tested against - Screenshots help for frontend changes Before submitting: - `python3 -c "import main"` in `backend/` should not error - Hard-refresh the frontend and click through the flow you changed - No new warnings in the browser console ## What we don't want - **Don't add telemetry.** No analytics, no trackers, no external beacons. - **Don't add a build step.** The frontend is deliberately three files. - **Don't gate features behind accounts.** This is a personal tool. - **Don't rewrite working scrapers "for cleanliness."** Site HTML is fragile; the working scraper is the spec. - **Don't commit `.env`, `venv/`, `__pycache__/`, or anything under `.gitignore`.** If a file feels like it shouldn't be tracked, it probably shouldn't. ## Legal note This project scrapes publicly available pages. Only contribute scrapers for sources you're comfortable handling personally. Don't add DRM circumvention, paid-content bypasses, or anything that impersonates a logged-in user. ## Questions Open an issue. If it's small, ask in the issue itself — no need for a separate discussion thread.