2026-02-13 20:56:23 +01:00
2026-02-13 20:56:23 +01:00
2026-02-13 20:56:23 +01:00
2026-02-13 20:56:23 +01:00
2026-02-13 20:56:23 +01:00
2026-02-13 20:56:23 +01:00

Discord AI Companion

Nova is a friendly, slightly witty Discord companion that chats naturally in DMs or when mentioned in servers. It runs on Node.js, uses discord.js v14, and leans on OpenAI's cost-efficient models plus lightweight local memory for persistent personality.

Features

  • Conversational replies in DMs automatically; replies in servers when mentioned or in a pinned channel.
  • OpenAI chat model (gpt-4o-mini by default) for dialogue and text-embedding-3-small for memory.
  • Short-term, long-term, and summarized memory layers with cosine-similarity retrieval.
  • Automatic memory pruning, importance scoring, and transcript summarization when chats grow long.
  • Local JSON vector store (no extra infrastructure) plus graceful retries for OpenAI rate limits.
  • Optional "miss u" pings that DM your coder at random intervals (06h) when CODER_USER_ID is set.
  • Dynamic per-message prompt directives that tune Nova's tone (empathetic, hype, roleplay, etc.) before every OpenAI call.
  • Lightweight DuckDuckGo scraping for "Google-like" answers without paid APIs (locally cached).

Prerequisites

  • Node.js 18+
  • Discord bot token with Message Content Intent enabled
  • OpenAI API key

Setup

  1. Install dependencies:
    npm install
    
  2. Copy the environment template:
    cp .env.example .env
    
  3. Fill .env with your secrets:
    • DISCORD_TOKEN: Discord bot token
    • OPENAI_API_KEY: OpenAI key
    • OPENAI_MODEL: Optional chat model override (default gpt-4o-mini)
    • OPENAI_EMBED_MODEL: Optional embedding model (default text-embedding-3-small)
    • BOT_CHANNEL_ID: Optional guild channel ID where the bot can reply without mentions
    • CODER_USER_ID: Optional Discord user ID to receive surprise DMs every 06 hours
    • ENABLE_WEB_SEARCH: Set to false to disable DuckDuckGo lookups (default true)

Running

  • Development: npm run dev
  • Production: npm start

Optional PM2 Setup

npm install -g pm2
pm2 start npm --name nova-bot -- run start
pm2 save

PM2 restarts the bot if it crashes and keeps logs (pm2 logs nova-bot).

File Structure

src/
  bot.js        # Discord client + routing logic
  config.js     # Environment and tuning knobs
  openai.js     # Chat + embedding helpers with retry logic
  memory.js     # Multi-layer memory engine
.env.example
README.md

How Memory Works

  • Short-term (recency buffer): Last 10 conversation turns kept verbatim for style and continuity. Stored per user in data/memory.json.
  • Long-term (vector store): Every user message + bot reply pair becomes an embedding via text-embedding-3-small. Embeddings, raw text, timestamps, and heuristic importance scores are stored in the JSON vector store. Retrieval uses cosine similarity plus a small importance boost; top 5 results feed the prompt.
  • Summary layer: When the recency buffer grows past ~3000 characters, Nova asks OpenAI to condense the transcript to <120 words, keeps the summary, and trims the raw buffer down to the last few turns. This keeps token usage low while retaining story arcs.
  • Importance scoring: Messages mentioning intent words ("plan", "remember", etc.), showing length, or emotional weight receive higher scores. When the store exceeds its cap, the lowest-importance/oldest memories are pruned. You can also call pruneLowImportanceMemories() manually if needed.

Memory Deep Dive

  • Embedding math: text-embedding-3-small returns 1,536 floating-point numbers for each text chunk. That giant array is a vector map of the messages meaning; similar moments land near each other in 1,536-dimensional space.
  • What gets embedded: After every user→bot turn, recordInteraction() (see src/memory.js) bundles the pair, scores its importance, asks OpenAI for an embedding, and stores { content, embedding, importance, timestamp } inside data/memory.json.
  • Why so many numbers: Cosine similarity needs raw vectors to compare new thoughts to past ones. When a fresh message arrives, retrieveRelevantMemories() embeds it too, calculates cosine similarity against every stored vector, adds a small importance boost, and returns the top five memories to inject into the system prompt.
  • Self-cleaning: If the JSON file grows past the configured limits, low-importance items are trimmed, summaries compress the short-term transcript, and you can delete data/memory.json to reset everything cleanly.

Conversation Flow

  1. Incoming message triggers only if it is a DM, mentions the bot, or appears in the configured channel.
  2. The user turn is appended to short-term memory immediately.
  3. The memory engine retrieves relevant long-term memories and summary text.
  4. A compact system prompt injects personality, summary, and relevant memories before passing short-term history to OpenAI.
  5. The reply is sent back to Discord. If Nova wants to send a burst of thoughts, she emits the <SPLIT> token and the runtime fans it out into multiple sequential Discord messages.
  6. Long chats automatically summarize; low-value memories eventually get pruned.

Dynamic Prompting

  • Each turn, Nova inspects the fresh user message (tone, instructions, roleplay cues, explicit “split this” requests) plus the last few utterances.
  • A helper (composeDynamicPrompt in src/bot.js) emits short directives like “User mood: fragile, be gentle” or “They asked for roleplay—stay in character.”
  • These directives slot into the system prompt ahead of memories, so OpenAI gets real-time guidance tailored to the latest vibe without losing the core persona.
  • src/search.js scrapes DuckDuckGo's HTML endpoint with a normal browser user-agent, extracts the top results (title/link/snippet), and caches them for 10 minutes to avoid hammering the site.
  • bot.js detects when a question sounds “live” (mentions today/news/google/etc.) and injects the formatted snippets into the prompt as "Live intel". No paid APIs involved—its just outbound HTTPS from your machine.
  • Toggle this via ENABLE_WEB_SEARCH=false if you dont want Nova to look things up.

Proactive Pings

  • When CODER_USER_ID is provided, Nova spins up a timer on startup that waits a random duration (anywhere from immediate to 6 hours) before DMing that user.
  • Each ping goes through OpenAI with the prompt "you havent messaged your coder in a while, and you wanna chat with him!" so responses stay playful and unscripted.
  • The ping gets typed out (sendTyping) for realism and is stored back into the memory layers so the next incoming reply has context.

Notes

  • The bot retries OpenAI requests up to 3 times with incremental backoff when rate limited.
  • data/memory.json is ignored by git but will grow with usage; back it up if you want persistent personality.
  • To reset persona, delete data/memory.json while the bot is offline.

Happy chatting!

Description
Open-source AI Discord chatbot built with modular architecture, memory handling etc.
Readme MIT 163 KiB
Languages
JavaScript 70.4%
HTML 29.6%