Wiki
猫Table of Contents
- Getting Started
- What you need first
- The smallest useful bot
- The first things to understand
- 1. NewBot[T] uses a generic dependency type
- 2. Commands live inside plugins
- 3. Handlers return error
- 4. Run() is not reusable
- 5. Always close the bot
- Recommended startup sequence
- A slightly more realistic example
- Common first-run pitfalls
- Missing token
- No plugins
- No prefixes
- Forgetting that handlers receive parsed command text
- Using value types for shared dependencies
- Where to go next
Getting Started
Russian version: Getting-Started-RU
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.26 or newer
- a Telegram bot token from
@BotFather - a module that can import
git.scuroneko.dev/scuroneko/laniakea
Install the module with one of:
go get git.scuroneko.dev/scuroneko/laniakea
or
go get github.com/scuroneko/laniakea
The smallest useful bot
package main
import (
"log"
"git.scuroneko.dev/scuroneko/laniakea"
)
func ping(ctx *laniakea.MessageContext, db laniakea.NoData) error {
ctx.Answer("Pong")
return nil
}
func main() {
bot, err := laniakea.NewBot[laniakea.NoData](&laniakea.BotOpts{
Token: "TOKEN",
})
if err != nil {
log.Fatal(err)
}
defer bot.Close()
plugin := laniakea.NewPlugin[laniakea.NoData]("main")
plugin.Command("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.NoDatawhen you do not need dependency injection- a pointer type like
*sql.DB,*Store, or*Appwhen you do
Example:
type App struct {
Users *sql.DB
}
app := &App{Users: db}
bot, err := laniakea.NewBot[*App](opts)
if err != nil {
return err
}
bot.SetAppData(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:
plugin := laniakea.NewPlugin[laniakea.NoData]("admin")
plugin.Command("ping", ping)
bot.AddPlugins(plugin)
See Commands-and-Plugins for the full model.
3. Handlers return error
The command handler signature is:
func(ctx *laniakea.MessageContext, db T) error
This means:
- do your normal work inside the handler
- return
nilon success - return an error when you want centralized bot error handling
Example:
func profile(ctx *laniakea.MessageContext, 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(), RunWithContext(...), or RunWebhookWithContext(...) returns:
- do not call a runtime entry point 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
Run(), RunWithContext(...), and RunWebhookWithContext(...) do not replace Close().
You should still release bot-owned resources explicitly:
defer bot.Close()
Recommended startup sequence
For most bots, this order is the least surprising:
- Build
BotOpts - Call
NewBot[T](opts) - Attach database context, localization, or other configuration
- Create plugins
- Add commands, payloads, and middleware to plugins
- Register plugins with
AddPlugins(...) - Optionally call
AutoGenerateCommands() - Call
Run(),RunWithContext(...), orRunWebhookWithContext(...) - Call
Close()when done
A slightly more realistic example
package main
import (
"log"
"git.scuroneko.dev/scuroneko/laniakea"
)
type App struct{}
func echo(ctx *laniakea.MessageContext, 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.SetAppData(&App{})
plugin := laniakea.NewPlugin[*App]("main")
plugin.Command("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 worldctx.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 MessageContext next if you want to understand reply, edit, callback, and draft helpers.
- Read Bot-Lifecycle if you need shutdown, worker, or startup details.
Navigation
Start here
Runtime and Architecture
- Bot-Lifecycle
- Webhook-Runtime
- Middleware
- Runners
- Error-Handling
- Logging
- Update-Routing-Model
- Policies
- Scenes
Interaction and Telegram API
- Inline-Keyboards-and-Payloads
- Auto-Generated-Commands
- Drafts
- Rich-Messages
- Localization
- Rate-Limiting
- tgapi-Overview