# Zakirullin Files Telegram Bot Architecture: A Deep Dive into the Go Implementation

> Explore the Zakirullin Files Telegram bot architecture. Discover its Go implementation, concurrent subsystems, and per user filesystem store. Learn how updates are routed.

- Repository: [Artem Zakirullin/files.md](https://github.com/zakirullin/files.md)
- Tags: architecture
- Published: 2026-05-21

---

**The Zakirullin Files Telegram bot is a single-binary Go server that runs three concurrent subsystems—Telegram update polling, an HTTP sync server, and a background worker—routing each user’s updates through dedicated goroutines to a per-user filesystem store.**

This open-source project ([[`zakirullin/files.md`](https://github.com/zakirullin/files.md/blob/main/zakirullin/files.md)](https://github.com/zakirullin/files.md)) implements a unique approach to Telegram bot architecture, treating user data as a filesystem of markdown files rather than a traditional database. Understanding its architecture reveals how to build scalable, stateful bots with strong concurrency guarantees and seamless web integration.

## Core Architecture Overview

The bot operates as a **monolithic Go binary** that simultaneously manages three long-running subsystems:

- **Telegram Update Loop**: Long-polls the Telegram Bot API and distributes updates to per-user channels ([[`cmd/server/server.go`](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go)](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go#L48-L66))
- **HTTP Sync Server**: Serves the Progressive Web App (PWA) and handles filename synchronization, file uploads, and web UI notifications ([`server/sync`](https://github.com/zakirullin/files.md/tree/main/server/sync))
- **Worker Ticker**: Executes every 5 seconds to process scheduled tasks and prune completed checklist items ([[`cmd/server/server.go`](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go)](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go#L59-L74))

All subsystems share a unified storage layer. The **UserFS** (`storage/<userID>/*.md`) serves as the single source of truth for both Telegram interactions and web-based edits.

## Data Storage and State Management

The architecture separates data into three distinct persistence layers:

### UserFS: Filesystem as Database

User-generated content lives in a **per-user filesystem tree** managed by the `UserFS` abstraction. Each user receives an isolated directory (`storage/<userID>/`) containing markdown files. This design enables git-like versioning and allows the HTTP server to read and write notes using standard file operations ([[`server/fs/fs.go`](https://github.com/zakirullin/files.md/blob/main/server/fs/fs.go)](https://github.com/zakirullin/files.md/blob/main/server/fs/fs.go)).

### In-Memory Transient State

Runtime state—including last keyboard message IDs, recent commands, and temporary buffers—resides in an in-memory **DB** package (`server/db`). This layer provides fast access to ephemeral data that does not require persistence across restarts.

### Static Configuration

System-wide preferences and bot settings are loaded from [`config.json`](https://github.com/zakirullin/files.md/blob/main/config.json) via the `server/config` package, while per-user preferences (timezones, quick-buttons) are managed by [[`server/userconfig/userconfig.go`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go)](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go).

## The Bot Core and Request Flow

At the heart of the system lies the **`Bot`** type defined in [[`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go)](https://github.com/zakirullin/files.md/blob/main/server/bot.go). This struct implements the main `Reply` method, which processes incoming `Update` objects through a deterministic decision tree:

1. **Inline Query Handling**: Responds to inline mode requests immediately
2. **Plugin Execution**: Routes text through optional plugins (e.g., world-clock) that can answer without UI interaction
3. **Command Dispatch**: Extracts commands and dispatches to a handler map (e.g., `/help`, `/start`)
4. **Content Persistence**: Saves plain text or image messages to the user's markdown files

The `Update` abstraction in [[`server/pkg/tg/upd.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/upd.go)](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/upd.go) normalizes raw `tgbotapi.Update` structs into a consistent interface used throughout the bot logic.

## Concurrency Model and Goroutine Architecture

The bot achieves **per-user sequential consistency** through a channel-based worker pattern:

### Per-User Supervisor Pattern

When the main update loop receives a message, it resolves the `userID` and checks for an existing channel. If absent, it spawns a new **supervisor goroutine** with panic recovery:

```go
ch, ok := userChannels[userID]
if !ok {
    ch = make(chan tgbotapi.Update, 100)
    userChannels[userID] = ch
    go supervisor(userID, ch, telegram) // panic-recovering worker
}
ch <- upd

```

This pattern ensures that a single user’s updates are processed **sequentially**, eliminating race conditions on shared markdown files. Different users process updates in parallel, each with isolated `UserFS` and in-memory DB instances.

### Thread-Safe File Access

Because the bot never writes concurrently for the same user, the HTTP sync server can safely read and write the same markdown files without locking mechanisms. This architecture simplifies the concurrency model while maintaining data integrity.

## Key Components and Source Files

| Component | File Path | Responsibility |
|-----------|-----------|----------------|
| **Entry Point** | [[`cmd/server/server.go`](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go)](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go) | Initializes Telegram client, starts update loop, launches HTTP server |
| **Bot Logic** | [[`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go)](https://github.com/zakirullin/files.md/blob/main/server/bot.go) | Implements `Bot.Reply`, command routing, and plugin orchestration |
| **Telegram Wrapper** | [[`server/pkg/tg/tg.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go)](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go) | Thin abstraction over Bot API for sending, editing, and deleting messages |
| **Update Adapter** | [[`server/pkg/tg/upd.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/upd.go)](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/upd.go) | Normalizes Telegram updates into the internal `Update` interface |
| **Filesystem Layer** | [[`server/fs/fs.go`](https://github.com/zakirullin/files.md/blob/main/server/fs/fs.go)](https://github.com/zakirullin/files.md/blob/main/server/fs/fs.go) | Handles `Write`, `Read`, directory creation, and quota enforcement |
| **User Config** | [[`server/userconfig/userconfig.go`](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go)](https://github.com/zakirullin/files.md/blob/main/server/userconfig/userconfig.go) | Manages per-user [`config.json`](https://github.com/zakirullin/files.md/blob/main/config.json) (timezones, quick-buttons) |
| **Example Plugin** | [[`server/plugins/world_clock.go`](https://github.com/zakirullin/files.md/blob/main/server/plugins/world_clock.go)](https://github.com/zakirullin/files.md/blob/main/server/plugins/world_clock.go) | Demonstrates autonomous text processing without bot UI |

## Implementation Examples

### Initializing the Update Loop and Supervisors

The main function in [[`cmd/server/server.go`](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go)](https://github.com/zakirullin/files.md/blob/main/cmd/server/server.go#L48-L66) demonstrates the startup sequence and long-polling mechanism:

```go
func main() {
    _ = godotenv.Load()
    _ = config.LoadBotConfig()

    api, err := tgbotapi.NewBotAPI(config.ServerCfg.BotAPIToken)
    if err != nil {
        fmt.Printf("No Telegram bot token: %s\n", err)
        select {} // web-only mode
    }
    telegram := tg.NewTG(api)

    tgConfig := tgbotapi.NewUpdate(0)
    tgConfig.Timeout = 60
    updates := api.GetUpdatesChan(tgConfig)

    userChannels := map[int64]chan tgbotapi.Update{}
    for upd := range updates {
        tgUpd := tg.NewTGUpd(upd)
        userID, _ := resolveUserID(tgUpd, telegram)

        ch, ok := userChannels[userID]
        if !ok {
            ch = make(chan tgbotapi.Update, 100)
            userChannels[userID] = ch
            go supervisor(userID, ch, telegram)
        }
        ch <- upd
    }
}

```

### Sending Messages with Inline Keyboards

The [[`server/pkg/tg/tg.go`](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go)](https://github.com/zakirullin/files.md/blob/main/server/pkg/tg/tg.go#L34-L46) wrapper simplifies message composition:

```go
func (tg *TG) SendHTML(userID int64, text string, kb *Keyboard) (int, error) {
    return tg.Send(userID, text, kb, MarkupHTML)
}

// Usage in a command handler
func (b *Bot) showHelp(_ []string) error {
    kb := tg.NewInlineKeyboard(
        tg.Button{Text: "Docs", URL: "https://github.com/zakirullin/files.md"},
        tg.Button{Text: "Close", CallbackData: CmdDoNothing},
    )
    _, err := b.tg.SendHTML(b.userID, "🤖 Welcome to Files Bot!", kb)
    return err
}

```

### Registering a New Command Handler

Adding commands requires three steps in [[`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go)](https://github.com/zakirullin/files.md/blob/main/server/bot.go#L24-L33):

```go
// 1. Define constant
const CmdPing = "ping"

// 2. Register in handler map
handlers[CmdPing] = b.handlePing

// 3. Implement handler
func (b *Bot) handlePing(_ []string) error {
    _, err := b.tg.Send(b.userID, "🏓 Pong!", nil, tg.MarkupHTML)
    return err
}

```

## Summary

- The Zakirullin Files bot uses a **single-binary architecture** combining Telegram polling, HTTP serving, and background workers in one Go process.
- **Per-user goroutines** with supervisor patterns ensure sequential update processing while enabling parallel user handling.
- **UserFS** treats markdown files as the primary data store, creating a natural bridge between the Telegram bot and the web-based PWA.
- The **Bot.Reply** method serves as the central router, handling inline queries, plugins, commands, and content persistence through a deterministic decision tree.
- All file operations flow through the `server/fs` package, maintaining consistency between chat interactions and sync API operations.

## Frequently Asked Questions

### How does the bot handle concurrent updates from multiple users?

Each user receives a dedicated goroutine with a buffered channel. When an update arrives, the main loop routes it to the user's specific channel, ensuring sequential processing per user while allowing parallel execution across different users. This eliminates file corruption risks without requiring complex locking mechanisms.

### What storage backend does the bot use for notes and messages?

The bot uses the local filesystem as its primary storage backend. User data resides in `storage/<userID>/*.md` markdown files accessed through the `UserFS` abstraction. Transient runtime state stays in an in-memory map, and user preferences are stored in JSON configuration files.

### Can the bot function without a Telegram token?

Yes. If the Telegram Bot API token is missing or invalid, the binary enters "web-only mode" by blocking indefinitely with `select {}`, allowing the HTTP sync server and PWA to operate independently of the Telegram integration.

### How are commands processed within the bot architecture?

Commands flow through the `Bot.Reply` method in [`server/bot.go`](https://github.com/zakirullin/files.md/blob/main/server/bot.go), which extracts the command string and dispatches to a predefined handler map. Developers register new commands by defining a constant, adding an entry to the handler map, and implementing a method with the signature `func ([]string) error`.