From d9eba254fc4966c8605f23ef1833dabdc428fdb9 Mon Sep 17 00:00:00 2001 From: ScuroNeko Date: Thu, 26 Mar 2026 18:31:57 +0300 Subject: [PATCH] wip --- AGENTS.md | 44 ++++ Bot-Lifecycle.md | 9 + Commands-and-Plugins.md | 307 ++++++++++++++++++++++++++ Drafts.md | 9 + FAQ.md | 10 + Getting-Started.md | 261 ++++++++++++++++++++++ Home.md | 31 ++- Inline-Keyboards-and-Payloads.md | 10 + Localization.md | 9 + Middleware.md | 10 + Migration.md | 9 + MsgContext.md | 358 +++++++++++++++++++++++++++++++ Page-Priority.md | 30 +++ Rate-Limiting.md | 9 + Recipes.md | 11 + Semver-and-Releases.md | 9 + tgapi-Overview.md | 10 + 17 files changed, 1135 insertions(+), 1 deletion(-) create mode 100644 AGENTS.md create mode 100644 Bot-Lifecycle.md create mode 100644 Commands-and-Plugins.md create mode 100644 Drafts.md create mode 100644 FAQ.md create mode 100644 Getting-Started.md create mode 100644 Inline-Keyboards-and-Payloads.md create mode 100644 Localization.md create mode 100644 Middleware.md create mode 100644 Migration.md create mode 100644 MsgContext.md create mode 100644 Page-Priority.md create mode 100644 Rate-Limiting.md create mode 100644 Recipes.md create mode 100644 Semver-and-Releases.md create mode 100644 tgapi-Overview.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..d5f2e94 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,44 @@ +# AGENTS.md + +## Scope +These instructions apply only to the wiki repository in this directory. + +## Purpose +The wiki is the structured documentation layer for Laniakea. +It should complement the main `README.md` rather than duplicate it line-by-line. + +## Writing goals +- Optimize for navigation and fast orientation. +- Prefer short pages with strong links over giant all-in-one documents. +- Explain design intent, API boundaries, defaults, and migration-relevant behavior. +- Keep examples practical and consistent with the current codebase. +- Sort information by importance, necessity, and expected popularity of use. +- Put setup and first-use material before advanced extension points. +- Apply that ordering consistently on `Home.md`, when adding new pages, and inside each page when listing methods, types, structures, or workflows. +- Documentation should be as detailed as needed to remove ambiguity. +- Add examples whenever they materially improve understanding. + +## Content rules +- Reflect the current public API from the main repository. +- When behavior is version-sensitive, mention the relevant version explicitly. +- Prefer English page titles and stable page names. +- Keep Home.md concise and link-focused. +- Avoid restating low-value godoc verbatim unless the wiki adds context. + +## Page structure +- Start each page with a one-paragraph summary. +- Then list the most important concepts or decisions first. +- Use short sections and flat bullet lists. +- Link related pages whenever a topic crosses page boundaries. +- Within a page, explain the most necessary and most frequently used APIs before less common or advanced ones. +- Prefer complete explanations over minimal notes when the topic has real usage nuance. + +## Maintenance rules +- When adding a new page, update `Home.md` if the page is user-relevant. +- Prefer editing existing pages over creating overlapping pages. +- Keep terminology aligned with the code and README. + +## Commit discipline +- Wiki commit messages should follow the same repository rule: + 1. one short summary line; + 2. up to three additional high-signal lines. diff --git a/Bot-Lifecycle.md b/Bot-Lifecycle.md new file mode 100644 index 0000000..dccecb1 --- /dev/null +++ b/Bot-Lifecycle.md @@ -0,0 +1,9 @@ +# Bot Lifecycle + +This page explains how a bot is configured, started, stopped, and why a `Bot` instance is single-use. + +## This page should cover +- `BotOpts`, defaults, and startup validation; +- `Run`, `RunWithContext`, `Close`, and `CloseRemote`; +- workers, polling, and shutdown behavior; +- which configuration must be finalized before `Run`. diff --git a/Commands-and-Plugins.md b/Commands-and-Plugins.md new file mode 100644 index 0000000..d426576 --- /dev/null +++ b/Commands-and-Plugins.md @@ -0,0 +1,307 @@ +# Commands and Plugins + +Laniakea organizes most bot behavior through plugins. + +If you understand how plugins, commands, payload handlers, and update handlers fit together, the rest of the library becomes much easier to reason about. + +## The important model first + +The normal layering is: +- `Bot` owns runtime, update flow, logging, and API clients +- `Plugin` groups related handlers +- commands handle text commands like `/start` +- payload handlers handle inline-button callback payloads +- update handlers handle non-command Telegram updates + +In practice, most bots start with: +- one or more plugins +- a few commands +- optional plugin middleware +- maybe payload handlers once inline keyboards appear + +## Plugins + +A plugin is a named group of: +- commands +- payload handlers +- update handlers +- shared middleware +- optional plugin logger and close hook + +Example: + +```go +plugin := laniakea.NewPlugin[laniakea.NoDB]("admin") +``` + +Use plugins to group functionality by concern: +- `admin` +- `payments` +- `profile` +- `support` + +That keeps command registration and middleware ownership clear. + +## Command handlers + +The command handler signature is: + +```go +func(ctx *laniakea.MsgContext, db T) error +``` + +Where: +- `ctx` is the current message/update context +- `db` is the dependency value of the bot’s generic type `T` + +Return: +- `nil` on success +- `error` when the centralized bot error flow should handle failure + +Example: + +```go +func start(ctx *laniakea.MsgContext, db *App) error { + ctx.Answer("Welcome") + return nil +} +``` + +## Registering commands + +Create a command with `NewCommand(...)` and add it to a plugin: + +```go +plugin := laniakea.NewPlugin[*App]("main") +plugin.AddCommand(plugin.NewCommand(start, "start")) +``` + +The command name: +- must not include the slash +- is matched against the parsed command token + +So: +- `"start"` matches `/start` +- `"help"` matches `/help` + +## The easiest command example + +```go +func echo(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + if ctx.Text == "" { + ctx.Answer("Usage: /echo ") + return nil + } + + ctx.Answer(ctx.Text) + return nil +} + +plugin.AddCommand(plugin.NewCommand(echo, "echo")) +``` + +For `/echo hello world`: +- `ctx.Text == "hello world"` +- `ctx.Args == []string{"hello", "world"}` + +## Commands with argument validation + +You can declare command arguments using `CommandArg`. + +Example: + +```go +plugin.AddCommand( + plugin.NewCommand(banUser, "ban", + laniakea.NewCommandArg("user_id"). + SetValueType(laniakea.CommandValueIntType). + SetRequired(), + ), +) +``` + +This lets the framework validate: +- required argument presence +- integer/string/bool shape +- custom regex-based restrictions through the argument configuration + +If validation fails, the command does not run and the bot error path is used. + +## Payload handlers + +Payload handlers are for callback data coming from inline keyboard buttons. + +Register them with `NewPayload(...)` or `AddPayload(...)`: + +```go +func confirmDelete(ctx *laniakea.MsgContext, db *App) error { + ctx.EditCallback("Deleted", nil) + return nil +} + +plugin.AddPayload(plugin.NewPayload(confirmDelete, "delete.confirm")) +``` + +Payload handlers: +- are triggered by callback payload command names +- use the same handler signature as normal commands +- receive parsed payload args in `ctx.Args` + +Use payloads when the trigger source is a button press, not a text command. + +## Update handlers + +Update handlers are for Telegram updates outside the normal command/payload flow. + +Register them with `AddUpdateHandler(...)`: + +```go +plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MsgContext, db *App) error { + // handle inline query here + return nil +}) +``` + +This is the right tool for update types like: +- `inline_query` +- `chosen_inline_result` +- `poll` +- `chat_member` +- other non-command updates + +Important: +- `message` +- `channel_post` +- `callback_query` + +stay on the command/payload flow and are not meant to be registered through `AddUpdateHandler(...)`. + +## The runtime flow + +For text commands: + +1. Telegram update arrives +2. bot prepares `MsgContext` +3. bot middleware runs +4. matching plugin is found +5. plugin middleware runs +6. command argument validation runs +7. command-specific middleware runs +8. command handler runs +9. returned error, if any, goes through centralized error handling + +For callback payloads, the same idea applies, except the trigger comes from decoded callback data instead of text command parsing. + +## Middleware placement + +You have two main middleware levels: + +### Plugin middleware + +Added with: + +```go +plugin.AddMiddleware(...) +``` + +Use this for logic shared by most handlers in the plugin. + +### Command-specific middleware + +Added with: + +```go +plugin.NewCommand(handler, "name").Use(middleware) +``` + +Use this when only one command or payload needs the check. + +See [[Middleware]] for behavior details. + +## Good plugin boundaries + +Good plugin grouping usually follows one of these patterns: +- by business domain: `billing`, `admin`, `profile` +- by update source: `inline`, `support`, `moderation` +- by ownership: one plugin per subsystem or package + +Avoid one giant plugin for the entire bot unless the bot is very small. + +## Examples + +### Example: admin command with plugin middleware + +```go +admin := laniakea.NewPlugin[*App]("admin") +admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MsgContext, app *App) bool { + if !app.IsAdmin(ctx.FromID) { + ctx.Answer("Access denied") + return false + } + return true +})) + +admin.AddCommand(admin.NewCommand(func(ctx *laniakea.MsgContext, app *App) error { + ctx.Answer("Banned") + return nil +}, "ban")) +``` + +### Example: payload handler for inline keyboard callback + +```go +plugin.AddPayload(plugin.NewPayload(func(ctx *laniakea.MsgContext, app *App) error { + ctx.AnswerCbQueryText("Accepted") + ctx.EditCallback("Done", nil) + return nil +}, "approve")) +``` + +## Common mistakes + +### Putting the slash into command names + +Wrong: + +```go +plugin.NewCommand(start, "/start") +``` + +Right: + +```go +plugin.NewCommand(start, "start") +``` + +### Treating payloads like commands + +Payloads are not matched from text messages. They come from button callback data. + +### Using `AddUpdateHandler(...)` for command updates + +Reserved update types like `message`, `channel_post`, and `callback_query` belong to the normal command/payload pipeline. + +### Returning errors for normal user-facing branching + +Not every branch is an error. For normal usage failures, often the better pattern is: + +```go +ctx.Answer("Usage: /ban ") +return nil +``` + +Return an error when the failure is genuinely exceptional or when you want the centralized error flow. + +## When to use what + +Use: +- commands for slash-prefixed user messages +- payload handlers for inline button callbacks +- update handlers for other Telegram update types +- plugin middleware for shared checks +- command middleware for narrow, local checks + +## Where to go next +- Read [[MsgContext]] next to understand what handlers can do once they are triggered. +- Read [[Inline-Keyboards-and-Payloads]] if you are starting to use buttons and callback data. +- Read [[Middleware]] for execution-order and async details. diff --git a/Drafts.md b/Drafts.md new file mode 100644 index 0000000..f98f443 --- /dev/null +++ b/Drafts.md @@ -0,0 +1,9 @@ +# Drafts + +Drafts provide a staged way to accumulate and then flush messages. + +## This page should cover +- `DraftProvider`, `Draft`, `Push`, and `Flush`; +- random vs linear draft IDs; +- validation and send-time behavior; +- when drafts are better than direct replies. diff --git a/FAQ.md b/FAQ.md new file mode 100644 index 0000000..6cb6310 --- /dev/null +++ b/FAQ.md @@ -0,0 +1,10 @@ +# FAQ + +This page should answer the recurring design and usage questions around Laniakea. + +## This page should cover +- why handlers return `error`; +- why `Bot` is single-use; +- why `AnswerLong` is separate from `Answer`; +- why both JSON and Base64 payloads exist; +- how to choose between high-level helpers and `tgapi`. diff --git a/Getting-Started.md b/Getting-Started.md new file mode 100644 index 0000000..f04fa12 --- /dev/null +++ b/Getting-Started.md @@ -0,0 +1,261 @@ +# Getting Started + +Start here if you are integrating Laniakea into a new bot for the first time. + +This page covers the shortest path to a working bot, the minimum concepts you need to understand, and the most important defaults that affect startup and runtime behavior. + +## What you need first +- Go 1.24 or newer +- a Telegram bot token from `@BotFather` +- a module that can import `git.nix13.pw/scuroneko/laniakea` + +Install the module with one of: + +```bash +go get git.nix13.pw/scuroneko/laniakea +``` + +or + +```bash +go get github.com/scuroneko/laniakea +``` + +## The smallest useful bot + +```go +package main + +import ( + "log" + + "git.nix13.pw/scuroneko/laniakea" +) + +func ping(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + ctx.Answer("Pong") + return nil +} + +func main() { + bot, err := laniakea.NewBot[laniakea.NoDB](&laniakea.BotOpts{ + Token: "TOKEN", + }) + if err != nil { + log.Fatal(err) + } + defer bot.Close() + + plugin := laniakea.NewPlugin[laniakea.NoDB]("main") + plugin.AddCommand(plugin.NewCommand(ping, "ping")) + + bot.AddPlugins(plugin) + + if err := bot.Run(); err != nil { + log.Fatal(err) + } +} +``` + +If the user sends `/ping`, the bot replies with `Pong`. + +## The first things to understand + +### 1. `NewBot[T]` uses a generic dependency type + +The type parameter `T` is the shared dependency context passed into handlers, middleware, and runners. + +Use: +- `laniakea.NoDB` when you do not need dependency injection +- a pointer type like `*sql.DB`, `*Store`, or `*App` when you do + +Example: + +```go +type App struct { + Users *sql.DB +} + +app := &App{Users: db} + +bot, err := laniakea.NewBot[*App](opts) +if err != nil { + return err +} + +bot.DatabaseContext(app) +``` + +Pointer types are usually the right default for shared application state. + +### 2. Commands live inside plugins + +Laniakea does not register commands directly on the bot. The normal flow is: +- create a bot +- create one or more plugins +- add commands and middleware to plugins +- register plugins on the bot + +Example: + +```go +plugin := laniakea.NewPlugin[laniakea.NoDB]("admin") +plugin.AddCommand(plugin.NewCommand(ping, "ping")) +bot.AddPlugins(plugin) +``` + +See [[Commands-and-Plugins]] for the full model. + +### 3. Handlers return `error` + +The command handler signature is: + +```go +func(ctx *laniakea.MsgContext, db T) error +``` + +This means: +- do your normal work inside the handler +- return `nil` on success +- return an error when you want centralized bot error handling + +Example: + +```go +func profile(ctx *laniakea.MsgContext, db *App) error { + user, err := db.LoadUser(ctx.FromID) + if err != nil { + return err + } + + ctx.Answerf("Hello, %s", user.Name) + return nil +} +``` + +### 4. `Run()` is not reusable + +A `Bot` instance is single-use. + +After `Run()` or `RunWithContext(...)` returns: +- do not call `Run()` again on the same bot +- create a new bot instance for the next run + +This is an intentional lifecycle rule, not a temporary limitation. + +See [[Bot-Lifecycle]] for details. + +### 5. Always close the bot + +`RunWithContext(...)` and `Run()` do not replace `Close()`. + +You should still release bot-owned resources explicitly: + +```go +defer bot.Close() +``` + +## Recommended startup sequence + +For most bots, this order is the least surprising: + +1. Build `BotOpts` +2. Call `NewBot[T](opts)` +3. Attach database context, localization, or other configuration +4. Create plugins +5. Add commands, payloads, and middleware to plugins +6. Register plugins with `AddPlugins(...)` +7. Optionally call `AutoGenerateCommands()` +8. Call `Run()` or `RunWithContext(...)` +9. Call `Close()` when done + +## A slightly more realistic example + +```go +package main + +import ( + "log" + + "git.nix13.pw/scuroneko/laniakea" +) + +type App struct{} + +func echo(ctx *laniakea.MsgContext, app *App) error { + if ctx.Text == "" { + ctx.Answer("Send some text after the command.") + return nil + } + + ctx.Answer(ctx.Text) + return nil +} + +func main() { + bot, err := laniakea.NewBot[*App](&laniakea.BotOpts{ + Token: "TOKEN", + Debug: true, + }) + if err != nil { + log.Fatal(err) + } + defer bot.Close() + + bot.DatabaseContext(&App{}) + + plugin := laniakea.NewPlugin[*App]("main") + plugin.AddCommand(plugin.NewCommand(echo, "echo")) + + bot.AddPlugins(plugin) + + if err := bot.AutoGenerateCommands(); err != nil { + log.Println(err) + } + if err := bot.Run(); err != nil { + log.Fatal(err) + } +} +``` + +This bot: +- enables debug logging +- injects an application dependency +- registers a command through a plugin +- generates Telegram command metadata +- echoes the command text back to the user + +## Common first-run pitfalls + +### Missing token + +`NewBot(...)` validates the token configuration and returns an error if it is missing. + +### No plugins + +Running a bot without registered plugins is invalid. The bot expects at least one plugin before start. + +### No prefixes + +The bot also requires command prefixes. If you do not configure them, the default is usually `"/"`. + +### Forgetting that handlers receive parsed command text + +For commands, `ctx.Text` contains the text after the command itself, not the original raw message. + +Example: +- incoming message: `/echo hello world` +- command name: `echo` +- `ctx.Text`: `hello world` +- `ctx.Args`: `[]string{"hello", "world"}` + +### Using value types for shared dependencies + +This often works, but it is easy to accidentally copy state. + +Prefer pointer types unless you have a strong reason not to. + +## Where to go next +- Read [[Commands-and-Plugins]] next if you want to build the handler layer correctly. +- Read [[MsgContext]] next if you want to understand reply, edit, callback, and draft helpers. +- Read [[Bot-Lifecycle]] if you need shutdown, worker, or startup details. diff --git a/Home.md b/Home.md index 8b7bd78..dcbec44 100644 --- a/Home.md +++ b/Home.md @@ -1 +1,30 @@ -Добро пожаловать в вики. \ No newline at end of file +# Laniakea Wiki + +Laniakea is a Go framework and Telegram Bot API wrapper built around plugins, typed handlers, middleware, and explicit control over update and reply flow. + +Use this wiki as the structured companion to the README: start with setup, then move through commands, context, keyboards, and lower-level API usage. + +Current fill plan: [[Page-Priority]] + +## Start here +- [[Getting-Started]] +- [[Commands-and-Plugins]] +- [[MsgContext]] + +## Core API +- [[Inline-Keyboards-and-Payloads]] +- [[tgapi-Overview]] +- [[Bot-Lifecycle]] +- [[Middleware]] + +## Changes and troubleshooting +- [[Migration]] +- [[FAQ]] + +## Additional topics +- [[Drafts]] +- [[Localization]] +- [[Rate-Limiting]] +- [[Recipes]] +- [[Semver-and-Releases]] +- [[Page-Priority]] diff --git a/Inline-Keyboards-and-Payloads.md b/Inline-Keyboards-and-Payloads.md new file mode 100644 index 0000000..6859254 --- /dev/null +++ b/Inline-Keyboards-and-Payloads.md @@ -0,0 +1,10 @@ +# Inline Keyboards and Payloads + +This page documents how Laniakea builds inline keyboards and encodes callback payloads. + +## This page should cover +- `InlineKeyboard` builders and row layout; +- JSON vs Base64 callback payloads; +- bot default payload type vs keyboard-local override; +- strict payload mode and tolerant fallback behavior; +- payload size and compatibility guidance. diff --git a/Localization.md b/Localization.md new file mode 100644 index 0000000..e632127 --- /dev/null +++ b/Localization.md @@ -0,0 +1,9 @@ +# Localization + +Localization in Laniakea is centered around `L10n` and key-based translation lookup. + +## This page should cover +- fallback language behavior; +- attaching localization to the bot; +- `MsgContext.Translate`; +- organizing dictionaries and keeping them maintainable. diff --git a/Middleware.md b/Middleware.md new file mode 100644 index 0000000..33be2c8 --- /dev/null +++ b/Middleware.md @@ -0,0 +1,10 @@ +# Middleware + +Middleware lets you run logic before commands, payloads, and update handlers. + +## This page should cover +- synchronous vs asynchronous middleware; +- stop/continue behavior; +- ordering expectations; +- race and mutation caveats for async middleware; +- where middleware fits relative to plugins and handlers. diff --git a/Migration.md b/Migration.md new file mode 100644 index 0000000..0f905fe --- /dev/null +++ b/Migration.md @@ -0,0 +1,9 @@ +# Migration + +Use this page to track version-to-version changes that affect existing bots. + +## This page should cover +- breaking changes by release; +- migration notes for major release-candidate milestones; +- changed defaults and behavior contracts; +- links to [[Semver-and-Releases]] and `CHANGELOG.md`. diff --git a/MsgContext.md b/MsgContext.md new file mode 100644 index 0000000..93c2521 --- /dev/null +++ b/MsgContext.md @@ -0,0 +1,358 @@ +# MsgContext + +`MsgContext` is the runtime object passed into command handlers, payload handlers, middleware, and update handlers. + +It gives you access to: +- the incoming update +- the current message and sender +- parsed command or payload arguments +- reply, edit, delete, callback, draft, and localization helpers + +If you write handlers, `MsgContext` is the API surface you will use most often. + +## The fields you will use first + +### `Text` + +`ctx.Text` is the parsed text payload after the command name. + +Example: +- incoming message: `/echo hello world` +- command: `echo` +- `ctx.Text == "hello world"` + +This is usually the easiest field to use for simple commands. + +### `Args` + +`ctx.Args` is the tokenized version of `ctx.Text`. + +Example: +- `ctx.Text == "hello world"` +- `ctx.Args == []string{"hello", "world"}` + +Use this when you want simple positional arguments. + +### `Msg` + +`ctx.Msg` points to the current Telegram message when the current update has one. + +You will often use it for: +- chat ID +- thread ID +- original message metadata + +Not every update has a message. For some non-message update types, `ctx.Msg` is nil. + +### `From` and `FromID` + +`ctx.From` is the sender user when one exists. + +`ctx.FromID` is the same sender’s numeric ID, extracted for convenience. + +Use `FromID` when you only need the identifier and do not want to keep checking for nil. + +## The helpers you will use most often + +### `Answer` + +Use `Answer(...)` for the normal “reply with text” case. + +```go +func start(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + ctx.Answer("Welcome") + return nil +} +``` + +This is the default high-level reply helper for plain text. + +### `AnswerLong` + +Use `AnswerLong(...)` when plain text may exceed Telegram’s message limit. + +```go +func help(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + ctx.AnswerLong(buildLargeHelpText()) + return nil +} +``` + +Important: +- this is explicit on purpose +- normal `Answer(...)` keeps single-message semantics +- `AnswerLong(...)` may send multiple messages + +### `Keyboard` + +Use `Keyboard(...)` when you want to send a message with an inline keyboard. + +```go +func menu(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + kb := ctx.NewInlineKeyboard(2). + AddCallbackButton("Profile", "profile.open"). + AddCallbackButton("Settings", "settings.open") + + ctx.Keyboard("Choose an action", kb) + return nil +} +``` + +### `KeyboardLong` + +Use `KeyboardLong(...)` when plain text may be too long and the keyboard should stay attached to the final chunk. + +This is useful for: +- long help text +- generated summaries +- reports with an action button at the end + +## Markdown helpers + +Use: +- `AnswerMarkdown(...)` +- `KeyboardMarkdown(...)` +- `EditCallbackMarkdown(...)` +- other `...Markdown` variants + +Important rule: +- user input must be escaped before passing it into MarkdownV2 helpers + +Use `laniakea.EscapeMarkdownV2(...)` for this. + +Example: + +```go +func whoami(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + name := laniakea.EscapeMarkdownV2(ctx.From.FirstName) + ctx.AnswerMarkdown("*User:* " + name) + return nil +} +``` + +## Editing and deleting + +Once you already have an `AnswerMessage`, you can edit or delete it. + +Example: + +```go +func slowTask(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + msg := ctx.Answer("Working...") + if msg == nil { + return nil + } + + // do work + + msg.Edit("Done") + return nil +} +``` + +Available patterns include: +- `Edit(...)` +- `EditMarkdown(...)` +- `EditCaption(...)` +- `Delete()` + +These methods assume a single concrete message target. + +That is why multi-message helpers like `AnswerLong(...)` are separate APIs. + +## Callback-specific helpers + +When handling inline button callbacks, these helpers are especially useful. + +### `EditCallback` + +Edits the callback-linked message. + +```go +func approve(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + ctx.EditCallback("Approved", nil) + return nil +} +``` + +### `AnswerCbQuery` + +Acknowledges the callback query itself. + +Use: +- `AnswerCbQuery()` for empty acknowledgement +- `AnswerCbQueryText(...)` for a short notice +- `AnswerCbQueryAlert(...)` for a visible alert +- `AnswerCbQueryUrl(...)` for redirect behavior + +Example: + +```go +func approve(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + ctx.AnswerCbQueryText("Saved") + ctx.EditCallback("Saved", nil) + return nil +} +``` + +### `CallbackDelete` + +Deletes the message that triggered the callback. + +Use this only when that behavior is really clear to the user. + +## Photos and captions + +Use: +- `AnswerPhoto(...)` +- `AnswerPhotoKeyboard(...)` +- `AnswerPhotoMarkdown(...)` + +These helpers are for sending a photo with an optional caption. + +Caption rules differ from normal message text: +- captions have a smaller Telegram limit +- caption editing uses the caption-specific edit helpers + +## Drafts + +`MsgContext` also exposes draft creation helpers: +- `NewDraft()` +- `NewDraftMarkdown()` + +Drafts are useful when: +- a response is built incrementally +- you want to stage text before flushing +- the workflow benefits from draft IDs or batching behavior + +For ordinary one-shot replies, `Answer(...)` is simpler. + +See [[Drafts]] for the full model. + +## Localization + +Use: + +```go +ctx.Translate("some.key") +``` + +This looks up text using the current user’s language when available and falls back to the configured default language. + +Example: + +```go +func ping(ctx *laniakea.MsgContext, db laniakea.NoDB) error { + ctx.Answer(ctx.Translate("ping.answer")) + return nil +} +``` + +See [[Localization]] for setup and dictionary structure. + +## `NewInlineKeyboard` + +The recommended way to build keyboards inside handlers is: + +```go +kb := ctx.NewInlineKeyboard(2) +``` + +This is important because it inherits the bot’s default payload configuration automatically. + +A keyboard can still override its own payload type locally if needed. + +See [[Inline-Keyboards-and-Payloads]]. + +## Sending chat actions + +Use `SendAction(...)` to show activity like typing or uploading. + +Example: + +```go +func report(ctx *laniakea.MsgContext, db *App) error { + ctx.SendAction(tgapi.ChatActionTyping) + text, err := db.BuildReport(ctx.FromID) + if err != nil { + return err + } + ctx.AnswerLong(text) + return nil +} +``` + +This is especially useful for slower handlers. + +## Error handling inside handlers + +A common pattern is: + +```go +func profile(ctx *laniakea.MsgContext, db *App) error { + user, err := db.LoadUser(ctx.FromID) + if err != nil { + return err + } + ctx.Answer(user.Name) + return nil +} +``` + +You usually do not call `ctx.Error(...)` directly in normal handlers unless you intentionally want immediate explicit error messaging there. + +The more idiomatic pattern is: +- return `error` +- let the bot’s centralized error flow handle it + +## Common pitfalls + +### `ctx.Msg` can be nil + +Do not assume every update has a message object. + +This especially matters in custom update handlers. + +### `Answer(...)` and `Edit(...)` are not interchangeable + +`Answer(...)` creates a new message. + +`Edit(...)` changes an existing one and requires a valid target. + +### Long replies are explicit + +If the text may exceed Telegram’s normal message limit, use `AnswerLong(...)` or `KeyboardLong(...)`. + +### Markdown helpers require escaping + +Do not pass raw user input to MarkdownV2 methods without escaping. + +### Callback helpers only make sense in callback flow + +Methods like `EditCallback(...)` and `AnswerCbQueryText(...)` depend on callback-specific context. + +## A practical example + +```go +func settings(ctx *laniakea.MsgContext, db *App) error { + kb := ctx.NewInlineKeyboard(1). + AddCallbackButton("Enable notifications", "settings.notifications.enable"). + AddCallbackButton("Disable notifications", "settings.notifications.disable") + + ctx.Keyboard("Notification settings", kb) + return nil +} +``` + +This example uses: +- a handler +- `ctx.NewInlineKeyboard(...)` +- callback payload routing +- `ctx.Keyboard(...)` + +That is a typical Laniakea interaction pattern. + +## Where to go next +- Read [[Inline-Keyboards-and-Payloads]] if you are building button-driven flows. +- Read [[Commands-and-Plugins]] if you want the full handler registration model. +- Read [[Drafts]] if you need staged or multi-step message assembly. diff --git a/Page-Priority.md b/Page-Priority.md new file mode 100644 index 0000000..374b887 --- /dev/null +++ b/Page-Priority.md @@ -0,0 +1,30 @@ +# Page Priority + +This page tracks the recommended fill order for the wiki while documentation is still being built out. + +## Priority 1 +- [[Getting-Started]] +- [[Commands-and-Plugins]] +- [[MsgContext]] +- [[Inline-Keyboards-and-Payloads]] +- [[tgapi-Overview]] + +## Priority 2 +- [[Bot-Lifecycle]] +- [[Middleware]] +- [[Migration]] +- [[FAQ]] + +## Priority 3 +- [[Drafts]] +- [[Localization]] +- [[Rate-Limiting]] +- [[Recipes]] + +## Priority 4 +- [[Semver-and-Releases]] + +## Notes +- Keep `Home.md` ordered the same way. +- Within each page, explain the most necessary and most frequently used APIs first. +- Add new pages into this list before writing large amounts of content for them. diff --git a/Rate-Limiting.md b/Rate-Limiting.md new file mode 100644 index 0000000..4409342 --- /dev/null +++ b/Rate-Limiting.md @@ -0,0 +1,9 @@ +# Rate Limiting + +This page explains how Laniakea applies request throttling and reacts to Telegram rate-limit responses. + +## This page should cover +- configured limiter behavior; +- drop mode vs waiting; +- `retry_after` handling; +- practical tuning guidelines for different bot sizes. diff --git a/Recipes.md b/Recipes.md new file mode 100644 index 0000000..cc00c90 --- /dev/null +++ b/Recipes.md @@ -0,0 +1,11 @@ +# Recipes + +This page should collect short, task-focused examples for common bot patterns. + +## This page should cover +- admin-only commands; +- callback button flows; +- long replies; +- file uploads; +- localized commands; +- custom update handlers. diff --git a/Semver-and-Releases.md b/Semver-and-Releases.md new file mode 100644 index 0000000..6e00275 --- /dev/null +++ b/Semver-and-Releases.md @@ -0,0 +1,9 @@ +# Semver and Releases + +This page is for maintainers and contributors who need the project’s release rules in one place. + +## This page should cover +- what counts as public API; +- what is considered a breaking change; +- how version numbers are chosen; +- how changelog sections map to published tags. diff --git a/tgapi-Overview.md b/tgapi-Overview.md new file mode 100644 index 0000000..49f445d --- /dev/null +++ b/tgapi-Overview.md @@ -0,0 +1,10 @@ +# tgapi Overview + +`tgapi` is the lower-level Telegram API layer used by Laniakea. + +## This page should cover +- the difference between `API` and `Uploader`; +- when to use typed helpers vs low-level request builders; +- context-aware methods; +- request/response modeling and Telegram wire compatibility; +- where `tgapi` ends and higher-level bot behavior begins.