Skip to content

Architecture

Solstice uses a hybrid dual-process architecture - a Go engine handles all Telegram Bot API interactions, command parsing, database operations, and queue management, while a Python daemon manages the actual voice chat streaming via PyTgCalls/Pyrogram.

High-Level Overview

┌─────────────────────────────────────────────────────────────────┐
│                         Telegram Cloud                          │
│                    (Bot API + MTProto API)                       │
└──────────────┬──────────────────────────────────┬───────────────┘
               │ Bot API (HTTPS)                  │ MTProto (TCP)
               ▼                                  ▼
┌──────────────────────────┐    ┌──────────────────────────────────┐
│     Go Engine (bot)      │    │   Python Daemon (vc_daemon.py)   │
│                          │    │                                  │
│  • Command parsing       │    │  • Pyrogram MTProto client       │
│  • User authentication   │    │  • PyTgCalls voice chat engine   │
│  • Queue management      │    │  • FFmpeg audio/video pipeline   │
│  • YouTube search/extract│    │  • Stream lifecycle management   │
│  • MongoDB persistence   │    │  • HTTP API server (:5050)       │
│  • i18n string rendering │    │                                  │
│  • Admin/sudo logic      │    │                                  │
│  • HTTP client (:5051)   │    │                                  │
└────────────┬─────────────┘    └──────────────┬───────────────────┘
             │                                 │
             │    JSON-over-HTTP (localhost)    │
             │◄────────────────────────────────►│
             │                                 │
             │  Go → Python  :5050             │
             │  Python → Go  :5051             │
             │                                 │
             ▼                                 ▼
┌──────────────────────────┐    ┌──────────────────────────────────┐
│       MongoDB            │    │         Voice Chat               │
│  (persistent state)      │    │    (Telegram Group VC)           │
└──────────────────────────┘    └──────────────────────────────────┘

Why Two Languages?

ConcernGoPython
Bot API command handlingNative telebot.v3-
Concurrency & speedGoroutines, channels-
Voice chat streaming-PyTgCalls (only available in Python)
MTProto userbot session-Pyrogram
YouTube extractionyt-dlp subprocessUsed as library in search
Database accessgo.mongodb.org/mongo-driver-

The PyTgCalls library - which provides the low-level interface to Telegram's voice chat protocol - is only available for Python. This constraint drives the hybrid architecture. Go handles everything else because of its superior concurrency model and compile-time safety.


Inter-Process Communication (IPC)

The two processes communicate over localhost HTTP using simple JSON payloads. This is intentionally lightweight - no gRPC, no message queues - just plain HTTP POST requests.

Go → Python (port 5050)

The Go engine sends commands to the Python daemon's HTTP server:

EndpointMethodPayloadDescription
/playPOST{"chat_id", "stream_url", "video", "title"}Start or enqueue a stream
/pausePOST{"chat_id"}Pause current stream
/resumePOST{"chat_id"}Resume paused stream
/stopPOST{"chat_id"}Stop stream and leave VC
/seekPOST{"chat_id", "position"}Seek to position in seconds
/volumePOST{"chat_id", "volume"}Set volume (1-200)

Python → Go (port 5051)

The Python daemon sends event notifications back to the Go engine:

EndpointMethodPayloadDescription
/stream_endPOST{"chat_id"}Current stream finished, triggers auto-queue advancement

Sequence: Playing a Song

User sends: /play Bohemian Rhapsody


┌─── Go Engine ──────────────────────────────────────────────┐
│ 1. Parse command, authenticate user                        │
│ 2. Search YouTube (HTML scrape → proxy → yt-dlp fallback)  │
│ 3. Extract direct stream URL via yt-dlp                    │
│ 4. Check Redis cache (Upstash) for cached URL              │
│ 5. Add song to per-chat queue (QueueManager)               │
│ 6. If queue position == 0, POST /play to Python daemon     │
│ 7. Send "Now Playing" card to chat with inline controls    │
│ 8. Log play event to logger channel                        │
└────────────────────────────────────────────────────────────┘

          │ HTTP POST localhost:5050/play

┌─── Python Daemon ──────────────────────────────────────────┐
│ 1. Receive play request                                    │
│ 2. Join voice chat via Pyrogram (if not already joined)    │
│ 3. Start FFmpeg pipeline for audio/video stream            │
│ 4. Feed stream to PyTgCalls                                │
│ 5. When stream ends, POST /stream_end to Go engine         │
└────────────────────────────────────────────────────────────┘

          │ HTTP POST localhost:5051/stream_end

┌─── Go Engine ──────────────────────────────────────────────┐
│ 1. Advance queue: pop current song, peek next              │
│ 2. If next song exists, POST /play to Python daemon        │
│ 3. If queue empty + autoplay enabled:                      │
│    a. Fetch related videos via YouTube Mix (RD playlist)   │
│    b. Extract stream URL, POST /play                       │
│ 4. If queue empty + autoplay disabled:                     │
│    a. POST /stop to Python daemon                          │
│    b. Send "Queue finished" message                        │
└────────────────────────────────────────────────────────────┘

Concurrency Model

Go Engine

  • Goroutines are used extensively for non-blocking operations:
    • YouTube search and extraction run in goroutines to avoid blocking the bot's message loop
    • Broadcast messages iterate chats in a background goroutine with 100ms delays to avoid Telegram flood limits
    • Global ban enforcement runs asynchronously across all served chats
  • sync.Mutex protects shared state:
    • QueueManager uses a mutex to safely manage per-chat song queues from concurrent command handlers
    • voteSkipStore uses a mutex for thread-safe vote counting
    • Whisper message storage uses sync.RWMutex

Python Daemon

  • asyncio event loop drives the entire daemon:
    • PyTgCalls stream callbacks are async
    • The HTTP server (aiohttp) runs on the same event loop
    • All Pyrogram API calls are awaited

Process Lifecycle

main.go

  ├── Load .env → config.LoadConfig()
  ├── Load i18n strings (en.yml)
  ├── Connect to MongoDB → database.InitDB()
  ├── Load YouTube cookies from DB/env → cookies.txt
  ├── Load Pyrogram session string

  ├── Create VoiceChatManager
  │   └── Spawns vc_daemon.py as subprocess
  │       └── Python process starts HTTP server on :5050

  ├── Create BotHandler (telebot.v3)
  │   ├── Register all command routes
  │   └── Wire up OnStreamEnd callback

  ├── Start bot polling (goroutine)
  │   └── Long polling with 10s timeout

  └── Wait for SIGINT/SIGTERM → graceful shutdown

Released under the GPL-3.0 License.