Code Monkey home page Code Monkey logo

telego's Introduction

Telego • Go Telegram Bot API

Go Reference Telego Docs Go Version Telegram Bot API Version
Mentioned in Awesome Go Discussions Telegram Chat

CI Status Race Testing Quality Gate Status Go Report
Coverage Code Smells Lines of Code

Telego logo

Telego is Telegram Bot API library for Golang with full API implementation (one-to-one)

The goal of this library was to create API with the same types and methods as actual Telegram Bot API. Every type and method have been represented in types.go and methods.go files with mostly all documentation from Telegram.

⚠️ Telego is still in v0.x.x version, so do expect breaking changes! ⚠️

For more detailed documentation, see docs at telego.pixelbox.dev.

Note: Telego uses fasthttp instead of net/http and go-json instead of encoding/json by default (both can be changed).

📋 Table Of Content

Click to show • hide

⚡ Getting Started

How to get the library:

go get github.com/mymmrac/telego

Make sure you get the latest version to have all new features & fixes.

More examples can be seen here:

Click to show • hide

Note: Error handling may be missing in examples, but I strongly recommend handling all errors.

Generally, useful information about Telegram Bots and their features:

Click to show • hide

🧩 Basic setup

▲ Go Up ▲

For start, you need to create an instance of your bot and specify token.

package main

import (
	"fmt"
	"os"

	"github.com/mymmrac/telego"
)

func main() {
	// Get Bot token from environment variables
	botToken := os.Getenv("TOKEN")

	// Create bot and enable debugging info
	// Note: Please keep in mind that default logger may expose sensitive information,
	// use in development only
	// (more on configuration in examples/configuration/main.go)
	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Call method getMe (https://core.telegram.org/bots/api#getme)
	botUser, err := bot.GetMe()
	if err != nil {
		fmt.Println("Error:", err)
	}

	// Print Bot information
	fmt.Printf("Bot user: %+v\n", botUser)
}

📩 Getting updates

▲ Go Up ▲

In order to receive updates, you can use one of two methods:

  • using long polling (bot.UpdatesViaLongPolling)
  • using webhook (bot.UpdatesViaWebhook)

Let's start from long polling (easier for local testing):

package main

import (
	"fmt"
	"os"

	"github.com/mymmrac/telego"
)

func main() {
	botToken := os.Getenv("TOKEN")

	// Note: Please keep in mind that default logger may expose sensitive information,
	// use in development only
	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Get updates channel
	// (more on configuration in examples/updates_long_polling/main.go)
	updates, _ := bot.UpdatesViaLongPolling(nil)

	// Stop reviving updates from update channel
	defer bot.StopLongPolling()

	// Loop through all updates when they came
	for update := range updates {
		fmt.Printf("Update: %+v\n", update)
	}
}

Webhook example (recommended way):

package main

import (
	"fmt"
	"os"

	"github.com/mymmrac/telego"
)

func main() {
	botToken := os.Getenv("TOKEN")

	// Note: Please keep in mind that default logger may expose sensitive information,
	// use in development only
	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Set up a webhook on Telegram side
	_ = bot.SetWebhook(&telego.SetWebhookParams{
		URL: "https://example.com/bot" + bot.Token(),
	})

	// Receive information about webhook
	info, _ := bot.GetWebhookInfo()
	fmt.Printf("Webhook Info: %+v\n", info)

	// Get an update channel from webhook.
	// (more on configuration in examples/updates_webhook/main.go)
	updates, _ := bot.UpdatesViaWebhook("/bot" + bot.Token())

	// Start server for receiving requests from the Telegram
	go func() {
		_ = bot.StartWebhook("localhost:443")
	}()

	// Stop reviving updates from update channel and shutdown webhook server
	defer func() {
		_ = bot.StopWebhook()
	}()

	// Loop through all updates when they came
	for update := range updates {
		fmt.Printf("Update: %+v\n", update)
	}
}

For running multiple bots from a single server, see this example.

Tip: For testing webhooks, you can use Ngrok to make a tunnel to your localhost, and get a random domain available from the Internet. It's as simple as ngrok http 8080. Or follow Telego + Ngrok example using ngrok/ngrok-go for most convenient bot testing.

Tip: You may wish to use Let's Encrypt in order to generate your free TLS certificate.

🪁 Using Telegram methods

▲ Go Up ▲

All Telegram Bot API methods described in documentation can be used by the library. They have the same names and the same parameters, parameters represented by struct with name: <methodName> + Params. If method doesn't have required parameters nil value can be used as a parameter.

Note: types.go and methods.go were automatically generated from documentation, and it's possible that they have errors or missing parts both in comments and actual code. Feel free to report such things.

package main

import (
	"fmt"
	"os"

	"github.com/mymmrac/telego"
	tu "github.com/mymmrac/telego/telegoutil"
)

func main() {
	botToken := os.Getenv("TOKEN")

	// Note: Please keep in mind that default logger may expose sensitive information,
	// use in development only
	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Call method getMe
	botUser, _ := bot.GetMe()
	fmt.Printf("Bot User: %+v\n", botUser)

	updates, _ := bot.UpdatesViaLongPolling(nil)
	defer bot.StopLongPolling()

	for update := range updates {
		if update.Message != nil {
			// Retrieve chat ID
			chatID := update.Message.Chat.ID

			// Call method sendMessage.
			// Send a message to sender with the same text (echo bot).
			// (https://core.telegram.org/bots/api#sendmessage)
			sentMessage, _ := bot.SendMessage(
				tu.Message(
					tu.ID(chatID),
					update.Message.Text,
				),
			)

			fmt.Printf("Sent Message: %v\n", sentMessage)
		}
	}
}

🧼 Utility methods

▲ Go Up ▲

In Telego even though you have all types and methods available, it's often not so convenient to use them directly. To solve this issues telegoutil package was created. It contains utility-helper function that will make your life a bit easier.

I suggest including it with alias to get cleaner code:

import tu "github.com/mymmrac/telego/telegoutil"

The package contains couple methods for creating send parameters with all required parameters like:

  • Message(chatID, text) => SendMessageParams
  • Photo(chatID, photoFile) => SendPhotoParams
  • Location(chatID, latitude, longitude) => SendLocationParams
  • ...

Or other useful methods like:

  • ID(intID) => ChatID
  • File(namedReader) => InputFile
  • ...

Utils related to methods can be found in telegoutil/methods, for types in telegoutil/types, for handlers in telegoutil/handler, for api in telegoutil/api.

Note: If you think that something can be added to telegoutil package fill free to create an issue or pull request with desired changes.

🦾 Helper With... methods

▲ Go Up ▲

Creating method parameters is sometimes bulky and not convenient, so you can use with methods in combination with utility methods.

Here is a simple example of creating a message with a keyboard that has 4 buttons with different parameters.

package main

import (
	"github.com/mymmrac/telego"
	tu "github.com/mymmrac/telego/telegoutil"
)

func main() {
	// ... initializing bot (full example in examples/keyboard/main.go)

	// Creating keyboard
	keyboard := tu.Keyboard(
		tu.KeyboardRow( // Row 1
			// Column 1
			tu.KeyboardButton("Button"),

			// Column 2, `with` method
			tu.KeyboardButton("Poll Regular").
				WithRequestPoll(tu.PollTypeRegular()),
		),
		tu.KeyboardRow( // Row 2
			// Column 1, `with` method 
			tu.KeyboardButton("Contact").WithRequestContact(),

			// Column 2, `with` method 
			tu.KeyboardButton("Location").WithRequestLocation(),
		),
	).WithResizeKeyboard().WithInputFieldPlaceholder("Select something")
	// Multiple `with` methods can be chained

	// Creating message
	msg := tu.Message(
		tu.ID(123),
		"Hello World",
	).WithReplyMarkup(keyboard).WithProtectContent() // Multiple `with` method 

	bot.SendMessage(msg)
}

Those methods allow you to modify values without directly accessing them, also as you saw with methods can be staked one to another in order to update multiple values.

🌥️ Bot handlers

▲ Go Up ▲

Processing updates just in for loop is not the most pleasing thing to do, so Telego provides net/http like handlers, but instead of the path, you provide predicates.

One update will only match to the first handler whose predicates are satisfied, predicates checked in order of handler registration (it's useful to first specify the most specific predicates and then more general).

Also, all handlers (but not their predicates) are processed in parallel.

I suggest including it with alias to get cleaner code:

import th "github.com/mymmrac/telego/telegohandler"

Here is an example of using handlers with long polling updates. You can see the full list of available predicates in telegohandler/predicates, or define your own.

package main

import (
	"fmt"
	"os"

	"github.com/mymmrac/telego"
	th "github.com/mymmrac/telego/telegohandler"
	tu "github.com/mymmrac/telego/telegoutil"
)

func main() {
	botToken := os.Getenv("TOKEN")

	// Note: Please keep in mind that default logger may expose sensitive information,
	// use in development only
	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Get updates channel
	updates, _ := bot.UpdatesViaLongPolling(nil)

	// Create bot handler and specify from where to get updates
	bh, _ := th.NewBotHandler(bot, updates)

	// Stop handling updates
	defer bh.Stop()

	// Stop getting updates
	defer bot.StopLongPolling()

	// Register new handler with match on command `/start`
	bh.Handle(func(bot *telego.Bot, update telego.Update) {
		// Send message
		_, _ = bot.SendMessage(tu.Message(
			tu.ID(update.Message.Chat.ID),
			fmt.Sprintf("Hello %s!", update.Message.From.FirstName),
		))
	}, th.CommandEqual("start"))

	// Register new handler with match on any command
	// Handlers will match only once and in order of registration, 
	// so this handler will be called on any command except `/start` command
	bh.Handle(func(bot *telego.Bot, update telego.Update) {
		// Send message
		_, _ = bot.SendMessage(tu.Message(
			tu.ID(update.Message.Chat.ID),
			"Unknown command, use /start",
		))
	}, th.AnyCommand())

	// Start handling updates
	bh.Start()
}

Also, just handling updates is useful, but handling specific updates like messages or callback queries in most of the cases are more straightforward and provides cleaner code.

So Telego provides specific handles for all fields of telego.Update. See the list of all available handler types in telegohandler/update_handlers, or define your own.

package main

import (
	"fmt"

	"github.com/mymmrac/telego"
	th "github.com/mymmrac/telego/telegohandler"
	tu "github.com/mymmrac/telego/telegoutil"
)

func main() {
	// ... initializing bot and bot handler 
	// (full example in examples/handler_specific/main.go)

	// Register new handler with match on command `/start`
	bh.HandleMessage(func(bot *telego.Bot, message telego.Message) {
		// Send a message with inline keyboard
		_, _ = bot.SendMessage(tu.Message(
			tu.ID(message.Chat.ID),
			fmt.Sprintf("Hello %s!", message.From.FirstName),
		).WithReplyMarkup(tu.InlineKeyboard(
			tu.InlineKeyboardRow(
				tu.InlineKeyboardButton("Go!").WithCallbackData("go"),
			)),
		))
	}, th.CommandEqual("start"))

	// Register new handler with match on the call back query 
	// with data equal to `go` and non-nil message
	bh.HandleCallbackQuery(func(bot *telego.Bot, query telego.CallbackQuery) {
		// Send message
		_, _ = bot.SendMessage(tu.Message(tu.ID(query.Message.Chat.ID), "GO GO GO"))

		// Answer callback query
		_ = bot.AnswerCallbackQuery(tu.CallbackQuery(query.ID).WithText("Done"))
	}, th.AnyCallbackQueryWithMessage(), th.CallbackDataEqual("go"))

	// ... start bot handler
}

One more important part of handlers are groups and middlewares. Telego allows creating groups with and without predicates and attaching middleware to groups.

package main

import (
	"fmt"

	"github.com/mymmrac/telego"
	th "github.com/mymmrac/telego/telegohandler"
)

func main() {
	// Init ...

	// Add global middleware, it will be applied in order of addition
	bh.Use(func(bot *telego.Bot, update telego.Update, next th.Handler) {
		fmt.Println("Global middleware") // Will be called first
		next(bot, update)
	})

	// Create any groups with or without predicates
	// Note: Updates first checked by groups and only then by handlers 
	// (group -> ... -> group -> handler)
	task := bh.Group(th.TextContains("task"))

	// Add middleware to groups
	task.Use(func(bot *telego.Bot, update telego.Update, next th.Handler) {
		fmt.Println("Group-based middleware") // Will be called second

		if len(update.Message.Text) < 10 {
			next(bot, update)
		}
	})

	// Handle updates on a group
	task.HandleMessage(func(bot *telego.Bot, message telego.Message) {
		fmt.Println("Task...") // Will be called third
	})
}

⚙️ Build configuration

▲ Go Up ▲

Telego supports multiple build configurations via Go's build tags (right now only to change JSON encoding/decoding library):

Note: Use sonic only on supported platforms as it has its own limitations, more here.

🎨 Contribution

Contribution guidelines listed here.

⭐ Stargazers over time

Stargazers over time

Powered by caarlos0/starcharts

🔐 License

Telego is distributed under MIT licence.

telego's People

Contributors

abakum avatar ah-dark avatar alexandear avatar charibdys avatar code-a1 avatar dependabot[bot] avatar linbuxiao avatar mymmrac avatar vitkarpov avatar witjem avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

telego's Issues

⭐️ Result of methods that only returns boolean

⭐️ Feature description

In case of methods like deleteWebhook there is no way to get proper error message in case if result is true

API response deleteWebhook: Ok: true, Err: [0 "Webhook is already deleted"], Result: true

🌈 Your view

No response

🧐 Code example

No response

Cannot send quizzes with `CorrectOptionID = 0`

💬 Telego version

v0.27.0

👾 Issue description

According to the API doc comment, SendPollParams.CorrectOptionID is the 0-based index of the correct option in a quiz.

When you create a poll of type quiz where the first answer is correct (i.e. SendPollParams.CorrectOptionID = 0), CorrectOptionID is not included in the serialized request JSON.

This leads to the following error when trying to send the poll:

telego: sendPoll(): api: 400 "Bad Request: wrong correct option ID specified"

This seems to be the case because SendPollParams.CorrectOptionID is of type int, whose 0-value is 0, and the field has tag json:"omitempty".

⚡️ Expected behavior

It should somehow be possible to create poll params with CorrectOptionID = 0 and include it in the request JSON.

Edit: I see there were similar issues with omitempty in the issue history.
Maybe it would make sense to turn all non-nillable param types into pointers to avoid future issues like this?

🧐 Code example

bot.SendPoll(tu.Poll(tu.ID(chatID, "Question?")).
    WithType(telego.PollTypeQuiz).
    WithOptions("Correct option", "Incorrect option").
    WithCorrectOptionID(0))

How to send a message in markdown v2 formatting containing an inline link?

💬 Telego version

v0.29.2

👾 Issue description

How to send a message in markdown v2 formatting containing an inline link like this:
Link

⚡️ Expected behavior

A text like this:
Link

🧐 Code example

text := "\\[Link\\]\\(https://example.com\\)"
msg := tu.Message(tu.ID(query.Message.GetChat().ID), text)
msg.WithParseMode(telego.ModeMarkdownV2)
bot.sendMessage(msg)

SendPhoto whith MarkdownV2, cant send newline in caption

Discussed in #97

Originally posted by Kris24333 April 21, 2023
Hello

I use this code

text := "Some text \n\n Some text"
if _, err := telegramBot.Bot.SendPhoto(&telego.SendPhotoParams{
			ChatID:      tu.ID(update.Message.Chat.ID),
			ParseMode:   parseMode,
			Caption:     text,
			ReplyMarkup: keyboard,
			Photo:       tu.File(mustOpen(photoPath)),
		}); err != nil {
			log.Println(err)
		}

Newline dont work

debug:
API call to: "https://api.telegram.org/botBOT_TOKEN/sendPhoto", with data: parameters: {"caption":"Some text\\n\\nSome text","chat_id":"122345","parse_mode":"MarkdownV2"... - extra \ in Api call

⭐️ `MessageEntity` utilities

⭐️ Feature description

MessageEntity utilities for creating complex formatting

🌈 Your view

No response

🧐 Code example

text, entries := tu.MessageEntities(
    tu.Entity("Hello").Bold(), tu.Entity(" "), tu.Entity("world!").Italic().Spoiler(),
)

⭐️ tu.Messagef

⭐️ Feature description

Add tu.Messagef utility for formatted messages

🌈 Your view

No response

🧐 Code example

No response

⭐️ telegohandler.HandlerWrapper

⭐️ Feature description

It can accept multiple Handlers to return a Handler to simulate a gin-like route capability.

This will provide more power over the original telego handler. Examples include middleware (necessary for authenticated bots), error handling, and more complex logic controls.

If you like the idea, I can submit a PR.

🌈 Your view

You can see the code example.

🧐 Code example

package middleware

import (
	"context"
	"github.com/mymmrac/telego"
	"github.com/mymmrac/telego/telegohandler"
	"github.com/mymmrac/telego/telegoutil"
)

type Ctx struct {
	stack        []WrapperHandler
	context      context.Context
	indexHandler int
	update       telego.Update
	bot          *telego.Bot
}

func (c *Ctx) Next() error {
	c.indexHandler++
	if c.indexHandler == len(c.stack) {
		return nil
	}
	h := c.stack[c.indexHandler]
	return h(c)
}

func (c *Ctx) SendMessage(text string) error {
	chatID := telegoutil.ID(c.update.Message.Chat.ID)
	_, err := c.bot.SendMessage(telegoutil.Message(
		chatID,
		text))
	return err
}

type WrapperHandlerBuilder struct {
	ErrorHandler WrapperErrorHandler
	stack        []WrapperHandler
}

func defaultErrorHandler(ctx *Ctx, err error) {
	ctx.SendMessage(err.Error())
}

func (w *WrapperHandlerBuilder) Add(h ...WrapperHandler) {
	w.stack = append(w.stack, h...)
}

func (w *WrapperHandlerBuilder) Build() telegohandler.Handler {
	return func(bot *telego.Bot, update telego.Update) {
		c := &Ctx{
			context:      context.Background(),
			indexHandler: 0,
			update:       update,
			bot:          bot,
		}
		if len(w.stack) == 0 {
			return
		}
		h := w.stack[0]
		if err := h(c); err != nil {
			w.ErrorHandler(c, err)
		}
	}
}

type WrapperHandler func(ctx *Ctx) error
type WrapperErrorHandler func(ctx *Ctx, err error)

func NewWrapperHandlerBuilder() *WrapperHandlerBuilder {
	return &WrapperHandlerBuilder{
		ErrorHandler: defaultErrorHandler,
	}
}

👾 ChatID for optional fields

💬 Telego version

v0.10.3

👾 Issue description

ChatID can't be empty even if it's optional

⚡️ Expected behavior

ChatID to be able to use in optional fields

🧐 Code example

// MarshalJSON returns JSON representation of ChatID
func (c ChatID) MarshalJSON() ([]byte, error) {
	if c.ID != 0 {
		return json.Marshal(c.ID)
	}

	if c.Username != "" {
		return json.Marshal(c.Username)
	}

	return nil, errors.New("chat ID and username are empty")
}

⭐️ Ability to hide bot token in logs

⭐️ Feature description

Add ability to hide bot token in logs (enabled by default)

  • Add new bot option

🌈 Your view

Log message:
Log https://api.telegram.org/bot123456789:aaaabbbbaaaabbbbaaaabbbbaaaabbbbccc/getMe

Will be changed to:
Log https://api.telegram.org/bot<BOT_TOKEN>/getMe

🧐 Code example

No response

SendAudio

Hi! I can't send the audio correctly. I use this code:

func main() {
	botToken := "token"

	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	updates, _ := bot.UpdatesViaLongPolling(nil)

	defer bot.StopLongPolling()

        //my package for getting audio parameters
	var m music.Music
	m.Authorization()

	for update := range updates {
		if update.Message != nil {
			chatID := tu.ID(update.Message.Chat.ID)

			//getting audio parameters
			params, err := m.GetAudioParams(update.Message.Text)
			if err != nil {
				_, _ = bot.SendMessage(&telego.SendMessageParams{
					ChatID: chatID,
					Text:   err.Error(),
				})
				continue
			}

			//link to download audio
			audio := tu.Audio(chatID, tu.FileByURL(params.URL))
			//filling performer
			audio.WithPerformer(params.Performer)
			//filling title
			audio.WithTitle(params.Title)

			_, _ = bot.SendAudio(audio)
		}
	}
}

I get such a debag response:

[Mon Apr 24 17:40:17 MSK 2023] DEBUG API call to: "https://api.telegram.org/botBOT_TOKEN/sendAudio", with data: {"chat_id":848447660,"audio":"https://s147iva.storage.yandex.net/get-mp3/bda9fda28fe66f5e3d4032b4a10bd888/0005fa16d2d871bc/rmusic/U2FsdGVkX19wkWz_DC4Mg4N6EoV332DXOynFrntt3AvzEYKgmcZd93n6fMrhy5u9Mcb7YcBr14nlkBrKxiEvJ2HXUM3AQ7RRFvxNXFkyYGU/575f1549004e4aa8ecc991e6696224e6af4248ddad339812300119ffbff2b6a8/27023","performer":"Performer","title":"Title"}
[Mon Apr 24 17:40:18 MSK 2023] DEBUG API response sendAudio: Ok: true, Err: [<nil>], Result: {"message_id":90,"from":{"id":6210745530,"is_bot":true,"first_name":"ya_music","username":"cleonia_music_bot"},"chat":{"id":848447660,"first_name":"\u0414\u0430\u043d\u0438\u0438\u043b","username":"Cleonia_21","type":"private"},"date":1682347218,"audio":{"duration":171,"file_name":"27023.mp3","mime_type":"audio/mpeg","file_id":"CQACAgQAAxkDAANaZEaU0qB2MyMvdaurhgOqYkDQdaoAAtsDAAIXTDVSLA0Gj1gWr_gvBA","file_unique_id":"AgAD2wMAAhdMNVI","file_size":4116480}}

Audio is sent to the user, but title and performer are not added. The sent audio looks like this:
Screenshot 2023-04-24 at 18 01 09

How do I fill in the name of the audio and artist?

⭐️ Refactor `UpdatesViaWebhook` to be server agnostic

⭐️ Feature description

UpdatesViaWebhook should really be server agnostic to achieve ease of creating multiple bots from one server and use with custom servers with already existing functionality.

This is definitely a breaking change.

Modify to accept custom options:

  • StartListeningForWebhook

Deprecate after changes:

  • StartListeningForWebhookTLS
  • StartListeningForWebhookTLSEmbed
  • StartListeningForWebhookUNIX

🌈 Your view

Create an interface for Server with default implementation of net/http and fasthttp servers.

🧐 Code example

No response

Cant send remote audio file

💬 Telego version

v0.28.0

👾 Issue description

API response sendAudio: Ok: false, Err: [400 "Bad Request: failed to get HTTP URL content"]

⚡️ Expected behavior

When I use this, everything working fine : https://github.com/cavaliergopher/grab :))

No response

🧐 Code example

audioMessage := tu.Audio(
tu.ID(update.Message.Chat.ID),tu.FileFromURL(track[2].URL),).WithReplyToMessageID(update.Message.MessageID
)
bot.SendAudio(audioMessage)

Typo in telego/pradicates

💬 Telego version

v0.10.2

👾 Issue description

AnyMassage instead of AnyMessage.

https://github.com/mymmrac/telego/blob/main/telegohandler/pradicates.go#L29

It's a minor typo, not sure worth opening a bug report
later when I feel myself more confident about how this library works, I'll create pull requests directly

⚡️ Expected behavior

AnyMessage

🧐 Code example

// AnyMassage is true if message isn't nil
func AnyMassage() Predicate {
	return func(update telego.Update) bool {
		return update.Message != nil
	}
}

⭐️Run 2 and more bots on the same server

⭐️ Feature description

Run 2 bots on the same server with the single golang binary

🌈 Your view

Webhook mode
Now we can run only 1 bot. To run 2 and more bot we need polling mode or change ports.
When we try to run second bot - we get error (port busy)
How can we use one fast http server for 2 and more bots?

🧐 Code example

No response

👾 Modifying update inside predicate will change original update

💬 Telego version

v0.13.2

👾 Issue description

All (apart from update_id) telego.Update fields are pointers, so there is a possibility that one predicate changed update which is not expected

⚡️ Expected behavior

Predicates should receive a deep copy of the update and not original update

🧐 Code example

No response

👾 InlineKeyboardButton no way to use empty inline query

💬 Telego version

v0.15.3

👾 Issue description

When using InlineKeyboardButton there is no way to use SwitchInlineQuery or SwitchInlineQueryCurrentChat with empty query.

Need to check other places.

⚡️ Expected behavior

No response

🧐 Code example

No response

👾 Fix race conditions

💬 Telego version

v0.13.2

👾 Issue description

Fix race conditions for BotHandler, seems to be related to running var

⚡️ Expected behavior

Race tests should always pass (on GitHub Actions especially)

🧐 Code example

No response

Typo on LongPolling.

💬 Telego version

v0.18.1

👾 Issue description

It's not a bug actually, but it seems like that there is a massive typo on LongPolling which is written as LongPulling.

I thought that I'm wrong and it's actually LongPulling, but as I checked again and even visited the official docs, it's actually LongPolling

I wanted to know, was this on purpose or it's a typo or...?

⚡️ Expected behavior

No response

🧐 Code example

No response

Bad Request: entity begins in a middle of a UTF-16 symbol at byte offset ...

💬 Telego version

0.22.0

👾 Issue description

entitys := []tu.MessageEntityCollection{} entitys = append(entitys, tu.Entity("🌗")) bot.SendMessage(tu.MessageWithEntities(tu.ID(ctm.Chat.ID), entitys...).WithReplyToMessageID(ctm.MessageID)) //[Thu Apr 13 13:07:41 MSK 2023] DEBUG API call to: "https://api.telegram.org/botBOT_TOKEN/sendMessage", with data: {"chat_id":1,"text":"🌗Last quarter","entities":[{"type":"code","offset":1,"length":12}],"reply_to_message_id":1} //[Thu Apr 13 13:07:41 MSK 2023] DEBUG API response sendMessage: Ok: false, Err: [400 "Bad Request: entity begins in a middle of a UTF-16 symbol at byte offset 4"]
Please look:
https://core.telegram.org/bots/api#messageentity
MessageEntity
...
offset Integer Offset in UTF-16 code units to the start of the entity
length Integer Length of the entity in UTF-16 code units
...
https://core.telegram.org/api/entities#entity-length
Computing entity length
Code points in the BMP (U+0000 to U+FFFF) count as 1, because they are encoded into a single UTF-16 code unit
Code points in all other planes count as 2, because they are encoded into two UTF-16 code units (also called surrogate pairs)
...
However, since UTF-8 encodes codepoints in non-BMP planes as a 32-bit code unit starting with 0b11110, a more efficient way to compute the entity length without converting the message to UTF-16 is the following:

If the byte marks the beginning of a 32-bit UTF-8 code unit (all bytes starting with 0b11110) increment the count by 2, otherwise
If the byte marks the beginning of a UTF-8 code unit (all bytes not starting with 0b10) increment the count by 1.
Example:

length := 0
for byte in text {
if (byte & 0xc0) != 0x80 {
length += 1 + (byte >= 0xf0)
}
}
Note: the length of an entity must not include the length of trailing newlines or whitespaces, rtrim entities before computing their length: however, the next offset must include the length of newlines or whitespaces that precede it.

⚡️ Expected behavior

{code 2 12 }

🧐 Code example

`					entitys := []tu.MessageEntityCollection{}
					entitys = append(entitys, tu.Entity("🌗"))
					t, es := tu.MessageEntities(entitys...)
					stdo.Printf("%U %v %v\n", []rune(t)[0], []byte(t), es) //15:46:48 main.go:471: U+1F317 [240 159 140 151] []
					entitys = append(entitys, tu.Entity("Last quarter").Code())
					t, es = tu.MessageEntities(entitys...)
					stdo.Printf("%s %v\n", t, es) //15:46:48 main.go:474: 🌗Last quarter [{code 1 12  <nil>  }]
					bot.SendMessage(tu.MessageWithEntities(tu.ID(ctm.Chat.ID), entitys...).WithReplyToMessageID(ctm.MessageID))
//[Thu Apr 13 13:07:41 MSK 2023] DEBUG API call to: "https://api.telegram.org/botBOT_TOKEN/sendMessage", with data: {"chat_id":1,"text":"🌗Last quarter","entities":[{"type":"code","offset":1,"length":12}],"reply_to_message_id":1}
//[Thu Apr 13 13:07:41 MSK 2023] DEBUG API response sendMessage: Ok: false, Err: [400 "Bad Request: entity begins in a middle of a UTF-16 symbol at byte offset 4"]
`

ReplyKeyboardMarkup.Keyboard incorrect type

"ReplyKeyboardMarkup.Keyboard" type is []KeyboardButton, but it's supposed be [][]KeyboardButton

Field Type Description
keyboard Array of Array of KeyboardButton Array of button rows, each represented by an Array of KeyboardButton objects

KeyboardButtonRequestUser type error

💬 Telego version

v0.25.1

👾 Issue description

telego/types.go

Line 1275 in ee83ced

UserIsBot bool `json:"user_is_bot,omitempty"`

when set UserIsBot = false not working.
need to remove the omitempty

⚡️ Expected behavior

when set UserIsBot = false. the request body should have {"UserIsBot":false}

🧐 Code example

No response

⭐️ telego/telegohandler change order of arguments

⭐️ Feature description

It's kinda odd that "func (h *BotHandler) Handle(handler,foo)" takes in handler first, and then path, usually path is the first argument
for example in
http.Handle("/foo", fooHandler)

🌈 Your view

func (h *BotHandler) Handle(predicates ...Predicate,handler Handler)

🧐 Code example

bh.Handle(th.CommandEqual("start"),func(bot *telego.Bot, update telego.Update) {
		// Send message
		_, _ = bot.SendMessage(tu.Message(
			tu.ID(update.Message.Chat.ID),
			fmt.Sprintf("Hello %s!", update.Message.From.FirstName),
		))
	})

Handler middleware

⭐️ Feature description

Bot handlers should support middleware and possibly handler groups (routes)

🌈 Your view

Probably UpdateProcessor can be used as base layer

🧐 Code example

bh.Use(func (upd) upd, Predicates...)

⭐️ More predicates

⭐️ Feature description

Add more useful predicates & update handlers

🌈 Your view

Predicates for:

  • update.CallbackQuery
  • update.CallbackQuery.Message
  • update.CallbackQuery.Data

Handlers for:

  • update.CallbackQuery

🧐 Code example

No response

Language codes

⭐️ Feature description

Hello! I guess we need to replace Lang string fields with language codes from golang.org/x/text/language

It may be very helpful to avoid errors with language's constants

🌈 Your view

No response

🧐 Code example

No response

Memory leaks when calling SendMediaGroup

💬 Telego version

v0.31.0

👾 Issue description

Calling SendMediaGroup with image but deeper I have a strange growth of bytes buffer

⚡️ Expected behavior

just send media

🧐 Code example

package main

import (
	"bytes"
	"fmt"
	"log"
	"os"

	"github.com/mymmrac/telego"
)

type ByteNamedReader struct {
	data []byte
	name string
}

func (b *ByteNamedReader) Name() string {
	return b.name
}

func (b *ByteNamedReader) Read(p []byte) (n int, err error) {
	return bytes.NewReader(b.data).Read(p)
}

func main() {
	// Get Bot token from environment variables
	botToken := "token"

	// Note: Please keep in mind that default logger may expose sensitive information,
	// use in development only
	bot, err := telego.NewBot(botToken, telego.WithDefaultDebugLogger())
	if err != nil {
		fmt.Println(err)
		os.Exit(1)
	}

	// Get updates channel
	// (more on configuration in examples/updates_long_polling/main.go)
	updates, _ := bot.UpdatesViaLongPolling(nil)

	// Stop reviving updates from update channel
	defer bot.StopLongPolling()

	f, err := os.Open("proxy-image.jpg")
	if err != nil {
		panic(err)
	}

	media := []telego.InputMedia{}

	stat, err := f.Stat()
	if err != nil {
		panic(err)
	}

	fc := make([]byte, stat.Size())
	_, err = f.Read(fc)
	if err != nil {
		panic(err)
	}

	namedReader := &ByteNamedReader{
		data: fc,
		name: "proxy-image.jpg",
	}

	log.Print(len(fc))

	media = append(media, &telego.InputMediaPhoto{
		Media: telego.InputFile{File: namedReader},
	})

	// Loop through all updates when they came
	for update := range updates {
		_, err = bot.SendMediaGroup(&telego.SendMediaGroupParams{ChatID: update.Message.Chat.ChatID(), Media: media})
		if err != nil {
			panic(err)
		}
	}
}

⭐️ Update CI flow for PR

⭐️ Feature description

Make two different flows for master and PRs or fix SonarCloud not being able to run

🌈 Your view

No response

🧐 Code example

No response

⭐️ Webhook options

⭐️ Feature description

Be able to specify some optional webhook parameters like health API or any other additional options in future

🌈 Your view

Same API as for Bot (BotOption) but for webhook listening

🧐 Code example

No response

API response setWebhook: Ok: false on second call, from UpdatesViaWebhook

I gave the https://github.com/mymmrac/telego/blob/main/examples/updates_webhook/main.go a try, but get DEBUG API response setWebhook: Ok: false on second call, from within UpdatesViaWebhook:

// Calls SetWebhook before starting webhook
telego.WithWebhookSet(&telego.SetWebhookParams{
URL: "https://example.com/bot" + bot.Token(),
}),

Here is the full log:

[Sat Apr 22 23:30:35 EDT 2023] DEBUG API call to: "https://api.telegram.org/botBOT_TOKEN/setWebhook", with data: {"url":"https://my.server/bot"}
[Sat Apr 22 23:30:35 EDT 2023] DEBUG API response setWebhook: Ok: true, Err: [0 "Webhook is already set"], Result: true
[Sat Apr 22 23:30:35 EDT 2023] DEBUG API call to: "https://api.telegram.org/botBOT_TOKEN/getWebhookInfo", with data: null
[Sat Apr 22 23:30:35 EDT 2023] DEBUG API response getWebhookInfo: Ok: true, Err: [<nil>], Result: {"url":"https://my.server/bot","has_custom_certificate":false,"pending_update_count":0,"last_error_date":1682219649,"last_error_message":"Connection timed out","max_connections":40,"ip_address":"my.server"}
Webhook Info: &{URL:https://my.server/bot HasCustomCertificate:false PendingUpdateCount:0 IPAddress:my.server LastErrorDate:1682219649 LastErrorMessage:Connection timed out LastSynchronizationErrorDate:0 MaxConnections:40 AllowedUpdates:[]}
[Sat Apr 22 23:30:35 EDT 2023] DEBUG API call to: "https://api.telegram.org/botBOT_TOKEN/setWebhook", with data: {"url":"https://my.server/bot"}
[Sat Apr 22 23:30:35 EDT 2023] DEBUG API response setWebhook: Ok: false, Err: [429 "Too Many Requests: retry after 1", migrate to chat ID: 0, retry after: 1]

Full test code at:
https://github.com/suntong/go_telebot_demos/blob/master/telego/webhook/main.go

👾 Inline Button

💬 Telego version

0.18.1

👾 Issue description

When i want to use inline keyboard button i got response : API response sendMessage: Ok: false, Err: [400 "Bad Request: can't parse inline keyboard button: Text buttons are unallowed in the inline keyboard"].

from this i found that we should pass more param than only text in tu.InlineKeyboardButton().

when i try to use :

telego.InlineKeyboardButton{
			Text:         "test",
			CallbackData: "yoda",
		},

it's work normally.

⚡️ Expected behavior

tu.InlineKeyboardButton() can pass optional params for Callback or thing that support in inline Mode

🧐 Code example

var Inline = tu.InlineKeyboard(
	tu.InlineKeyboardRow(
		tu.InlineKeyboardButton("test"), //  return error like above
		telego.InlineKeyboardButton{ // work
			Text:         "test",
			CallbackData: "yoda",
		},
	),
)

It is not possible to send non-anonymous polls

💬 Telego version

v0.25.0

👾 Issue description

Using SendPoll with IsAnonymous = false won't work, because IsAnonymous is set to omitempty, so false will be omited, and Telegram will use the default value for IsAnonymous, which is true.
So, in other words, you can't send a non-anonymous poll.

I tried fixing it, but all solutions I thought involves potential breaking changes for current users of SendPoll (if any), for example:

  1. Changing IsAnonymous to a *bool
    Probably won't break much because the default will still be anonymous poll.
    Only if the user explicitly pass IsAnonymous: true that he will notice compile errors.

  2. Removing omitempty and making default polls not anonymous
    It won't give compile time errors, but will make polls behave differently by default.

⚡️ Expected behavior

No response

🧐 Code example

_, err = bot.SendPoll(&tg.SendPollParams{
	ChatID: tg.ChatID{
		ID: u.Message.Chat.ID,
	},
	Question: "is this anonymous?",
	Options:  []string{"yes", "no"},
	IsAnonymous: false,
})

Inject `context.Context` through `WebhookHandler` for Update entity.

⭐️ Feature description

The current WebhookHandler presents the structure of func(data []byte) error, and injects the entity into updatesChan after parsing the json data in the UpdatesViaWebhook function.

But the context.Context in the Update entity comes from the initial setting, and usually each http request has an independent goroutine, so naturally it should have an independent context.Context.

🌈 Your view

I think the ctx context.Context parameter should be added to WebhookHandler to inject the context of each http request starting from the establishment of a connection, and injected into the Update entity in UpdatesViaWebhook.

🧐 Code example

func (b *Bot) UpdatesViaWebhook(path string, options ...WebhookOption) (<-chan Update, error) {
	if b.webhookContext != nil {
		return nil, errors.New("telego: webhook context already exists")
	}

	webhookContext, err := b.createWebhookContext(options)
	if err != nil {
		return nil, err
	}

	webhookContext.runningLock.Lock()
	defer webhookContext.runningLock.Unlock()

	b.webhookContext = webhookContext
	webhookContext.stop = make(chan struct{})
	webhookContext.configured = true

	updatesChan := make(chan Update, ctx.updateChanBuffer)

	err = webhookContext.server.RegisterHandler(path, func(ctx context.Context, data []byte) error {
		b.log.Debugf("Webhook request with data: %s", string(data))

		var update Update
		err = json.Unmarshal(data, &update)
		if err != nil {
			b.log.Errorf("Webhook decoding error: %s", err)
			return fmt.Errorf("telego: webhook decoding update: %w", err)
		}

		select {
		case <-webhookContext.stop:
			return fmt.Errorf("telego: webhook stopped")
		case <-webhookContext.ctx.Done():
			return fmt.Errorf("telego: %w", webhookContext.ctx.Err())
		default:
			if safeSend(updatesChan, update.WithContext(ctx)) {
				return fmt.Errorf("telego: webhook stopped")
			}
			return nil
		}
	})
	if err != nil {
		return nil, fmt.Errorf("telego: webhook register handler: %w", err)
	}

	go func() {
		select {
		case <-webhookContext.stop:
		case <-webhookContext.ctx.Done():
		}
		close(updatesChan)
	}()

	return updatesChan, nil
}

Be able to set a retry count for commands

⭐️ Feature description

I've been suffering a lot with requests failing because of timeout and non-reliable network...
I think it would be great to wrap commands behind a retry function, that tries to execute the command N times.
If it fails N times, it then returns an error.

Just for elucidating:

bot, err := telego.NewBot(token, telego.Retry(5))
...
bot.SendMessage(...)

This would make bot retry to SendMessage until success or retry limit.

If you agree with the suggestions, I would like to work on it :)

🌈 Your view

No response

🧐 Code example

No response

List of members in a group

⭐️ Feature description

Add a function which returns list of members in a group

🌈 Your view

No response

🧐 Code example

No response

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.