Compare commits
2 Commits
v1.0.0-bet
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
fb81bb91bd
|
|||
|
589e11b22d
|
206
bot.go
206
bot.go
@@ -1,41 +1,9 @@
|
|||||||
// Package laniakea provides a modular, extensible framework for building scalable
|
|
||||||
// Telegram bots with support for plugins, middleware, localization, draft messages,
|
|
||||||
// rate limiting, structured logging, and dependency injection.
|
|
||||||
//
|
|
||||||
// The framework is designed around a fluent API for configuration and separation of concerns:
|
|
||||||
//
|
|
||||||
// - Plugins: Handle specific commands or events (e.g., /start, /help)
|
|
||||||
// - Middleware: Intercept and modify updates before plugins run (auth, logging, validation)
|
|
||||||
// - Runners: Background goroutines for cleanup, cron jobs, or monitoring
|
|
||||||
// - DraftProvider: Safely build and resume multi-step messages
|
|
||||||
// - L10n: Multi-language support via key-based translation
|
|
||||||
// - RateLimiter: Enforces Telegram API limits to avoid bans
|
|
||||||
// - Structured Logging: JSON stdout + optional file output with request-level tracing
|
|
||||||
// - Dependency Injection: Inject custom database contexts (e.g., *gorm.DB, *sql.DB)
|
|
||||||
//
|
|
||||||
// Example usage:
|
|
||||||
//
|
|
||||||
// bot := laniakea.NewBot[mydb.DBContext](laniakea.LoadOptsFromEnv()).
|
|
||||||
// DatabaseContext(&myDB).
|
|
||||||
// AddUpdateType(tgapi.UpdateTypeMessage).
|
|
||||||
// AddPrefixes("/", "!").
|
|
||||||
// AddPlugins(&startPlugin, &helpPlugin).
|
|
||||||
// AddMiddleware(&authMiddleware, &logMiddleware).
|
|
||||||
// AddRunner(&cleanupRunner).
|
|
||||||
// AddL10n(l10n.New())
|
|
||||||
//
|
|
||||||
// go bot.Run()
|
|
||||||
//
|
|
||||||
// All methods are thread-safe except direct field access. Use provided accessors
|
|
||||||
// (e.g., GetDBContext, SetUpdateOffset) for safe concurrent access.
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -47,113 +15,6 @@ import (
|
|||||||
"github.com/alitto/pond/v2"
|
"github.com/alitto/pond/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BotOpts holds configuration options for initializing a Bot.
|
|
||||||
//
|
|
||||||
// Values are loaded from environment variables via LoadOptsFromEnv().
|
|
||||||
// Use NewOpts() to create a zero-value struct and set fields manually.
|
|
||||||
type BotOpts struct {
|
|
||||||
// Token is the Telegram bot token (required).
|
|
||||||
Token string
|
|
||||||
|
|
||||||
// UpdateTypes is a semicolon-separated list of update types to listen for.
|
|
||||||
// Example: "message;edited_message;callback_query"
|
|
||||||
// Defaults to empty (Telegram will return all types).
|
|
||||||
UpdateTypes []string
|
|
||||||
|
|
||||||
// Debug enables debug-level logging.
|
|
||||||
Debug bool
|
|
||||||
|
|
||||||
// ErrorTemplate is the format string used to wrap error messages sent to users.
|
|
||||||
// Use "%s" to insert the actual error. Example: "❌ Error: %s"
|
|
||||||
ErrorTemplate string
|
|
||||||
|
|
||||||
// Prefixes is a list of command prefixes (e.g., ["/", "!"]).
|
|
||||||
// Defaults to ["/"] if not set via environment.
|
|
||||||
Prefixes []string
|
|
||||||
|
|
||||||
// LoggerBasePath is the directory where log files are written.
|
|
||||||
// Defaults to "./".
|
|
||||||
LoggerBasePath string
|
|
||||||
|
|
||||||
// UseRequestLogger enables detailed logging of all Telegram API requests.
|
|
||||||
UseRequestLogger bool
|
|
||||||
|
|
||||||
// WriteToFile enables writing logs to files (main.log and requests.log).
|
|
||||||
WriteToFile bool
|
|
||||||
|
|
||||||
// UseTestServer uses Telegram's test server (https://api.test.telegram.org).
|
|
||||||
UseTestServer bool
|
|
||||||
|
|
||||||
// APIUrl overrides the default Telegram API endpoint (useful for proxies or self-hosted).
|
|
||||||
APIUrl string
|
|
||||||
|
|
||||||
// RateLimit is the maximum number of API requests per second.
|
|
||||||
// Telegram allows up to 30 req/s for most bots. Defaults to 30.
|
|
||||||
RateLimit int
|
|
||||||
|
|
||||||
// DropRLOverflow drops incoming updates when rate limit is exceeded instead of queuing.
|
|
||||||
// Use this to prioritize responsiveness over reliability.
|
|
||||||
DropRLOverflow bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewOpts returns a new BotOpts with zero values.
|
|
||||||
func NewOpts() *BotOpts { return new(BotOpts) }
|
|
||||||
|
|
||||||
// LoadOptsFromEnv loads BotOpts from environment variables.
|
|
||||||
//
|
|
||||||
// Environment variables:
|
|
||||||
// - TG_TOKEN: Bot token (required)
|
|
||||||
// - UPDATE_TYPES: semicolon-separated update types (e.g., "message;callback_query")
|
|
||||||
// - DEBUG: "true" to enable debug logging
|
|
||||||
// - ERROR_TEMPLATE: format string for error messages (e.g., "❌ %s")
|
|
||||||
// - PREFIXES: semicolon-separated prefixes (e.g., "/;!bot")
|
|
||||||
// - LOGGER_BASE_PATH: directory for log files (default: "./")
|
|
||||||
// - USE_REQ_LOG: "true" to enable request logging
|
|
||||||
// - WRITE_TO_FILE: "true" to write logs to files
|
|
||||||
// - USE_TEST_SERVER: "true" to use Telegram test server
|
|
||||||
// - API_URL: custom API endpoint
|
|
||||||
// - RATE_LIMIT: max requests per second (default: 30)
|
|
||||||
// - DROP_RL_OVERFLOW: "true" to drop updates on rate limit overflow
|
|
||||||
//
|
|
||||||
// Returns a populated BotOpts. If TG_TOKEN is missing, behavior is undefined.
|
|
||||||
func LoadOptsFromEnv() *BotOpts {
|
|
||||||
rateLimit := 30
|
|
||||||
if rl := os.Getenv("RATE_LIMIT"); rl != "" {
|
|
||||||
if n, err := strconv.Atoi(rl); err == nil {
|
|
||||||
rateLimit = n
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &BotOpts{
|
|
||||||
Token: os.Getenv("TG_TOKEN"),
|
|
||||||
UpdateTypes: strings.Split(os.Getenv("UPDATE_TYPES"), ";"),
|
|
||||||
|
|
||||||
Debug: os.Getenv("DEBUG") == "true",
|
|
||||||
ErrorTemplate: os.Getenv("ERROR_TEMPLATE"),
|
|
||||||
Prefixes: LoadPrefixesFromEnv(),
|
|
||||||
|
|
||||||
LoggerBasePath: os.Getenv("LOGGER_BASE_PATH"),
|
|
||||||
UseRequestLogger: os.Getenv("USE_REQ_LOG") == "true",
|
|
||||||
WriteToFile: os.Getenv("WRITE_TO_FILE") == "true",
|
|
||||||
|
|
||||||
UseTestServer: os.Getenv("USE_TEST_SERVER") == "true",
|
|
||||||
APIUrl: os.Getenv("API_URL"),
|
|
||||||
|
|
||||||
RateLimit: rateLimit,
|
|
||||||
DropRLOverflow: os.Getenv("DROP_RL_OVERFLOW") == "true",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// LoadPrefixesFromEnv returns the PREFIXES environment variable split by semicolon.
|
|
||||||
// Defaults to ["/"] if not set.
|
|
||||||
func LoadPrefixesFromEnv() []string {
|
|
||||||
prefixesS, exists := os.LookupEnv("PREFIXES")
|
|
||||||
if !exists {
|
|
||||||
return []string{"/"}
|
|
||||||
}
|
|
||||||
return strings.Split(prefixesS, ";")
|
|
||||||
}
|
|
||||||
|
|
||||||
// DbContext is an interface representing the application's database context.
|
// DbContext is an interface representing the application's database context.
|
||||||
// It is injected into plugins and middleware via Bot.DatabaseContext().
|
// It is injected into plugins and middleware via Bot.DatabaseContext().
|
||||||
//
|
//
|
||||||
@@ -169,6 +30,10 @@ type DbContext any
|
|||||||
// Use Bot[NoDB] to indicate no dependency injection is required.
|
// Use Bot[NoDB] to indicate no dependency injection is required.
|
||||||
type NoDB struct{ DbContext }
|
type NoDB struct{ DbContext }
|
||||||
|
|
||||||
|
// DbLogger is a function type that returns a slog.LoggerWriter for database logging.
|
||||||
|
// Used to inject database-specific log output (e.g., SQL queries, ORM events).
|
||||||
|
type DbLogger[T DbContext] func(db *T) slog.LoggerWriter
|
||||||
|
|
||||||
// BotPayloadType defines the serialization format for callback data payloads.
|
// BotPayloadType defines the serialization format for callback data payloads.
|
||||||
type BotPayloadType string
|
type BotPayloadType string
|
||||||
|
|
||||||
@@ -195,6 +60,7 @@ type Bot[T DbContext] struct {
|
|||||||
errorTemplate string
|
errorTemplate string
|
||||||
username string
|
username string
|
||||||
payloadType BotPayloadType
|
payloadType BotPayloadType
|
||||||
|
maxWorkers int
|
||||||
|
|
||||||
logger *slog.Logger // Main bot logger (JSON stdout + optional file)
|
logger *slog.Logger // Main bot logger (JSON stdout + optional file)
|
||||||
RequestLogger *slog.Logger // Optional request-level API logging
|
RequestLogger *slog.Logger // Optional request-level API logging
|
||||||
@@ -254,10 +120,16 @@ func NewBot[T any](opts *BotOpts) *Bot[T] {
|
|||||||
prefixes = []string{"/"}
|
prefixes = []string{"/"}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
workers := 32
|
||||||
|
if opts.MaxWorkers > 0 {
|
||||||
|
workers = opts.MaxWorkers
|
||||||
|
}
|
||||||
|
|
||||||
bot := &Bot[T]{
|
bot := &Bot[T]{
|
||||||
updateOffset: 0,
|
updateOffset: 0,
|
||||||
errorTemplate: "%s",
|
errorTemplate: "%s",
|
||||||
payloadType: BotPayloadBase64,
|
payloadType: BotPayloadBase64,
|
||||||
|
maxWorkers: workers,
|
||||||
updateQueue: updateQueue,
|
updateQueue: updateQueue,
|
||||||
api: api,
|
api: api,
|
||||||
uploader: uploader,
|
uploader: uploader,
|
||||||
@@ -398,34 +270,6 @@ func (bot *Bot[T]) SetDraftProvider(p *DraftProvider) *Bot[T] {
|
|||||||
return bot
|
return bot
|
||||||
}
|
}
|
||||||
|
|
||||||
// DbLogger is a function type that returns a slog.LoggerWriter for database logging.
|
|
||||||
// Used to inject database-specific log output (e.g., SQL queries, ORM events).
|
|
||||||
type DbLogger[T DbContext] func(db *T) slog.LoggerWriter
|
|
||||||
|
|
||||||
// AddDatabaseLoggerWriter adds a database logger writer to all loggers.
|
|
||||||
//
|
|
||||||
// The writer will receive logs from:
|
|
||||||
// - Main bot logger
|
|
||||||
// - Request logger (if enabled)
|
|
||||||
// - API and Uploader loggers
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// bot.AddDatabaseLoggerWriter(func(db *MyDB) slog.LoggerWriter {
|
|
||||||
// return db.QueryLogger()
|
|
||||||
// })
|
|
||||||
func (bot *Bot[T]) AddDatabaseLoggerWriter(writer DbLogger[T]) *Bot[T] {
|
|
||||||
w := writer(bot.dbContext)
|
|
||||||
bot.logger.AddWriter(w)
|
|
||||||
if bot.RequestLogger != nil {
|
|
||||||
bot.RequestLogger.AddWriter(w)
|
|
||||||
}
|
|
||||||
for _, l := range bot.extraLoggers {
|
|
||||||
l.AddWriter(w)
|
|
||||||
}
|
|
||||||
return bot
|
|
||||||
}
|
|
||||||
|
|
||||||
// DatabaseContext injects a database context into the bot.
|
// DatabaseContext injects a database context into the bot.
|
||||||
// This context is accessible to plugins and middleware via GetDBContext().
|
// This context is accessible to plugins and middleware via GetDBContext().
|
||||||
func (bot *Bot[T]) DatabaseContext(ctx *T) *Bot[T] {
|
func (bot *Bot[T]) DatabaseContext(ctx *T) *Bot[T] {
|
||||||
@@ -569,13 +413,37 @@ func (bot *Bot[T]) AddL10n(l *L10n) *Bot[T] {
|
|||||||
return bot
|
return bot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddDatabaseLoggerWriter adds a database logger writer to all loggers.
|
||||||
|
//
|
||||||
|
// The writer will receive logs from:
|
||||||
|
// - Main bot logger
|
||||||
|
// - Request logger (if enabled)
|
||||||
|
// - API and Uploader loggers
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
//
|
||||||
|
// bot.AddDatabaseLoggerWriter(func(db *MyDB) slog.LoggerWriter {
|
||||||
|
// return db.QueryLogger()
|
||||||
|
// })
|
||||||
|
func (bot *Bot[T]) AddDatabaseLoggerWriter(writer DbLogger[T]) *Bot[T] {
|
||||||
|
w := writer(bot.dbContext)
|
||||||
|
bot.logger.AddWriter(w)
|
||||||
|
if bot.RequestLogger != nil {
|
||||||
|
bot.RequestLogger.AddWriter(w)
|
||||||
|
}
|
||||||
|
for _, l := range bot.extraLoggers {
|
||||||
|
l.AddWriter(w)
|
||||||
|
}
|
||||||
|
return bot
|
||||||
|
}
|
||||||
|
|
||||||
// RunWithContext starts the bot with a given context for graceful shutdown.
|
// RunWithContext starts the bot with a given context for graceful shutdown.
|
||||||
//
|
//
|
||||||
// This is the main entry point for bot execution. It:
|
// This is the main entry point for bot execution. It:
|
||||||
// - Validates required configuration (prefixes, plugins)
|
// - Validates required configuration (prefixes, plugins)
|
||||||
// - Starts all registered runners as background goroutines
|
// - Starts all registered runners as background goroutines
|
||||||
// - Begins polling for updates via Telegram's GetUpdates API
|
// - Begins polling for updates via Telegram's GetUpdates API
|
||||||
// - Processes updates concurrently using a worker pool (16 goroutines)
|
// - Processes updates concurrently using a worker pool with size configurable via BotOpts.MaxWorkers
|
||||||
//
|
//
|
||||||
// The context controls graceful shutdown. When canceled, the bot:
|
// The context controls graceful shutdown. When canceled, the bot:
|
||||||
// - Stops polling for new updates
|
// - Stops polling for new updates
|
||||||
@@ -636,7 +504,7 @@ func (bot *Bot[T]) RunWithContext(ctx context.Context) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// Start worker pool for concurrent update handling
|
// Start worker pool for concurrent update handling
|
||||||
pool := pond.NewPool(16)
|
pool := pond.NewPool(bot.maxWorkers)
|
||||||
for update := range bot.updateQueue {
|
for update := range bot.updateQueue {
|
||||||
u := update // capture loop variable
|
u := update // capture loop variable
|
||||||
pool.Submit(func() {
|
pool.Submit(func() {
|
||||||
|
|||||||
226
bot_opts.go
Normal file
226
bot_opts.go
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
package laniakea
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"git.nix13.pw/scuroneko/laniakea/tgapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
// BotOpts holds configuration options for initializing a Bot.
|
||||||
|
//
|
||||||
|
// Values are loaded from environment variables via LoadOptsFromEnv().
|
||||||
|
// Use NewOpts() to create a zero-value struct and set fields manually.
|
||||||
|
type BotOpts struct {
|
||||||
|
// Token is the Telegram bot token (required).
|
||||||
|
Token string
|
||||||
|
|
||||||
|
// UpdateTypes is a list of update types to listen for.
|
||||||
|
// Example: "["message", "edited_message", "callback_query"]"
|
||||||
|
// Defaults to empty (Telegram will return all types).
|
||||||
|
UpdateTypes []tgapi.UpdateType
|
||||||
|
|
||||||
|
// Debug enables debug-level logging.
|
||||||
|
Debug bool
|
||||||
|
|
||||||
|
// ErrorTemplate is the format string used to wrap error messages sent to users.
|
||||||
|
// Use "%s" to insert the actual error. Example: "❌ Error: %s"
|
||||||
|
ErrorTemplate string
|
||||||
|
|
||||||
|
// Prefixes is a list of command prefixes (e.g., ["/", "!"]).
|
||||||
|
// Defaults to ["/"] if not set via environment.
|
||||||
|
Prefixes []string
|
||||||
|
|
||||||
|
// LoggerBasePath is the directory where log files are written.
|
||||||
|
// Defaults to "./".
|
||||||
|
LoggerBasePath string
|
||||||
|
|
||||||
|
// UseRequestLogger enables detailed logging of all Telegram API requests.
|
||||||
|
UseRequestLogger bool
|
||||||
|
|
||||||
|
// WriteToFile enables writing logs to files (main.log and requests.log).
|
||||||
|
WriteToFile bool
|
||||||
|
|
||||||
|
// UseTestServer uses Telegram's test server (https://api.test.telegram.org).
|
||||||
|
UseTestServer bool
|
||||||
|
|
||||||
|
// APIUrl overrides the default Telegram API endpoint (useful for proxies or self-hosted).
|
||||||
|
APIUrl string
|
||||||
|
|
||||||
|
// RateLimit is the maximum number of API requests per second.
|
||||||
|
// Telegram allows up to 30 req/s for most bots. Defaults to 30.
|
||||||
|
RateLimit int
|
||||||
|
|
||||||
|
// DropRLOverflow drops incoming updates when rate limit is exceeded instead of queuing.
|
||||||
|
// Use this to prioritize responsiveness over reliability.
|
||||||
|
DropRLOverflow bool
|
||||||
|
|
||||||
|
// MaxWorkers is the maximum number of concurrency running update handlers.
|
||||||
|
MaxWorkers int
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadOptsFromEnv loads BotOpts from environment variables.
|
||||||
|
//
|
||||||
|
// Environment variables:
|
||||||
|
// - TG_TOKEN: Bot token (required)
|
||||||
|
// - UPDATE_TYPES: semicolon-separated update types (e.g., "message;callback_query")
|
||||||
|
// - DEBUG: "true" to enable debug logging
|
||||||
|
// - ERROR_TEMPLATE: format string for error messages (e.g., "❌ %s")
|
||||||
|
// - PREFIXES: semicolon-separated prefixes (e.g., "/;!bot")
|
||||||
|
// - LOGGER_BASE_PATH: directory for log files (default: "./")
|
||||||
|
// - USE_REQ_LOG: "true" to enable request logging
|
||||||
|
// - WRITE_TO_FILE: "true" to write logs to files
|
||||||
|
// - USE_TEST_SERVER: "true" to use Telegram test server
|
||||||
|
// - API_URL: custom API endpoint
|
||||||
|
// - RATE_LIMIT: max requests per second (default: 30)
|
||||||
|
// - DROP_RL_OVERFLOW: "true" to drop updates on rate limit overflow
|
||||||
|
//
|
||||||
|
// Returns a populated BotOpts. If TG_TOKEN is missing, behavior is undefined.
|
||||||
|
func LoadOptsFromEnv() *BotOpts {
|
||||||
|
rateLimit := 30
|
||||||
|
if rl := os.Getenv("RATE_LIMIT"); rl != "" {
|
||||||
|
if n, err := strconv.Atoi(rl); err == nil {
|
||||||
|
rateLimit = n
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stringUpdateTypes := strings.Split(os.Getenv("UPDATE_TYPES"), ";")
|
||||||
|
updateTypes := make([]tgapi.UpdateType, len(stringUpdateTypes))
|
||||||
|
for i, updateType := range stringUpdateTypes {
|
||||||
|
updateTypes[i] = tgapi.UpdateType(updateType)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &BotOpts{
|
||||||
|
Token: os.Getenv("TG_TOKEN"),
|
||||||
|
UpdateTypes: updateTypes,
|
||||||
|
|
||||||
|
Debug: os.Getenv("DEBUG") == "true",
|
||||||
|
ErrorTemplate: os.Getenv("ERROR_TEMPLATE"),
|
||||||
|
Prefixes: LoadPrefixesFromEnv(),
|
||||||
|
|
||||||
|
LoggerBasePath: os.Getenv("LOGGER_BASE_PATH"),
|
||||||
|
UseRequestLogger: os.Getenv("USE_REQ_LOG") == "true",
|
||||||
|
WriteToFile: os.Getenv("WRITE_TO_FILE") == "true",
|
||||||
|
|
||||||
|
UseTestServer: os.Getenv("USE_TEST_SERVER") == "true",
|
||||||
|
APIUrl: os.Getenv("API_URL"),
|
||||||
|
|
||||||
|
RateLimit: rateLimit,
|
||||||
|
DropRLOverflow: os.Getenv("DROP_RL_OVERFLOW") == "true",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetToken sets the Telegram bot token (required).
|
||||||
|
func (opts *BotOpts) SetToken(token string) *BotOpts {
|
||||||
|
opts.Token = token
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdateTypes sets the list of update types to listen for.
|
||||||
|
// If empty (default), Telegram will return all update types.
|
||||||
|
// Example: opts.SetUpdateTypes("message", "callback_query")
|
||||||
|
func (opts *BotOpts) SetUpdateTypes(types ...tgapi.UpdateType) *BotOpts {
|
||||||
|
opts.UpdateTypes = types
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDebug enables or disables debug-level logging.
|
||||||
|
// Default is false.
|
||||||
|
func (opts *BotOpts) SetDebug(debug bool) *BotOpts {
|
||||||
|
opts.Debug = debug
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetErrorTemplate sets the format string for error messages sent to users.
|
||||||
|
// Use "%s" to insert the actual error. Example: "❌ Error: %s"
|
||||||
|
// If not set, defaults to "%s".
|
||||||
|
func (opts *BotOpts) SetErrorTemplate(tpl string) *BotOpts {
|
||||||
|
opts.ErrorTemplate = tpl
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrefixes sets the command prefixes (e.g., "/", "!").
|
||||||
|
// If not set via environment, defaults to ["/"].
|
||||||
|
func (opts *BotOpts) SetPrefixes(prefixes ...string) *BotOpts {
|
||||||
|
opts.Prefixes = prefixes
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLoggerBasePath sets the directory where log files are written.
|
||||||
|
// Defaults to "./".
|
||||||
|
func (opts *BotOpts) SetLoggerBasePath(path string) *BotOpts {
|
||||||
|
opts.LoggerBasePath = path
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUseRequestLogger enables detailed logging of all Telegram API requests.
|
||||||
|
// Default is false.
|
||||||
|
func (opts *BotOpts) SetUseRequestLogger(use bool) *BotOpts {
|
||||||
|
opts.UseRequestLogger = use
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWriteToFile enables writing logs to files (main.log and requests.log).
|
||||||
|
// Default is false.
|
||||||
|
func (opts *BotOpts) SetWriteToFile(write bool) *BotOpts {
|
||||||
|
opts.WriteToFile = write
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUseTestServer enables using Telegram's test server (https://api.telegram.org/bot<token>/test).
|
||||||
|
// Default is false.
|
||||||
|
func (opts *BotOpts) SetUseTestServer(use bool) *BotOpts {
|
||||||
|
opts.UseTestServer = use
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAPIUrl overrides the default Telegram API endpoint (useful for proxies or self-hosted).
|
||||||
|
// If not set, defaults to "https://api.telegram.org".
|
||||||
|
func (opts *BotOpts) SetAPIUrl(url string) *BotOpts {
|
||||||
|
opts.APIUrl = url
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRateLimit sets the maximum number of API requests per second.
|
||||||
|
// Telegram allows up to 30 req/s for most bots. Defaults to 30.
|
||||||
|
func (opts *BotOpts) SetRateLimit(limit int) *BotOpts {
|
||||||
|
opts.RateLimit = limit
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetDropRLOverflow drops incoming updates when rate limit is exceeded instead of queuing.
|
||||||
|
// Use this to prioritize responsiveness over reliability. Default is false.
|
||||||
|
func (opts *BotOpts) SetDropRLOverflow(drop bool) *BotOpts {
|
||||||
|
opts.DropRLOverflow = drop
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMaxWorkers sets the maximum number of concurrent update handlers.
|
||||||
|
// Must be called before NewBot, as the value is captured during bot creation.
|
||||||
|
//
|
||||||
|
// The optimal value depends on your bot's workload:
|
||||||
|
// - For I/O-bound handlers (e.g., database queries, external API calls), you may
|
||||||
|
// need more workers, but be mindful of downstream service limits.
|
||||||
|
// - For CPU-bound handlers, keep workers close to the number of CPU cores.
|
||||||
|
//
|
||||||
|
// Recommended starting points (adjust based on profiling and monitoring):
|
||||||
|
// - Small to medium bots with fast handlers: 16–32
|
||||||
|
// - Medium to large bots with fast handlers: 32–64
|
||||||
|
// - Large bots with heavy I/O: 64–128 (ensure your infrastructure can handle it)
|
||||||
|
//
|
||||||
|
// The default is 32. Monitor queue length and processing latency to fine-tune.
|
||||||
|
func (opts *BotOpts) SetMaxWorkers(workers int) *BotOpts {
|
||||||
|
opts.MaxWorkers = workers
|
||||||
|
return opts
|
||||||
|
}
|
||||||
|
|
||||||
|
// LoadPrefixesFromEnv returns the PREFIXES environment variable split by semicolon.
|
||||||
|
// Defaults to ["/"] if not set.
|
||||||
|
func LoadPrefixesFromEnv() []string {
|
||||||
|
prefixesS, exists := os.LookupEnv("PREFIXES")
|
||||||
|
if !exists {
|
||||||
|
return []string{"/"}
|
||||||
|
}
|
||||||
|
return strings.Split(prefixesS, ";")
|
||||||
|
}
|
||||||
@@ -1,12 +1,3 @@
|
|||||||
// Package laniakea provides a framework for building Telegram bots with plugin-based
|
|
||||||
// command registration and automatic command scope management.
|
|
||||||
//
|
|
||||||
// This module automatically generates and registers bot commands across different
|
|
||||||
// chat scopes (private, group, admin) based on plugin-defined commands.
|
|
||||||
//
|
|
||||||
// Commands are derived from Plugin and Command structs, with optional descriptions
|
|
||||||
// and argument formatting. Automatic registration avoids manual command setup and
|
|
||||||
// ensures consistency across chat types.
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
55
doc.go
Normal file
55
doc.go
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
Package laniakea provides a modular, extensible framework for building scalable Telegram bots.
|
||||||
|
|
||||||
|
It offers a fluent API for configuration and separates concerns through several core concepts:
|
||||||
|
|
||||||
|
- Bot: The central instance managing API communication, update processing, logging,
|
||||||
|
rate limiting, and dependency injection. Created via NewBot[T].
|
||||||
|
|
||||||
|
- Plugins: Organize commands and payloads into reusable units.
|
||||||
|
A plugin can have multiple commands and shared middlewares.
|
||||||
|
|
||||||
|
- Commands: Named bot commands with descriptions, argument validation, and
|
||||||
|
execution logic. Automatically registrable across different chat scopes.
|
||||||
|
|
||||||
|
- Middleware: Functions that intercept and modify updates before they reach plugins.
|
||||||
|
Useful for authentication, logging, validation, etc. Return false to stop processing.
|
||||||
|
|
||||||
|
- MsgContext: Provides access to the incoming update and convenient methods for
|
||||||
|
responding, editing, deleting, and translating messages. Includes built-in rate limiting
|
||||||
|
and error handling. ⚠️ MarkdownV2 methods require manual escaping via EscapeMarkdownV2().
|
||||||
|
|
||||||
|
- InlineKeyboard: A fluent builder for constructing inline keyboards with styled buttons,
|
||||||
|
icons, URLs, and structured callback data (JSON or Base64).
|
||||||
|
|
||||||
|
- DraftProvider: Manages ephemeral, multi-step message drafts with automatic ID generation
|
||||||
|
(random or linear). Drafts can be built incrementally and flushed atomically.
|
||||||
|
|
||||||
|
- L10n: Simple key-based localization system with fallback language support.
|
||||||
|
|
||||||
|
- Runners: Background goroutines for periodic tasks or one‑off initialization,
|
||||||
|
with configurable timeouts and async execution.
|
||||||
|
|
||||||
|
- RateLimiting & Logging: Built‑in rate limiter (respects Telegram's retry_after)
|
||||||
|
and structured logging (JSON stdout + optional file output) with request‑level tracing.
|
||||||
|
|
||||||
|
- Dependency Injection: Pass any custom database context (e.g., *sql.DB) to all handlers
|
||||||
|
via the type parameter T in Bot[T].
|
||||||
|
|
||||||
|
Example usage:
|
||||||
|
|
||||||
|
bot := laniakea.NewBot[mydb.DBContext](laniakea.LoadOptsFromEnv()).
|
||||||
|
DatabaseContext(&myDB).
|
||||||
|
AddUpdateType(tgapi.UpdateTypeMessage).
|
||||||
|
AddPrefixes("/", "!").
|
||||||
|
AddPlugins(&startPlugin, &helpPlugin).
|
||||||
|
AddMiddleware(&authMiddleware, &logMiddleware).
|
||||||
|
AddRunner(&cleanupRunner).
|
||||||
|
AddL10n(l10n.New())
|
||||||
|
|
||||||
|
bot.Run()
|
||||||
|
|
||||||
|
All public methods are safe for concurrent use unless stated otherwise.
|
||||||
|
Direct field access is not recommended; use provided accessors (e.g., GetDBContext, SetUpdateOffset).
|
||||||
|
*/
|
||||||
|
package laniakea
|
||||||
28
drafts.go
28
drafts.go
@@ -1,31 +1,3 @@
|
|||||||
// Package laniakea provides a safe, high-level interface for managing Telegram
|
|
||||||
// message drafts using the tgapi library. It allows creating, editing, and
|
|
||||||
// flushing drafts with automatic ID generation and optional bulk flushing.
|
|
||||||
//
|
|
||||||
// Drafts are designed to be ephemeral, mutable buffers that can be built up
|
|
||||||
// incrementally and then sent as final messages. The package ensures safe
|
|
||||||
// state management by copying entities and isolating draft contexts.
|
|
||||||
//
|
|
||||||
// Two draft ID generation strategies are supported:
|
|
||||||
// - Random: Cryptographically secure random IDs (default). Ideal for distributed systems.
|
|
||||||
// - Linear: Monotonically increasing IDs. Useful for persistence, debugging, or recovery.
|
|
||||||
//
|
|
||||||
// Example usage:
|
|
||||||
//
|
|
||||||
// provider := laniakea.NewRandomDraftProvider(api)
|
|
||||||
//
|
|
||||||
// draft := provider.NewDraft(tgapi.ParseModeMarkdown)
|
|
||||||
// draft.SetChat(-1001234567890, 0)
|
|
||||||
// draft.Push("*Hello*").Push(" **world**!")
|
|
||||||
// err := draft.Flush() // Sends message and deletes draft
|
|
||||||
// if err != nil {
|
|
||||||
// log.Printf("Failed to send draft: %v", err)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Or flush all pending drafts at once:
|
|
||||||
// err = provider.FlushAll() // Sends all drafts and clears them
|
|
||||||
//
|
|
||||||
// Note: Drafts are NOT thread-safe. Concurrent access requires external synchronization.
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
10
keyboard.go
10
keyboard.go
@@ -1,13 +1,3 @@
|
|||||||
// Package laniakea provides a fluent builder system for constructing Telegram
|
|
||||||
// inline keyboards with callback data and custom styling.
|
|
||||||
//
|
|
||||||
// This package supports:
|
|
||||||
// - Button builders with style (danger/success/primary), icons, URLs, and callbacks
|
|
||||||
// - Line-based keyboard layout with configurable max row size
|
|
||||||
// - Structured, JSON-serialized callback data for bot command routing
|
|
||||||
//
|
|
||||||
// Keyboard construction is stateful and builder-style: methods return the receiver
|
|
||||||
// to enable chaining. Call Get() to finalize and retrieve the tgapi.ReplyMarkup.
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
10
l10n.go
10
l10n.go
@@ -1,13 +1,3 @@
|
|||||||
// Package laniakea provides a simple, key-based localization system for
|
|
||||||
// multi-language text translation.
|
|
||||||
//
|
|
||||||
// The system supports:
|
|
||||||
// - Multiple language entries per key (e.g., "ru", "en", "es")
|
|
||||||
// - Fallback language for missing translations
|
|
||||||
// - Key-as-fallback behavior: if a key or language is not found, returns the key itself
|
|
||||||
//
|
|
||||||
// This is designed for lightweight, static localization in bots or services
|
|
||||||
// where dynamic translation services are unnecessary.
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
// DictEntry represents a single localized entry with language-to-text mappings.
|
// DictEntry represents a single localized entry with language-to-text mappings.
|
||||||
|
|||||||
@@ -1,22 +1,3 @@
|
|||||||
// Package laniakea provides a high-level context-based API for handling Telegram
|
|
||||||
// bot interactions, including message responses, callback queries, inline keyboards,
|
|
||||||
// localization, and message drafting. It wraps tgapi and adds convenience methods
|
|
||||||
// with built-in rate limiting, error handling, and i18n support.
|
|
||||||
//
|
|
||||||
// The core type is MsgContext, which encapsulates the state of a Telegram update
|
|
||||||
// and provides methods to respond, edit, delete, and translate messages.
|
|
||||||
//
|
|
||||||
// # Markdown Safety Warning
|
|
||||||
//
|
|
||||||
// All methods that accept MarkdownV2 formatting (e.g., AnswerMarkdown, EditCallbackfMarkdown)
|
|
||||||
// require that user-provided text be escaped using laniakea.EscapeMarkdownV2().
|
|
||||||
// Failure to escape user input may result in Telegram API errors, malformed messages,
|
|
||||||
// or security issues.
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
//
|
|
||||||
// text := laniakea.EscapeMarkdownV2(userInput)
|
|
||||||
// ctx.AnswerMarkdown("You said: " + text)
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
12
plugins.go
12
plugins.go
@@ -1,15 +1,3 @@
|
|||||||
// Package laniakea provides a structured system for defining and executing
|
|
||||||
// bot commands and payloads with middleware support, argument validation,
|
|
||||||
// and plugin-based organization.
|
|
||||||
//
|
|
||||||
// The core concepts are:
|
|
||||||
// - Command: A named bot command with arguments, description, and executor.
|
|
||||||
// - Plugin: A collection of commands and payloads, with shared middlewares.
|
|
||||||
// - Middleware: Interceptors that can validate, modify, or block execution.
|
|
||||||
// - CommandArg: Type-safe argument definitions with regex validation.
|
|
||||||
//
|
|
||||||
// This system is designed to be used with MsgContext from the laniakea package
|
|
||||||
// to handle Telegram bot interactions in a modular, type-safe way.
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
10
runners.go
10
runners.go
@@ -1,13 +1,3 @@
|
|||||||
// Package laniakea provides a system for managing background and one-time
|
|
||||||
// runner functions that operate on a Bot instance, with support for
|
|
||||||
// asynchronous execution, timeouts, and lifecycle control.
|
|
||||||
//
|
|
||||||
// Runners are used for periodic tasks (e.g., cleanup, stats updates) or
|
|
||||||
// one-time initialization logic. They are executed via Bot.ExecRunners().
|
|
||||||
//
|
|
||||||
// Important: Runners are not thread-safe for concurrent modification.
|
|
||||||
// Builder methods (Onetime, Async, Timeout) must be called sequentially
|
|
||||||
// and only before Execute().
|
|
||||||
package laniakea
|
package laniakea
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package tgapi
|
package tgapi
|
||||||
|
|
||||||
// UpdateType represents the type of an incoming update.
|
// UpdateType represents the type of incoming update.
|
||||||
type UpdateType string
|
type UpdateType string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
const (
|
const (
|
||||||
VersionString = "1.0.0-beta.19"
|
VersionString = "1.0.0-beta.20"
|
||||||
VersionMajor = 1
|
VersionMajor = 1
|
||||||
VersionMinor = 0
|
VersionMinor = 0
|
||||||
VersionPatch = 0
|
VersionPatch = 0
|
||||||
VersionBeta = 19
|
VersionBeta = 20
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user