{"id":13409287,"url":"https://github.com/mr-linch/go-tg","last_synced_at":"2026-02-09T18:09:07.113Z","repository":{"id":37013043,"uuid":"504485990","full_name":"mr-linch/go-tg","owner":"mr-linch","description":"🤖 Go client library for accessing Telegram Bot API, with batteries for building complex bots included.","archived":false,"fork":false,"pushed_at":"2024-06-03T15:13:07.000Z","size":497,"stargazers_count":84,"open_issues_count":5,"forks_count":7,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-07-31T20:35:56.855Z","etag":null,"topics":["api-bindings","bot-framework","go","golang","telegram-bot-api"],"latest_commit_sha":null,"homepage":"https://pkg.go.dev/github.com/mr-linch/go-tg","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mr-linch.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-06-17T10:09:41.000Z","updated_at":"2024-07-13T10:34:25.000Z","dependencies_parsed_at":"2022-07-11T15:03:50.144Z","dependency_job_id":"56f16475-dcf9-40e7-a04f-133508fa0a7d","html_url":"https://github.com/mr-linch/go-tg","commit_stats":null,"previous_names":[],"tags_count":25,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-linch%2Fgo-tg","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-linch%2Fgo-tg/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-linch%2Fgo-tg/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mr-linch%2Fgo-tg/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mr-linch","download_url":"https://codeload.github.com/mr-linch/go-tg/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243593335,"owners_count":20316170,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["api-bindings","bot-framework","go","golang","telegram-bot-api"],"created_at":"2024-07-30T20:00:59.599Z","updated_at":"2026-02-06T23:10:30.099Z","avatar_url":"https://github.com/mr-linch.png","language":"Go","funding_links":[],"categories":["Bot Building","Bot建设","Go"],"sub_categories":[],"readme":"# go-tg\n\n[![Go Reference](https://pkg.go.dev/badge/github.com/mr-linch/go-tg.svg)](https://pkg.go.dev/github.com/mr-linch/go-tg)\n[![go.mod](https://img.shields.io/github/go-mod/go-version/mr-linch/go-tg)](go.mod)\n[![GitHub release (latest by date)](https://img.shields.io/github/v/release/mr-linch/go-tg?label=latest%20release)](https://github.com/mr-linch/go-tg/releases/latest)\n\u003c!-- auto-generated: Telegram Bot API badge --\u003e\n[![Telegram Bot API](https://img.shields.io/badge/Telegram%20Bot%20API-9.3%20%28from%20December%2031%2C%202025%29-blue?logo=telegram)](https://core.telegram.org/bots/api#december-31-2025)\n\u003c!-- end: auto-generated --\u003e\n[![CI](https://github.com/mr-linch/go-tg/actions/workflows/ci.yml/badge.svg)](https://github.com/mr-linch/go-tg/actions/workflows/ci.yml)\n[![codecov](https://codecov.io/gh/mr-linch/go-tg/branch/main/graph/badge.svg?token=9EI5CEIYXL)](https://codecov.io/gh/mr-linch/go-tg)\n[![Go Report Card](https://goreportcard.com/badge/github.com/mr-linch/go-tg)](https://goreportcard.com/report/github.com/mr-linch/go-tg)\n[![[Telegram]](https://img.shields.io/badge/%20chat-@go__tg__devs-blue.svg?style=flat-square)](https://t.me/go_tg_devs)\n\n[![beta](https://img.shields.io/badge/-beta-yellow)](https://go-faster.org/docs/projects/status)\n\n- [Features](#features)\n- [Install](#install)\n- [Quick Example](#quick-example)\n- [API Client](#api-client)\n  - [Creating](#creating)\n  - [Bot API methods](#bot-api-methods)\n  - [Low-level Bot API methods call](#low-level-bot-api-methods-call)\n  - [Helper methods](#helper-methods)\n  - [Sending files](#sending-files)\n  - [Downloading files](#downloading-files)\n  - [Interceptors](#interceptors)\n- [Updates](#updates)\n  - [Handlers](#handlers)\n  - [Typed Handlers](#typed-handlers)\n  - [Receive updates via Polling](#receive-updates-via-polling)\n  - [Receive updates via Webhook](#receive-updates-via-webhook)\n  - [Routing updates](#routing-updates)\n- [Extensions](#extensions)\n  - [Sessions](#sessions)\n- [Related Projects](#related-projects)\n- [Projects using this package](#projects-using-this-package)\n- [Thanks](#thanks)\n\ngo-tg is a Go client library for accessing [Telegram Bot API](https://core.telegram.org/bots/api), with batteries for building complex bots included.\n\n\u003e ⚠️ Although the API definitions are considered stable package is well tested and used in production, please keep in mind that go-tg is still under active development and therefore full backward compatibility is not guaranteed before reaching v1.0.0.\n\n## Features\n\n- :rocket: Code for Bot API types and methods is generated with embedded official documentation.\n- :white_check_mark: Support [context.Context](https://pkg.go.dev/context).\n- :link: API Client and bot framework are strictly separated, you can use them independently.\n- :zap: No runtime reflection overhead.\n- :arrows_counterclockwise: Supports Webhook and Polling natively;\n- :mailbox_with_mail: [Webhook reply](https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates) for high load bots;\n- :raised_hands: Handlers, filters, and middleware are supported.\n- :globe_with_meridians: [WebApps](https://core.telegram.org/bots/webapps) and [Login Widget](https://core.telegram.org/widgets/login) helpers.\n- :handshake: Business connections support\n\n\n## Install\n\n```bash\n# go 1.18+\ngo get -u github.com/mr-linch/go-tg\n```\n\n## Quick Example\n\n```go\npackage main\n\nimport (\n  \"context\"\n  \"fmt\"\n  \"os\"\n  \"os/signal\"\n  \"regexp\"\n  \"syscall\"\n  \"time\"\n\n  \"github.com/mr-linch/go-tg\"\n  \"github.com/mr-linch/go-tg/tgb\"\n)\n\nfunc main() {\n  ctx := context.Background()\n\n  ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, os.Kill, syscall.SIGTERM)\n  defer cancel()\n\n  if err := run(ctx); err != nil {\n    fmt.Println(err)\n    defer os.Exit(1)\n  }\n}\n\nfunc run(ctx context.Context) error {\n  client := tg.New(os.Getenv(\"BOT_TOKEN\"))\n\n  router := tgb.NewRouter().\n    // handles /start and /help\n    Message(func(ctx context.Context, msg *tgb.MessageUpdate) error {\n      return msg.Answer(\n        tg.HTML.Text(\n          tg.HTML.Bold(\"👋 Hi, I'm echo bot!\"),\n          \"\",\n          tg.HTML.Italic(\"🚀 Powered by\", tg.HTML.Spoiler(tg.HTML.Link(\"go-tg\", \"github.com/mr-linch/go-tg\"))),\n        ),\n      ).ParseMode(tg.HTML).DoVoid(ctx)\n    }, tgb.Command(\"start\", tgb.WithCommandAlias(\"help\"))).\n    // handles gopher image\n    Message(func(ctx context.Context, msg *tgb.MessageUpdate) error {\n      if err := msg.Update.Reply(ctx, msg.AnswerChatAction(tg.ChatActionUploadPhoto)); err != nil {\n        return fmt.Errorf(\"answer chat action: %w\", err)\n      }\n\n      // emulate thinking :)\n      time.Sleep(time.Second)\n\n      return msg.AnswerPhoto(\n        tg.NewFileArgURL(\"https://go.dev/blog/go-brand/Go-Logo/PNG/Go-Logo_Blue.png\"),\n      ).DoVoid(ctx)\n\n    }, tgb.Regexp(regexp.MustCompile(`(?mi)(go|golang|gopher)[$\\s+]?`))).\n    // handle other messages\n    Message(func(ctx context.Context, msg *tgb.MessageUpdate) error {\n      return msg.Copy(msg.Chat).DoVoid(ctx)\n    }).\n    MessageReaction(func(ctx context.Context, reaction *tgb.MessageReactionUpdate) error {\n\t\t\t// sets same reaction to the message\n\t\t\tanswer := tg.NewSetMessageReactionCall(reaction.Chat, reaction.MessageID).Reaction(reaction.NewReaction)\n\t\t\treturn reaction.Update.Reply(ctx, answer)\n\t\t})\n\n  return tgb.NewPoller(\n    router,\n    client,\n    tgb.WithPollerAllowedUpdates(\n\t\t\ttg.UpdateTypeMessage,\n      tg.UpdateTypeMessageReaction,\n    )\n  ).Run(ctx)\n}\n```\n\nMore examples can be found in [examples](https://github.com/mr-linch/go-tg/tree/main/examples).\n\n## API Client\n\n### Creating\n\nThe simplest way for create client it's call `tg.New` with token. That constructor use `http.DefaultClient` as default client and `api.telegram.org` as server URL:\n\n```go\nclient := tg.New(\"\u003cTOKEN\u003e\") // from @BotFather\n```\n\nWith custom [http.Client](https://pkg.go.dev/net/http#Client):\n\n```go\nproxyURL, err := url.Parse(\"http://user:pass@ip:port\")\nif err != nil {\n  return err\n}\n\nhttpClient := \u0026http.Client{\n  Transport: \u0026http.Transport{\n    Proxy: http.ProxyURL(proxyURL),\n  },\n}\n\nclient := tg.New(\"\u003cTOKEN\u003e\",\n tg.WithClientDoer(httpClient),\n)\n```\n\nWith self hosted Bot API server:\n\n```go\nclient := tg.New(\"\u003cTOKEN\u003e\",\n tg.WithClientServerURL(\"http://localhost:8080\"),\n)\n```\n\n### Bot API methods\n\nAll API methods are supported with embedded official documentation.\nIt's provided via Client methods.\n\ne.g. [`getMe`](https://core.telegram.org/bots/api#getme) call:\n\n```go\nme, err := client.GetMe().Do(ctx)\nif err != nil {\n return err\n}\n\nlog.Printf(\"authorized as @%s\", me.Username)\n```\n\n[`sendMessage`](https://core.telegram.org/bots/api#sendmessage) call with required and optional arguments:\n\n```go\npeer := tg.Username(\"MrLinch\")\n\nmsg, err := client.SendMessage(peer, \"\u003cb\u003eHello, world!\u003c/b\u003e\").\n  ParseMode(tg.HTML). // optional passed like this\n  Do(ctx)\n\nif err != nil {\n  return err\n}\n\nlog.Printf(\"sended message id %d\", msg.ID)\n```\n\nSome Bot API methods do not return the object and just say `True`. So, you should use the `DoVoid` method to execute calls like that.\n\nAll calls with the returned object also have the `DoVoid` method. Use it when you do not care about the result, just ensure it's not an error (unmarshaling also be skipped).\n\n```go\npeer := tg.Username(\"MrLinch\")\n\nif err := client.SendChatAction(\n  peer,\n  tg.ChatActionTyping\n).DoVoid(ctx); err != nil {\n  return err\n}\n```\n\n### Low-level Bot API methods call\n\nClient has method [`Do`](https://pkg.go.dev/github.com/mr-linch/go-tg#Client.Do) for low-level [requests](https://pkg.go.dev/github.com/mr-linch/go-tg#Request) execution:\n\n```go\nreq := tg.NewRequest(\"sendChatAction\").\n  PeerID(\"chat_id\", tg.Username(\"@MrLinch\")).\n  String(\"action\", \"typing\")\n\nif err := client.Do(ctx, req, nil); err != nil {\n  return err\n}\n```\n\n### Helper methods\n\nMethod [`Client.Me()`](https://pkg.go.dev/github.com/mr-linch/go-tg#Client.Me) fetches authorized bot info via [`Client.GetMe()`](https://pkg.go.dev/github.com/mr-linch/go-tg#Client.GetMe) and cache it between calls.\n\n```go\nme, err := client.Me(ctx)\nif err != nil {\n  return err\n}\n```\n\n### Sending files\n\nThere are several ways to send files to Telegram:\n\n- uploading a file along with a method call;\n- sending a previously uploaded file by its identifier;\n- sending a file using a URL from the Internet;\n\nThe [`FileArg`](https://pkg.go.dev/github.com/mr-linch/go-tg#FileArg) type is used to combine all these methods. It is an object that can be passed to client methods and depending on its contents the desired method will be chosen to send the file.\n\nConsider each method by example.\n\n**Uploading a file along with a method call:**\n\nFor upload a file you need to create an object [`tg.InputFile`](https://pkg.go.dev/github.com/mr-linch/go-tg#InputFile). It is a structure with two fields: file name and [`io.Reader`](https://pkg.go.dev/io#Reader) with its contents.\n\nType has some handy constructors, for example consider uploading a file from a local file system:\n\n```go\ninputFile, err := tg.NewInputFileLocal(\"/path/to/file.pdf\")\nif err != nil {\n  return err\n}\ndefer inputFile.Close()\n\npeer := tg.Username(\"MrLinch\")\n\nif err := client.SendDocument(\n  peer,\n  tg.NewFileArgUpload(inputFile),\n).DoVoid(ctx); err != nil {\n\treturn err\n}\n```\n\nLoading a file from a buffer in memory:\n\n```go\n\nbuf := bytes.NewBufferString(\"\u003chtml\u003e...\u003c/html\u003e\")\n\ninputFile := tg.NewInputFile(\"index.html\", buf)\n\npeer := tg.Username(\"MrLinch\")\n\nif err := client.SendDocument(\n  peer,\n  tg.NewFileArgUpload(inputFile),\n).DoVoid(ctx); err != nil {\n\treturn err\n}\n```\n\n**Sending a file using a URL from the Internet:**\n\n```go\npeer := tg.Username(\"MrLinch\")\n\nif err := client.SendPhoto(\n  peer,\n  tg.NewFileArgURL(\"https://picsum.photos/500\"),\n).DoVoid(ctx); err != nil {\n\treturn err\n}\n```\n\n**Sending a previously uploaded file by its identifier:**\n\n```go\npeer := tg.Username(\"MrLinch\")\n\nif err := client.SendPhoto(\n  peer,\n  tg.NewFileArgID(tg.FileID(\"AgACAgIAAxk...\")),\n).DoVoid(ctx); err != nil {\n\treturn err\n}\n```\n\nPlease checkout [examples](https://github.com/mr-linch/go-tg/tree/main/examples) with \"File Upload\" features for more usecases.\n\n### Downloading files\n\nTo download a file you need to get its [`FileID`](https://pkg.go.dev/github.com/mr-linch/go-tg#FileID).\nAfter that you need to call method [`Client.GetFile`](https://pkg.go.dev/github.com/mr-linch/go-tg#Client.GetFile) to get metadata about the file.\nAt the end we call method [`Client.Download`](https://pkg.go.dev/github.com/mr-linch/go-tg#Client.Download) to fetch the contents of the file.\n\n```go\n\nfid := tg.FileID(\"AgACAgIAAxk...\")\n\nfile, err := client.GetFile(fid).Do(ctx)\nif err != nil {\n  return err\n}\n\nf, err := client.Download(ctx, file.FilePath)\nif err != nil {\n  return err\n}\ndefer f.Close()\n\n// ...\n```\n\n### Interceptors\n\nInterceptors are used to modify or process the request before it is sent to the server and the response before it is returned to the caller. It's like a [[tgb.Middleware](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Middleware)], but for outgoing requests.\n\nAll interceptors should be registered on the client before the request is made.\n\n```go\nclient := tg.New(\"\u003cTOKEN\u003e\",\n  tg.WithClientInterceptors(\n    tg.Interceptor(func(ctx context.Context, req *tg.Request, dst any, invoker tg.InterceptorInvoker) error {\n      started := time.Now()\n\n      // before request\n      err := invoker(ctx, req, dst)\n      // after request\n\n      log.Print(\"call %s took %s\", req.Method, time.Since(started))\n\n      return err\n    }),\n  ),\n)\n```\n\nArguments of the interceptor are:\n\n- `ctx` - context of the request;\n- `req` - request object [tg.Request](https://pkg.go.dev/github.com/mr-linch/go-tg#Request);\n- `dst` - pointer to destination for the response, can be `nil` if the request is made with `DoVoid` method;\n- `invoker` - function for calling the next interceptor or the actual request.\n\nContrib package has some useful interceptors:\n\n- [InterceptorRetryFloodError](https://pkg.go.dev/github.com/mr-linch/go-tg#NewInterceptorRetryFloodError) - retry request if the server returns a flood error. Parameters can be customized via options;\n- [InterceptorRetryInternalServerError](https://pkg.go.dev/github.com/mr-linch/go-tg#NewInterceptorRetryInternalServerError) - retry request if the server returns an error. Parameters can be customized via options;\n- [InterceptorMethodFilter](https://pkg.go.dev/github.com/mr-linch/go-tg#NewInterceptorMethodFilter) - call underlying interceptor only for specified methods;\n- [InterceptorDefaultParseMethod](https://pkg.go.dev/github.com/mr-linch/go-tg#NewInterceptorDefaultParseMethod) - set default `parse_mode` for messages if not specified.\n\nInterceptors are called in the order they are registered.\n\nExample of using retry flood interceptor: [examples/retry-flood](https://github.com/mr-linch/go-tg/blob/main/examples/retry-flood/main.go)\n\n## Updates\n\nEverything related to receiving and processing updates is in the [`tgb`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb) package.\n\n### Handlers\n\nYou can create an update handler in three ways:\n\n1. Declare the structure that implements the interface [`tgb.Handler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Handler):\n\n```go\ntype MyHandler struct {}\n\nfunc (h *MyHandler) Handle(ctx context.Context, update *tgb.Update) error {\n  if update.Message != nil {\n    return nil\n  }\n\n log.Printf(\"update id: %d, message id: %d\", update.ID, update.Message.ID)\n\n return nil\n}\n```\n\n2. Wrap the function to the type [`tgb.HandlerFunc`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#HandlerFunc):\n\n```go\nvar handler tgb.Handler = tgb.HandlerFunc(func(ctx context.Context, update *tgb.Update) error {\n // skip updates of other types\n if update.Message == nil {\n  return nil\n }\n\n log.Printf(\"update id: %d, message id: %d\", update.ID, update.Message.ID)\n\n return nil\n})\n```\n\n3. Wrap the function to the type `tgb.*Handler` for creating typed handlers with null pointer check:\n\n```go\n// that handler will be called only for messages\n// other updates will be ignored\nvar handler tgb.Handler = tgb.MessageHandler(func(ctx context.Context, mu *tgb.MessageUpdate) error {\n  log.Printf(\"update id: %d, message id: %d\", mu.Update.ID, mu.ID)\n  return nil\n})\n```\n\n### Typed Handlers\n\nFor each subtype (field) of [`tg.Update`](https://pkg.go.dev/github.com/mr-linch/go-tg/tg#Update) you can create a typed handler.\n\nTyped handlers it's not about routing updates but about handling them.\nThese handlers will only be called for updates of a certain type, the rest will be skipped. Also, they implement the [`tgb.Handler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Handler) interface.\n\nList of typed handlers:\n\n- [`tgb.MessageHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#MessageHandler) with [`tgb.MessageUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#MessageUpdate) for `message`, `edited_message`, `channel_post`, `edited_channel_post`, `business_message`, `edited_business_message`;\n- [`tgb.InlineQueryHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#InlineQueryHandler) with [`tgb.InlineQueryUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#InlineQueryUpdate) for `inline_query`\n- [`tgb.ChosenInlineResult`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ChosenInlineResult) with [`tgb.ChosenInlineResultUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ChosenInlineResultUpdate) for `chosen_inline_result`;\n- [`tgb.CallbackQueryHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#CallbackQueryHandler) with [`tgb.CallbackQueryUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#CallbackQueryUpdate) for `callback_query`;\n- [`tgb.ShippingQueryHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ShippingQueryHandler) with [`tgb.ShippingQueryUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ShippingQueryUpdate) for `shipping_query`;\n- [`tgb.PreCheckoutQueryHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PreCheckoutQueryHandler) with [`tgb.PreCheckoutQueryUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PreCheckoutQueryUpdate) for `pre_checkout_query`;\n- [`tgb.PollHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PollHandler) with [`tgb.PollUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PollUpdate) for `poll`;\n- [`tgb.PollAnswerHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PollAnswerHandler) with [`tgb.PollAnswerUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PollAnswerUpdate) for `poll_answer`;\n- [`tgb.ChatMemberUpdatedHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ChatMemberUpdatedHandler) with [`tgb.ChatMemberUpdatedUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ChatMemberUpdatedUpdate) for `my_chat_member`, `chat_member`;\n- [`tgb.ChatJoinRequestHandler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ChatJoinRequestHandler) with [`tgb.ChatJoinRequestUpdate`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#ChatJoinRequestUpdate) for `chat_join_request`;\n\n`tgb.*Updates` has many useful methods for \"answer\" the update, please checkout godoc by links above.\n\n### Receive updates via Polling\n\nUse [`tgb.NewPoller`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#NewPoller) to create a poller with specified [`tg.Client`](https://pkg.go.dev/github.com/mr-linch/go-tg/tg#Client) and [`tgb.Handler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Handler). Also accepts [`tgb.PollerOption`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#PollerOption) for customizing the poller.\n\n```go\nhandler := tgb.HandlerFunc(func(ctx context.Context, update *tgb.Update) error {\n  // ...\n})\n\npoller := tgb.NewPoller(handler, client,\n  // recieve max 100 updates in a batch\n  tgb.WithPollerLimit(100),\n)\n\n// polling will be stopped on context cancel\nif err := poller.Run(ctx); err != nil {\n  return err\n}\n\n```\n\n### Receive updates via Webhook\n\nWebhook handler and server can be created by [`tgb.NewWebhook`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#NewWebhook).\nThat function has following arguments:\n\n- `handler` - [`tgb.Handler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Handler) for handling updates;\n- `client` - [`tg.Client`](https://pkg.go.dev/github.com/mr-linch/go-tg/tg#Client) for making setup requests;\n- `url` - full url of the webhook server\n- optional `options` - [`tgb.WebhookOption`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#WebhookOption) for customizing the webhook.\n\nWebhook has several security checks that are enabled by default:\n\n- Check if the IP of the sender is in the [allowed ranges](https://core.telegram.org/bots/webhooks#the-short-version).\n- Check if the request has a valid security token [header](https://core.telegram.org/bots/api#setwebhook). By default, the token is the SHA256 hash of the Telegram Bot API token.\n\n\u003e ℹ️ That checks can be disabled by passing `tgb.WithWebhookSecurityToken(\"\"), tgb.WithWebhookSecuritySubnets()` when creating the webhook.\n\n\u003e ⚠️ At the moment, the webhook does not integrate custom certificate. So, you should handle HTTPS requests on load balancer.\n\n```go\nhandler := tgb.HandlerFunc(func(ctx context.Context, update *tgb.Update) error {\n   // ...\n})\n\n\nwebhook := tgb.NewWebhook(handler, client, \"https://bot.com/webhook\",\n  tgb.WithDropPendingUpdates(true),\n)\n\n// configure telegram webhook and start HTTP server.\n// the server will be stopped on context cancel.\nif err := webhook.Run(ctx, \":8080\"); err != nil {\n  return err\n}\n```\n\nWebhook is a regular [`http.Handler`](https://pkg.go.dev/net/http#Handler) that can be used in any HTTP-compatible router. But you should call [`Webhook.Setup`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Webhook.Setup) before starting the server to configure the webhook on the Telegram side.\n\n**e.g. integration with [chi](https://pkg.go.dev/github.com/go-chi/chi/v5) router**\n\n```go\nhandler := tgb.HandlerFunc(func(ctx context.Context, update *tgb.Update) error {\n  // ...\n})\n\nwebhook := tgb.NewWebhook(handler, client, \"https://bot.com/webhook\",\n  tgb.WithDropPendingUpdates(true),\n)\n\n// get current webhook configuration and sync it if needed.\nif err := webhook.Setup(ctx); err != nil {\n  return err\n}\n\nr := chi.NewRouter()\n\nr.Get(\"/webhook\", webhook)\n\nhttp.ListenAndServe(\":8080\", r)\n\n```\n\n### Routing updates\n\nWhen building complex bots, routing updates is one of the most boilerplate parts of the code.\nThe [`tgb`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb) package contains a number of primitives to simplify this.\n\n#### [`tgb.Router`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Router)\n\nThis is an implementation of [`tgb.Handler`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Handler), which provides the ability to route updates between multiple related handlers.\nIt is useful for handling updates in different ways depending on the update subtype.\n\n```go\nrouter := tgb.NewRouter()\n\nrouter.Message(func(ctx context.Context, mu *tgb.MessageUpdate) error {\n  // will be called for every Update with not nil `Message` field\n})\n\nrouter.EditedMessage(func(ctx context.Context, mu *tgb.MessageUpdate) error {\n  // will be called for every Update with not nil `EditedMessage` field\n})\n\nrouter.CallbackQuery(func(ctx context.Context, update *tgb.CallbackQueryUpdate) error {\n  // will be called for every Update with not nil `CallbackQuery` field\n})\n\nclient := tg.NewClient(...)\n\n// e.g. run in long polling mode\nif err := tgb.NewPoller(router, client).Run(ctx); err != nil {\n  return err\n}\n```\n\n#### [tgb.Filter](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Filter)\n\nRouting by update subtype is first level of the routing. Second is **filters**. Filters are needed to determine more precisely which handler to call, for which update, depending on its contents.\n\nIn essence, filters are predicates. Functions that return a boolean value.\nIf the value is `true`, then the given update corresponds to a handler and the handler will be called.\nIf the value is `false`, check the subsequent handlers.\n\nThe [`tgb`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb) package contains many built-in filters.\n\ne.g. [command filter](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Command) (can be customized via [`CommandFilterOption`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#CommandFilterOption))\n\n```go\nrouter.Message(func(ctx context.Context, mu *tgb.MessageUpdate) error {\n  // will be called for every Update with not nil `Message` field and if the message text contains \"/start\"\n}, tgb.Command(\"start\", ))\n```\n\nThe handler registration function accepts any number of filters.\nThey will be combined using the boolean operator `and`\n\ne.g. handle /start command in private chats only\n\n```go\nrouter.Message(func(ctx context.Context, mu *tgb.MessageUpdate) error {\n  // will be called for every Update with not nil `Message` field\n  //  and\n  // if the message text contains \"/start\"\n  //  and\n  // if the Message.Chat.Type is private\n}, tgb.Command(\"start\"), tgb.ChatType(tg.ChatTypePrivate))\n```\n\nLogical operator `or` also supported.\n\ne.g. handle /start command in groups or supergroups only\n\n```go\nisGroupOrSupergroup := tgb.Any(\n  tgb.ChatType(tg.ChatTypeGroup),\n  tgb.ChatType(tg.ChatTypeSupergroup),\n)\n\nrouter.Message(func(ctx context.Context, mu *tgb.MessageUpdate) error {\n  // will be called for every Update with not nil `Message` field\n  //  and\n  // if the message text contains \"/start\"\n  //  and\n  //    if the Message.Chat.Type is group\n  //      or\n  //    if the Message.Chat.Type is supergroup\n}, tgb.Command(\"start\"), isGroupOrSupergroup)\n```\n\nAll filters are universal. e.g. the command filter can be used in the `Message`, `EditedMessage`, `ChannelPost`, `EditedChannelPost` handlers.\nPlease checkout [`tgb.Filter`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Filter) constructors for more information about built-in filters.\n\nFor define a custom filter you should implement the [`tgb.Filter`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Filter) interface. Also you can use [`tgb.FilterFunc`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#FilterFunc) wrapper to define a filter in functional way.\n\ne.g. filter for messages with document attachments with image type\n\n```go\n// tgb.All works like boolean `and` operator.\nvar isDocumentPhoto = tgb.All(\n  tgb.MessageType(tg.MessageTypeDocument),\n  tgb.FilterFunc(func(ctx context.Context, update *tgb.Update) (bool, error) {\n    return strings.HasPrefix(update.Message.Document.MIMEType, \"image/\"), nil\n  }),\n)\n```\n\n#### [tgb.Middleware](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Middleware)\n\nMiddleware is used to modify or process the Update before it is passed to the handler.\nAll middleware should be registered before the handlers registration.\n\ne.g. log all updates\n\n```go\nrouter.Use(func(next tgb.Handler) tgb.Handler {\n  return tgb.HandlerFunc(func(ctx context.Context, update *tgb.Update) error {\n    defer func(started time.Time) {\n      log.Printf(\"%#v [%s]\", update, time.Since(started))\n    }(time.Now())\n\n    return next(ctx, update)\n  })\n})\n```\n\n#### Error Handler\n\nAs you all handlers returns an `error`. If any error occurs in the chain, it will be passed to that handler. By default, errors are returned back by handler method. You can customize this behavior by passing a custom error handler.\n\ne.g. log all errors\n\n```go\nrouter.Error(func(ctx context.Context, update *tgb.Update, err error) error {\n  log.Printf(\"error when handling update #%d: %v\", update.ID, err)\n  return nil\n})\n```\n\nThat example is not useful and just demonstrates the error handler.\nThe better way to achieve this is simply to enable logging in [`Webhook`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Webhook) or [`Poller`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb#Poller).\n\n## Extensions\n\n### Sessions\n\n#### What is a Session?\n\nSession it's a simple storage for data related to the Telegram chat.\nIt allow you to share data between different updates from the same chat.\nThis data is persisted in the [session store](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb/session#Store) and will be available for the next updates from the same chat.\n\nIn fact, the session is the usual `struct` and you can define it as you wish.\nOne requirement is that the session must be serializable.\nBy default, the session is serialized using [`encoding/json`](https://pkg.go.dev/encoding/json) package, but you can use any other marshal/unmarshal funcs.\n\n#### When not to use sessions?\n\n- you need to store large amount of data;\n- your data is not serializable;\n- you need access to data from other chat sessions;\n- session data should be used by other systems;\n\n#### Where sessions store\n\nSession store is simple key-value storage.\nWhere key is a string value unique for each chat and value is serialized session data.\nBy default, manager use [`StoreMemory`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb/session#StoreMemory) implementation.\nAlso package has [`StoreFile`](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb/session#StoreFile) based on FS.\n\n#### How to use sessions?\n\n1. You should define a session struct:\n   ```go\n    type Session struct {\n      PizzaCount int\n    }\n   ```\n2. Create a session manager:\n   ```go\n    var sessionManager = session.NewManager(Session{\n      PizzaCount: 0,\n    })\n   ```\n3. Attach the session manager to the router:\n   ```go\n    router.Use(sessionManager)\n   ```\n4. Use the session manager in the handlers:\n   ```go\n    router.Message(func(ctx context.Context, mu *tgb.Update) error {\n      count := strings.Count(strings.ToLower(mu.Message.Text), \"pizza\") + strings.Count(mu.Message.Text, \"🍕\")\n      if count \u003e 0 {\n        session := sessionManager.Get(ctx)\n        session.PizzaCount += count\n      }\n      return nil\n    })\n   ```\n\nSee [session](https://pkg.go.dev/github.com/mr-linch/go-tg/tgb/session) package and [examples](https://github.com/mr-linch/go-tg/tree/main/examples) with `Session Manager` feature for more information.\n\n## Related Projects\n\n- [`mr-linch/go-tg-bot`](https://github.com/mr-linch/go-tg-bot) - one click boilerplate for creating Telegram bots with PostgreSQL database and clean architecture;\n- [`bots-house/docker-telegram-bot-api`](https://github.com/bots-house/docker-telegram-bot-api) - docker image for running self-hosted Telegram Bot API with automated CI build;\n\n## Projects using this package\n\n- [@ttkeeperbot](https://t.me/ttkeeperbot) - Automatically upload tiktoks in groups and verify users 🇺🇦\n\n## Thanks\n\n- [gotd/td](https://github.com/gotd/td) for inspiration for the use of codegen;\n- [aiogram/aiogram](https://github.com/aiogram/aiogram) for handlers, middlewares, filters concepts;\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmr-linch%2Fgo-tg","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmr-linch%2Fgo-tg","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmr-linch%2Fgo-tg/lists"}