Skip to content

Keyboard & Callback Bot ​

This example demonstrates inline keyboards, reply keyboards, text formatting with entities, callback queries, force reply, and keyboard removal.

Full Code ​

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"

	"github.com/mtgo-labs/mtgo/telegram"
	"github.com/mtgo-labs/mtgo/telegram/params"
	"github.com/mtgo-labs/mtgo/telegram/types"
)

func main() {
	apiID := mustEnv("API_ID")
	apiHash := mustEnv("API_HASH")
	botToken := mustEnv("BOT_TOKEN")

	client, err := telegram.NewClient(mustAtoi(apiID), apiHash, &telegram.Config{
		BotToken:    botToken,
		SessionName: "keyboard_bot",
		SavePeers:   true,
	})
	if err != nil {
		log.Fatalf("new client: %v", err)
	}

	// /start — welcome with inline URL button.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		text := "Here is some bot commands:\n\n"
		text += "- /keyboard - show keyboard\n"
		text += "- /inline - show inline keyboard\n"
		text += "- /entities - show formatted text\n"
		text += "- /remove - remove keyboard\n"
		text += "- /force - force reply"

		_, _ = ctx.Reply(text, &params.SendMessage{
			ReplyMarkup: telegram.Keyboard().
				URL("GitHub", "https://github.com/mtgo-labs/mtgo").
				Build(),
		})
	}, telegram.Command("start"))

	// /inline — inline keyboard with callback buttons.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		_, _ = ctx.Reply("This is an inline keyboard", &params.SendMessage{
			ReplyMarkup: telegram.Keyboard().
				Callback("OwO", "OwO").
				Callback("UwU", "UwU").
				Next().
				URL("Docs", "https://example.com").
				Build(),
		})
	}, telegram.Command("inline"))

	// /keyboard — reply keyboard.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		_, _ = ctx.Reply("This is a keyboard", &params.SendMessage{
			ReplyMarkup: telegram.Keyboard().
				Text("OwO").
				Text("UwU").
				BuildReply(telegram.ReplyOpts{Resize: true, OneTime: true}),
		})
	}, telegram.Command("keyboard"))

	// /entities — demonstrate formatted text using params.Entities.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		text := "Hello World"
		_, _ = ctx.Reply(text, &params.SendMessage{
			Entities: params.Entities(
				params.Bold(0, 5),
				params.Italic(6, 5),
			),
		})
	}, telegram.Command("entities"))

	// /formatted — more advanced entity formatting example.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		text := "Bold Italic Code Pre Underline Strike Spoiler"
		_, _ = ctx.Reply(text, &params.SendMessage{
			Entities: params.Entities(
				params.Bold(0, 4),
				params.Italic(5, 6),
				params.Code(12, 4),
				params.Pre(17, 3, "go"),
				params.Underline(21, 9),
				params.Strikethrough(31, 6),
				params.Spoiler(38, 7),
			),
		})
	}, telegram.Command("formatted"))

	// /remove — remove reply keyboard.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		_, _ = ctx.Reply("Keyboards removed", &params.SendMessage{
			ReplyMarkup: telegram.RemoveKeyboard(),
		})
	}, telegram.Command("remove"))

	// /force — force reply.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		_, _ = ctx.Reply("This is a force reply", &params.SendMessage{
			ReplyMarkup: telegram.ForceReplyMarkup(),
		})
	}, telegram.Command("force"))

	// Echo any other text.
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		_, _ = ctx.Reply(fmt.Sprintf("You said %q", msg.Text))
	})

	// Handle inline keyboard button presses.
	client.OnCallbackQuery(func(ctx *telegram.Context) {
		data := string(ctx.CallbackQuery.Data)
		_, _ = ctx.CallbackEditText(
			fmt.Sprintf("You pressed %s", data),
			&params.EditMessage{
				ReplyMarkup: telegram.Keyboard().
					URL("GitHub", "https://github.com/mtgo-labs/mtgo").
					Build(),
			},
		)
		_ = ctx.Answer("", false)
	})

	if err := client.Connect(0); err != nil {
		log.Fatalf("connect: %v", err)
	}
	defer client.Stop()

	bot, err := client.GetMe(context.Background())
	if err != nil {
		log.Fatalf("get me: %v", err)
	}

	fmt.Println("=== Keyboard Bot ===")
	fmt.Printf("  ID:   %d\n", bot.ID)
	fmt.Printf("  Name: %s\n", bot.FirstName)
	fmt.Println("────────────────────")
	fmt.Println("keyboard bot running, press Ctrl+C to stop")

	client.Idle()
}

func mustEnv(key string) string {
	v := os.Getenv(key)
	if v == "" {
		log.Fatalf("environment variable %s is required", key)
	}
	return v
}

func mustAtoi(s string) int32 {
	var n int32
	if _, err := fmt.Sscanf(s, "%d", &n); err != nil {
		log.Fatalf("invalid integer %q: %v", s, err)
	}
	return n
}

How It Works ​

Inline Keyboards ​

Inline keyboards are buttons attached to a message. They use the builder pattern:

go
telegram.Keyboard().
    Callback("Button Label", "callback_data").
    URL("Link", "https://example.com").
    Build()
  • Callback(label, data) — sends a callback query when pressed (data up to 64 bytes)
  • URL(label, url) — opens a URL when pressed
  • Next() — moves to the next row
  • Build() — returns a ReplyMarkup for inline keyboards

Reply Keyboards ​

Reply keyboards replace the user's keyboard with custom buttons:

go
telegram.Keyboard().
    Text("Button 1").
    Text("Button 2").
    BuildReply(telegram.ReplyOpts{
        Resize:  true,
        OneTime: true,
    })
  • Text(label) — sends the label as a text message when pressed
  • Resize: true — resize keyboard to fit button labels
  • OneTime: true — hide keyboard after first use

Callback Queries ​

When a user presses an inline button, a callback query is received:

go
client.OnCallbackQuery(func(ctx *telegram.Context) {
    data := string(ctx.CallbackQuery.Data)
    // Edit the original message
    ctx.CallbackEditText(fmt.Sprintf("You pressed %s", data), nil)
    // Answer the callback (required, removes loading spinner)
    ctx.Answer("", false)
})
  • ctx.CallbackQuery.Data — the data string from the button
  • ctx.CallbackEditText — edits the message the button was attached to
  • ctx.Answer(text, showAlert) — acknowledges the callback. Required to remove the loading spinner. Set showAlert to true for a popup alert.

Text Entities ​

Format text without parse modes by specifying entity ranges:

go
params.Entities(
    params.Bold(0, 5),       // bold from offset 0, length 5
    params.Italic(6, 5),     // italic from offset 6, length 5
    params.Code(12, 4),      // monospace from offset 12, length 4
    params.Pre(17, 3, "go"), // preformatted code block with language
    params.Underline(21, 9),
    params.Strikethrough(31, 6),
    params.Spoiler(38, 7),
)

Each entity takes offset and length in UTF-16 code units. params.Pre also accepts a language string.

Keyboard Removal ​

go
telegram.RemoveKeyboard()

Removes the reply keyboard from the user's chat. Use this when you no longer need the custom keyboard.

Force Reply ​

go
telegram.ForceReplyMarkup()

Forces the user to reply to the message. The user's text input field becomes a reply to your message.

Running ​

bash
export API_ID=12345
export API_HASH=your_api_hash
export BOT_TOKEN=your_bot_token

go run main.go

Try /start, /inline, /keyboard, /entities, /formatted, /remove, and /force.

Released under the Apache-2.0 License.