(doc): CHANGELOG v1.0.0, README MessageContext + Runners, TODO wiki done
(fix): typo in scene overwrite warning
This commit is contained in:
+20
-1
@@ -10,8 +10,15 @@
|
||||
- Renamed final public APIs to idiomatic names before the stable release: `RunWebhookWithContext(...)`, `RunWebhook(...)`, `CloseWebhook()`, `BotWebhookOpts`, `NewBotWebhookOpts()`, `SetWebhookLogger(...)`, and `GetWebhookLogger()`.
|
||||
- Renamed plugin builder helpers from `NewCommand(...)` and `NewScene(...)` to `Command(...)` and `Scene(...)`; the surviving `NewCommand(...)` takes the command string before the executor.
|
||||
- Renamed command argument value constants to `CommandValueString`, `CommandValueInt`, `CommandValueBool`, and `CommandValueAny`; `NewCommandArg(...)` now defaults to unvalidated `CommandValueAny`.
|
||||
- Renamed runner builders from `Onetime(...)` and `Timeout(...)` to `Once(...)` and `Every(...)`.
|
||||
- Renamed runner builders from `Onetime(...)` and `Timeout(...)` to `Every(...)` and `Async(...)`; `Runner.Once()` is removed. Use the default configuration (every=0, async=true) for a fire-and-forget goroutine, or `Async(false)` for a synchronous blocking one-shot.
|
||||
- Renamed remaining public acronym/casing outliers including `AnswerCallback...`, `ParseMarkdownV2`, `ParseMarkdown`, `GetChatMemberCount`, `DropRateLimitOverflow`, `SetDropRateLimitOverflow`, and inline keyboard builder APIs.
|
||||
- Renamed `Observer` event delivery methods `OnReceiveUpdate` → `OnUpdateReceived` and `OnHandledUpdate` → `OnUpdateHandled` to match the `UpdateReceivedEvent`/`UpdateHandledEvent` names and the `OnX` pattern of all other observer methods.
|
||||
- `Scene.PluginName` is now unexported; it is assigned by the framework during plugin registration and must not be set by callers.
|
||||
- `SceneSession.Data` is now unexported; use the `Set`/`Get`/`HasData`/`ClearData`/`BindData`/`SaveData` helpers instead.
|
||||
- `BotPayloadType*` sentinels are now `const` instead of `var`; code that assigned to them will no longer compile.
|
||||
|
||||
### Bot API 10.0
|
||||
- Added full support for Telegram Bot API 10.0 types, methods, and update kinds.
|
||||
|
||||
### Added
|
||||
- Added `MessageContext.IsCallback()` and `MessageContext.HasPhoto()` helpers for callback-aware handler code.
|
||||
@@ -24,6 +31,9 @@
|
||||
- Added `RateLimiter.Cleanup(idleThreshold)` to evict per-chat limiter state and expired chat cooldowns; the limiter now tracks per-chat last-seen time so long-running bots can bound memory through a periodic runner.
|
||||
- Added cached bot identity (`Bot.userID`) populated at `NewBot` so chat-admin policies and similar lookups reuse it instead of issuing a fresh `GetMe` request.
|
||||
- Added `tgapi.ResponseError` so Telegram API error codes, descriptions, and response parameters remain inspectable through returned errors.
|
||||
- Added nine exported webhook error sentinels — `ErrSetWebhookFailed`, `ErrBotAPINil`, `ErrBotWebhookOptsEmptyPath`, `ErrBotWebhookOptsPathNoSlash`, `ErrBotWebhookOptsPathHasQueryOrFragment`, `ErrBotWebhookOptsPathCollidesStatus`, `ErrBotWebhookTLSFilesIncomplete`, `ErrBotWebhookTLSFilesTooMany`, and `ErrStatusPathSecretRequired` — replacing the previous inline `errors.New(...)` calls so callers can match webhook startup errors with `errors.Is`.
|
||||
- Added `ErrInvalidPayload` for compact payload decoding failures so callers can distinguish malformed payload bytes from other decode errors.
|
||||
- Panics inside `Bot.handle` and the polling goroutine now emit an `ErrorEvent` through the observer so instrumentation sees runtime panics in addition to normal handler errors.
|
||||
|
||||
### Changed
|
||||
- Version metadata now reports the stable `v1.0.0` release instead of `v1.0.0-rc.16`.
|
||||
@@ -47,6 +57,10 @@
|
||||
- Fixed webhook startup so empty-secret warnings are logged only after the webhook logger is initialized.
|
||||
- Fixed webhook startup so a logger configured through `SetWebhookLogger(...)` is preserved.
|
||||
- Fixed long-polling 429 handling so `getUpdates` retries use Telegram `retry_after` directly and do not inflate later transient-error backoff.
|
||||
- Fixed `BotOptsFileJSON` silently dropping `PollTimeout` on round-trip; the field is now encoded and decoded correctly.
|
||||
- Fixed the `tgapi.Uploader` returning an ad-hoc error string on Telegram API failures; it now returns `*tgapi.ResponseError` matching the JSON API client, so `errors.As(err, &respErr)` works consistently for both upload and JSON paths.
|
||||
- Fixed webhook secret validation to use `subtle.ConstantTimeCompare` instead of a plain string equality check, removing the timing side-channel.
|
||||
- Fixed the `/status` handler returning HTTP 403 for a wrong secret, which disclosed endpoint existence; it now returns 404 uniformly for any unauthenticated request.
|
||||
|
||||
### Tests
|
||||
- Added regression coverage proving bot-level middleware blocks still complete the observer update lifecycle.
|
||||
@@ -61,6 +75,11 @@
|
||||
- Added regression coverage for `Draft.Push` preserving the existing message when validation rejects the candidate.
|
||||
- Added regression coverage for `RateLimiter.Cleanup` evicting idle chat limiters and expired chat locks while leaving active state in place.
|
||||
- Updated `MessageContext.Error` tests so unclassified errors stay internal-only and only `AsUserError` reaches the user.
|
||||
- Added regression coverage for `BotOptsFileJSON` `PollTimeout` round-trip.
|
||||
- Added regression coverage proving the `tgapi.Uploader` surfaces `*tgapi.ResponseError` for Telegram 4xx responses.
|
||||
- Added regression coverage proving a panic inside `Bot.handle` emits an `ErrorEvent` through the observer.
|
||||
- Added regression coverage for the webhook `/status` endpoint rejecting wrong and same-length-but-different secrets with HTTP 404, and accepting the correct secret.
|
||||
- Added table-driven regression coverage for `parseCommand` with `/cmd@botname` stripping, bare commands, commands with arguments, and empty input.
|
||||
|
||||
## v1.0.0-rc.16
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ import (
|
||||
// It receives two parameters:
|
||||
// - ctx: the message context (contains info about the message, sender, chat, etc.)
|
||||
// - data: your shared application data (here we use NoData, a placeholder for no shared data)
|
||||
func echo(ctx *laniakea.MsgContext, data laniakea.NoData) error {
|
||||
func echo(ctx *laniakea.MessageContext, data laniakea.NoData) error {
|
||||
// Answer the user with the text they sent, without any command prefix.
|
||||
// ctx.Text contains the user's message with the command part stripped off.
|
||||
ctx.Answer(ctx.Text) // User input WITHOUT command
|
||||
@@ -85,7 +85,7 @@ func main() {
|
||||
|
||||
// 5. Add another command using an anonymous function (closure).
|
||||
// This command simply replies "Pong" when the user sends "/ping".
|
||||
p.Command("ping", func(ctx *laniakea.MsgContext, data laniakea.NoData) error {
|
||||
p.Command("ping", func(ctx *laniakea.MessageContext, data laniakea.NoData) error {
|
||||
ctx.Answer("Pong")
|
||||
return nil
|
||||
})
|
||||
@@ -112,8 +112,8 @@ func main() {
|
||||
1. `BotOpts`: Holds configuration like the API token.
|
||||
2. `NewBot[T]`: Creates a bot instance. The type parameter T allows you to pass custom shared application data (for example, *sql.DB or a service container) that will be available in all handlers. Use laniakea.NoData if you don't need it.
|
||||
3. `NewPlugin`: Creates a logical group for commands and middlewares.
|
||||
4. `Command`: Creates and registers a command. The first argument is the command name without the slash, the second is the handler function (`func(*MsgContext, T) error`).
|
||||
5. **Handler Functions**: Receive *MsgContext (message details, methods like Answer) and your custom application data T, and return an error for centralized error handling.
|
||||
4. `Command`: Creates and registers a command. The first argument is the command name without the slash, the second is the handler function (`func(*MessageContext, T) error`).
|
||||
5. **Handler Functions**: Receive *MessageContext (message details, methods like Answer) and your custom application data T, and return an error for centralized error handling.
|
||||
6. `SetErrorTemplate`: Sets a template for error messages. The %s placeholder is replaced by the actual error.
|
||||
7. `AutoGenerateCommands`: Registers plugin-defined commands with Telegram across the supported scopes.
|
||||
8. `Run()`: Starts the bot's update polling loop and returns an error if startup or polling fails.
|
||||
@@ -181,14 +181,14 @@ bot.AddPlugins(plugin)
|
||||
|
||||
A command is a function that handles a specific bot command (e.g., /start).
|
||||
```go
|
||||
func myHandler(ctx *laniakea.MsgContext, db *MyDB) error {
|
||||
func myHandler(ctx *laniakea.MessageContext, db *MyDB) error {
|
||||
// Access command arguments via ctx.Args ([]string)
|
||||
// Reply to the user: ctx.Answer("some text")
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### MsgContext
|
||||
### MessageContext
|
||||
|
||||
Provides access to the incoming message and useful reply methods:
|
||||
|
||||
@@ -200,8 +200,8 @@ Provides access to the incoming message and useful reply methods:
|
||||
- `KeyboardMarkdown(text string, keyboard *InlineKeyboard) *AnswerMessage`: Sends a message formatted with MarkdownV2 (you handle escaping) and inline keyboard.
|
||||
- `AnswerPhoto(photoID, text string) *AnswerMessage`: Sends a message with photo with parse_mode none.
|
||||
- `AnswerPhotoMarkdown(photoID, text string) *AnswerMessage`: Sends a photo with MarkdownV2 caption (you handle escaping).
|
||||
- `EditCallback(text string)`: Edits message with parse_mode none after clicking inline button.
|
||||
- `EditCallbackMarkdown(text string)`: Edits a message formatted with MarkdownV2 (you handle escaping) after clicking inline button.
|
||||
- `EditCallback(text string, keyboard *InlineKeyboard) *AnswerMessage`: Edits message with parse_mode none after clicking inline button.
|
||||
- `EditCallbackMarkdown(text string, keyboard *InlineKeyboard) *AnswerMessage`: Edits a message formatted with MarkdownV2 (you handle escaping) after clicking inline button.
|
||||
- `SendAction(action tgapi.ChatActionType)`: Sends a “typing”, “uploading photo”, etc., action.
|
||||
- Fields: `Text`, `Args`, `From`, `FromID`, `Msg`, `InlineMsgID`, `CallbackQueryID`, etc.
|
||||
- And more methods and fields!
|
||||
@@ -268,6 +268,39 @@ plugin.Scene("signup").
|
||||
- Use `SceneContext.SaveData(...)` and `SceneContext.BindData(...)` for JSON session state.
|
||||
- Use `SceneScopeUser`, `SceneScopeChat`, or `SceneScopeUserChat` depending on how widely a conversation should be shared.
|
||||
|
||||
## ⏱️ Runners
|
||||
|
||||
Runners are background tasks that execute alongside the bot runtime. They are registered before the bot starts and launched automatically when the bot starts.
|
||||
|
||||
```go
|
||||
import "time"
|
||||
|
||||
// One-shot runner — fires once in a goroutine when the bot starts (default).
|
||||
bot.AddRunner(
|
||||
laniakea.NewRunner("seed-cache", func(b *laniakea.Bot[*MyDB]) error {
|
||||
return b.GetAppData().SeedCache()
|
||||
}),
|
||||
)
|
||||
|
||||
// Periodic runner — fires every 10 minutes in a goroutine.
|
||||
bot.AddRunner(
|
||||
laniakea.NewRunner("refresh-stats", func(b *laniakea.Bot[*MyDB]) error {
|
||||
return b.GetAppData().RefreshStats()
|
||||
}).Every(10 * time.Minute),
|
||||
)
|
||||
|
||||
// Synchronous one-shot — blocks runtime startup until it completes.
|
||||
bot.AddRunner(
|
||||
laniakea.NewRunner("migrate", func(b *laniakea.Bot[*MyDB]) error {
|
||||
return b.GetAppData().Migrate()
|
||||
}).Async(false),
|
||||
)
|
||||
```
|
||||
|
||||
Builder methods:
|
||||
- `Async(bool) *Runner[T]` — if `true` (default), runs in a goroutine; if `false`, blocks runtime startup.
|
||||
- `Every(time.Duration) *Runner[T]` — sets the repeat interval. Zero (default) means run once; positive value repeats. Periodic runners require `Async(true)`.
|
||||
|
||||
## 🧩 Middleware
|
||||
Middleware are functions that run before a command handler. They are perfect for cross-cutting concerns like logging, access control, rate limiting, or modifying the context.
|
||||
|
||||
@@ -275,7 +308,7 @@ Middleware are functions that run before a command handler. They are perfect for
|
||||
A middleware function has the same signature as a command handler, but it must return a bool:
|
||||
|
||||
```go
|
||||
func(ctx *MsgContext, db T) bool
|
||||
func(ctx *MessageContext, db T) bool
|
||||
```
|
||||
|
||||
- If it returns true, the next middleware (or the command) will be executed.
|
||||
@@ -295,7 +328,7 @@ plugin.Command("ban", banUser)
|
||||
|
||||
1. Logging Middleware – logs every command execution.
|
||||
```go
|
||||
func loggingMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
func loggingMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool {
|
||||
log.Printf("User %d executed command: %s", ctx.FromID, ctx.Msg.Text)
|
||||
return true // continue to next middleware/command
|
||||
}
|
||||
@@ -303,7 +336,7 @@ func loggingMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
|
||||
2. Admin-Only Middleware – restricts access to users with a specific role.
|
||||
```go
|
||||
func adminOnlyMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
func adminOnlyMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool {
|
||||
if !db.IsAdmin(ctx.FromID) { // assume db has IsAdmin method
|
||||
ctx.Answer("⛔ Access denied. Admins only.")
|
||||
return false // stop execution
|
||||
@@ -313,7 +346,7 @@ func adminOnlyMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
```
|
||||
|
||||
### Important Notes
|
||||
- Middleware can modify the MsgContext (e.g., add custom fields) before the command runs.
|
||||
- Middleware can modify the MessageContext (e.g., add custom fields) before the command runs.
|
||||
|
||||
## ⚙️ Advanced Configuration
|
||||
- **Inline Keyboards**: Build keyboards using `laniakea.NewInlineKeyboardJSON`, `laniakea.NewInlineKeyboardBase64`, or `laniakea.NewInlineKeyboard`. `Bot.SetPayloadType(...)` defines the default payload format, and `InlineKeyboard.SetPayloadType(...)` overrides it for one keyboard.
|
||||
|
||||
+45
-12
@@ -56,7 +56,7 @@ import (
|
||||
// Она получает два параметра:
|
||||
// - ctx: контекст сообщения (содержит информацию о сообщении, отправителе, чате и т.д.)
|
||||
// - data: ваши общие данные приложения (здесь мы используем NoData — заглушку без общих зависимостей)
|
||||
func echo(ctx *laniakea.MsgContext, data laniakea.NoData) error {
|
||||
func echo(ctx *laniakea.MessageContext, data laniakea.NoData) error {
|
||||
// Отвечаем пользователю текстом, который он прислал, без префикса команды.
|
||||
// ctx.Text содержит сообщение пользователя, из которого удалена часть с командой.
|
||||
ctx.Answer(ctx.Text) // Ввод пользователя БЕЗ команды
|
||||
@@ -86,7 +86,7 @@ func main() {
|
||||
|
||||
// 5. Добавляем ещё одну команду, используя анонимную функцию (замыкание).
|
||||
// Эта команда просто отвечает "Pong", когда пользователь отправляет "/ping".
|
||||
p.Command("ping", func(ctx *laniakea.MsgContext, data laniakea.NoData) error {
|
||||
p.Command("ping", func(ctx *laniakea.MessageContext, data laniakea.NoData) error {
|
||||
ctx.Answer("Pong")
|
||||
return nil
|
||||
})
|
||||
@@ -113,8 +113,8 @@ func main() {
|
||||
1. `BotOpts`: Содержит конфигурацию, например, токен API.
|
||||
2. `NewBot[T]`: Создаёт экземпляр бота. Параметр типа T позволяет передать общие данные приложения (например, *sql.DB или контейнер сервисов), которые будут доступны во всех обработчиках. Используйте laniakea.NoData, если они не нужны.
|
||||
3. `NewPlugin`: Создаёт логическую группу для команд и Middleware.
|
||||
4. `Command`: Создаёт и регистрирует команду. Первый аргумент — имя команды без слеша, второй — функция-обработчик (`func(*MsgContext, T) error`).
|
||||
5. **Функции-обработчики**: Получают *MsgContext (детали сообщения, методы типа Answer) и ваши данные приложения типа T, а ошибку возвращают для централизованной обработки.
|
||||
4. `Command`: Создаёт и регистрирует команду. Первый аргумент — имя команды без слеша, второй — функция-обработчик (`func(*MessageContext, T) error`).
|
||||
5. **Функции-обработчики**: Получают *MessageContext (детали сообщения, методы типа Answer) и ваши данные приложения типа T, а ошибку возвращают для централизованной обработки.
|
||||
6. `SetErrorTemplate`: Устанавливает шаблон для сообщений об ошибках. Плейсхолдер %s заменяется на текст ошибки.
|
||||
7. `AutoGenerateCommands`: Регистрирует команды из плагинов в Telegram для поддерживаемых scope.
|
||||
8. `Run()`: Запускает цикл опроса обновлений бота и возвращает ошибку, если старт или polling завершился неуспешно.
|
||||
@@ -182,14 +182,14 @@ bot.AddPlugins(plugin)
|
||||
Команда — это функция, которая обрабатывает конкретную команду бота (например, /start).
|
||||
|
||||
```go
|
||||
func myHandler(ctx *laniakea.MsgContext, db *MyDB) error {
|
||||
func myHandler(ctx *laniakea.MessageContext, db *MyDB) error {
|
||||
// Доступ к аргументам команды через ctx.Args ([]string)
|
||||
// Ответ пользователю: ctx.Answer("какой-то текст")
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
### Контекст сообщения (MsgContext)
|
||||
### Контекст сообщения (MessageContext)
|
||||
Предоставляет доступ к входящему сообщению и полезные методы для ответа:
|
||||
|
||||
- `Answer(text string)`: Отправляет сообщение с parse_mode none.
|
||||
@@ -200,8 +200,8 @@ func myHandler(ctx *laniakea.MsgContext, db *MyDB) error {
|
||||
- `KeyboardMarkdown(text string, keyboard *InlineKeyboard) *AnswerMessage`: Отправляет сообщение, отформатированное MarkdownV2 (экранирование на вашей стороне), и Inline клавиатурой.
|
||||
- `AnswerPhoto(photoID, text string) *AnswerMessage`: Отправляет фотографию с подписью и parse_mode none.
|
||||
- `AnswerPhotoMarkdown(photoID, text string) *AnswerMessage`: Отправляет фотографию с подписью, отформатированной MarkdownV2 (экранирование на вашей стороне).
|
||||
- `EditCallback(text string)`: Редактирует сообщение с `parse_mode` none после нажатия inline-кнопки.
|
||||
- `EditCallbackMarkdown(text string)`: Редактирует сообщение в формате MarkdownV2 (экранирование на вашей стороне) после нажатия inline-кнопки.
|
||||
- `EditCallback(text string, keyboard *InlineKeyboard) *AnswerMessage`: Редактирует сообщение с `parse_mode` none после нажатия inline-кнопки.
|
||||
- `EditCallbackMarkdown(text string, keyboard *InlineKeyboard) *AnswerMessage`: Редактирует сообщение в формате MarkdownV2 (экранирование на вашей стороне) после нажатия inline-кнопки.
|
||||
- `SendAction(action tgapi.ChatActionType)`: Отправляет действие "печатает", "загружает фото" и т.д.
|
||||
- Поля: `Text`, `Args`, `From`, `FromID`, `Msg`, `InlineMsgID`, `CallbackQueryID` и другие.
|
||||
- И много других методов и полей!
|
||||
@@ -256,6 +256,39 @@ plugin.Scene("signup").
|
||||
- Для JSON-состояния сцены используйте `SceneContext.SaveData(...)` и `SceneContext.BindData(...)`.
|
||||
- Выбирайте `SceneScopeUser`, `SceneScopeChat` или `SceneScopeUserChat` в зависимости от того, насколько широко должен разделяться диалог.
|
||||
|
||||
## ⏱️ Раннеры (Runners)
|
||||
|
||||
Раннеры — фоновые задачи, которые выполняются вместе с bot runtime. Они регистрируются до запуска бота и автоматически запускаются при старте.
|
||||
|
||||
```go
|
||||
import "time"
|
||||
|
||||
// Одноразовый раннер — запускается один раз в горутине при старте (по умолчанию).
|
||||
bot.AddRunner(
|
||||
laniakea.NewRunner("seed-cache", func(b *laniakea.Bot[*MyDB]) error {
|
||||
return b.GetAppData().SeedCache()
|
||||
}),
|
||||
)
|
||||
|
||||
// Периодический раннер — запускается каждые 10 минут в горутине.
|
||||
bot.AddRunner(
|
||||
laniakea.NewRunner("refresh-stats", func(b *laniakea.Bot[*MyDB]) error {
|
||||
return b.GetAppData().RefreshStats()
|
||||
}).Every(10 * time.Minute),
|
||||
)
|
||||
|
||||
// Синхронный одноразовый — блокирует запуск runtime до завершения.
|
||||
bot.AddRunner(
|
||||
laniakea.NewRunner("migrate", func(b *laniakea.Bot[*MyDB]) error {
|
||||
return b.GetAppData().Migrate()
|
||||
}).Async(false),
|
||||
)
|
||||
```
|
||||
|
||||
Методы builder:
|
||||
- `Async(bool) *Runner[T]` — если `true` (по умолчанию), запускается в горутине; если `false`, блокирует запуск runtime.
|
||||
- `Every(time.Duration) *Runner[T]` — задаёт интервал повторного запуска. Ноль (по умолчанию) означает одноразовый запуск; положительное значение — периодический. Периодические раннеры требуют `Async(true)`.
|
||||
|
||||
### tgapi: API и Uploader
|
||||
|
||||
В `tgapi` есть два клиента:
|
||||
@@ -272,7 +305,7 @@ Middleware — это функции, которые выполняются пе
|
||||
Функция middleware имеет ту же сигнатуру, что и обработчик команды, но должна возвращать bool:
|
||||
|
||||
```go
|
||||
func(ctx *MsgContext, db T) bool
|
||||
func(ctx *MessageContext, db T) bool
|
||||
```
|
||||
|
||||
- Если возвращается true, выполняется следующий middleware (или сама команда).
|
||||
@@ -292,7 +325,7 @@ plugin.Command("ban", banUser)
|
||||
|
||||
1. Логирующий middleware – логирует каждое выполнение команды.
|
||||
```go
|
||||
func loggingMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
func loggingMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool {
|
||||
log.Printf("Пользователь %d выполнил команду: %s", ctx.FromID, ctx.Msg.Text)
|
||||
return true // продолжаем к следующему middleware/команде
|
||||
}
|
||||
@@ -300,7 +333,7 @@ func loggingMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
|
||||
2. Middleware только для администраторов – ограничивает доступ пользователям с определённой ролью.
|
||||
```go
|
||||
func adminOnlyMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
func adminOnlyMiddleware(ctx *laniakea.MessageContext, db *MyDB) bool {
|
||||
if !db.IsAdmin(ctx.FromID) { // предполагается, что db имеет метод IsAdmin
|
||||
ctx.Answer("⛔ Доступ запрещён. Только для администраторов.")
|
||||
return false // останавливаем выполнение
|
||||
@@ -310,7 +343,7 @@ func adminOnlyMiddleware(ctx *laniakea.MsgContext, db *MyDB) bool {
|
||||
```
|
||||
|
||||
### Важные замечания
|
||||
- Middleware может изменять MsgContext (например, добавлять пользовательские поля) перед запуском команды.
|
||||
- Middleware может изменять MessageContext (например, добавлять пользовательские поля) перед запуском команды.
|
||||
|
||||
## ⚙️ Расширенная настройка
|
||||
- **Инлайн-клавиатуры**: Создавайте клавиатуры с помощью `laniakea.NewInlineKeyboardJSON`, `laniakea.NewInlineKeyboardBase64` или `laniakea.NewInlineKeyboard`. `Bot.SetPayloadType(...)` задаёт payload format по умолчанию, а `InlineKeyboard.SetPayloadType(...)` переопределяет его для конкретной клавиатуры.
|
||||
|
||||
@@ -1,52 +1,5 @@
|
||||
# TODO
|
||||
|
||||
## v1.0.0 pre-release review
|
||||
|
||||
Findings from the full-repo review against `AGENTS.md` priorities. Build, vet, tests, and lint are clean; items below are public-API and godoc hygiene before the stable tag.
|
||||
|
||||
### Major — close before 1.0.0 tag
|
||||
|
||||
- [X] **M1. `BotPayloadType*` are `var`, must be `const`** — `bot.go:50-59`. Public sentinels are user-mutable globals. `KeyboardButtonStyle*` in `keyboard.go:10-17` already uses `const`; match the pattern.
|
||||
- [X] **M2. `Observer` method naming asymmetry** — `observer.go:147-157`. `OnReceiveUpdate` → `OnUpdateReceived`; `OnHandledUpdate` → `OnUpdateHandled` to match `UpdateReceivedEvent` / `UpdateHandledEvent` and the rest of the `OnX` pattern. Breaking after 1.0.
|
||||
- [X] **M3. Uploader returns ad-hoc error string instead of `*ResponseError`** — `tgapi/uploader_api.go:183`. `tgapi/api.go:258-292` returns `*ResponseError`; uploader must do the same so `errors.As(err, &tgapi.ResponseError{})` works for upload paths too.
|
||||
- [X] **M4. `BotOptsFileJSON` is missing `PollTimeout`** — `bot_opts_loader.go:35-46`, plus `FromBytes`/`ToBytes` mapping. File round-trip silently drops `PollTimeout`.
|
||||
- [X] **M5. Stale `Bot.Updates` godoc** — `methods.go:11-44`. Claims "30-second timeout" and "empty slice if none"; in reality timeout is `bot.pollTimeout` and the function returns `nil` on error.
|
||||
- [X] **M6. Self-contradicting `NewRandomDraftProvider` godoc** — `drafts.go:50-59`. Says "cryptographically secure random numbers" but uses `math/rand/v2` (the underlying generator type correctly notes it is not crypto-secure).
|
||||
- [X] **M7. `Draft.Delete` godoc says "internal method"** — `drafts.go:190-201`. Method is exported; either rewrite the godoc with a public-intent description or unexport.
|
||||
- [X] **M8. Russian comments in production code**
|
||||
- `msg_handler.go:28` — "Ищем команду по точному совпадению"
|
||||
- `tgapi/uploader_api.go:181` — "Повторяем запрос"
|
||||
- [X] **M9. `MessageContext.Error` godoc references unexported helper** — `msg_context.go:540`. "Error is an alias for error()" — rewrite to describe the centralized handler error path and `IsUserError` gating.
|
||||
- [X] **M10. `Scene` and `SceneSession` mix exported fields with setters**
|
||||
- `Scene` exports `Name/Scope/Entry/PluginName` and also has `SetScope/SetEntry`; `PluginName` is framework-assigned but publicly mutable.
|
||||
- `SceneSession` exports `Data []byte` and also has `Set/Get/HasData/ClearData/BindData/SaveData`.
|
||||
- Pick one model per type before 1.0.0.
|
||||
- [X] **M11. Constant-time compare for webhook secret** — `bot_webhook.go:296` (update handler) and `bot_webhook.go:341` (`/status`). Use `subtle.ConstantTimeCompare`.
|
||||
|
||||
### Minor — can slip to 1.0.x
|
||||
|
||||
- [X] Strip `// Internal helper …` godoc from unexported funcs (~23 occurrences in repo); `AGENTS.md` explicitly forbids godoc-style comments on unexported declarations without a strong reason.
|
||||
- [X] `Plugin.AddCommand` godoc references unexported field `.command` — `plugins.go:48-49`.
|
||||
- [X] `Runner` builder naming: `runner.Once(true)`, `runner.Async(true)` read awkwardly; consider `SetOnce`/`SetAsync` to match `Set*` on other types, or zero-arg `Once()` + paired `Repeat(every)`.
|
||||
- [X] Typo in webhook error string: `bot_webhook.go:143` — "MaxConnections must between 1 and 100" (missing `be`).
|
||||
- [X] `RunWebhookWithContext` uses inline `errors.New(...)` instead of `Err*` sentinels (`bot_webhook.go:131-156`); rest of the package uses sentinels from `errors.go`.
|
||||
- [X] `tgapi.UpdateTypeManagedBot` (`tgapi/types.go:61`) has no godoc.
|
||||
- [X] `Bot.GetAPI`, `Bot.GetUploader`, `InlineKeyboard.GetMaxRow` have no godoc.
|
||||
- [X] `Bot.L10n` godoc says "Returns empty string if translation not found"; actually returns the key (`l10n.go:48-59`).
|
||||
- [X] `Bot.handle` panic recovery only logs — emit `ErrorEvent` so observers see panics (`handler.go:18-23`).
|
||||
- [X] `handleCallback` vs `handleMessage` differ in plugin-logger assignment: callback assigns unconditionally then falls back to bot logger (`msg_handler.go:209-212`); message only assigns if non-nil (`msg_handler.go:35-37`). Align.
|
||||
- [X] `SetCallbackData` godoc says "default payload type is JSON" — actually the zero `BotPayloadType` falls through to the `default` branch (which happens to be JSON). Either document the zero-value behavior explicitly or initialize the builder with the bot's default (`keyboard.go:106-122`).
|
||||
- [X] `commands.go:62-66` — empty `case CommandValueAny:` next to `default: regex = nil` looks like an incomplete switch. Merge or add a one-line comment.
|
||||
- [X] `Bot.SetDebug` does not call `configMutable` unlike sibling setters; if intentional, note it in godoc.
|
||||
|
||||
### Tests to add after the fixes
|
||||
|
||||
- [X] `BotOptsFileJSON` round-trip for `PollTimeout` (after M4).
|
||||
- [X] Uploader 4xx/429 surfaces `*tgapi.ResponseError` (after M3).
|
||||
- [X] `Bot.handle` panic → observer receives `ErrorEvent` (after panic-recovery fix).
|
||||
- [X] Webhook `/status` with wrong `SecretToken` returns 403 / `403`-equivalent (after M11), incl. a constant-time-compare smoke.
|
||||
- [X] Table-driven `parseCommand` cases for `/cmd@botname` and stripping behavior.
|
||||
|
||||
---
|
||||
|
||||
The framework backlog has moved to the wiki.
|
||||
|
||||
+1
-1
@@ -107,7 +107,7 @@ func (p *Plugin[T]) AddScene(scene *Scene[T]) *Plugin[T] {
|
||||
}
|
||||
scene.pluginName = p.name
|
||||
if _, exists := p.scenes[scene.name]; exists && p.logger != nil {
|
||||
p.logger.Warnf("scene '%s'да already registered in plugin '%s'; overwriting", scene.name, p.name)
|
||||
p.logger.Warnf("scene '%s' already registered in plugin '%s'; overwriting", scene.name, p.name)
|
||||
}
|
||||
p.scenes[scene.name] = scene
|
||||
return p
|
||||
|
||||
Reference in New Issue
Block a user