Skip to content
Alita Robot
Esc
navigateopen⌘Jpreview
On this page

Contributing

How to contribute to Alita Robot development.

Thank you for your interest in contributing to Alita Robot! This guide will help you get started with development.

Development Setup

Prerequisites

  • Go 1.26+
  • PostgreSQL 16+
  • Redis 7+
  • Make (for running commands)

Clone and Setup

Clone the repository

git clone https://github.com/divkix/Alita_Robot.git
cd Alita_Robot

Configure environment

cp sample.env .env
# Edit .env with your configuration

Verify the build

make lint
make test
make run

Essential Commands

make run          # Run the bot locally
make build        # Build release artifacts using goreleaser
make lint         # Run golangci-lint for code quality checks
make test         # Run test suite
make tidy         # Clean up and download go.mod dependencies

Database Commands

make psql-migrate  # Apply all pending PostgreSQL migrations
make psql-status   # Check current migration status
make psql-reset    # Reset database (DANGEROUS: drops all tables)

Useful Environment Variables

Variable Purpose
DEBUG=true Enable verbose logging and stack traces
AUTO_MIGRATE=true Apply database migrations on startup
ENABLE_DB_MONITORING=true Log database pool stats every minute
ENABLE_PPROF=true Expose pprof profiling endpoints (dev only)

Project Structure

Alita_Robot/
├── alita/
│   ├── config/       # Configuration and environment parsing
│   ├── db/           # Database operations and repositories
│   ├── i18n/         # Internationalization
│   ├── modules/      # Command handlers (one file per module)
│   └── utils/        # Utility functions and decorators
├── locales/          # Translation files (YAML)
├── migrations/       # SQL migration files
└── main.go           # Entry point

Adding a New Module

Create database operations

Add them in alita/db/{module}/repository.go (following the domain-package pattern).

Implement command handlers

Put them in alita/modules/{module}.go.

Register commands

Register commands in a LoadXxx(dispatcher) function.

Add translations

Add translations to locales/en.yml (and other locale files).

Self-register in init()

Use RegisterLegacyModule("MyModule", priority, LoadMyModule) — modules auto-load via LoadAllModules() in alita/main.go.

Module Template

package modules

import (
    "github.com/PaulSonOfLars/gotgbot/v2"
    "github.com/PaulSonOfLars/gotgbot/v2/ext"
    "github.com/PaulSonOfLars/gotgbot/v2/ext/handlers"
)

var myModule = moduleStruct{moduleName: "MyModule"}

func (m moduleStruct) myCommand(b *gotgbot.Bot, ctx *ext.Context) error {
    // Implementation
    return ext.EndGroups
}

func LoadMyModule(d *ext.Dispatcher) {
    d.AddHandler(handlers.NewCommand("mycommand", myModule.myCommand))
}

func init() {
    RegisterLegacyModule("MyModule", 100, LoadMyModule)
}

Code Style

The repository uses pre-commit hooks that run automatically on git commit:

  • golangci-lint — Code quality and lint checks
  • gofmt — Code formatting enforcement
  • go mod tidy — Dependency cleanup

Install hooks:

pip install pre-commit && pre-commit install
  • Run make lint before committing
  • Run make test before committing
  • Follow Go conventions and idioms
  • Add proper error handling with panic recovery
  • Use decorators for common middleware (permissions, error handling)

Security Best Practices

HTML Escaping

Always escape user-controlled input before rendering in HTML-formatted messages:

import "github.com/divkix/Alita_Robot/alita/utils/formatting"

// Wrong - vulnerable to HTML injection
text := fmt.Sprintf("Welcome to %s!", chat.Title)

// Correct - escaped
text := fmt.Sprintf("Welcome to %s!", formatting.HtmlEscape(chat.Title))

When to escape:

  • Chat titles and descriptions
  • Usernames (when displaying as text, not as @mentions)
  • Any user-supplied text in HTML-formatted messages

Safe alternatives:

  • formatting.MentionHtml(userId, name) - Already escapes the name
  • formatting.MentionUrl(url, name) - Already escapes the name

Goroutine Error Handling

When running database operations in goroutines, always:

  1. Capture variables for closure safety
  2. Add panic recovery
  3. Handle and log errors
// Correct pattern
chatId := chat.Id  // Capture variable
go func() {
    defer error_handling.RecoverFromPanic("SetAnonAdminMode", "admin")
    if err := db.SetAnonAdminMode(chatId, true); err != nil {
        log.Errorf("[Admin] Failed to set anon admin mode: %v", err)
    }
}()

User Input Validation

  • Never trust usernames from user input for security-critical operations
  • Validate user IDs against Telegram API when necessary
  • Use extraction.ExtractUserAndText() for consistent user resolution

Translation Guidelines

Add help messages to locales/en.yml:

mymodule_help_msg: |
  Help text for my module.

  *Commands:*
  × /mycommand: Description of command.

Testing

Automated regression tests are required for all contributions:

  1. Run make test (or go test ./...) and make sure all tests pass
  2. Run make lint and fix any lint findings
  3. Manually verify behavior with a test bot/group for user-facing changes

Submitting Changes

Fork the repository

Create a feature branch

git checkout -b feature/my-feature

Make your changes

Run quality checks

make test && make lint

Commit with a descriptive message

Push to your fork

Open a Pull Request

Common Pitfalls

These are common bugs to avoid when developing modules:

Nil Pointer on User Extraction

Problem: ctx.EffectiveSender.User can be nil for channel posts.

// Wrong - will panic on channel posts
user := ctx.EffectiveSender.User
userId := user.Id

// Correct - use the safe helper
user := chat_status.RequireUser(b, ctx, false)
if user == nil {
    return ext.EndGroups
}

Shadow Variables in Conditionals

Problem: Using := inside conditionals creates a new variable that shadows the outer one.

// Wrong - shadows outer userId
if condition {
    userId := someValue  // New variable!
}
// userId here is still the original value

// Correct - reassigns the outer variable
if condition {
    userId = someValue  // Reassigns existing variable
}

Empty Slice Access

Problem: Accessing slice elements without bounds checking.

// Wrong - panics if args is empty
args := strings.Fields(input)
firstArg := args[0]

// Correct - check length first
args := strings.Fields(input)
if len(args) == 0 {
    // Handle empty case
    return
}
firstArg := args[0]

Callback Data Validation

Problem: Not validating callback query data before parsing.

// Wrong - panics on malformed data
args := strings.Split(query.Data, ".")
action := args[1]
userId, _ := strconv.Atoi(args[2])

// Correct - validate first
args := strings.Split(query.Data, ".")
if len(args) < 3 {
    log.Error("Malformed callback data")
    return ext.EndGroups
}
action := args[1]
userId, err := strconv.Atoi(args[2])
if err != nil {
    log.Error("Invalid userId in callback")
    return ext.EndGroups
}

Goroutine Variable Capture

Problem: Goroutines capturing loop variables or mutable state.

// Risky - captures variables by reference
go func() {
    doSomething(userId)  // userId might change
}()

// Safer - pass as parameters
go func(uid int64) {
    doSomething(uid)
}(userId)

Markdown/HTML Parse Mode Mismatch

Problem: Locale strings use Markdown formatting (*bold*, `code`) but the bot sends messages with HTML parse mode, causing raw asterisks to appear instead of formatted text.

// Wrong - locale uses Markdown, but sending with HTML parse mode
helpMsg, _ := tr.GetString("module_help_msg")  // Contains *bold*
b.SendMessage(chatId, helpMsg, &gotgbot.SendMessageOpts{
    ParseMode: helpers.HTML,  // Markdown won't render!
})

// Correct - convert Markdown to HTML before sending
helpMsg, _ := tr.GetString("module_help_msg")
htmlMsg := tgmd2html.MD2HTMLV2(helpMsg)  // Converts *bold* to <b>bold</b>
b.SendMessage(chatId, htmlMsg, &gotgbot.SendMessageOpts{
    ParseMode: helpers.HTML,
})

The tgmd2html library provides conversion functions:

  • tgmd2html.MD2HTMLV2(text) - Converts Markdown formatting to HTML
  • tgmd2html.MD2HTMLButtonsV2(text) - Converts and extracts inline buttons

Getting Help

Was this page helpful?