REPOSITORY / ScuroNeko/Laniakea

Wiki

KNOWLEDGE REPOSITORY
3
MessageContext
ScuroNeko edited this page 2026-08-19 14:59:10 +03:00
This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

MessageContext

Russian version: MessageContext-RU

MessageContext 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, MessageContext is the API surface you will use most often.

For the full routing and field-guarantee matrix by update kind, see Update-Routing-Model.

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 senders 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.

func start(ctx *laniakea.MessageContext, db laniakea.NoData) 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 Telegrams message limit.

func help(ctx *laniakea.MessageContext, db laniakea.NoData) 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.

func menu(ctx *laniakea.MessageContext, db laniakea.NoData) 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

Rich messages

Use RichAnswer(...) and RichAnswerKeyboard(...) to validate and send Bot API 10.2 input rich blocks built with tgrich:

ctx.RichAnswer(
	tgrich.H1(tgrich.Text("Report")),
	tgrich.P(tgrich.Bold(tgrich.Text("all systems go"))),
)

See Rich-Messages for all constructors, media uploads, validation, and the receive side.

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:

func whoami(ctx *laniakea.MessageContext, db laniakea.NoData) 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:

func slowTask(ctx *laniakea.MessageContext, db laniakea.NoData) 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.

func approve(ctx *laniakea.MessageContext, db laniakea.NoData) 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:

func approve(ctx *laniakea.MessageContext, db laniakea.NoData) 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

MessageContext 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:

ctx.Translate("some.key")

This looks up text using the current users language when available and falls back to the configured default language.

Example:

func ping(ctx *laniakea.MessageContext, db laniakea.NoData) 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:

kb := ctx.NewInlineKeyboard(2)

This is important because it inherits the bots 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:

func report(ctx *laniakea.MessageContext, 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:

func profile(ctx *laniakea.MessageContext, 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 bots 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 Telegrams 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

func settings(ctx *laniakea.MessageContext, 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