Nebula:WhatsApp 上的动漫下载与 AI 助手机器人
JCVERSA/nebula-p
Nebula 是一个部署在 WhatsApp 上的多设备机器人,默认以法语配音(VF)下载动漫剧集,单集约 90 MB,支持 1-12 集批量处理并生成离线 HTML 下载页。它集成 Gemini AI 提供聊天、图像生成、语音对话等功能,并以 NVIDIA NIM 上的 meta/llama-3.3-70b-instruct 作为故障回退,单个部署最多可运行 8 个机器人。
🧭 Contents
What it looks like · Features · Command Flow · Architecture · Quick Start · Anime engine · Environment variables · Tests · Security · Docs · Extending
📸 What it looks like
Real bot output (VF by default, one high-speed link per episode, honest size labels):
.a sparks of tomorrow s1 1-12 r1
📦 Nebula Novabox - Batch Media Preparation 🚀
🎬 Anime: Sparks of Tomorrow
🗣️ Language: VF ← French dub by default (VOSTFR on request)
⚙️ Resolution: 480P
📦 Episodes to Process: 9
🚀 NEBULA NOVABOX - BATCH DOWNLOAD COMPLETED 🚀
📦 Ready Episodes: 9/9 ⏳ Links Validity: 2 Hours
📥 Direct Episode Links:
• 🎬 Episode 1: [88.93 MB] 🔗 https://your-domain/api/media/download/<token>
• 🎬 Episode 2: [89.84 MB] 🔗 …
The interactive flow defaults to VF too — and never lies about the language actually delivered:
🎬 Novabox - Select Season 🎬
• Anime: Tomb Raider King
• Language: 🇫🇷 VF (Default)
💡 (Pour passer en VOSTFR, tape `.a vostfr`)
✨ Features
| Feature | |
|---|---|
| 📺 | Anime VF downloader — voir-anime.to (VF-first) with nakanime fallback, VidMoly/Voe HLS mirrors, cat-catch style segment downloader, honest quality+size labels, WhatsApp-friendly files (~90 MB/ep), MyAnimeList info cards (.anime, Jikan), anime identification from a screenshot (.trace, trace.moe) |
| 🗣️ | VF by default — quick mode and interactive menus; .a vostfr switches back; honest "VF non disponible" when a title has no dub |
| 🔔 | New-episode watcher — .a watch on a VF season: cron polling (default every 6 h), quiet hours 23h–7h, WhatsApp notification with the ready-made download command; .a unwatch <title> / .a watchlist |
| 📦 | Batch episodes — 1-12 ranges, sequential pipeline hardened for ~1 GB containers, one offline HTML download page per batch (per-episode buttons + "Tout télécharger" in Chrome, sliding 30 min TTL reset on each download, 2 h total cap), optional season ZIP via NEBULA_BATCH_ZIP=1 |
| 🤖 | Gemini AI — chat, image generation, audio transcription, voice conversations with TTS, per-user daily budget + global concurrency cap; NVIDIA NIM fallback keeps .ai alive through Gemini outages (text, meta/llama-3.3-70b-instruct by default); defined persona (sober, mirrors the user's language, WhatsApp-tailored, overridable via NEBULA_AI_PERSONALITY; **command-aware (8.79)** — the AI knows every registered command (auto-synced, panel commands included) and proactively guides users to the exact one to type, with a hand-written step-by-step .awalkthrough; per-conversation persistent memory (sliding 10 h TTL, rolling summary,.ai forget`) |
| 💬 | WhatsApp multi-device (Baileys) — QR and pairing-code linking, auto-reconnect, bad-session recovery |
| 🤖 | Multi-bots (8.75) — up to 8 WhatsApp bots in one deployment: per-bot process/session/persona, crash-isolated, see docs/MULTI_BOTS.md |
| 🧩 | Curated command set (owner decision 8.59): .a anime, .ytv/.ytm/.yts YouTube, .tiktok/.instagram downloads, .up (temporary share links for any file), .w episode watch, .ai/.image, .define, .sweb, .qr, .base64, .getpp, .whois, .ping/.menu/.help, .purge/.p (owner: immediate disk purge sparing in-flight batches), .gce/.g (Cameroon GCE past papers as PDFs from cameroongcerevision.com — O/L, A/L and regional mocks, interactive or one-line, text fallback when no PDF is published), .agent/.ag (AI agent, beta: understands natural-language requests in DMs and executes the matching command as the user, with confirmations for heavy actions) + sandboxed panel-created ones (no fs/process/network, no restart) — the inventory is locked by a registry test; the legacy corpus, its CJS bridge (8.56), the media toolkit, moderation suite and coin system were REMOVED |
| 🛡️ | Welcome/goodbye + RoleGuard core wiring kept for the group experience; the moderation command suite was removed in the 8.59 curation |
| 🖥️ | Web control panel — live simulator, secrets manager (masked), command customizer, analytics, ZIP export |
| 🛰️ | One-command ops — manage.sh start/stop/update/doctor/env/logs/clean on any VPS, behind a Cloudflare Tunnel |
🔄 Command Flow
🏗️ Architecture
flowchart TD
subgraph group_runtime["Bot Runtime"]
node_server["Server bootstrap<br/>[server.ts]"]
node_supervisor["Bot supervisor<br/>[botSupervisor.ts]"]
node_engine["WhatsApp bot engine<br/>[botEngine.ts]"]
node_registry["Command registry<br/>[commandRegistry.ts]"]
end
subgraph group_anime["Anime Media"]
node_anime_command["Anime commands<br/>[anime.ts]"]
node_anime_parser["Quick anime parser"]
node_anime_sources["Anime source resolver<br/>[animeSources.ts]"]
node_anime_clients["Anime source clients"]
node_stream["Stream extraction"]
node_hls["HLS downloader<br/>[hlsDownloader.ts]"]
node_watch["Episode watcher"]
end
subgraph group_ai["AI Services"]
node_ai_command["AI commands<br/>[ai.ts]"]
node_ai_engine["AI fallback client<br/>[geminiClient.ts]"]
node_ai_memory[("Conversation memory<br/>[aiMemory.ts]")]
end
subgraph group_panel["Web Control Panel"]
node_web_app["Panel API<br/>[app.ts]"]
node_panel_ui["Panel interface<br/>[App.tsx]"]
node_panel_factory["Panel app factory<br/>[panelApp.ts]"]
end
subgraph group_delivery["Download Delivery"]
node_batch["Batch job manager"]
node_zip["Batch ZIP manager<br/>[batchZipManager.ts]"]
node_temp_store[("Temporary download store")]
end
node_whatsapp(("WhatsApp users"))
node_browser(("Panel users"))
node_baileys["WhatsApp platform"]
node_catalogs["Anime catalogs"]
node_gemini["Gemini AI"]
node_nim["NVIDIA NIM"]
node_whatsapp -->|"sends commands"| node_engine
node_engine -->|"connects through"| node_baileys
node_server -->|"starts bots"| node_supervisor
node_server -->|"creates panel"| node_panel_factory
node_panel_factory -->|"controls bots"| node_supervisor
node_engine -->|"dispatches commands"| node_registry
node_registry -->|"registers"| node_anime_command
node_engine -->|"dispatches"| node_anime_command
node_anime_command -->|"parses request"| node_anime_parser
node_anime_command -->|"resolves source"| node_anime_sources
node_anime_sources -->|"queries catalog"| node_anime_clients
node_anime_clients -->|"fetches metadata"| node_catalogs
node_anime_command -->|"resolves episode stream"| node_stream
node_stream -->|"downloads segments"| node_hls
node_engine -->|"starts watcher"| node_watch
node_watch -->|"sends notifications"| node_engine
node_engine -->|"dispatches"| node_ai_command
node_ai_command -->|"generates response"| node_ai_engine
node_engine -->|"generates direct reply"| node_ai_engine
node_engine -->|"reads context"| node_ai_memory
node_ai_engine -->|"requests generation"| node_gemini
node_ai_engine -.->|"falls back"| node_nim
node_browser -->|"uses panel"| node_panel_ui
node_panel_ui -->|"calls API"| node_web_app
node_web_app -->|"controls session"| node_engine
node_anime_command -->|"tracks batch"| node_batch
node_batch -->|"requests packaging"| node_zip
node_zip -->|"registers archive"| node_temp_store
node_web_app -->|"serves downloads"| node_temp_store
click node_server "https://github.com/jcversa/nebula-p/blob/main/server.ts"
click node_supervisor "https://github.com/jcversa/nebula-p/blob/main/src/bot/botSupervisor.ts"
click node_engine "https://github.com/jcversa/nebula-p/blob/main/src/bot/botEngine.ts"
click node_registry "https://github.com/jcversa/nebula-p/blob/main/src/bot/commandRegistry.ts"
click node_anime_command "https://github.com/jcversa/nebula-p/blob/main/src/bot/commands/anime.ts"
click node_anime_parser "https://github.com/jcversa/nebula-p/blob/main/src/bot/utils/quickAnimeParser.ts"
click node_anime_sources "https://github.com/jcversa/nebula-p/blob/main/src/bot/services/animeSources.ts"
click node_anime_clients "https://github.com/jcversa/nebula-p/tree/main/src/bot/services"
click node_stream "https://github.com/jcversa/nebula-p/blob/main/src/bot/services/animeStreamExtractor.ts"
click node_hls "https://github.com/jcversa/nebula-p/blob/main/src/bot/services/hlsDownloader.ts"
click node_watch "https://github.com/jcversa/nebula-p/blob/main/src/bot/services/episodeWatchService.ts"
click node_ai_command "https://github.com/jcversa/nebula-p/blob/main/src/bot/commands/ai.ts"
click node_ai_engine "https://github.com/jcversa/nebula-p/blob/main/src/bot/geminiClient.ts"
click node_ai_memory "https://github.com/jcversa/nebula-p/blob/main/src/bot/services/aiMemory.ts"
click node_web_app "https://github.com/jcversa/nebula-p/blob/main/app.ts"
click node_panel_ui "https://github.com/jcversa/nebula-p/blob/main/src/App.tsx"
click node_panel_factory "https://github.com/jcversa/nebula-p/blob/main/src/panel/panelApp.ts"
click node_batch "https://github.com/jcversa/nebula-p/blob/main/src/bot/batchDownloadManager.ts"
click node_zip "https://github.com/jcversa/nebula-p/blob/main/src/bot/services/batchZipManager.ts"
click node_temp_store "https://github.com/jcversa/nebula-p/blob/main/src/bot/tempDownloadManager.ts"
classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_server,node_supervisor,node_engine,node_registry,node_whatsapp,node_browser toneBlue
class node_anime_command,node_anime_parser,node_anime_sources,node_anime_clients,node_stream,node_hls,node_watch toneAmber
class node_ai_command,node_ai_engine,node_ai_memory toneMint
class node_web_app,node_panel_ui,node_panel_factory toneRose
class node_batch,node_zip,node_temp_store,node_baileys,node_catalogs,node_gemini,node_nim toneIndigo
Loading
Repo layout
server.ts (entry) ── createApp() (app.ts: auth, rate limiting, /api routes)
├── src/bot/botEngine.ts Baileys socket, QR, reconnect, moderation
├── src/bot/commands/*.ts individual commands (BotCommand interface)
├── src/bot/services/ anime clients, HLS downloader, streaming zip,
│ temp downloads, batch manager, proxies
├── src/bot/panelAuth.ts HttpOnly session cookies (12h sliding)
├── app.ts panel SPA + media API + health
├── manage.sh VPS lifecycle: start/stop/update/doctor/env…
└── docs/ deployment, tunnel & migration guides
🚀 Quick Start
On a VPS (recommended — one line)
curl -fsSL "https://raw.githubusercontent.com/JCVERSA/nebula-p/main/scripts/install.sh" | sh
This installs dependencies (git, Node ≥ 22, ffmpeg), clones the repo, builds it,
creates the nebula command available everywhere and offers the .env wizard.
Then:
nebula env # APP_URL, PANEL_TOKEN, GEMINI_API_KEY… (if not done yet) nebula start # start + wait for the panel
The installer is idempotent — running it again just updates the installation.
Manual equivalent: git clone -b main https://github.com/JCVERSA/nebula-p /root/p && cd /root/p && ./manage.sh setup.
Then put it behind HTTPS with a Cloudflare Tunnel and set APP_URL —
see docs/MIGRATION_NOUVEAU_VPS.md (French, step-by-step).
Tip: alias nebula='bash /root/p/manage.sh' — then everything is nebula <command>.
Local development
npm install
npm run dev # panel at http://localhost:3000Requirements: Node.js ≥ 22 and ffmpeg on PATH for media — see Requirements.
Day-to-day operations
| Command | What it does |
|---|---|
./manage.sh start / stop / restart |
Lifecycle; start waits for the panel and tails the log on failure |
./manage.sh update |
git pull --ff-only → npm install (only if deps changed) → build → restart |
./manage.sh status |
Process RAM vs cgroup cap, panel + public URL probes, temp usage, disk |
./manage.sh logs [filter] |
Live log tail, optionally filtered (e.g. logs NOVABOX) |
./manage.sh env |
Interactive .env wizard — 26 documented keys, secrets masked |
./manage.sh doctor |
Full diagnostic: node/ffmpeg/.env/RAM/disk/network, exit 1 on blockers |
./manage.sh clean |
Purges orphan staging (>1h) and expired temp files (>3h) |
./manage.sh pair [bot] <phone> |
Links a WhatsApp number with an 8-digit pairing code (no QR needed) |
./manage.sh bots |
Multi-bots overview: per-bot process + WhatsApp state (8.75) |
./manage.sh bot <id> <start/stop/restart/status> |
Controls one bot's engine process |
📺 Anime engine
Built and battle-tested against real mirrors (every fix traced in ANIME_DOWNLOAD_AUDIT.md, §8.1–8.18):
- VF by default from
voir-anime.to(VF guaranteed by URL structure), nakanime VOSTFR fallback, VidMoly-first mirror ranking with Voe/voembed support; since 8.96voir-animeis also the DEFAULT catalog for a bare.a <titre>(.a as <titre>reaches the full catalog; if the operator disables voir-anime the implicit default falls back to it) - Interactive choices memory (8.97/8.98) — the bot remembers each chat's anime downloads (last one detailed + the 4 previous on a compact line, 7-day TTL,
NEBULA_ANIME_CHOICES_TTL_HOURS) so the agent resolves "le même anime", "l'épisode suivant", "le même en 720p", "qu'est-ce qu'on avait pris avant ?" into an exact command or answer; wiped by.ai forget - .up — temporary file share links (9.5) —
.up(alias.upload .tr .lu .opload .opl .upl) turns any file sent with the command as its caption — or quoted in a reply — into a temporary download link: the bot streams the media straight to disk (never fully buffered in RAM — the engine heap is 192 MB) and stores it in the temp-download vault behind the same/d/<token>route as the anime episodes (2 h sliding window — each download resets the timer, 2 h total life cap), and replies with name, size and expiry. Oversized files are refused before processing (NEBULA_UPLOAD_MAX_MB, default 500, editable in the dashboard env editor, clamped 10-4096), each user is capped at 50 uploads/hour, and a bare.upwith no media answers with a clear usage hint. Light command — the agent may run it directly. - Agent media awareness + .up reply fix (9.6b) — production transcript: « je veux un lien » + a reply to a video took FOUR clarification questions and then
.upanswered « Aucun fichier détecté ». Two fixes: (1) a reply wrapped inephemeralMessage/viewOncelayers (disappearing-message chats) hid the quoted media from.up— extraction now unwraps the invoking message (defense in depth, the util itself is wrapper-tolerant); (2) the agent now receives a one-line media hint when the user's message attaches or quotes media (« répond (reply) à une vidéo » — nature only, never the content), so « un lien pour ça » resolves straight to.upinstead of a question loop. - YouTube hardening (9.6) — three production-driven changes: (1) downloads now prefer H.264+AAC (real mp4 — "best video" is VP9/AV1 remuxed into an .mp4 container, which WhatsApp and Windows players refuse with « souci avec le fichier vidéo »; YouTube serves avc1 up to 1080p, old behavior kept as fallback); (2) the session cookies are only attached as a fallback when a video demands sign-in — anonymous first, so the authenticated volume (what Google actually watches from a datacenter IP) drops to the strict minimum, with a global cap of 50 session downloads/hour (honest refusal beyond; anonymous stays unlimited); (3)
nebula updatenow self-updates the yt-dlp binary (stale versions trigger more bot checks). Best practices remain: use a throwaway Google account, never the personal one, and only from the VPS. - YouTube cookies pasted in the dashboard (9.4) — the Système section now takes the cookies.txt content directly: paste the file exported by "Get cookies.txt LOCALLY" from a signed-in youtube.com, and the panel validates it (strict Netscape format, at least one youtube.com cookie, not already expired), writes it atomically with 0600 perms, auto-configures
NEBULA_YTDLP_COOKIESand shows a full diagnostic (cookie count, domains, furthest expiry date). Replacing the file applies hot (no restart); only the first setup needs one. A delete button reverts to anonymous mode. The cookie content is never displayed nor returned by the API — write-only, like the API keys. - .env editor in the dashboard + agent honesty fixes (9.3) — the Système section now edits a whitelisted set of
.envvariables directly from the panel (AI/web quotas, primary engine, digest on/off & hour, timezone) and takes API keys write-only (Tavily/Gemini/NIM masked at display — paste, never shown; YouTube cookies path). Strict per-type validation (no newlines/quotes — nothing injectable), atomic write in manage.sh format, and each variable reports whether it applies live or needs a restart (the restart button is right there). Agent fixes from production transcripts: recency questions now MUST go through.search(no more invented 2026 dates presented as "recent web research"), a failed anime search triggers the international-title recovery instead of a catalog switch ("attack des titans" → "attack on titan"), and the error recovery can no longer offer doc placeholders likeexample.com. - System admin console (9.2) — a new "Système" section in the panel dashboard: current version/commit/branch, a one-click Mettre à jour button (launches the exact
nebula updatescript — git pull, install, build, restart — detached so it survives the panel restart), plus restart, stop (with an explicit "SSH only" warning) and a livebot.logtail with Copy (copies the displayed lines) and Export (downloads a.txtof the last 300 lines with a dated version header — share it as-is for debugging). Confirmation modal on every action; while updating, the UI tracks the panel going down and back up and auto-reloads on the new version ("Déjà à jour" when nothing changed). All routes sit behind the panel session with a dedicated rate limit, and no user input ever reaches a shell — only the fixed manage.sh actions. - Autonomous agent loop (9.1) — the agent now observes what the commands it runs reply (selection lists, season/quality pickers) and drives them to completion: you ask in natural language once ("télécharge mushoku tensei saison 3 épisode 12 en 480p"), it builds the full one-liner, auto-picks the right entry from the list, and hands you the link — zero typing. Bounded loop: max 3 AI decisions per message, identical-command rerun guard, quota re-checked at every step, and every iteration goes through the same zero-trust validation (denylist, argument guardrail, heavy-command confirmation). The last command screen stays in the agent's context for 10 minutes so a natural reply ("le 2e", "en VF") is grounded instead of looping. The
.aparser now accepts natural French (saison 3,épisode 12,en 480p) — useful for humans too. Preference questions (VF/VOSTFR, unrequested quality) are always relayed to the user, never auto-answered. - Web research tools (9.0) —
.search(real web results, keyless DuckDuckGo by default, optionalTAVILY_API_KEYupgrades to the Tavily API),.fetch <url>(read a web page as clean text) and.wiki(French Wikipedia summaries). Freshness flags on.search:-dlast 24h,-wweek,-mmonth,-yyear (no filter = all years, relevance first). Zero AI tokens (deterministic commands the agent can execute directly when asked to look something up). All requests go through the SSRF-hardenedsafeFetch(no localhost/private IPs — the panel and engine stay unreachable), sizes and timeouts capped, and a shared per-user daily budget (NEBULA_WEB_DAILY_LIMIT, default 20) protects the server IP. - Agent hardening + daily digest (8.99) — research-driven upgrades from hermes-agent, OpenAI Agents SDK, Mastra & Pydantic AI: native JSON mode for agent decisions (Gemini
responseMimeType/ NIMresponse_format, tolerant parsing kept as the fail-closed guard), an argument guardrail (URLs/backticks/shell flags stripped before execution), selective anime-history injection (context only when the message is about anime), per-turn agent metrics (/api/bot/agent-health+ an "AI Agent — last 24 h" card in the panel, no message contents stored) and a daily owner DM digest (watches, disk, AI quota, agent health — 8 AM,NEBULA_DIGEST_HOUR,NEBULA_DIGEST=0to disable).agent-replaygained scenario severities and a--jsonscored output. - Agent safety model & replay harness (8.98) — the agent's security model is documented in SAFETY.md (scope, denylist, fail-closed confirmations, budgets, privacy — lessons from hermes-agent & deepseek-harness), the knowledge sheet is kept under a CI-enforced context budget, and
npm run agent-replayreplays reference conversations against the REAL AI to catch decision regressions (manual, needs an AI key — in the container:docker exec -it <container> node dist/agent-replay.cjs) - Cross-catalog language fallback (8.95) — when the requested language is missing on the current catalog, the bot looks for the SAME anime (exact title match only, language verified structurally) on the other catalog and continues there with a
🔄notice; if the language exists nowhere, it stops and asks (.a oui/.a non) instead of silently continuing in the other language - Honest labels — real HLS variant resolutions and sizes; a fat 403 MB "480P" is auto-downgraded to the lightest ≤480p variant (fast-lane size guard)
- Container-friendly — sequential batches, disk-streamed segments with backpressure, capped V8 heap, streaming (STORE) ZIP writer instead of in-RAM archives, startup debris purge
- Resilience — when every mirror of an episode fails (CDN-level 403), the bot retries it on the secondary anime catalog (VF lists first, then VOSTFR; honest language in the filename) — disable with
NEBULA_VOSTFR_FALLBACK=0 - YouTube / TikTok / Instagram —
.ytv [360|480|720|1080],.ytm(AAC-converted audio),.yts(search links),.tiktok(no watermark, tikwm),.instagram(up to 20 media per post): original neb command ports; since 8.86 downloads go through the localyt-dlpbinary first (API chains kept as fallback), and 8.87 adds--js-runtimes nodeplus optional session cookies for videos that require a signed-in session (NEBULA_YTDLP_COOKIES— see.env.example); 9.6 prefers H.264/AAC formats, keeps the session cookies as an anonymous-first fallback (50/hour global cap) and self-updates yt-dlp on every update - Delivery — batches >1 episode arrive as ONE offline HTML page: per-episode direct buttons + automatic "Tout télécharger" (sliding 30 min link TTL reset on each download, 2 h total cap —
NEBULA_LINK_TTL_MIN; HTTP range streaming); single episodes still get a plain link; optional season ZIP behindNEBULA_BATCH_ZIP=1 - Resource ceilings:
NEBULA_NOVABOX_MAX_EPISODES(12),NEBULA_NOVABOX_MAX_BATCH_MB(2048/bot),NEBULA_TEMP_MAX_BYTES(2 GiB), plus a cross-bot disk guard (8.78, real-need claim since 8.81): every batch claims its real estimated need (per-episode size × episodes × 1.5, capped by the batch ceiling) in a shared claims dir and new batches are refused while free space would drop underNEBULA_MIN_FREE_DISK_MB(500) — the refusal tells the user how many episodes still fit
🔑 Environment Variables
Copy .env.example to .env (or run ./manage.sh env). Highlights:
| Variable | Required | Description |
|---|---|---|
APP_URL |
for public links | Public panel URL (e.g. https://bot.example.com). Also enables the host-header guard — must match the tunnel hostname exactly |
PANEL_TOKEN |
recommended | Panel access key; exchanged for an HttpOnly session cookie, never stored in the browser. Auto-generated and printed once if unset |
GEMINI_API_KEY |
for AI | Google Gemini key (settable from the panel, masked display) |
NVIDIA_NIM_API_KEY |
optional | NVIDIA NIM key — AI fallback when Gemini is unavailable (free on build.nvidia.com) |
PORT |
no | HTTP port (default 3000) |
NEBULA_VF_DEFAULT |
no | 0 disables VF-by-default |
NEBULA_VOIRANIME_DISABLED |
no | 1 disables the voir-anime.to source |
NEBULA_BATCH_ZIP |
no | 1 re-enables the all-in-one season ZIP |
NEBULA_BATCH_CONCURRENCY |
no | Parallel episode downloads (default 1 — sequential; keep 1 under ~1 GB RAM) |
NEBULA_NOVABOX_MAX_EPISODES / _MAX_BATCH_MB |
no | Batch ceilings (12 / 2048) |
NEBULA_DOWNLOAD_TIMEOUT_MS |
no | Hard global deadline per episode download (default 600000 = 10 min) |
NEBULA_WATCH_CRON / _QUIET / _TZ |
no | Episode watcher schedule (0 */6 * * *), quiet window (23-7) and timezone (Africa/Douala) |
NEBULA_TEMP_MAX_BYTES |
no | Temp storage ceiling (2 GiB since 8.83 — sized for small containers) |
NEBULA_LINK_TTL_MIN |
no | Sliding TTL of download links in minutes (30, clamped 5-120) — each download resets the timer, total link life capped at 2 h (8.83) |
NEBULA_UPLOAD_MAX_MB |
no | Max file size in MB accepted by .up share links (500, clamped 10-4096) — bigger files are refused before processing (9.5) |
NEBULA_MIN_FREE_DISK_MB |
no | Cross-bot disk guard (8.78): free-space floor kept when admitting a new batch (500 MB) |
NEBULA_DISK_GUARD |
no | off disables the cross-bot disk guard |
NEBULA_HEALTH_SWEEP_MS / NEBULA_HEALTH_FAILS |
no | Supervisor health sweep (8.78): probe interval (60 s) and consecutive failures before a frozen engine is force-restarted (3) |
NEBULA_LOG_FILE / NEBULA_LOG_MAX_MB |
no | In-app log size guard (8.82): log path (set by manage.sh) and cap before truncation (150 MB) — the WhatsApp crypto layer dumps large session traces per message, and in Docker containers cron/logrotate often never runs |
NEBULA_AI_DAILY_LIMIT / _MAX_CONCURRENT |
no | AI budget (40/day/user) and concurrency (3) |
NEBULA_PANEL_COMMANDS |
no | off disables sandboxed panel-created commands |
NEBULA_ENABLE_LEGACY |
no | 1 re-enables the vendored legacy command corpus (quarantined by default) |
NEBULA_DATA_DIR / NEBULA_ENV_FILE / NEBULA_AUTH_DIR |
no | Runtime state, env file and WhatsApp session locations |
Secrets can also be managed from the panel (Settings & Access → API Secrets): values are written atomically to .env, applied live without restart, and only ever shown masked. Only allowlisted variables can be set from the web UI.
🧪 Tests
npm test # vitest — 439 tests across 47 files npm run lint # strict TypeScript typecheck npm run build # production build (client + server) npm start # serve the production build (capped V8 heap, gc exposed)
Tests run against an isolated temp data directory and never touch real WhatsApp sessions or the Gemini API. The suite covers the anime engine (labels, language routing, fast-lane guard), the streaming ZIP writer (byte-exact round-trips), temp-download purges and start-flag regressions.
🔒 Security Notes
- Panel auth: every
/api/*route requires login; cookies are server-side HttpOnly + CSRF-checked (Origin/Referer); bearer tokens work for tooling - SSRF guard: user-supplied URLs validated per redirect hop with DNS pinning + host allowlists
- Sandboxed commands: panel-created commands run in a
vmwith no fs/process/network - Host-header guard: foreign
Hostheaders rejected whenAPP_URLis set; temp links inherit the validated base URL - Resource caps: streamed downloads with byte caps, temp quotas, batch limits, AI budgets
- ZIP export never embeds your live API key; simulator output is HTML-escaped
Details: SECURITY.md, AUDIT_REPORT.md.
📚 Documentation
| Doc | Contents |
|---|---|
| ANIME_DOWNLOAD_AUDIT.md | Full anime-download engineering log: every bug, root cause and fix (§1–8.18) |
| docs/MIGRATION_NOUVEAU_VPS.md | Move to a new VPS/container; Cloudflare Tunnel on a bare domain (subdomain left empty) |
| docs/GUIDE_DEPLOIEMENT_VPS.md | Zero-to-production French guide (tunnel, pairing, doctor, troubleshooting) |
| docs/CLOUDFLARE_TUNNEL_DEPLOYMENT.md | Cloudflare Tunnel reference (tokens, headers, checklist) |
| docs/RAPPORT_SYSTEME_TELECHARGEMENT.md | Download-system design report |
| PHASE2_STATUS.md · PHASE3_SCOPE.md · RELEASE_NOTES_v1.1.0.md | Hardening status, roadmap, release notes |
🧩 Extending
- Add a command: drop a
BotCommandfile insrc/bot/commands/(seeping.ts) — auto-loaded at startup; or generate one from the panel (sandboxed, stored as data) - Data flow: WhatsApp message → engine → moderation filters → command registry → command context (
reply,react,downloadMedia,isOwner,isAdmin) - Local runner: the panel's ZIP export ships a self-contained bot (transpiled commands, Baileys runtime, config, placeholder
.env)
Nebula Bot — built container-first, validated on real mirrors. ⭐ if it saves you time.
./manage.sh doctor should always end with RIEN DE BLOQUANT 🎉
来源:GitHub · github.com