REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY
7
Recipes
ScuroNeko edited this page 2026-07-09 18:13:22 +03:00

Recipes

Russian version: Recipes-RU

This page collects short, task-focused examples for common bot patterns. Each recipe is intentionally small and copy-friendly, and you can combine them with the deeper pages such as Commands-and-Plugins, Middleware, and Inline-Keyboards-and-Payloads.

Admin-only command

Use plugin middleware when several commands share the same access rule.

type App struct{}

func (a *App) IsAdmin(userID int64) bool { return userID == 42 }

admin := laniakea.NewPlugin[*App]("admin")
admin.AddMiddleware(laniakea.NewMiddleware("admin-only", func(ctx *laniakea.MessageContext, app *App) bool {
	return ctx.From != nil && app.IsAdmin(ctx.From.ID)
}))

admin.NewCommand(func(ctx *laniakea.MessageContext, app *App) error {
	ctx.Answer("Admin command executed")
	return nil
}, "reload")

Callback button flow

Use a payload handler for inline keyboard callbacks.

menu := laniakea.NewPlugin[laniakea.NoData]("menu")

menu.NewCommand(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
	kb := ctx.NewInlineKeyboard(1)
	kb.AddCallbackButton("Open settings", "settings")
	ctx.Keyboard("Choose an action", kb)
	return nil
}, "menu")

menu.NewPayload(func(ctx *laniakea.MessageContext, db laniakea.NoData) error {
	ctx.EditCallback("Settings screen", nil)
	return nil
}, "settings")

Long plain-text reply

Use AnswerLong(...) when you want explicit splitting into multiple safe Telegram messages.

plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
	report := buildLargePlainTextReport()
	ctx.AnswerLong(report)
	return nil
}, "report")

If you need an inline keyboard on the final chunk, use KeyboardLong(...).

Localized command

Attach an L10n store to the bot and use ctx.Translate(...) inside handlers.

l10n := laniakea.NewL10n("en").
	AddDictEntry("greeting", laniakea.DictEntry{
		"en": "Hello",
		"ru": "Privet",
	})

bot.SetL10n(l10n)

plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
	ctx.Answer(ctx.Translate("greeting"))
	return nil
}, "start")

Non-command update handler

Use AddUpdateHandler(...) for Telegram update types that are outside the command and payload flow.

plugin.AddUpdateHandler(tgapi.UpdateTypeInlineQuery, func(ctx *laniakea.MessageContext, db *App) error {
	if ctx.From != nil {
		ctx.Logger.Infoln("inline query from", ctx.From.ID)
	}
	return nil
})

This is usually cleaner than forcing non-command traffic through a command parser.

Draft-based staged reply

Use drafts when you want to build a reply progressively and publish it once at the end.

plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
	draft := ctx.NewDraft()
	if draft == nil {
		return nil
	}

	if err := draft.Push("Collecting data...\n"); err != nil {
		return err
	}
	if err := draft.Push("Formatting output...\n"); err != nil {
		return err
	}

	return draft.Flush()
}, "build")

File upload with tgapi

Use the higher-level handler flow for routing, but drop down to tgapi uploader methods when you need multipart upload behavior.

plugin.NewCommand(func(ctx *laniakea.MessageContext, db *App) error {
	uploader := tgapi.NewUploader(ctx.Api)
	defer uploader.Close()

	_, err := uploader.SendPhoto(tgapi.UploadPhoto{
		ChatID: ctx.Msg.Chat.ID,
	}, tgapi.NewUploaderFile("report.jpg", []byte("hello")))
	return err
}, "upload")

If your exact uploader call differs, keep the general rule in mind: handler routing can stay high-level even when the send path needs tgapi.

Strict payload mode

Enable strict payload decoding if you want callback payload formats to be enforced consistently.

opts := (&laniakea.BotOpts{}).
	SetToken("TOKEN").
	SetStrictPayloadType(true)

Or after bot creation:

bot.SetStrictPayloadType(true)

This is most useful when you want payload-type mismatches to fail loudly instead of being decoded tolerantly.