REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY
7
Getting Started
ScuroNeko edited this page 2026-08-19 14:59:10 +03:00

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.NoData when you do not need dependency injection
  • a pointer type like *sql.DB, *Store, or *App when 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 nil on 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()

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(), RunWithContext(...), or RunWebhookWithContext(...)
  9. 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 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 MessageContext next if you want to understand reply, edit, callback, and draft helpers.
  • Read Bot-Lifecycle if you need shutdown, worker, or startup details.