pax_global_header00006660000000000000000000000064146543703510014522gustar00rootroot0000000000000052 comment=864bef4e4d3a60b8079db4fdb51cac2e779349cd telebot-3.3.8/000077500000000000000000000000001465437035100131735ustar00rootroot00000000000000telebot-3.3.8/.github/000077500000000000000000000000001465437035100145335ustar00rootroot00000000000000telebot-3.3.8/.github/workflows/000077500000000000000000000000001465437035100165705ustar00rootroot00000000000000telebot-3.3.8/.github/workflows/go.yml000066400000000000000000000006131465437035100177200ustar00rootroot00000000000000name: Go on: [push, pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Go uses: actions/setup-go@v2 with: go-version: 1.16 - name: Test run: export TELEBOT_SECRET=${{ secrets.TELEBOT_SECRET }} && export CHAT_ID=${{ secrets.CHAT_ID }} && export USER_ID=${{ secrets.USER_ID }} && go test -v ./...telebot-3.3.8/.gitignore000066400000000000000000000006101465437035100151600ustar00rootroot00000000000000# Compiled Object files, Static and Dynamic libs (Shared Objects) *.o *.a *.so # Folders _obj _test # Architecture specific extensions/prefixes *.[568vq] [568vq].out *.cgo1.go *.cgo2.c _cgo_defun.c _cgo_gotypes.go _cgo_export.* _testmain.go *.exe *.test *.prof .idea .DS_Store coverage.txt # Terraform artifacts *.zip .terraform* terraform* /examples/awslambdaechobot/awslambdaechobot telebot-3.3.8/LICENSE000066400000000000000000000020731465437035100142020ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2015 llya Kowalewski Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. telebot-3.3.8/README.md000066400000000000000000000340331465437035100144550ustar00rootroot00000000000000# Telebot >"I never knew creating Telegram bots could be so _sexy_!" [![GoDoc](https://godoc.org/gopkg.in/telebot.v3?status.svg)](https://godoc.org/gopkg.in/telebot.v3) [![GitHub Actions](https://github.com/tucnak/telebot/actions/workflows/go.yml/badge.svg)](https://github.com/tucnak/telebot/actions) [![codecov.io](https://codecov.io/gh/tucnak/telebot/coverage.svg?branch=v3)](https://codecov.io/gh/tucnak/telebot) [![Discuss on Telegram](https://img.shields.io/badge/telegram-discuss-0088cc.svg)](https://t.me/go_telebot) ```bash go get -u gopkg.in/telebot.v3 ``` * [Overview](#overview) * [Getting Started](#getting-started) - [Context](#context) - [Middleware](#middleware) - [Poller](#poller) - [Commands](#commands) - [Files](#files) - [Sendable](#sendable) - [Editable](#editable) - [Keyboards](#keyboards) - [Inline mode](#inline-mode) * [Contributing](#contributing) * [Donate](#donate) * [License](#license) # Overview Telebot is a bot framework for [Telegram Bot API](https://core.telegram.org/bots/api). This package provides the best of its kind API for command routing, inline query requests and keyboards, as well as callbacks. Actually, I went a couple steps further, so instead of making a 1:1 API wrapper I chose to focus on the beauty of API and performance. Some strong sides of Telebot are: * Real concise API * Command routing * Middleware * Transparent File API * Effortless bot callbacks All the methods of Telebot API are _extremely_ easy to memorize and get used to. Also, consider Telebot a highload-ready solution. I'll test and benchmark the most popular actions and if necessary, optimize against them without sacrificing API quality. # Getting Started Let's take a look at the minimal Telebot setup: ```go package main import ( "log" "os" "time" tele "gopkg.in/telebot.v3" ) func main() { pref := tele.Settings{ Token: os.Getenv("TOKEN"), Poller: &tele.LongPoller{Timeout: 10 * time.Second}, } b, err := tele.NewBot(pref) if err != nil { log.Fatal(err) return } b.Handle("/hello", func(c tele.Context) error { return c.Send("Hello!") }) b.Start() } ``` Simple, innit? Telebot's routing system takes care of delivering updates to their endpoints, so in order to get to handle any meaningful event, all you got to do is just plug your function into one of the Telebot-provided endpoints. You can find the full list [here](https://godoc.org/gopkg.in/telebot.v3#pkg-constants). There are dozens of supported endpoints (see package consts). Let me know if you'd like to see some endpoint or endpoint ideas implemented. This system is completely extensible, so I can introduce them without breaking backwards compatibility. ## Context Context is a special type that wraps a huge update structure and represents the context of the current event. It provides several helpers, which allow getting, for example, the chat that this update had been sent in, no matter what kind of update this is. ```go b.Handle(tele.OnText, func(c tele.Context) error { // All the text messages that weren't // captured by existing handlers. var ( user = c.Sender() text = c.Text() ) // Use full-fledged bot's functions // only if you need a result: msg, err := b.Send(user, text) if err != nil { return err } // Instead, prefer a context short-hand: return c.Send(text) }) b.Handle(tele.OnChannelPost, func(c tele.Context) error { // Channel posts only. msg := c.Message() }) b.Handle(tele.OnPhoto, func(c tele.Context) error { // Photos only. photo := c.Message().Photo }) b.Handle(tele.OnQuery, func(c tele.Context) error { // Incoming inline queries. return c.Answer(...) }) ``` ## Middleware Telebot has a simple and recognizable way to set up middleware — chained functions with access to `Context`, called before the handler execution. Import a `middleware` package to get some basic out-of-box middleware implementations: ```go import "gopkg.in/telebot.v3/middleware" ``` ```go // Global-scoped middleware: b.Use(middleware.Logger()) b.Use(middleware.AutoRespond()) // Group-scoped middleware: adminOnly := b.Group() adminOnly.Use(middleware.Whitelist(adminIDs...)) adminOnly.Handle("/ban", onBan) adminOnly.Handle("/kick", onKick) // Handler-scoped middleware: b.Handle(tele.OnText, onText, middleware.IgnoreVia()) ``` Custom middleware example: ```go // AutoResponder automatically responds to every callback update. func AutoResponder(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { if c.Callback() != nil { defer c.Respond() } return next(c) // continue execution chain } } ``` ## Poller Telebot doesn't really care how you provide it with incoming updates, as long as you set it up with a Poller, or call ProcessUpdate for each update: ```go // Poller is a provider of Updates. // // All pollers must implement Poll(), which accepts bot // pointer and subscription channel and start polling // synchronously straight away. type Poller interface { // Poll is supposed to take the bot object // subscription channel and start polling // for Updates immediately. // // Poller must listen for stop constantly and close // it as soon as it's done polling. Poll(b *Bot, updates chan Update, stop chan struct{}) } ``` ## Commands When handling commands, Telebot supports both direct (`/command`) and group-like syntax (`/command@botname`) and will never deliver messages addressed to some other bot, even if [privacy mode](https://core.telegram.org/bots#privacy-mode) is off. For simplified deep-linking, Telebot also extracts payload: ```go // Command: /start b.Handle("/start", func(c tele.Context) error { fmt.Println(c.Message().Payload) // }) ``` For multiple arguments use: ```go // Command: /tags <...> b.Handle("/tags", func(c tele.Context) error { tags := c.Args() // list of arguments splitted by a space for _, tag := range tags { // iterate through passed arguments } }) ``` ## Files >Telegram allows files up to 50 MB in size. Telebot allows to both upload (from disk or by URL) and download (from Telegram) files in bot's scope. Also, sending any kind of media with a File created from disk will upload the file to Telegram automatically: ```go a := &tele.Audio{File: tele.FromDisk("file.ogg")} fmt.Println(a.OnDisk()) // true fmt.Println(a.InCloud()) // false // Will upload the file from disk and send it to the recipient b.Send(recipient, a) // Next time you'll be sending this very *Audio, Telebot won't // re-upload the same file but rather utilize its Telegram FileID b.Send(otherRecipient, a) fmt.Println(a.OnDisk()) // true fmt.Println(a.InCloud()) // true fmt.Println(a.FileID) // ``` You might want to save certain `File`s in order to avoid re-uploading. Feel free to marshal them into whatever format, `File` only contain public fields, so no data will ever be lost. ## Sendable Send is undoubtedly the most important method in Telebot. `Send()` accepts a `Recipient` (could be user, group or a channel) and a `Sendable`. Other types other than the Telebot-provided media types (`Photo`, `Audio`, `Video`, etc.) are `Sendable`. If you create composite types of your own, and they satisfy the `Sendable` interface, Telebot will be able to send them out. ```go // Sendable is any object that can send itself. // // This is pretty cool, since it lets bots implement // custom Sendables for complex kinds of media or // chat objects spanning across multiple messages. type Sendable interface { Send(*Bot, Recipient, *SendOptions) (*Message, error) } ``` The only type at the time that doesn't fit `Send()` is `Album` and there is a reason for that. Albums were added not so long ago, so they are slightly quirky for backwards compatibilities sake. In fact, an `Album` can be sent, but never received. Instead, Telegram returns a `[]Message`, one for each media object in the album: ```go p := &tele.Photo{File: tele.FromDisk("chicken.jpg")} v := &tele.Video{File: tele.FromURL("http://video.mp4")} msgs, err := b.SendAlbum(user, tele.Album{p, v}) ``` ### Send options Send options are objects and flags you can pass to `Send()`, `Edit()` and friends as optional arguments (following the recipient and the text/media). The most important one is called `SendOptions`, it lets you control _all_ the properties of the message supported by Telegram. The only drawback is that it's rather inconvenient to use at times, so `Send()` supports multiple shorthands: ```go // regular send options b.Send(user, "text", &tele.SendOptions{ // ... }) // ReplyMarkup is a part of SendOptions, // but often it's the only option you need b.Send(user, "text", &tele.ReplyMarkup{ // ... }) // flags: no notification && no web link preview b.Send(user, "text", tele.Silent, tele.NoPreview) ``` Full list of supported option-flags you can find [here](https://pkg.go.dev/gopkg.in/telebot.v3#Option). ## Editable If you want to edit some existing message, you don't really need to store the original `*Message` object. In fact, upon edit, Telegram only requires `chat_id` and `message_id`. So you don't really need the Message as a whole. Also, you might want to store references to certain messages in the database, so I thought it made sense for *any* Go struct to be editable as a Telegram message, to implement `Editable`: ```go // Editable is an interface for all objects that // provide "message signature", a pair of 32-bit // message ID and 64-bit chat ID, both required // for edit operations. // // Use case: DB model struct for messages to-be // edited with, say two columns: msg_id,chat_id // could easily implement MessageSig() making // instances of stored messages editable. type Editable interface { // MessageSig is a "message signature". // // For inline messages, return chatID = 0. MessageSig() (messageID int, chatID int64) } ``` For example, `Message` type is Editable. Here is the implementation of `StoredMessage` type, provided by Telebot: ```go // StoredMessage is an example struct suitable for being // stored in the database as-is or being embedded into // a larger struct, which is often the case (you might // want to store some metadata alongside, or might not.) type StoredMessage struct { MessageID int `sql:"message_id" json:"message_id"` ChatID int64 `sql:"chat_id" json:"chat_id"` } func (x StoredMessage) MessageSig() (int, int64) { return x.MessageID, x.ChatID } ``` Why bother at all? Well, it allows you to do things like this: ```go // just two integer columns in the database var msgs []tele.StoredMessage db.Find(&msgs) // gorm syntax for _, msg := range msgs { bot.Edit(&msg, "Updated text") // or bot.Delete(&msg) } ``` I find it incredibly neat. Worth noting, at this point of time there exists another method in the Edit family, `EditCaption()` which is of a pretty rare use, so I didn't bother including it to `Edit()`, just like I did with `SendAlbum()` as it would inevitably lead to unnecessary complications. ```go var m *Message // change caption of a photo, audio, etc. bot.EditCaption(m, "new caption") ``` ## Keyboards Telebot supports both kinds of keyboards Telegram provides: reply and inline keyboards. Any button can also act as endpoints for `Handle()`. ```go var ( // Universal markup builders. menu = &tele.ReplyMarkup{ResizeKeyboard: true} selector = &tele.ReplyMarkup{} // Reply buttons. btnHelp = menu.Text("ℹ Help") btnSettings = menu.Text("⚙ Settings") // Inline buttons. // // Pressing it will cause the client to // send the bot a callback. // // Make sure Unique stays unique as per button kind // since it's required for callback routing to work. // btnPrev = selector.Data("⬅", "prev", ...) btnNext = selector.Data("➡", "next", ...) ) menu.Reply( menu.Row(btnHelp), menu.Row(btnSettings), ) selector.Inline( selector.Row(btnPrev, btnNext), ) b.Handle("/start", func(c tele.Context) error { return c.Send("Hello!", menu) }) // On reply button pressed (message) b.Handle(&btnHelp, func(c tele.Context) error { return c.Edit("Here is some help: ...") }) // On inline button pressed (callback) b.Handle(&btnPrev, func(c tele.Context) error { return c.Respond() }) ``` You can use markup constructor for every type of possible button: ```go r := b.NewMarkup() // Reply buttons: r.Text("Hello!") r.Contact("Send phone number") r.Location("Send location") r.Poll(tele.PollQuiz) // Inline buttons: r.Data("Show help", "help") // data is optional r.Data("Delete item", "delete", item.ID) r.URL("Visit", "https://google.com") r.Query("Search", query) r.QueryChat("Share", query) r.Login("Login", &tele.Login{...}) ``` ## Inline mode So if you want to handle incoming inline queries you better plug the `tele.OnQuery` endpoint and then use the `Answer()` method to send a list of inline queries back. I think at the time of writing, Telebot supports all of the provided result types (but not the cached ones). This is what it looks like: ```go b.Handle(tele.OnQuery, func(c tele.Context) error { urls := []string{ "http://photo.jpg", "http://photo2.jpg", } results := make(tele.Results, len(urls)) // []tele.Result for i, url := range urls { result := &tele.PhotoResult{ URL: url, ThumbURL: url, // required for photos } results[i] = result // needed to set a unique string ID for each result results[i].SetResultID(strconv.Itoa(i)) } return c.Answer(&tele.QueryResponse{ Results: results, CacheTime: 60, // a minute }) }) ``` There's not much to talk about really. It also supports some form of authentication through deep-linking. For that, use fields `SwitchPMText` and `SwitchPMParameter` of `QueryResponse`. # Contributing 1. Fork it 2. Clone v3: `git clone -b v3 https://github.com/tucnak/telebot` 3. Create your feature branch: `git checkout -b v3-feature` 4. Make changes and add them: `git add .` 5. Commit: `git commit -m "add some feature"` 6. Push: `git push origin v3-feature` 7. Pull request # Donate I do coding for fun, but I also try to search for interesting solutions and optimize them as much as possible. If you feel like it's a good piece of software, I wouldn't mind a tip! Litecoin: `ltc1qskt5ltrtyg7esfjm0ftx6jnacwffhpzpqmerus` Ethereum: `0xB78A2Ac1D83a0aD0b993046F9fDEfC5e619efCAB` # License Telebot is distributed under MIT. telebot-3.3.8/admin.go000066400000000000000000000225331465437035100146170ustar00rootroot00000000000000package telebot import ( "encoding/json" "strconv" "time" ) // Rights is a list of privileges available to chat members. type Rights struct { // Anonymous is true, if the user's presence in the chat is hidden. Anonymous bool `json:"is_anonymous"` CanBeEdited bool `json:"can_be_edited"` CanChangeInfo bool `json:"can_change_info"` CanPostMessages bool `json:"can_post_messages"` CanEditMessages bool `json:"can_edit_messages"` CanDeleteMessages bool `json:"can_delete_messages"` CanPinMessages bool `json:"can_pin_messages"` CanInviteUsers bool `json:"can_invite_users"` CanRestrictMembers bool `json:"can_restrict_members"` CanPromoteMembers bool `json:"can_promote_members"` CanSendMessages bool `json:"can_send_messages"` CanSendPolls bool `json:"can_send_polls"` CanSendOther bool `json:"can_send_other_messages"` CanAddPreviews bool `json:"can_add_web_page_previews"` CanManageVideoChats bool `json:"can_manage_video_chats"` CanManageChat bool `json:"can_manage_chat"` CanManageTopics bool `json:"can_manage_topics"` CanSendMedia bool `json:"can_send_media_messages,omitempty"` // deprecated CanSendAudios bool `json:"can_send_audios"` CanSendDocuments bool `json:"can_send_documents"` CanSendPhotos bool `json:"can_send_photos"` CanSendVideos bool `json:"can_send_videos"` CanSendVideoNotes bool `json:"can_send_video_notes"` CanSendVoiceNotes bool `json:"can_send_voice_notes"` CanPostStories bool `json:"can_post_stories"` CanEditStories bool `json:"can_edit_stories"` CanDeleteStories bool `json:"can_delete_stories"` // Independent defines whether the chat permissions are set independently. // If not, the can_send_other_messages and can_add_web_page_previews permissions // will imply the can_send_messages, can_send_audios, can_send_documents, can_send_photos, // can_send_videos, can_send_video_notes, and can_send_voice_notes permissions; // the can_send_polls permission will imply the can_send_messages permission. // // Works for Restrict and SetGroupPermissions methods only. Independent bool `json:"-"` } // NoRights is the default Rights{}. func NoRights() Rights { return Rights{} } // NoRestrictions should be used when un-restricting or // un-promoting user. // // member.Rights = tele.NoRestrictions() // b.Restrict(chat, member) func NoRestrictions() Rights { return Rights{ CanBeEdited: true, CanChangeInfo: false, CanPostMessages: false, CanEditMessages: false, CanDeleteMessages: false, CanInviteUsers: false, CanRestrictMembers: false, CanPinMessages: false, CanPromoteMembers: false, CanSendMessages: true, CanSendPolls: true, CanSendOther: true, CanAddPreviews: true, CanManageVideoChats: false, CanManageChat: false, CanManageTopics: false, CanSendAudios: true, CanSendDocuments: true, CanSendPhotos: true, CanSendVideos: true, CanSendVideoNotes: true, CanSendVoiceNotes: true, } } // AdminRights could be used to promote user to admin. func AdminRights() Rights { return Rights{ CanBeEdited: true, CanChangeInfo: true, CanPostMessages: true, CanEditMessages: true, CanDeleteMessages: true, CanInviteUsers: true, CanRestrictMembers: true, CanPinMessages: true, CanPromoteMembers: true, CanSendMessages: true, CanSendPolls: true, CanSendOther: true, CanAddPreviews: true, CanManageVideoChats: true, CanManageChat: true, CanManageTopics: true, CanSendAudios: true, CanSendDocuments: true, CanSendPhotos: true, CanSendVideos: true, CanSendVideoNotes: true, CanSendVoiceNotes: true, CanPostStories: true, CanEditStories: true, CanDeleteStories: true, } } // Forever is a ExpireUnixtime of "forever" banning. func Forever() int64 { return time.Now().Add(367 * 24 * time.Hour).Unix() } // Ban will ban user from chat until `member.RestrictedUntil`. func (b *Bot) Ban(chat *Chat, member *ChatMember, revokeMessages ...bool) error { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": member.User.Recipient(), "until_date": strconv.FormatInt(member.RestrictedUntil, 10), } if len(revokeMessages) > 0 { params["revoke_messages"] = strconv.FormatBool(revokeMessages[0]) } _, err := b.Raw("kickChatMember", params) return err } // Unban will unban user from chat, who would have thought eh? // forBanned does nothing if the user is not banned. func (b *Bot) Unban(chat *Chat, user *User, forBanned ...bool) error { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": user.Recipient(), } if len(forBanned) > 0 { params["only_if_banned"] = strconv.FormatBool(forBanned[0]) } _, err := b.Raw("unbanChatMember", params) return err } // Restrict lets you restrict a subset of member's rights until // member.RestrictedUntil, such as: // // - can send messages // - can send media // - can send other // - can add web page previews func (b *Bot) Restrict(chat *Chat, member *ChatMember) error { perms, until := member.Rights, member.RestrictedUntil params := map[string]interface{}{ "chat_id": chat.Recipient(), "user_id": member.User.Recipient(), "until_date": strconv.FormatInt(until, 10), "permissions": perms, } if perms.Independent { params["use_independent_chat_permissions"] = true } _, err := b.Raw("restrictChatMember", params) return err } // Promote lets you update member's admin rights, such as: // // - can change info // - can post messages // - can edit messages // - can delete messages // - can invite users // - can restrict members // - can pin messages // - can promote members func (b *Bot) Promote(chat *Chat, member *ChatMember) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "user_id": member.User.Recipient(), "is_anonymous": member.Anonymous, } embedRights(params, member.Rights) _, err := b.Raw("promoteChatMember", params) return err } // AdminsOf returns a member list of chat admins. // // On success, returns an Array of ChatMember objects that // contains information about all chat administrators except other bots. // // If the chat is a group or a supergroup and // no administrators were appointed, only the creator will be returned. func (b *Bot) AdminsOf(chat *Chat) ([]ChatMember, error) { params := map[string]string{ "chat_id": chat.Recipient(), } data, err := b.Raw("getChatAdministrators", params) if err != nil { return nil, err } var resp struct { Result []ChatMember } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // Len returns the number of members in a chat. func (b *Bot) Len(chat *Chat) (int, error) { params := map[string]string{ "chat_id": chat.Recipient(), } data, err := b.Raw("getChatMembersCount", params) if err != nil { return 0, err } var resp struct { Result int } if err := json.Unmarshal(data, &resp); err != nil { return 0, wrapError(err) } return resp.Result, nil } // SetAdminTitle sets a custom title for an administrator. // A title should be 0-16 characters length, emoji are not allowed. func (b *Bot) SetAdminTitle(chat *Chat, user *User, title string) error { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": user.Recipient(), "custom_title": title, } _, err := b.Raw("setChatAdministratorCustomTitle", params) return err } // BanSenderChat will use this method to ban a channel chat in a supergroup or a channel. // Until the chat is unbanned, the owner of the banned chat won't be able // to send messages on behalf of any of their channels. func (b *Bot) BanSenderChat(chat *Chat, sender Recipient) error { params := map[string]string{ "chat_id": chat.Recipient(), "sender_chat_id": sender.Recipient(), } _, err := b.Raw("banChatSenderChat", params) return err } // UnbanSenderChat will use this method to unban a previously banned channel chat in a supergroup or channel. // The bot must be an administrator for this to work and must have the appropriate administrator rights. func (b *Bot) UnbanSenderChat(chat *Chat, sender Recipient) error { params := map[string]string{ "chat_id": chat.Recipient(), "sender_chat_id": sender.Recipient(), } _, err := b.Raw("unbanChatSenderChat", params) return err } // DefaultRights returns the current default administrator rights of the bot. func (b *Bot) DefaultRights(forChannels bool) (*Rights, error) { params := map[string]bool{ "for_channels": forChannels, } data, err := b.Raw("getMyDefaultAdministratorRights", params) if err != nil { return nil, err } var resp struct { Result *Rights } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // SetDefaultRights changes the default administrator rights requested by the bot // when it's added as an administrator to groups or channels. func (b *Bot) SetDefaultRights(rights Rights, forChannels bool) error { params := map[string]interface{}{ "rights": rights, "for_channels": forChannels, } _, err := b.Raw("setMyDefaultAdministratorRights", params) return err } func embedRights(p map[string]interface{}, rights Rights) { data, _ := json.Marshal(rights) _ = json.Unmarshal(data, &p) } telebot-3.3.8/admin_test.go000066400000000000000000000025361465437035100156570ustar00rootroot00000000000000package telebot import ( "testing" "github.com/stretchr/testify/assert" ) func TestEmbedRights(t *testing.T) { rights := NoRestrictions() params := map[string]interface{}{ "chat_id": "1", "user_id": "2", } embedRights(params, rights) expected := map[string]interface{}{ "is_anonymous": false, "chat_id": "1", "user_id": "2", "can_be_edited": true, "can_send_messages": true, "can_send_polls": true, "can_send_other_messages": true, "can_add_web_page_previews": true, "can_change_info": false, "can_post_messages": false, "can_edit_messages": false, "can_delete_messages": false, "can_invite_users": false, "can_restrict_members": false, "can_pin_messages": false, "can_promote_members": false, "can_manage_video_chats": false, "can_manage_chat": false, "can_manage_topics": false, "can_send_audios": true, "can_send_documents": true, "can_send_photos": true, "can_send_videos": true, "can_send_video_notes": true, "can_send_voice_notes": true, "can_post_stories": false, "can_edit_stories": false, "can_delete_stories": false, } assert.Equal(t, expected, params) } telebot-3.3.8/api.go000066400000000000000000000203001465437035100142660ustar00rootroot00000000000000package telebot import ( "bytes" "context" "encoding/json" "fmt" "io" "io/ioutil" "log" "mime/multipart" "net/http" "os" "strconv" "strings" "time" ) // Raw lets you call any method of Bot API manually. // It also handles API errors, so you only need to unwrap // result field from json data. func (b *Bot) Raw(method string, payload interface{}) ([]byte, error) { url := b.URL + "/bot" + b.Token + "/" + method var buf bytes.Buffer if err := json.NewEncoder(&buf).Encode(payload); err != nil { return nil, err } // Cancel the request immediately without waiting for the timeout // when bot is about to stop. // This may become important if doing long polling with long timeout. ctx, cancel := context.WithCancel(context.Background()) defer cancel() go func() { b.stopMu.RLock() stopCh := b.stopClient b.stopMu.RUnlock() select { case <-stopCh: cancel() case <-ctx.Done(): } }() req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, &buf) if err != nil { return nil, wrapError(err) } req.Header.Set("Content-Type", "application/json") resp, err := b.client.Do(req) if err != nil { return nil, wrapError(err) } resp.Close = true defer resp.Body.Close() data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, wrapError(err) } if b.verbose { verbose(method, payload, data) } // returning data as well return data, extractOk(data) } func (b *Bot) sendFiles(method string, files map[string]File, params map[string]string) ([]byte, error) { rawFiles := make(map[string]interface{}) for name, f := range files { switch { case f.InCloud(): params[name] = f.FileID case f.FileURL != "": params[name] = f.FileURL case f.OnDisk(): rawFiles[name] = f.FileLocal case f.FileReader != nil: rawFiles[name] = f.FileReader default: return nil, fmt.Errorf("telebot: file for field %s doesn't exist", name) } } if len(rawFiles) == 0 { return b.Raw(method, params) } pipeReader, pipeWriter := io.Pipe() writer := multipart.NewWriter(pipeWriter) go func() { defer pipeWriter.Close() for field, file := range rawFiles { if err := addFileToWriter(writer, files[field].fileName, field, file); err != nil { pipeWriter.CloseWithError(err) return } } for field, value := range params { if err := writer.WriteField(field, value); err != nil { pipeWriter.CloseWithError(err) return } } if err := writer.Close(); err != nil { pipeWriter.CloseWithError(err) return } }() url := b.URL + "/bot" + b.Token + "/" + method resp, err := b.client.Post(url, writer.FormDataContentType(), pipeReader) if err != nil { err = wrapError(err) pipeReader.CloseWithError(err) return nil, err } resp.Close = true defer resp.Body.Close() if resp.StatusCode == http.StatusInternalServerError { return nil, ErrInternal } data, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, wrapError(err) } return data, extractOk(data) } func addFileToWriter(writer *multipart.Writer, filename, field string, file interface{}) error { var reader io.Reader if r, ok := file.(io.Reader); ok { reader = r } else if path, ok := file.(string); ok { f, err := os.Open(path) if err != nil { return err } defer f.Close() reader = f } else { return fmt.Errorf("telebot: file for field %v should be io.ReadCloser or string", field) } part, err := writer.CreateFormFile(field, filename) if err != nil { return err } _, err = io.Copy(part, reader) return err } func (f *File) process(name string, files map[string]File) string { switch { case f.InCloud(): return f.FileID case f.FileURL != "": return f.FileURL case f.OnDisk() || f.FileReader != nil: files[name] = *f return "attach://" + name } return "" } func (b *Bot) sendText(to Recipient, text string, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "text": text, } b.embedSendOptions(params, opt) data, err := b.Raw("sendMessage", params) if err != nil { return nil, err } return extractMessage(data) } func (b *Bot) sendMedia(media Media, params map[string]string, files map[string]File) (*Message, error) { kind := media.MediaType() what := "send" + strings.Title(kind) if kind == "videoNote" { kind = "video_note" } sendFiles := map[string]File{kind: *media.MediaFile()} for k, v := range files { sendFiles[k] = v } data, err := b.sendFiles(what, sendFiles, params) if err != nil { return nil, err } return extractMessage(data) } func (b *Bot) getMe() (*User, error) { data, err := b.Raw("getMe", nil) if err != nil { return nil, err } var resp struct { Result *User } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } func (b *Bot) getUpdates(offset, limit int, timeout time.Duration, allowed []string) ([]Update, error) { params := map[string]string{ "offset": strconv.Itoa(offset), "timeout": strconv.Itoa(int(timeout / time.Second)), } data, _ := json.Marshal(allowed) params["allowed_updates"] = string(data) if limit != 0 { params["limit"] = strconv.Itoa(limit) } data, err := b.Raw("getUpdates", params) if err != nil { return nil, err } var resp struct { Result []Update } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } func (b *Bot) forwardCopyMany(to Recipient, msgs []Editable, key string, opts ...*SendOptions) ([]Message, error) { params := map[string]string{ "chat_id": to.Recipient(), } embedMessages(params, msgs) if len(opts) > 0 { b.embedSendOptions(params, opts[0]) } data, err := b.Raw(key, params) if err != nil { return nil, err } var resp struct { Result []Message } if err := json.Unmarshal(data, &resp); err != nil { var resp struct { Result bool } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return nil, wrapError(err) } return resp.Result, nil } // extractOk checks given result for error. If result is ok returns nil. // In other cases it extracts API error. If error is not presented // in errors.go, it will be prefixed with `unknown` keyword. func extractOk(data []byte) error { var e struct { Ok bool `json:"ok"` Code int `json:"error_code"` Description string `json:"description"` Parameters map[string]interface{} `json:"parameters"` } if json.NewDecoder(bytes.NewReader(data)).Decode(&e) != nil { return nil // FIXME } if e.Ok { return nil } err := Err(e.Description) switch err { case nil: case ErrGroupMigrated: migratedTo, ok := e.Parameters["migrate_to_chat_id"] if !ok { return NewError(e.Code, e.Description) } return GroupError{ err: err.(*Error), MigratedTo: int64(migratedTo.(float64)), } default: return err } switch e.Code { case http.StatusTooManyRequests: retryAfter, ok := e.Parameters["retry_after"] if !ok { return NewError(e.Code, e.Description) } err = FloodError{ err: NewError(e.Code, e.Description), RetryAfter: int(retryAfter.(float64)), } default: err = fmt.Errorf("telegram: %s (%d)", e.Description, e.Code) } return err } // extractMessage extracts common Message result from given data. // Should be called after extractOk or b.Raw() to handle possible errors. func extractMessage(data []byte) (*Message, error) { var resp struct { Result *Message } if err := json.Unmarshal(data, &resp); err != nil { var resp struct { Result bool } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } if resp.Result { return nil, ErrTrueResult } return nil, wrapError(err) } return resp.Result, nil } func verbose(method string, payload interface{}, data []byte) { body, _ := json.Marshal(payload) body = bytes.ReplaceAll(body, []byte(`\"`), []byte(`"`)) body = bytes.ReplaceAll(body, []byte(`"{`), []byte(`{`)) body = bytes.ReplaceAll(body, []byte(`}"`), []byte(`}`)) indent := func(b []byte) string { var buf bytes.Buffer json.Indent(&buf, b, "", " ") return buf.String() } log.Printf( "[verbose] telebot: sent request\nMethod: %v\nParams: %v\nResponse: %v", method, indent(body), indent(data), ) } telebot-3.3.8/api_test.go000066400000000000000000000054031465437035100153340ustar00rootroot00000000000000package telebot import ( "encoding/json" "errors" "io" "net/http" "net/http/httptest" "strings" "testing" "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" ) // testPayload implements json.Marshaler // to test json encoding error behaviour. type testPayload struct{} func (testPayload) MarshalJSON() ([]byte, error) { return nil, errors.New("test error") } func testRawServer(w http.ResponseWriter, r *http.Request) { switch { // causes EOF error on ioutil.ReadAll case strings.HasSuffix(r.URL.Path, "/testReadError"): // tells the body is 1 byte length but actually it's 0 w.Header().Set("Content-Length", "1") // returns unknown telegram error case strings.HasSuffix(r.URL.Path, "/testUnknownError"): data, _ := json.Marshal(struct { Ok bool `json:"ok"` Code int `json:"error_code"` Description string `json:"description"` }{ Ok: false, Code: 400, Description: "unknown error", }) w.WriteHeader(400) w.Write(data) } } func TestRaw(t *testing.T) { if token == "" { t.Skip("TELEBOT_SECRET is required") } b, err := newTestBot() if err != nil { t.Fatal(err) } _, err = b.Raw("BAD METHOD", nil) assert.EqualError(t, err, ErrNotFound.Error()) _, err = b.Raw("", &testPayload{}) assert.Error(t, err) srv := httptest.NewServer(http.HandlerFunc(testRawServer)) defer srv.Close() b.URL = srv.URL b.client = srv.Client() _, err = b.Raw("testReadError", nil) assert.EqualError(t, err, "telebot: "+io.ErrUnexpectedEOF.Error()) _, err = b.Raw("testUnknownError", nil) assert.EqualError(t, err, "telegram: unknown error (400)") } func TestExtractOk(t *testing.T) { data := []byte(`{"ok": true, "result": {}}`) require.NoError(t, extractOk(data)) data = []byte(`{ "ok": false, "error_code": 400, "description": "Bad Request: reply message not found" }`) assert.EqualError(t, extractOk(data), ErrNotFoundToReply.Error()) data = []byte(`{ "ok": false, "error_code": 429, "description": "Too Many Requests: retry after 8", "parameters": {"retry_after": 8} }`) assert.Equal(t, FloodError{ err: NewError(429, "Too Many Requests: retry after 8"), RetryAfter: 8, }, extractOk(data)) data = []byte(`{ "ok": false, "error_code": 400, "description": "Bad Request: group chat was upgraded to a supergroup chat", "parameters": {"migrate_to_chat_id": -100123456789} }`) assert.Equal(t, GroupError{ err: ErrGroupMigrated, MigratedTo: -100123456789, }, extractOk(data)) } func TestExtractMessage(t *testing.T) { data := []byte(`{"ok":true,"result":true}`) _, err := extractMessage(data) assert.Equal(t, ErrTrueResult, err) data = []byte(`{"ok":true,"result":{"foo":"bar"}}`) _, err = extractMessage(data) require.NoError(t, err) } telebot-3.3.8/boost.go000066400000000000000000000060251465437035100146530ustar00rootroot00000000000000package telebot import ( "encoding/json" "time" ) // Boost contains information about a chat boost. type Boost struct { // Unique identifier of the boost. ID string `json:"boost_id"` // Point in time (Unix timestamp) when the chat was boosted. AddUnixtime int64 `json:"add_date"` // Point in time (Unix timestamp) when the boost will automatically expire, // unless the booster's Telegram Premium subscription is prolonged. ExpirationUnixtime int64 `json:"expiration_date"` // Source of the added boost. Source *BoostSource `json:"source"` } // AddDate returns the moment of time when the chat has been boosted in local time. func (c *Boost) AddDate() time.Time { return time.Unix(c.AddUnixtime, 0) } // ExpirationDate returns the moment of time when the boost of the channel // will expire in local time. func (c *Boost) ExpirationDate() time.Time { return time.Unix(c.ExpirationUnixtime, 0) } // BoostSourceType describes a type of boost. type BoostSourceType = string const ( BoostPremium BoostSourceType = "premium" BoostGiftCode BoostSourceType = "gift_code" BoostGiveaway BoostSourceType = "giveaway" ) // BoostSource describes the source of a chat boost. type BoostSource struct { // Source of the boost, always (“premium”, “gift_code”, “giveaway”). Source BoostSourceType `json:"source"` // User that boosted the chat. Booster *User `json:"user"` // Identifier of a message in the chat with the giveaway; the message // could have been deleted already. May be 0 if the message isn't sent yet. GiveawayMessageID int `json:"giveaway_message_id,omitempty"` // (Optional) True, if the giveaway was completed, but there was // no user to win the prize. Unclaimed bool `json:"is_unclaimed,omitempty"` } // BoostAdded represents a service message about a user boosting a chat. type BoostAdded struct { // Number of boosts added by the user. Count int `json:"boost_count"` } // BoostUpdated represents a boost added to a chat or changed. type BoostUpdated struct { // Chat which was boosted. Chat *Chat `json:"chat"` // Information about the chat boost. Boost *Boost `json:"boost"` } // BoostRemoved represents a boost removed from a chat. type BoostRemoved struct { // Chat which was boosted. Chat *Chat `json:"chat"` // Unique identifier of the boost. BoostID string `json:"boost_id"` // Point in time (Unix timestamp) when the boost was removed. RemoveUnixtime int64 `json:"remove_date"` // Source of the removed boost. Source *BoostSource `json:"source"` } // UserBoosts gets the list of boosts added to a chat by a user. // Requires administrator rights in the chat. func (b *Bot) UserBoosts(chat, user Recipient) ([]Boost, error) { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": user.Recipient(), } data, err := b.Raw("getUserChatBoosts", params) if err != nil { return nil, err } var resp struct { Result struct { Boosts []Boost `json:"boosts"` } } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result.Boosts, nil } telebot-3.3.8/bot.go000066400000000000000000000761041465437035100143160ustar00rootroot00000000000000package telebot import ( "encoding/json" "fmt" "io" "log" "net/http" "os" "regexp" "strconv" "strings" "sync" "time" ) // NewBot does try to build a Bot with token `token`, which // is a secret API key assigned to particular bot. func NewBot(pref Settings) (*Bot, error) { if pref.Updates == 0 { pref.Updates = 100 } client := pref.Client if client == nil { client = &http.Client{Timeout: time.Minute} } if pref.URL == "" { pref.URL = DefaultApiURL } if pref.Poller == nil { pref.Poller = &LongPoller{} } if pref.OnError == nil { pref.OnError = defaultOnError } bot := &Bot{ Token: pref.Token, URL: pref.URL, Poller: pref.Poller, onError: pref.OnError, Updates: make(chan Update, pref.Updates), handlers: make(map[string]HandlerFunc), stop: make(chan chan struct{}), synchronous: pref.Synchronous, verbose: pref.Verbose, parseMode: pref.ParseMode, client: client, } if pref.Offline { bot.Me = &User{} } else { user, err := bot.getMe() if err != nil { return nil, err } bot.Me = user } bot.group = bot.Group() return bot, nil } // Bot represents a separate Telegram bot instance. type Bot struct { Me *User Token string URL string Updates chan Update Poller Poller onError func(error, Context) group *Group handlers map[string]HandlerFunc synchronous bool verbose bool parseMode ParseMode stop chan chan struct{} client *http.Client stopMu sync.RWMutex stopClient chan struct{} } // Settings represents a utility struct for passing certain // properties of a bot around and is required to make bots. type Settings struct { URL string Token string // Updates channel capacity, defaulted to 100. Updates int // Poller is the provider of Updates. Poller Poller // Synchronous prevents handlers from running in parallel. // It makes ProcessUpdate return after the handler is finished. Synchronous bool // Verbose forces bot to log all upcoming requests. // Use for debugging purposes only. Verbose bool // ParseMode used to set default parse mode of all sent messages. // It attaches to every send, edit or whatever method. You also // will be able to override the default mode by passing a new one. ParseMode ParseMode // OnError is a callback function that will get called on errors // resulted from the handler. It is used as post-middleware function. // Notice that context can be nil. OnError func(error, Context) // HTTP Client used to make requests to telegram api Client *http.Client // Offline allows to create a bot without network for testing purposes. Offline bool } var defaultOnError = func(err error, c Context) { if c != nil { log.Println(c.Update().ID, err) } else { log.Println(err) } } func (b *Bot) OnError(err error, c Context) { b.onError(err, c) } func (b *Bot) debug(err error) { if b.verbose { b.OnError(err, nil) } } // Group returns a new group. func (b *Bot) Group() *Group { return &Group{b: b} } // Use adds middleware to the global bot chain. func (b *Bot) Use(middleware ...MiddlewareFunc) { b.group.Use(middleware...) } var ( cmdRx = regexp.MustCompile(`^(/\w+)(@(\w+))?(\s|$)(.+)?`) cbackRx = regexp.MustCompile(`^\f([-\w]+)(\|(.+))?$`) ) // Handle lets you set the handler for some command name or // one of the supported endpoints. It also applies middleware // if such passed to the function. // // Example: // // b.Handle("/start", func (c tele.Context) error { // return c.Reply("Hello!") // }) // // b.Handle(&inlineButton, func (c tele.Context) error { // return c.Respond(&tele.CallbackResponse{Text: "Hello!"}) // }) // // Middleware usage: // // b.Handle("/ban", onBan, middleware.Whitelist(ids...)) func (b *Bot) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) { end := extractEndpoint(endpoint) if end == "" { panic("telebot: unsupported endpoint") } if len(b.group.middleware) > 0 { m = appendMiddleware(b.group.middleware, m) } b.handlers[end] = func(c Context) error { return applyMiddleware(h, m...)(c) } } // Trigger executes the registered handler by the endpoint. func (b *Bot) Trigger(endpoint interface{}, c Context) error { end := extractEndpoint(endpoint) if end == "" { return fmt.Errorf("telebot: unsupported endpoint") } handler, ok := b.handlers[end] if !ok { return fmt.Errorf("telebot: no handler found for given endpoint") } return handler(c) } // Start brings bot into motion by consuming incoming // updates (see Bot.Updates channel). func (b *Bot) Start() { if b.Poller == nil { panic("telebot: can't start without a poller") } // do nothing if called twice b.stopMu.Lock() if b.stopClient != nil { b.stopMu.Unlock() return } b.stopClient = make(chan struct{}) b.stopMu.Unlock() stop := make(chan struct{}) stopConfirm := make(chan struct{}) go func() { b.Poller.Poll(b, b.Updates, stop) close(stopConfirm) }() for { select { // handle incoming updates case upd := <-b.Updates: b.ProcessUpdate(upd) // call to stop polling case confirm := <-b.stop: close(stop) <-stopConfirm close(confirm) return } } } // Stop gracefully shuts the poller down. func (b *Bot) Stop() { b.stopMu.Lock() if b.stopClient != nil { close(b.stopClient) b.stopClient = nil } b.stopMu.Unlock() confirm := make(chan struct{}) b.stop <- confirm <-confirm } // NewMarkup simply returns newly created markup instance. func (b *Bot) NewMarkup() *ReplyMarkup { return &ReplyMarkup{} } // NewContext returns a new native context object, // field by the passed update. func (b *Bot) NewContext(u Update) Context { return &nativeContext{ b: b, u: u, } } // Send accepts 2+ arguments, starting with destination chat, followed by // some Sendable (or string!) and optional send options. // // NOTE: // // Since most arguments are of type interface{}, but have pointer // method receivers, make sure to pass them by-pointer, NOT by-value. // // What is a send option exactly? It can be one of the following types: // // - *SendOptions (the actual object accepted by Telegram API) // - *ReplyMarkup (a component of SendOptions) // - Option (a shortcut flag for popular options) // - ParseMode (HTML, Markdown, etc) func (b *Bot) Send(to Recipient, what interface{}, opts ...interface{}) (*Message, error) { if to == nil { return nil, ErrBadRecipient } sendOpts := b.extractOptions(opts) switch object := what.(type) { case string: return b.sendText(to, object, sendOpts) case Sendable: return object.Send(b, to, sendOpts) default: return nil, ErrUnsupportedWhat } } // SendAlbum sends multiple instances of media as a single message. // To include the caption, make sure the first Inputtable of an album has it. // From all existing options, it only supports tele.Silent. func (b *Bot) SendAlbum(to Recipient, a Album, opts ...interface{}) ([]Message, error) { if to == nil { return nil, ErrBadRecipient } sendOpts := b.extractOptions(opts) media := make([]string, len(a)) files := make(map[string]File) for i, x := range a { repr := x.MediaFile().process(strconv.Itoa(i), files) if repr == "" { return nil, fmt.Errorf("telebot: album entry #%d does not exist", i) } im := x.InputMedia() im.Media = repr if len(sendOpts.Entities) > 0 { im.Entities = sendOpts.Entities } else { im.ParseMode = sendOpts.ParseMode } data, _ := json.Marshal(im) media[i] = string(data) } params := map[string]string{ "chat_id": to.Recipient(), "media": "[" + strings.Join(media, ",") + "]", } b.embedSendOptions(params, sendOpts) data, err := b.sendFiles("sendMediaGroup", files, params) if err != nil { return nil, err } var resp struct { Result []Message } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } for attachName := range files { i, _ := strconv.Atoi(attachName) r := resp.Result[i] var newID string switch { case r.Photo != nil: newID = r.Photo.FileID case r.Video != nil: newID = r.Video.FileID case r.Audio != nil: newID = r.Audio.FileID case r.Document != nil: newID = r.Document.FileID } a[i].MediaFile().FileID = newID } return resp.Result, nil } // Reply behaves just like Send() with an exception of "reply-to" indicator. // This function will panic upon nil Message. func (b *Bot) Reply(to *Message, what interface{}, opts ...interface{}) (*Message, error) { sendOpts := b.extractOptions(opts) if sendOpts == nil { sendOpts = &SendOptions{} } sendOpts.ReplyTo = to return b.Send(to.Chat, what, sendOpts) } // Forward behaves just like Send() but of all options it only supports Silent (see Bots API). // This function will panic upon nil Editable. func (b *Bot) Forward(to Recipient, msg Editable, opts ...interface{}) (*Message, error) { if to == nil { return nil, ErrBadRecipient } msgID, chatID := msg.MessageSig() params := map[string]string{ "chat_id": to.Recipient(), "from_chat_id": strconv.FormatInt(chatID, 10), "message_id": msgID, } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) data, err := b.Raw("forwardMessage", params) if err != nil { return nil, err } return extractMessage(data) } // ForwardMany method forwards multiple messages of any kind. // If some of the specified messages can't be found or forwarded, they are skipped. // Service messages and messages with protected content can't be forwarded. // Album grouping is kept for forwarded messages. func (b *Bot) ForwardMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) { if to == nil { return nil, ErrBadRecipient } return b.forwardCopyMany(to, msgs, "forwardMessages", opts...) } // Copy behaves just like Forward() but the copied message doesn't have a link to the original message (see Bots API). // // This function will panic upon nil Editable. func (b *Bot) Copy(to Recipient, msg Editable, opts ...interface{}) (*Message, error) { if to == nil { return nil, ErrBadRecipient } msgID, chatID := msg.MessageSig() params := map[string]string{ "chat_id": to.Recipient(), "from_chat_id": strconv.FormatInt(chatID, 10), "message_id": msgID, } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) data, err := b.Raw("copyMessage", params) if err != nil { return nil, err } return extractMessage(data) } // CopyMany this method makes a copy of messages of any kind. // If some of the specified messages can't be found or copied, they are skipped. // Service messages, giveaway messages, giveaway winners messages, and // invoice messages can't be copied. A quiz poll can be copied only if the value of the field // correct_option_id is known to the bot. The method is analogous // to the method forwardMessages, but the copied messages don't have a link to the original message. // Album grouping is kept for copied messages. func (b *Bot) CopyMany(to Recipient, msgs []Editable, opts ...*SendOptions) ([]Message, error) { if to == nil { return nil, ErrBadRecipient } return b.forwardCopyMany(to, msgs, "copyMessages", opts...) } // Edit is magic, it lets you change already sent message. // This function will panic upon nil Editable. // // If edited message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. // // Use cases: // // b.Edit(m, m.Text, newMarkup) // b.Edit(m, "new text", tele.ModeHTML) // b.Edit(m, &tele.ReplyMarkup{...}) // b.Edit(m, &tele.Photo{File: ...}) // b.Edit(m, tele.Location{42.1337, 69.4242}) // b.Edit(c, "edit inline message from the callback") // b.Edit(r, "edit message from chosen inline result") func (b *Bot) Edit(msg Editable, what interface{}, opts ...interface{}) (*Message, error) { var ( method string params = make(map[string]string) ) switch v := what.(type) { case *ReplyMarkup: return b.EditReplyMarkup(msg, v) case Inputtable: return b.EditMedia(msg, v, opts...) case string: method = "editMessageText" params["text"] = v case Location: method = "editMessageLiveLocation" params["latitude"] = fmt.Sprintf("%f", v.Lat) params["longitude"] = fmt.Sprintf("%f", v.Lng) if v.HorizontalAccuracy != nil { params["horizontal_accuracy"] = fmt.Sprintf("%f", *v.HorizontalAccuracy) } if v.Heading != 0 { params["heading"] = strconv.Itoa(v.Heading) } if v.AlertRadius != 0 { params["proximity_alert_radius"] = strconv.Itoa(v.AlertRadius) } default: return nil, ErrUnsupportedWhat } msgID, chatID := msg.MessageSig() if chatID == 0 { // if inline message params["inline_message_id"] = msgID } else { params["chat_id"] = strconv.FormatInt(chatID, 10) params["message_id"] = msgID } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) data, err := b.Raw(method, params) if err != nil { return nil, err } return extractMessage(data) } // EditReplyMarkup edits reply markup of already sent message. // This function will panic upon nil Editable. // Pass nil or empty ReplyMarkup to delete it from the message. // // If edited message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. func (b *Bot) EditReplyMarkup(msg Editable, markup *ReplyMarkup) (*Message, error) { msgID, chatID := msg.MessageSig() params := make(map[string]string) if chatID == 0 { // if inline message params["inline_message_id"] = msgID } else { params["chat_id"] = strconv.FormatInt(chatID, 10) params["message_id"] = msgID } if markup == nil { // will delete reply markup markup = &ReplyMarkup{} } processButtons(markup.InlineKeyboard) data, _ := json.Marshal(markup) params["reply_markup"] = string(data) data, err := b.Raw("editMessageReplyMarkup", params) if err != nil { return nil, err } return extractMessage(data) } // EditCaption edits already sent photo caption with known recipient and message id. // This function will panic upon nil Editable. // // If edited message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. func (b *Bot) EditCaption(msg Editable, caption string, opts ...interface{}) (*Message, error) { msgID, chatID := msg.MessageSig() params := map[string]string{ "caption": caption, } if chatID == 0 { // if inline message params["inline_message_id"] = msgID } else { params["chat_id"] = strconv.FormatInt(chatID, 10) params["message_id"] = msgID } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) data, err := b.Raw("editMessageCaption", params) if err != nil { return nil, err } return extractMessage(data) } // EditMedia edits already sent media with known recipient and message id. // This function will panic upon nil Editable. // // If edited message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. // // Use cases: // // b.EditMedia(m, &tele.Photo{File: tele.FromDisk("chicken.jpg")}) // b.EditMedia(m, &tele.Video{File: tele.FromURL("http://video.mp4")}) func (b *Bot) EditMedia(msg Editable, media Inputtable, opts ...interface{}) (*Message, error) { var ( repr string file = media.MediaFile() files = make(map[string]File) thumb *Photo thumbName = "thumb" ) switch { case file.InCloud(): repr = file.FileID case file.FileURL != "": repr = file.FileURL case file.OnDisk() || file.FileReader != nil: s := file.FileLocal if file.FileReader != nil { s = "0" } else if s == thumbName { thumbName = "thumb2" } repr = "attach://" + s files[s] = *file default: return nil, fmt.Errorf("telebot: cannot edit media, it does not exist") } switch m := media.(type) { case *Video: thumb = m.Thumbnail case *Audio: thumb = m.Thumbnail case *Document: thumb = m.Thumbnail case *Animation: thumb = m.Thumbnail } msgID, chatID := msg.MessageSig() params := make(map[string]string) sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) im := media.InputMedia() im.Media = repr if len(sendOpts.Entities) > 0 { im.Entities = sendOpts.Entities } else { im.ParseMode = sendOpts.ParseMode } if thumb != nil { im.Thumbnail = "attach://" + thumbName files[thumbName] = *thumb.MediaFile() } data, _ := json.Marshal(im) params["media"] = string(data) if chatID == 0 { // if inline message params["inline_message_id"] = msgID } else { params["chat_id"] = strconv.FormatInt(chatID, 10) params["message_id"] = msgID } data, err := b.sendFiles("editMessageMedia", files, params) if err != nil { return nil, err } return extractMessage(data) } // Delete removes the message, including service messages. // This function will panic upon nil Editable. // // - A message can only be deleted if it was sent less than 48 hours ago. // - A dice message in a private chat can only be deleted if it was sent more than 24 hours ago. // - Bots can delete outgoing messages in private chats, groups, and supergroups. // - Bots can delete incoming messages in private chats. // - Bots granted can_post_messages permissions can delete outgoing messages in channels. // - If the bot is an administrator of a group, it can delete any message there. // - If the bot has can_delete_messages permission in a supergroup or a // channel, it can delete any message there. func (b *Bot) Delete(msg Editable) error { msgID, chatID := msg.MessageSig() params := map[string]string{ "chat_id": strconv.FormatInt(chatID, 10), "message_id": msgID, } _, err := b.Raw("deleteMessage", params) return err } // DeleteMany deletes multiple messages simultaneously. // If some of the specified messages can't be found, they are skipped. func (b *Bot) DeleteMany(msgs []Editable) error { params := make(map[string]string) embedMessages(params, msgs) _, err := b.Raw("deleteMessages", params) return err } // Notify updates the chat action for recipient. // // Chat action is a status message that recipient would see where // you typically see "Harry is typing" status message. The only // difference is that bots' chat actions live only for 5 seconds // and die just once the client receives a message from the bot. // // Currently, Telegram supports only a narrow range of possible // actions, these are aligned as constants of this package. func (b *Bot) Notify(to Recipient, action ChatAction, threadID ...int) error { if to == nil { return ErrBadRecipient } params := map[string]string{ "chat_id": to.Recipient(), "action": string(action), } if len(threadID) > 0 { params["message_thread_id"] = strconv.Itoa(threadID[0]) } _, err := b.Raw("sendChatAction", params) return err } // Ship replies to the shipping query, if you sent an invoice // requesting an address and the parameter is_flexible was specified. // // Example: // // b.Ship(query) // OK // b.Ship(query, opts...) // OK with options // b.Ship(query, "Oops!") // Error message func (b *Bot) Ship(query *ShippingQuery, what ...interface{}) error { params := map[string]string{ "shipping_query_id": query.ID, } if len(what) == 0 { params["ok"] = "true" } else if s, ok := what[0].(string); ok { params["ok"] = "false" params["error_message"] = s } else { var opts []ShippingOption for _, v := range what { opt, ok := v.(ShippingOption) if !ok { return ErrUnsupportedWhat } opts = append(opts, opt) } params["ok"] = "true" data, _ := json.Marshal(opts) params["shipping_options"] = string(data) } _, err := b.Raw("answerShippingQuery", params) return err } // Accept finalizes the deal. func (b *Bot) Accept(query *PreCheckoutQuery, errorMessage ...string) error { params := map[string]string{ "pre_checkout_query_id": query.ID, } if len(errorMessage) == 0 { params["ok"] = "true" } else { params["ok"] = "False" params["error_message"] = errorMessage[0] } _, err := b.Raw("answerPreCheckoutQuery", params) return err } // Respond sends a response for a given callback query. A callback can // only be responded to once, subsequent attempts to respond to the same callback // will result in an error. // // Example: // // b.Respond(c) // b.Respond(c, response) func (b *Bot) Respond(c *Callback, resp ...*CallbackResponse) error { var r *CallbackResponse if resp == nil { r = &CallbackResponse{} } else { r = resp[0] } r.CallbackID = c.ID _, err := b.Raw("answerCallbackQuery", r) return err } // Answer sends a response for a given inline query. A query can only // be responded to once, subsequent attempts to respond to the same query // will result in an error. func (b *Bot) Answer(query *Query, resp *QueryResponse) error { resp.QueryID = query.ID for _, result := range resp.Results { result.Process(b) } _, err := b.Raw("answerInlineQuery", resp) return err } // AnswerWebApp sends a response for a query from Web App and returns // information about an inline message sent by a Web App on behalf of a user func (b *Bot) AnswerWebApp(query *Query, r Result) (*WebAppMessage, error) { r.Process(b) params := map[string]interface{}{ "web_app_query_id": query.ID, "result": r, } data, err := b.Raw("answerWebAppQuery", params) if err != nil { return nil, err } var resp struct { Result *WebAppMessage } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, err } // FileByID returns full file object including File.FilePath, allowing you to // download the file from the server. // // Usually, Telegram-provided File objects miss FilePath so you might need to // perform an additional request to fetch them. func (b *Bot) FileByID(fileID string) (File, error) { params := map[string]string{ "file_id": fileID, } data, err := b.Raw("getFile", params) if err != nil { return File{}, err } var resp struct { Result File } if err := json.Unmarshal(data, &resp); err != nil { return File{}, wrapError(err) } return resp.Result, nil } // Download saves the file from Telegram servers locally. // Maximum file size to download is 20 MB. func (b *Bot) Download(file *File, localFilename string) error { reader, err := b.File(file) if err != nil { return err } defer reader.Close() out, err := os.Create(localFilename) if err != nil { return wrapError(err) } defer out.Close() _, err = io.Copy(out, reader) if err != nil { return wrapError(err) } file.FileLocal = localFilename return nil } // File gets a file from Telegram servers. func (b *Bot) File(file *File) (io.ReadCloser, error) { f, err := b.FileByID(file.FileID) if err != nil { return nil, err } url := b.URL + "/file/bot" + b.Token + "/" + f.FilePath file.FilePath = f.FilePath // saving file path req, err := http.NewRequest(http.MethodGet, url, nil) if err != nil { return nil, wrapError(err) } resp, err := b.client.Do(req) if err != nil { return nil, wrapError(err) } if resp.StatusCode != http.StatusOK { resp.Body.Close() return nil, fmt.Errorf("telebot: expected status 200 but got %s", resp.Status) } return resp.Body, nil } // StopLiveLocation stops broadcasting live message location // before Location.LivePeriod expires. // // It supports ReplyMarkup. // This function will panic upon nil Editable. // // If the message is sent by the bot, returns it, // otherwise returns nil and ErrTrueResult. func (b *Bot) StopLiveLocation(msg Editable, opts ...interface{}) (*Message, error) { msgID, chatID := msg.MessageSig() params := map[string]string{ "chat_id": strconv.FormatInt(chatID, 10), "message_id": msgID, } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) data, err := b.Raw("stopMessageLiveLocation", params) if err != nil { return nil, err } return extractMessage(data) } // StopPoll stops a poll which was sent by the bot and returns // the stopped Poll object with the final results. // // It supports ReplyMarkup. // This function will panic upon nil Editable. func (b *Bot) StopPoll(msg Editable, opts ...interface{}) (*Poll, error) { msgID, chatID := msg.MessageSig() params := map[string]string{ "chat_id": strconv.FormatInt(chatID, 10), "message_id": msgID, } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) data, err := b.Raw("stopPoll", params) if err != nil { return nil, err } var resp struct { Result *Poll } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // Leave makes bot leave a group, supergroup or channel. func (b *Bot) Leave(chat Recipient) error { params := map[string]string{ "chat_id": chat.Recipient(), } _, err := b.Raw("leaveChat", params) return err } // Pin pins a message in a supergroup or a channel. // // It supports Silent option. // This function will panic upon nil Editable. func (b *Bot) Pin(msg Editable, opts ...interface{}) error { msgID, chatID := msg.MessageSig() params := map[string]string{ "chat_id": strconv.FormatInt(chatID, 10), "message_id": msgID, } sendOpts := b.extractOptions(opts) b.embedSendOptions(params, sendOpts) _, err := b.Raw("pinChatMessage", params) return err } // Unpin unpins a message in a supergroup or a channel. // It supports tb.Silent option. func (b *Bot) Unpin(chat Recipient, messageID ...int) error { params := map[string]string{ "chat_id": chat.Recipient(), } if len(messageID) > 0 { params["message_id"] = strconv.Itoa(messageID[0]) } _, err := b.Raw("unpinChatMessage", params) return err } // UnpinAll unpins all messages in a supergroup or a channel. // It supports tb.Silent option. func (b *Bot) UnpinAll(chat Recipient) error { params := map[string]string{ "chat_id": chat.Recipient(), } _, err := b.Raw("unpinAllChatMessages", params) return err } // ChatByID fetches chat info of its ID. // // Including current name of the user for one-on-one conversations, // current username of a user, group or channel, etc. func (b *Bot) ChatByID(id int64) (*Chat, error) { return b.ChatByUsername(strconv.FormatInt(id, 10)) } // ChatByUsername fetches chat info by its username. func (b *Bot) ChatByUsername(name string) (*Chat, error) { params := map[string]string{ "chat_id": name, } data, err := b.Raw("getChat", params) if err != nil { return nil, err } var resp struct { Result *Chat } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } if resp.Result.Type == ChatChannel && resp.Result.Username == "" { resp.Result.Type = ChatChannelPrivate } return resp.Result, nil } // ProfilePhotosOf returns list of profile pictures for a user. func (b *Bot) ProfilePhotosOf(user *User) ([]Photo, error) { params := map[string]string{ "user_id": user.Recipient(), } data, err := b.Raw("getUserProfilePhotos", params) if err != nil { return nil, err } var resp struct { Result struct { Count int `json:"total_count"` Photos []Photo `json:"photos"` } } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result.Photos, nil } // ChatMemberOf returns information about a member of a chat. func (b *Bot) ChatMemberOf(chat, user Recipient) (*ChatMember, error) { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": user.Recipient(), } data, err := b.Raw("getChatMember", params) if err != nil { return nil, err } var resp struct { Result *ChatMember } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // MenuButton returns the current value of the bot's menu button in a private chat, // or the default menu button. func (b *Bot) MenuButton(chat *User) (*MenuButton, error) { params := map[string]string{ "chat_id": chat.Recipient(), } data, err := b.Raw("getChatMenuButton", params) if err != nil { return nil, err } var resp struct { Result *MenuButton } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // SetMenuButton changes the bot's menu button in a private chat, // or the default menu button. // // It accepts two kinds of menu button arguments: // // - MenuButtonType for simple menu buttons (default, commands) // - MenuButton complete structure for web_app menu button type func (b *Bot) SetMenuButton(chat *User, mb interface{}) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), } switch v := mb.(type) { case MenuButtonType: params["menu_button"] = MenuButton{Type: v} case *MenuButton: params["menu_button"] = v } _, err := b.Raw("setChatMenuButton", params) return err } // Logout logs out from the cloud Bot API server before launching the bot locally. func (b *Bot) Logout() (bool, error) { data, err := b.Raw("logOut", nil) if err != nil { return false, err } var resp struct { Result bool `json:"result"` } if err := json.Unmarshal(data, &resp); err != nil { return false, wrapError(err) } return resp.Result, nil } // Close closes the bot instance before moving it from one local server to another. func (b *Bot) Close() (bool, error) { data, err := b.Raw("close", nil) if err != nil { return false, err } var resp struct { Result bool `json:"result"` } if err := json.Unmarshal(data, &resp); err != nil { return false, wrapError(err) } return resp.Result, nil } // BotInfo represents a single object of BotName, BotDescription, BotShortDescription instances. type BotInfo struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` ShortDescription string `json:"short_description,omitempty"` } // SetMyName change's the bot name. func (b *Bot) SetMyName(name, language string) error { params := map[string]string{ "name": name, "language_code": language, } _, err := b.Raw("setMyName", params) return err } // MyName returns the current bot name for the given user language. func (b *Bot) MyName(language string) (*BotInfo, error) { return b.botInfo(language, "getMyName") } // SetMyDescription change's the bot description, which is shown in the chat // with the bot if the chat is empty. func (b *Bot) SetMyDescription(desc, language string) error { params := map[string]string{ "description": desc, "language_code": language, } _, err := b.Raw("setMyDescription", params) return err } // MyDescription the current bot description for the given user language. func (b *Bot) MyDescription(language string) (*BotInfo, error) { return b.botInfo(language, "getMyDescription") } // SetMyShortDescription change's the bot short description, which is shown on // the bot's profile page and is sent together with the link when users share the bot. func (b *Bot) SetMyShortDescription(desc, language string) error { params := map[string]string{ "short_description": desc, "language_code": language, } _, err := b.Raw("setMyShortDescription", params) return err } // MyShortDescription the current bot short description for the given user language. func (b *Bot) MyShortDescription(language string) (*BotInfo, error) { return b.botInfo(language, "getMyShortDescription") } func (b *Bot) botInfo(language, key string) (*BotInfo, error) { params := map[string]string{ "language_code": language, } data, err := b.Raw(key, params) if err != nil { return nil, err } var resp struct { Result *BotInfo } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } func extractEndpoint(endpoint interface{}) string { switch end := endpoint.(type) { case string: return end case CallbackEndpoint: return end.CallbackUnique() } return "" } telebot-3.3.8/bot_test.go000066400000000000000000000504321465437035100153510ustar00rootroot00000000000000package telebot import ( "errors" "io" "io/ioutil" "net/http" "os" "strconv" "strings" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) var ( // required to test send and edit methods token = os.Getenv("TELEBOT_SECRET") chatID, _ = strconv.ParseInt(os.Getenv("CHAT_ID"), 10, 64) userID, _ = strconv.ParseInt(os.Getenv("USER_ID"), 10, 64) b, _ = newTestBot() // cached bot instance to avoid getMe method flooding to = &Chat{ID: chatID} // to chat recipient for send and edit methods user = &User{ID: userID} // to user recipient for some special cases logo = FromURL("https://telegra.ph/file/c95b8fe46dd3df15d12e5.png") thumb = FromURL("https://telegra.ph/file/fe28e378784b3a4e367fb.png") ) func defaultSettings() Settings { return Settings{Token: token} } func newTestBot() (*Bot, error) { return NewBot(defaultSettings()) } func TestNewBot(t *testing.T) { var pref Settings _, err := NewBot(pref) assert.Error(t, err) pref.Token = "BAD TOKEN" _, err = NewBot(pref) assert.Error(t, err) pref.URL = "BAD URL" _, err = NewBot(pref) assert.Error(t, err) b, err := NewBot(Settings{Offline: true}) if err != nil { t.Fatal(err) } assert.NotNil(t, b.Me) assert.Equal(t, DefaultApiURL, b.URL) assert.Equal(t, 100, cap(b.Updates)) assert.NotZero(t, b.client.Timeout) pref = defaultSettings() client := &http.Client{Timeout: time.Minute} pref.URL = "http://api.telegram.org" // not https pref.Client = client pref.Poller = &LongPoller{Timeout: time.Second} pref.Updates = 50 pref.ParseMode = ModeHTML pref.Offline = true b, err = NewBot(pref) require.NoError(t, err) assert.Equal(t, client, b.client) assert.Equal(t, pref.URL, b.URL) assert.Equal(t, pref.Poller, b.Poller) assert.Equal(t, 50, cap(b.Updates)) assert.Equal(t, ModeHTML, b.parseMode) } func TestBotHandle(t *testing.T) { if b == nil { t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)") } b.Handle("/start", func(c Context) error { return nil }) assert.Contains(t, b.handlers, "/start") reply := ReplyButton{Text: "reply"} b.Handle(&reply, func(c Context) error { return nil }) inline := InlineButton{Unique: "inline"} b.Handle(&inline, func(c Context) error { return nil }) btnReply := (&ReplyMarkup{}).Text("btnReply") b.Handle(&btnReply, func(c Context) error { return nil }) btnInline := (&ReplyMarkup{}).Data("", "btnInline") b.Handle(&btnInline, func(c Context) error { return nil }) assert.Contains(t, b.handlers, btnReply.CallbackUnique()) assert.Contains(t, b.handlers, btnInline.CallbackUnique()) assert.Contains(t, b.handlers, reply.CallbackUnique()) assert.Contains(t, b.handlers, inline.CallbackUnique()) } func TestBotStart(t *testing.T) { if token == "" { t.Skip("TELEBOT_SECRET is required") } pref := defaultSettings() pref.Poller = &LongPoller{} b, err := NewBot(pref) if err != nil { t.Fatal(err) } // remove webhook to be sure that bot can poll require.NoError(t, b.RemoveWebhook()) go b.Start() b.Stop() tp := newTestPoller() go func() { tp.updates <- Update{Message: &Message{Text: "/start"}} }() b, err = NewBot(pref) require.NoError(t, err) b.Poller = tp var ok bool b.Handle("/start", func(c Context) error { assert.Equal(t, c.Text(), "/start") tp.done <- struct{}{} ok = true return nil }) go b.Start() <-tp.done b.Stop() assert.True(t, ok) } func TestBotProcessUpdate(t *testing.T) { b, err := NewBot(Settings{Synchronous: true, Offline: true}) if err != nil { t.Fatal(err) } b.Handle(OnMedia, func(c Context) error { assert.NotNil(t, c.Message().Photo) return nil }) b.ProcessUpdate(Update{Message: &Message{Photo: &Photo{}}}) b.Handle("/start", func(c Context) error { assert.Equal(t, "/start", c.Text()) return nil }) b.Handle("hello", func(c Context) error { assert.Equal(t, "hello", c.Text()) return nil }) b.Handle(OnText, func(c Context) error { assert.Equal(t, "text", c.Text()) return nil }) b.Handle(OnPinned, func(c Context) error { assert.NotNil(t, c.Message()) return nil }) b.Handle(OnPhoto, func(c Context) error { assert.NotNil(t, c.Message().Photo) return nil }) b.Handle(OnVoice, func(c Context) error { assert.NotNil(t, c.Message().Voice) return nil }) b.Handle(OnAudio, func(c Context) error { assert.NotNil(t, c.Message().Audio) return nil }) b.Handle(OnAnimation, func(c Context) error { assert.NotNil(t, c.Message().Animation) return nil }) b.Handle(OnDocument, func(c Context) error { assert.NotNil(t, c.Message().Document) return nil }) b.Handle(OnSticker, func(c Context) error { assert.NotNil(t, c.Message().Sticker) return nil }) b.Handle(OnVideo, func(c Context) error { assert.NotNil(t, c.Message().Video) return nil }) b.Handle(OnVideoNote, func(c Context) error { assert.NotNil(t, c.Message().VideoNote) return nil }) b.Handle(OnContact, func(c Context) error { assert.NotNil(t, c.Message().Contact) return nil }) b.Handle(OnLocation, func(c Context) error { assert.NotNil(t, c.Message().Location) return nil }) b.Handle(OnVenue, func(c Context) error { assert.NotNil(t, c.Message().Venue) return nil }) b.Handle(OnDice, func(c Context) error { assert.NotNil(t, c.Message().Dice) return nil }) b.Handle(OnInvoice, func(c Context) error { assert.NotNil(t, c.Message().Invoice) return nil }) b.Handle(OnPayment, func(c Context) error { assert.NotNil(t, c.Message().Payment) return nil }) b.Handle(OnAddedToGroup, func(c Context) error { assert.NotNil(t, c.Message().GroupCreated) return nil }) b.Handle(OnUserJoined, func(c Context) error { assert.NotNil(t, c.Message().UserJoined) return nil }) b.Handle(OnUserLeft, func(c Context) error { assert.NotNil(t, c.Message().UserLeft) return nil }) b.Handle(OnNewGroupTitle, func(c Context) error { assert.Equal(t, "title", c.Message().NewGroupTitle) return nil }) b.Handle(OnNewGroupPhoto, func(c Context) error { assert.NotNil(t, c.Message().NewGroupPhoto) return nil }) b.Handle(OnGroupPhotoDeleted, func(c Context) error { assert.True(t, c.Message().GroupPhotoDeleted) return nil }) b.Handle(OnMigration, func(c Context) error { from, to := c.Migration() assert.Equal(t, int64(1), from) assert.Equal(t, int64(2), to) return nil }) b.Handle(OnEdited, func(c Context) error { assert.Equal(t, "edited", c.Message().Text) return nil }) b.Handle(OnChannelPost, func(c Context) error { assert.Equal(t, "post", c.Message().Text) return nil }) b.Handle(OnEditedChannelPost, func(c Context) error { assert.Equal(t, "edited post", c.Message().Text) return nil }) b.Handle(OnCallback, func(c Context) error { if data := c.Callback().Data; data[0] != '\f' { assert.Equal(t, "callback", data) } return nil }) b.Handle("\funique", func(c Context) error { assert.Equal(t, "callback", c.Callback().Data) return nil }) b.Handle(OnQuery, func(c Context) error { assert.Equal(t, "query", c.Data()) return nil }) b.Handle(OnInlineResult, func(c Context) error { assert.Equal(t, "result", c.InlineResult().ResultID) return nil }) b.Handle(OnShipping, func(c Context) error { assert.Equal(t, "shipping", c.ShippingQuery().ID) return nil }) b.Handle(OnCheckout, func(c Context) error { assert.Equal(t, "checkout", c.PreCheckoutQuery().ID) return nil }) b.Handle(OnPoll, func(c Context) error { assert.Equal(t, "poll", c.Poll().ID) return nil }) b.Handle(OnPollAnswer, func(c Context) error { assert.Equal(t, "poll", c.PollAnswer().PollID) return nil }) b.Handle(OnWebApp, func(c Context) error { assert.Equal(t, "webapp", c.Message().WebAppData.Data) return nil }) b.ProcessUpdate(Update{Message: &Message{Text: "/start"}}) b.ProcessUpdate(Update{Message: &Message{Text: "/start@other_bot"}}) b.ProcessUpdate(Update{Message: &Message{Text: "hello"}}) b.ProcessUpdate(Update{Message: &Message{Text: "text"}}) b.ProcessUpdate(Update{Message: &Message{PinnedMessage: &Message{}}}) b.ProcessUpdate(Update{Message: &Message{Photo: &Photo{}}}) b.ProcessUpdate(Update{Message: &Message{Voice: &Voice{}}}) b.ProcessUpdate(Update{Message: &Message{Audio: &Audio{}}}) b.ProcessUpdate(Update{Message: &Message{Animation: &Animation{}}}) b.ProcessUpdate(Update{Message: &Message{Document: &Document{}}}) b.ProcessUpdate(Update{Message: &Message{Sticker: &Sticker{}}}) b.ProcessUpdate(Update{Message: &Message{Video: &Video{}}}) b.ProcessUpdate(Update{Message: &Message{VideoNote: &VideoNote{}}}) b.ProcessUpdate(Update{Message: &Message{Contact: &Contact{}}}) b.ProcessUpdate(Update{Message: &Message{Location: &Location{}}}) b.ProcessUpdate(Update{Message: &Message{Venue: &Venue{}}}) b.ProcessUpdate(Update{Message: &Message{Invoice: &Invoice{}}}) b.ProcessUpdate(Update{Message: &Message{Payment: &Payment{}}}) b.ProcessUpdate(Update{Message: &Message{Dice: &Dice{}}}) b.ProcessUpdate(Update{Message: &Message{GroupCreated: true}}) b.ProcessUpdate(Update{Message: &Message{UserJoined: &User{ID: 1}}}) b.ProcessUpdate(Update{Message: &Message{UsersJoined: []User{{ID: 1}}}}) b.ProcessUpdate(Update{Message: &Message{UserLeft: &User{}}}) b.ProcessUpdate(Update{Message: &Message{NewGroupTitle: "title"}}) b.ProcessUpdate(Update{Message: &Message{NewGroupPhoto: &Photo{}}}) b.ProcessUpdate(Update{Message: &Message{GroupPhotoDeleted: true}}) b.ProcessUpdate(Update{Message: &Message{Chat: &Chat{ID: 1}, MigrateTo: 2}}) b.ProcessUpdate(Update{EditedMessage: &Message{Text: "edited"}}) b.ProcessUpdate(Update{ChannelPost: &Message{Text: "post"}}) b.ProcessUpdate(Update{ChannelPost: &Message{PinnedMessage: &Message{}}}) b.ProcessUpdate(Update{EditedChannelPost: &Message{Text: "edited post"}}) b.ProcessUpdate(Update{Callback: &Callback{MessageID: "inline", Data: "callback"}}) b.ProcessUpdate(Update{Callback: &Callback{Data: "callback"}}) b.ProcessUpdate(Update{Callback: &Callback{Data: "\funique|callback"}}) b.ProcessUpdate(Update{Query: &Query{Text: "query"}}) b.ProcessUpdate(Update{InlineResult: &InlineResult{ResultID: "result"}}) b.ProcessUpdate(Update{ShippingQuery: &ShippingQuery{ID: "shipping"}}) b.ProcessUpdate(Update{PreCheckoutQuery: &PreCheckoutQuery{ID: "checkout"}}) b.ProcessUpdate(Update{Poll: &Poll{ID: "poll"}}) b.ProcessUpdate(Update{PollAnswer: &PollAnswer{PollID: "poll"}}) b.ProcessUpdate(Update{Message: &Message{WebAppData: &WebAppData{Data: "webapp"}}}) } func TestBotOnError(t *testing.T) { b, err := NewBot(Settings{Synchronous: true, Offline: true}) if err != nil { t.Fatal(err) } var ok bool b.onError = func(err error, c Context) { assert.Equal(t, b, c.(*nativeContext).b) assert.NotNil(t, err) ok = true } b.runHandler(func(c Context) error { return errors.New("not nil") }, &nativeContext{b: b}) assert.True(t, ok) } func TestBotMiddleware(t *testing.T) { t.Run("calling order", func(t *testing.T) { var trace []string handler := func(name string) HandlerFunc { return func(c Context) error { trace = append(trace, name) return nil } } middleware := func(name string) MiddlewareFunc { return func(next HandlerFunc) HandlerFunc { return func(c Context) error { trace = append(trace, name+":in") err := next(c) trace = append(trace, name+":out") return err } } } b, err := NewBot(Settings{Synchronous: true, Offline: true}) if err != nil { t.Fatal(err) } b.Use(middleware("global1"), middleware("global2")) b.Handle("/a", handler("/a"), middleware("handler1a"), middleware("handler2a")) group := b.Group() group.Use(middleware("group1"), middleware("group2")) group.Handle("/b", handler("/b"), middleware("handler1b")) b.ProcessUpdate(Update{ Message: &Message{Text: "/a"}, }) assert.Equal(t, []string{ "global1:in", "global2:in", "handler1a:in", "handler2a:in", "/a", "handler2a:out", "handler1a:out", "global2:out", "global1:out", }, trace) trace = trace[:0] b.ProcessUpdate(Update{ Message: &Message{Text: "/b"}, }) assert.Equal(t, []string{ "global1:in", "global2:in", "group1:in", "group2:in", "handler1b:in", "/b", "handler1b:out", "group2:out", "group1:out", "global2:out", "global1:out", }, trace) }) fatal := func(next HandlerFunc) HandlerFunc { return func(c Context) error { t.Fatal("fatal middleware should not be called") return nil } } nop := func(next HandlerFunc) HandlerFunc { return func(c Context) error { return next(c) } } t.Run("combining with global middleware", func(t *testing.T) { b, err := NewBot(Settings{Synchronous: true, Offline: true}) if err != nil { t.Fatal(err) } // Pre-allocate middleware slice to make sure // it has extra capacity after group-level middleware is added. b.group.middleware = make([]MiddlewareFunc, 0, 2) b.Use(nop) b.Handle("/a", func(c Context) error { return nil }, nop) b.Handle("/b", func(c Context) error { return nil }, fatal) b.ProcessUpdate(Update{Message: &Message{Text: "/a"}}) }) t.Run("combining with group middleware", func(t *testing.T) { b, err := NewBot(Settings{Synchronous: true, Offline: true}) if err != nil { t.Fatal(err) } g := b.Group() // Pre-allocate middleware slice to make sure // it has extra capacity after group-level middleware is added. g.middleware = make([]MiddlewareFunc, 0, 2) g.Use(nop) g.Handle("/a", func(c Context) error { return nil }, nop) g.Handle("/b", func(c Context) error { return nil }, fatal) b.ProcessUpdate(Update{Message: &Message{Text: "/a"}}) }) } func TestBot(t *testing.T) { if b == nil { t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)") } if chatID == 0 { t.Skip("CHAT_ID is required for Bot methods test") } _, err := b.Send(to, nil) assert.Equal(t, ErrUnsupportedWhat, err) _, err = b.Edit(&Message{Chat: &Chat{}}, nil) assert.Equal(t, ErrUnsupportedWhat, err) _, err = b.Send(nil, "") assert.Equal(t, ErrBadRecipient, err) _, err = b.Forward(nil, nil) assert.Equal(t, ErrBadRecipient, err) photo := &Photo{ File: logo, Caption: t.Name(), } var msg *Message t.Run("Send(what=Sendable)", func(t *testing.T) { msg, err = b.Send(to, photo) require.NoError(t, err) assert.NotNil(t, msg.Photo) assert.Equal(t, photo.Caption, msg.Caption) }) t.Run("SendAlbum()", func(t *testing.T) { _, err = b.SendAlbum(nil, nil) assert.Equal(t, ErrBadRecipient, err) _, err = b.SendAlbum(to, nil) assert.Error(t, err) photo2 := *photo photo2.Caption = "" msgs, err := b.SendAlbum(to, Album{photo, &photo2}, ModeHTML) require.NoError(t, err) assert.Len(t, msgs, 2) assert.NotEmpty(t, msgs[0].AlbumID) }) t.Run("EditCaption()+ParseMode", func(t *testing.T) { b.parseMode = "html" edited, err := b.EditCaption(msg, "new caption with html") require.NoError(t, err) assert.Equal(t, "new caption with html", edited.Caption) assert.Equal(t, EntityBold, edited.CaptionEntities[0].Type) sleep() edited, err = b.EditCaption(msg, "*new caption with markdown*", ModeMarkdown) require.NoError(t, err) assert.Equal(t, "new caption with markdown", edited.Caption) assert.Equal(t, EntityBold, edited.CaptionEntities[0].Type) sleep() edited, err = b.EditCaption(msg, "_new caption with markdown \\(V2\\)_", ModeMarkdownV2) require.NoError(t, err) assert.Equal(t, "new caption with markdown (V2)", edited.Caption) assert.Equal(t, EntityItalic, edited.CaptionEntities[0].Type) }) t.Run("Edit(what=Media)", func(t *testing.T) { photo.Caption = "new caption with html" edited, err := b.Edit(msg, photo) require.NoError(t, err) assert.Equal(t, edited.Photo.UniqueID, photo.UniqueID) assert.Equal(t, EntityCode, edited.CaptionEntities[0].Type) resp, err := http.Get("https://telegra.ph/file/274e5eb26f348b10bd8ee.mp4") require.NoError(t, err) defer resp.Body.Close() file, err := ioutil.TempFile("", "") require.NoError(t, err) _, err = io.Copy(file, resp.Body) require.NoError(t, err) animation := &Animation{ File: FromDisk(file.Name()), Caption: t.Name(), FileName: "animation.gif", } msg, err := b.Send(msg.Chat, animation) require.NoError(t, err) if msg.Animation != nil { assert.Equal(t, msg.Animation.FileID, animation.FileID) } else { assert.Equal(t, msg.Document.FileID, animation.FileID) } _, err = b.Edit(edited, animation) require.NoError(t, err) }) t.Run("Edit(what=Animation)", func(t *testing.T) {}) t.Run("Send(what=string)", func(t *testing.T) { msg, err = b.Send(to, t.Name()) require.NoError(t, err) assert.Equal(t, t.Name(), msg.Text) rpl, err := b.Reply(msg, t.Name()) require.NoError(t, err) assert.Equal(t, rpl.Text, msg.Text) assert.NotNil(t, rpl.ReplyTo) assert.Equal(t, rpl.ReplyTo, msg) assert.True(t, rpl.IsReply()) fwd, err := b.Forward(to, msg) require.NoError(t, err) assert.NotNil(t, msg, fwd) assert.True(t, fwd.IsForwarded()) fwd.ID += 1 // nonexistent message _, err = b.Forward(to, fwd) assert.Equal(t, ErrNotFoundToForward, err) }) t.Run("Edit(what=string)", func(t *testing.T) { msg, err = b.Edit(msg, t.Name()) require.NoError(t, err) assert.Equal(t, t.Name(), msg.Text) _, err = b.Edit(msg, msg.Text) assert.Error(t, err) // message is not modified }) t.Run("Edit(what=ReplyMarkup)", func(t *testing.T) { good := &ReplyMarkup{ InlineKeyboard: [][]InlineButton{ {{ Data: "btn", Text: "Hi Telebot!", }}, }, } bad := &ReplyMarkup{ InlineKeyboard: [][]InlineButton{ {{ Data: strings.Repeat("*", 65), Text: "Bad Button", }}, }, } edited, err := b.Edit(msg, good) require.NoError(t, err) assert.Equal(t, edited.ReplyMarkup.InlineKeyboard, good.InlineKeyboard) edited, err = b.EditReplyMarkup(edited, nil) require.NoError(t, err) assert.Nil(t, edited.ReplyMarkup) _, err = b.Edit(edited, bad) assert.Equal(t, ErrBadButtonData, err) }) t.Run("Edit(what=Location)", func(t *testing.T) { loc := &Location{Lat: 42, Lng: 69, LivePeriod: 60} edited, err := b.Send(to, loc) require.NoError(t, err) assert.NotNil(t, edited.Location) loc = &Location{Lat: loc.Lng, Lng: loc.Lat} edited, err = b.Edit(edited, *loc) require.NoError(t, err) assert.NotNil(t, edited.Location) }) // Should be after the Edit tests. t.Run("Delete()", func(t *testing.T) { require.NoError(t, b.Delete(msg)) }) t.Run("Notify()", func(t *testing.T) { assert.Equal(t, ErrBadRecipient, b.Notify(nil, Typing)) require.NoError(t, b.Notify(to, Typing)) }) t.Run("Answer()", func(t *testing.T) { assert.Error(t, b.Answer(&Query{}, &QueryResponse{ Results: Results{&ArticleResult{}}, })) }) t.Run("Respond()", func(t *testing.T) { assert.Error(t, b.Respond(&Callback{}, &CallbackResponse{})) }) t.Run("Payments", func(t *testing.T) { assert.NotPanics(t, func() { b.Accept(&PreCheckoutQuery{}) b.Accept(&PreCheckoutQuery{}, "error") }) assert.NotPanics(t, func() { b.Ship(&ShippingQuery{}) b.Ship(&ShippingQuery{}, "error") b.Ship(&ShippingQuery{}, ShippingOption{}, ShippingOption{}) assert.Equal(t, ErrUnsupportedWhat, b.Ship(&ShippingQuery{}, 0)) }) }) t.Run("Commands", func(t *testing.T) { var ( set1 = []Command{{ Text: "test1", Description: "test command 1", }} set2 = []Command{{ Text: "test2", Description: "test command 2", }} scope = CommandScope{ Type: CommandScopeChat, ChatID: chatID, } ) err := b.SetCommands(set1) require.NoError(t, err) cmds, err := b.Commands() require.NoError(t, err) assert.Equal(t, set1, cmds) err = b.SetCommands(set2, "en", scope) require.NoError(t, err) cmds, err = b.Commands() require.NoError(t, err) assert.Equal(t, set1, cmds) cmds, err = b.Commands("en", scope) require.NoError(t, err) assert.Equal(t, set2, cmds) require.NoError(t, b.DeleteCommands("en", scope)) require.NoError(t, b.DeleteCommands()) }) t.Run("InviteLink", func(t *testing.T) { inviteLink, err := b.CreateInviteLink(&Chat{ID: chatID}, nil) require.NoError(t, err) assert.True(t, len(inviteLink.InviteLink) > 0) sleep() response, err := b.EditInviteLink(&Chat{ID: chatID}, &ChatInviteLink{InviteLink: inviteLink.InviteLink}) require.NoError(t, err) assert.True(t, len(response.InviteLink) > 0) sleep() response, err = b.RevokeInviteLink(&Chat{ID: chatID}, inviteLink.InviteLink) require.Nil(t, err) assert.True(t, len(response.InviteLink) > 0) }) } func sleep() { time.Sleep(time.Second) } telebot-3.3.8/callback.go000066400000000000000000000057741465437035100152730ustar00rootroot00000000000000package telebot // CallbackEndpoint is an interface any element capable // of responding to a callback `\f`. type CallbackEndpoint interface { CallbackUnique() string } // Callback object represents a query from a callback button in an // inline keyboard. type Callback struct { ID string `json:"id"` // For message sent to channels, Sender may be empty Sender *User `json:"from"` // Message will be set if the button that originated the query // was attached to a message sent by a bot. Message *Message `json:"message"` // MessageID will be set if the button was attached to a message // sent via the bot in inline mode. MessageID string `json:"inline_message_id"` // Data associated with the callback button. Be aware that // a bad client can send arbitrary data in this field. Data string `json:"data"` // ChatInstance is a global identifier, uniquely corresponding to // the chat to which the message with the callback button was sent. ChatInstance string `json:"chat_instance"` // GameShortName is a unique identifier of the game for which a URL // is requested from the bot when a user presses the Play button of // that game. GameShortName may be empty GameShortName string `json:"game_short_name"` // Unique displays an unique of the button from which the // callback was fired. Sets immediately before the handling, // while the Data field stores only with payload. Unique string `json:"-"` } // MessageSig satisfies Editable interface. func (c *Callback) MessageSig() (string, int64) { if c.IsInline() { return c.MessageID, 0 } return c.Message.MessageSig() } // IsInline says whether message is an inline message. func (c *Callback) IsInline() bool { return c.MessageID != "" } // CallbackResponse builds a response to a Callback query. type CallbackResponse struct { // The ID of the callback to which this is a response. // // Note: Telebot sets this field automatically! CallbackID string `json:"callback_query_id"` // Text of the notification. If not specified, nothing will be // shown to the user. Text string `json:"text,omitempty"` // (Optional) If true, an alert will be shown by the client instead // of a notification at the top of the chat screen. Defaults to false. ShowAlert bool `json:"show_alert,omitempty"` // (Optional) URL that will be opened by the user's client. // If you have created a Game and accepted the conditions via // @BotFather, specify the URL that opens your game. // // Note: this will only work if the query comes from a game // callback button. Otherwise, you may use deep-linking: // https://telegram.me/your_bot?start=XXXX URL string `json:"url,omitempty"` } // CallbackUnique returns ReplyButton.Text. func (t *ReplyButton) CallbackUnique() string { return t.Text } // CallbackUnique returns InlineButton.Unique. func (t *InlineButton) CallbackUnique() string { return "\f" + t.Unique } // CallbackUnique implements CallbackEndpoint. func (t *Btn) CallbackUnique() string { if t.Unique != "" { return "\f" + t.Unique } return t.Text } telebot-3.3.8/chat.go000066400000000000000000000345151465437035100144510ustar00rootroot00000000000000package telebot import ( "encoding/json" "strconv" "time" ) // User object represents a Telegram user, bot. type User struct { ID int64 `json:"id"` FirstName string `json:"first_name"` LastName string `json:"last_name"` IsForum bool `json:"is_forum"` Username string `json:"username"` LanguageCode string `json:"language_code"` IsBot bool `json:"is_bot"` IsPremium bool `json:"is_premium"` AddedToMenu bool `json:"added_to_attachment_menu"` Usernames []string `json:"active_usernames"` CustomEmojiStatus string `json:"emoji_status_custom_emoji_id"` // Returns only in getMe CanJoinGroups bool `json:"can_join_groups"` CanReadMessages bool `json:"can_read_all_group_messages"` SupportsInline bool `json:"supports_inline_queries"` } // Recipient returns user ID (see Recipient interface). func (u *User) Recipient() string { return strconv.FormatInt(u.ID, 10) } // Chat object represents a Telegram user, bot, group or a channel. type Chat struct { ID int64 `json:"id"` // See ChatType and consts. Type ChatType `json:"type"` // Won't be there for ChatPrivate. Title string `json:"title"` FirstName string `json:"first_name"` LastName string `json:"last_name"` Username string `json:"username"` // Returns only in getChat Bio string `json:"bio,omitempty"` Photo *ChatPhoto `json:"photo,omitempty"` Description string `json:"description,omitempty"` InviteLink string `json:"invite_link,omitempty"` PinnedMessage *Message `json:"pinned_message,omitempty"` Permissions *Rights `json:"permissions,omitempty"` Reactions []Reaction `json:"available_reactions"` SlowMode int `json:"slow_mode_delay,omitempty"` StickerSet string `json:"sticker_set_name,omitempty"` CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"` CustomEmojiSetName string `json:"custom_emoji_sticker_set_name"` LinkedChatID int64 `json:"linked_chat_id,omitempty"` ChatLocation *ChatLocation `json:"location,omitempty"` Private bool `json:"has_private_forwards,omitempty"` Protected bool `json:"has_protected_content,omitempty"` NoVoiceAndVideo bool `json:"has_restricted_voice_and_video_messages"` HasHiddenMembers bool `json:"has_hidden_members,omitempty"` AggressiveAntiSpam bool `json:"has_aggressive_anti_spam_enabled,omitempty"` CustomEmojiID string `json:"emoji_status_custom_emoji_id"` EmojiExpirationUnixtime int64 `json:"emoji_status_expiration_date"` BackgroundEmojiID string `json:"background_custom_emoji_id"` AccentColorID int `json:"accent_color_id"` ProfileAccentColorID int `json:"profile_accent_color_id"` ProfileBackgroundEmojiID string `json:"profile_background_custom_emoji_id"` HasVisibleHistory bool `json:"has_visible_history"` UnrestrictBoosts int `json:"unrestrict_boost_count"` } // Recipient returns chat ID (see Recipient interface). func (c *Chat) Recipient() string { return strconv.FormatInt(c.ID, 10) } // ChatType represents one of the possible chat types. type ChatType string const ( ChatPrivate ChatType = "private" ChatGroup ChatType = "group" ChatSuperGroup ChatType = "supergroup" ChatChannel ChatType = "channel" ChatChannelPrivate ChatType = "privatechannel" ) // ChatLocation represents a location to which a chat is connected. type ChatLocation struct { Location Location `json:"location,omitempty"` Address string `json:"address,omitempty"` } // ChatPhoto object represents a chat photo. type ChatPhoto struct { // File identifiers of small (160x160) chat photo SmallFileID string `json:"small_file_id"` SmallUniqueID string `json:"small_file_unique_id"` // File identifiers of big (640x640) chat photo BigFileID string `json:"big_file_id"` BigUniqueID string `json:"big_file_unique_id"` } // ChatMember object represents information about a single chat member. type ChatMember struct { Rights User *User `json:"user"` Role MemberStatus `json:"status"` Title string `json:"custom_title"` Anonymous bool `json:"is_anonymous"` Member bool `json:"is_member,omitempty"` // Date when restrictions will be lifted for the user, unix time. // // If user is restricted for more than 366 days or less than // 30 seconds from the current time, they are considered to be // restricted forever. // // Use tele.Forever(). // RestrictedUntil int64 `json:"until_date,omitempty"` JoinToSend string `json:"join_to_send_messages"` JoinByRequest string `json:"join_by_request"` } // MemberStatus is one's chat status. type MemberStatus string const ( Creator MemberStatus = "creator" Administrator MemberStatus = "administrator" Member MemberStatus = "member" Restricted MemberStatus = "restricted" Left MemberStatus = "left" Kicked MemberStatus = "kicked" ) // ChatMemberUpdate object represents changes in the status of a chat member. type ChatMemberUpdate struct { // Chat where the user belongs to. Chat *Chat `json:"chat"` // Sender which user the action was triggered. Sender *User `json:"from"` // Unixtime, use Date() to get time.Time. Unixtime int64 `json:"date"` // Previous information about the chat member. OldChatMember *ChatMember `json:"old_chat_member"` // New information about the chat member. NewChatMember *ChatMember `json:"new_chat_member"` // (Optional) InviteLink which was used by the user to // join the chat; for joining by invite link events only. InviteLink *ChatInviteLink `json:"invite_link"` // (Optional) True, if the user joined the chat via a chat folder invite link. ViaFolderLink bool `json:"via_chat_folder_invite_link"` } // Time returns the moment of the change in local time. func (c *ChatMemberUpdate) Time() time.Time { return time.Unix(c.Unixtime, 0) } // ChatID represents a chat or an user integer ID, which can be used // as recipient in bot methods. It is very useful in cases where // you have special group IDs, for example in your config, and don't // want to wrap it into *tele.Chat every time you send messages. // // Example: // // group := tele.ChatID(-100756389456) // b.Send(group, "Hello!") // // type Config struct { // AdminGroup tele.ChatID `json:"admin_group"` // } // b.Send(conf.AdminGroup, "Hello!") type ChatID int64 // Recipient returns chat ID (see Recipient interface). func (i ChatID) Recipient() string { return strconv.FormatInt(int64(i), 10) } // ChatJoinRequest represents a join request sent to a chat. type ChatJoinRequest struct { // Chat to which the request was sent. Chat *Chat `json:"chat"` // Sender is the user that sent the join request. Sender *User `json:"from"` // UserChatID is an ID of a private chat with the user // who sent the join request. The bot can use this ID // for 5 minutes to send messages until the join request // is processed, assuming no other administrator contacted the user. UserChatID int64 `json:"user_chat_id"` // Unixtime, use ChatJoinRequest.Time() to get time.Time. Unixtime int64 `json:"date"` // Bio of the user, optional. Bio string `json:"bio"` // InviteLink is the chat invite link that was used by //the user to send the join request, optional. InviteLink *ChatInviteLink `json:"invite_link"` } // ChatInviteLink object represents an invite for a chat. type ChatInviteLink struct { // The invite link. InviteLink string `json:"invite_link"` // Invite link name. Name string `json:"name"` // The creator of the link. Creator *User `json:"creator"` // If the link is primary. IsPrimary bool `json:"is_primary"` // If the link is revoked. IsRevoked bool `json:"is_revoked"` // (Optional) Point in time when the link will expire, // use ExpireDate() to get time.Time. ExpireUnixtime int64 `json:"expire_date,omitempty"` // (Optional) Maximum number of users that can be members of // the chat simultaneously. MemberLimit int `json:"member_limit,omitempty"` // (Optional) True, if users joining the chat via the link need to // be approved by chat administrators. If True, member_limit can't be specified. JoinRequest bool `json:"creates_join_request"` // (Optional) Number of pending join requests created using this link. PendingCount int `json:"pending_join_request_count"` } type Story struct { // Unique identifier for the story in the chat ID int `json:"id"` // Chat that posted the story Poster *Chat `json:"chat"` } // ExpireDate returns the moment of the link expiration in local time. func (c *ChatInviteLink) ExpireDate() time.Time { return time.Unix(c.ExpireUnixtime, 0) } // Time returns the moment of chat join request sending in local time. func (r ChatJoinRequest) Time() time.Time { return time.Unix(r.Unixtime, 0) } // Time returns the moment of the emoji status expiration. func (c *Chat) Time() time.Time { return time.Unix(c.EmojiExpirationUnixtime, 0) } // InviteLink should be used to export chat's invite link. func (b *Bot) InviteLink(chat *Chat) (string, error) { params := map[string]string{ "chat_id": chat.Recipient(), } data, err := b.Raw("exportChatInviteLink", params) if err != nil { return "", err } var resp struct { Result string } if err := json.Unmarshal(data, &resp); err != nil { return "", wrapError(err) } return resp.Result, nil } // CreateInviteLink creates an additional invite link for a chat. func (b *Bot) CreateInviteLink(chat Recipient, link *ChatInviteLink) (*ChatInviteLink, error) { params := map[string]string{ "chat_id": chat.Recipient(), } if link != nil { params["name"] = link.Name if link.ExpireUnixtime != 0 { params["expire_date"] = strconv.FormatInt(link.ExpireUnixtime, 10) } if link.MemberLimit > 0 { params["member_limit"] = strconv.Itoa(link.MemberLimit) } else if link.JoinRequest { params["creates_join_request"] = "true" } } data, err := b.Raw("createChatInviteLink", params) if err != nil { return nil, err } var resp struct { Result ChatInviteLink `json:"result"` } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return &resp.Result, nil } // EditInviteLink edits a non-primary invite link created by the bot. func (b *Bot) EditInviteLink(chat Recipient, link *ChatInviteLink) (*ChatInviteLink, error) { params := map[string]string{ "chat_id": chat.Recipient(), } if link != nil { params["invite_link"] = link.InviteLink params["name"] = link.Name if link.ExpireUnixtime != 0 { params["expire_date"] = strconv.FormatInt(link.ExpireUnixtime, 10) } if link.MemberLimit > 0 { params["member_limit"] = strconv.Itoa(link.MemberLimit) } else if link.JoinRequest { params["creates_join_request"] = "true" } } data, err := b.Raw("editChatInviteLink", params) if err != nil { return nil, err } var resp struct { Result ChatInviteLink `json:"result"` } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return &resp.Result, nil } // RevokeInviteLink revokes an invite link created by the bot. func (b *Bot) RevokeInviteLink(chat Recipient, link string) (*ChatInviteLink, error) { params := map[string]string{ "chat_id": chat.Recipient(), "invite_link": link, } data, err := b.Raw("revokeChatInviteLink", params) if err != nil { return nil, err } var resp struct { Result ChatInviteLink `json:"result"` } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return &resp.Result, nil } // ApproveJoinRequest approves a chat join request. func (b *Bot) ApproveJoinRequest(chat Recipient, user *User) error { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": user.Recipient(), } data, err := b.Raw("approveChatJoinRequest", params) if err != nil { return err } return extractOk(data) } // DeclineJoinRequest declines a chat join request. func (b *Bot) DeclineJoinRequest(chat Recipient, user *User) error { params := map[string]string{ "chat_id": chat.Recipient(), "user_id": user.Recipient(), } data, err := b.Raw("declineChatJoinRequest", params) if err != nil { return err } return extractOk(data) } // SetGroupTitle should be used to update group title. func (b *Bot) SetGroupTitle(chat *Chat, title string) error { params := map[string]string{ "chat_id": chat.Recipient(), "title": title, } _, err := b.Raw("setChatTitle", params) return err } // SetGroupDescription should be used to update group description. func (b *Bot) SetGroupDescription(chat *Chat, description string) error { params := map[string]string{ "chat_id": chat.Recipient(), "description": description, } _, err := b.Raw("setChatDescription", params) return err } // SetGroupPhoto should be used to update group photo. func (b *Bot) SetGroupPhoto(chat *Chat, p *Photo) error { params := map[string]string{ "chat_id": chat.Recipient(), } _, err := b.sendFiles("setChatPhoto", map[string]File{"photo": p.File}, params) return err } // SetGroupStickerSet should be used to update group's group sticker set. func (b *Bot) SetGroupStickerSet(chat *Chat, setName string) error { params := map[string]string{ "chat_id": chat.Recipient(), "sticker_set_name": setName, } _, err := b.Raw("setChatStickerSet", params) return err } // SetGroupPermissions sets default chat permissions for all members. func (b *Bot) SetGroupPermissions(chat *Chat, perms Rights) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "permissions": perms, } if perms.Independent { params["use_independent_chat_permissions"] = true } _, err := b.Raw("setChatPermissions", params) return err } // DeleteGroupPhoto should be used to just remove group photo. func (b *Bot) DeleteGroupPhoto(chat *Chat) error { params := map[string]string{ "chat_id": chat.Recipient(), } _, err := b.Raw("deleteChatPhoto", params) return err } // DeleteGroupStickerSet should be used to just remove group sticker set. func (b *Bot) DeleteGroupStickerSet(chat *Chat) error { params := map[string]string{ "chat_id": chat.Recipient(), } _, err := b.Raw("deleteChatStickerSet", params) return err } telebot-3.3.8/chat_test.go000066400000000000000000000006731465437035100155060ustar00rootroot00000000000000package telebot import ( "testing" "github.com/stretchr/testify/assert" ) func TestChat(t *testing.T) { user := &User{ID: 1} chat := &Chat{ID: 1} chatID := ChatID(1) assert.Implements(t, (*Recipient)(nil), user) assert.Implements(t, (*Recipient)(nil), chat) assert.Implements(t, (*Recipient)(nil), chatID) assert.Equal(t, "1", user.Recipient()) assert.Equal(t, "1", chat.Recipient()) assert.Equal(t, "1", chatID.Recipient()) } telebot-3.3.8/commands.go000066400000000000000000000052231465437035100153250ustar00rootroot00000000000000package telebot import "encoding/json" // Command represents a bot command. type Command struct { // Text is a text of the command, 1-32 characters. // Can contain only lowercase English letters, digits and underscores. Text string `json:"command"` // Description of the command, 3-256 characters. Description string `json:"description"` } // CommandParams controls parameters for commands-related methods (setMyCommands, deleteMyCommands and getMyCommands). type CommandParams struct { Commands []Command `json:"commands,omitempty"` Scope *CommandScope `json:"scope,omitempty"` LanguageCode string `json:"language_code,omitempty"` } type CommandScopeType = string const ( CommandScopeDefault CommandScopeType = "default" CommandScopeAllPrivateChats CommandScopeType = "all_private_chats" CommandScopeAllGroupChats CommandScopeType = "all_group_chats" CommandScopeAllChatAdmin CommandScopeType = "all_chat_administrators" CommandScopeChat CommandScopeType = "chat" CommandScopeChatAdmin CommandScopeType = "chat_administrators" CommandScopeChatMember CommandScopeType = "chat_member" ) // CommandScope object represents a scope to which bot commands are applied. type CommandScope struct { Type CommandScopeType `json:"type"` ChatID int64 `json:"chat_id,omitempty"` UserID int64 `json:"user_id,omitempty"` } // Commands returns the current list of the bot's commands for the given scope and user language. func (b *Bot) Commands(opts ...interface{}) ([]Command, error) { params := extractCommandsParams(opts...) data, err := b.Raw("getMyCommands", params) if err != nil { return nil, err } var resp struct { Result []Command } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // SetCommands changes the list of the bot's commands. func (b *Bot) SetCommands(opts ...interface{}) error { params := extractCommandsParams(opts...) _, err := b.Raw("setMyCommands", params) return err } // DeleteCommands deletes the list of the bot's commands for the given scope and user language. func (b *Bot) DeleteCommands(opts ...interface{}) error { params := extractCommandsParams(opts...) _, err := b.Raw("deleteMyCommands", params) return err } // extractCommandsParams extracts parameters for commands-related methods from the given options. func extractCommandsParams(opts ...interface{}) (params CommandParams) { for _, opt := range opts { switch value := opt.(type) { case []Command: params.Commands = value case string: params.LanguageCode = value case CommandScope: params.Scope = &value } } return } telebot-3.3.8/context.go000066400000000000000000000327631465437035100152210ustar00rootroot00000000000000package telebot import ( "errors" "strings" "sync" "time" ) // HandlerFunc represents a handler function, which is // used to handle actual endpoints. type HandlerFunc func(Context) error // Context wraps an update and represents the context of current event. type Context interface { // Bot returns the bot instance. Bot() *Bot // Update returns the original update. Update() Update // Message returns stored message if such presented. Message() *Message // Callback returns stored callback if such presented. Callback() *Callback // Query returns stored query if such presented. Query() *Query // InlineResult returns stored inline result if such presented. InlineResult() *InlineResult // ShippingQuery returns stored shipping query if such presented. ShippingQuery() *ShippingQuery // PreCheckoutQuery returns stored pre checkout query if such presented. PreCheckoutQuery() *PreCheckoutQuery // Poll returns stored poll if such presented. Poll() *Poll // PollAnswer returns stored poll answer if such presented. PollAnswer() *PollAnswer // ChatMember returns chat member changes. ChatMember() *ChatMemberUpdate // ChatJoinRequest returns the chat join request. ChatJoinRequest() *ChatJoinRequest // Migration returns both migration from and to chat IDs. Migration() (int64, int64) // Topic returns the topic changes. Topic() *Topic // Boost returns the boost instance. Boost() *BoostUpdated // BoostRemoved returns the boost removed from a chat instance. BoostRemoved() *BoostRemoved // Sender returns the current recipient, depending on the context type. // Returns nil if user is not presented. Sender() *User // Chat returns the current chat, depending on the context type. // Returns nil if chat is not presented. Chat() *Chat // Recipient combines both Sender and Chat functions. If there is no user // the chat will be returned. The native context cannot be without sender, // but it is useful in the case when the context created intentionally // by the NewContext constructor and have only Chat field inside. Recipient() Recipient // Text returns the message text, depending on the context type. // In the case when no related data presented, returns an empty string. Text() string // Entities returns the message entities, whether it's media caption's or the text's. // In the case when no entities presented, returns a nil. Entities() Entities // Data returns the current data, depending on the context type. // If the context contains command, returns its arguments string. // If the context contains payment, returns its payload. // In the case when no related data presented, returns an empty string. Data() string // Args returns a raw slice of command or callback arguments as strings. // The message arguments split by space, while the callback's ones by a "|" symbol. Args() []string // Send sends a message to the current recipient. // See Send from bot.go. Send(what interface{}, opts ...interface{}) error // SendAlbum sends an album to the current recipient. // See SendAlbum from bot.go. SendAlbum(a Album, opts ...interface{}) error // Reply replies to the current message. // See Reply from bot.go. Reply(what interface{}, opts ...interface{}) error // Forward forwards the given message to the current recipient. // See Forward from bot.go. Forward(msg Editable, opts ...interface{}) error // ForwardTo forwards the current message to the given recipient. // See Forward from bot.go ForwardTo(to Recipient, opts ...interface{}) error // Edit edits the current message. // See Edit from bot.go. Edit(what interface{}, opts ...interface{}) error // EditCaption edits the caption of the current message. // See EditCaption from bot.go. EditCaption(caption string, opts ...interface{}) error // EditOrSend edits the current message if the update is callback, // otherwise the content is sent to the chat as a separate message. EditOrSend(what interface{}, opts ...interface{}) error // EditOrReply edits the current message if the update is callback, // otherwise the content is replied as a separate message. EditOrReply(what interface{}, opts ...interface{}) error // Delete removes the current message. // See Delete from bot.go. Delete() error // DeleteAfter waits for the duration to elapse and then removes the // message. It handles an error automatically using b.OnError callback. // It returns a Timer that can be used to cancel the call using its Stop method. DeleteAfter(d time.Duration) *time.Timer // Notify updates the chat action for the current recipient. // See Notify from bot.go. Notify(action ChatAction) error // Ship replies to the current shipping query. // See Ship from bot.go. Ship(what ...interface{}) error // Accept finalizes the current deal. // See Accept from bot.go. Accept(errorMessage ...string) error // Answer sends a response to the current inline query. // See Answer from bot.go. Answer(resp *QueryResponse) error // Respond sends a response for the current callback query. // See Respond from bot.go. Respond(resp ...*CallbackResponse) error // RespondText sends a popup response for the current callback query. RespondText(text string) error // RespondAlert sends an alert response for the current callback query. RespondAlert(text string) error // Get retrieves data from the context. Get(key string) interface{} // Set saves data in the context. Set(key string, val interface{}) } // nativeContext is a native implementation of the Context interface. // "context" is taken by context package, maybe there is a better name. type nativeContext struct { b *Bot u Update lock sync.RWMutex store map[string]interface{} } func (c *nativeContext) Bot() *Bot { return c.b } func (c *nativeContext) Update() Update { return c.u } func (c *nativeContext) Message() *Message { switch { case c.u.Message != nil: return c.u.Message case c.u.Callback != nil: return c.u.Callback.Message case c.u.EditedMessage != nil: return c.u.EditedMessage case c.u.ChannelPost != nil: if c.u.ChannelPost.PinnedMessage != nil { return c.u.ChannelPost.PinnedMessage } return c.u.ChannelPost case c.u.EditedChannelPost != nil: return c.u.EditedChannelPost default: return nil } } func (c *nativeContext) Callback() *Callback { return c.u.Callback } func (c *nativeContext) Query() *Query { return c.u.Query } func (c *nativeContext) InlineResult() *InlineResult { return c.u.InlineResult } func (c *nativeContext) ShippingQuery() *ShippingQuery { return c.u.ShippingQuery } func (c *nativeContext) PreCheckoutQuery() *PreCheckoutQuery { return c.u.PreCheckoutQuery } func (c *nativeContext) ChatMember() *ChatMemberUpdate { switch { case c.u.ChatMember != nil: return c.u.ChatMember case c.u.MyChatMember != nil: return c.u.MyChatMember default: return nil } } func (c *nativeContext) ChatJoinRequest() *ChatJoinRequest { return c.u.ChatJoinRequest } func (c *nativeContext) Poll() *Poll { return c.u.Poll } func (c *nativeContext) PollAnswer() *PollAnswer { return c.u.PollAnswer } func (c *nativeContext) Migration() (int64, int64) { return c.u.Message.MigrateFrom, c.u.Message.MigrateTo } func (c *nativeContext) Topic() *Topic { m := c.u.Message if m == nil { return nil } switch { case m.TopicCreated != nil: return m.TopicCreated case m.TopicReopened != nil: return m.TopicReopened case m.TopicEdited != nil: return m.TopicEdited } return nil } func (c *nativeContext) Boost() *BoostUpdated { return c.u.Boost } func (c *nativeContext) BoostRemoved() *BoostRemoved { return c.u.BoostRemoved } func (c *nativeContext) Sender() *User { switch { case c.u.Callback != nil: return c.u.Callback.Sender case c.Message() != nil: return c.Message().Sender case c.u.Query != nil: return c.u.Query.Sender case c.u.InlineResult != nil: return c.u.InlineResult.Sender case c.u.ShippingQuery != nil: return c.u.ShippingQuery.Sender case c.u.PreCheckoutQuery != nil: return c.u.PreCheckoutQuery.Sender case c.u.PollAnswer != nil: return c.u.PollAnswer.Sender case c.u.MyChatMember != nil: return c.u.MyChatMember.Sender case c.u.ChatMember != nil: return c.u.ChatMember.Sender case c.u.ChatJoinRequest != nil: return c.u.ChatJoinRequest.Sender case c.u.Boost != nil: if b := c.u.Boost.Boost; b != nil && b.Source != nil { return b.Source.Booster } case c.u.BoostRemoved != nil: if b := c.u.BoostRemoved; b.Source != nil { return b.Source.Booster } } return nil } func (c *nativeContext) Chat() *Chat { switch { case c.Message() != nil: return c.Message().Chat case c.u.MyChatMember != nil: return c.u.MyChatMember.Chat case c.u.ChatMember != nil: return c.u.ChatMember.Chat case c.u.ChatJoinRequest != nil: return c.u.ChatJoinRequest.Chat default: return nil } } func (c *nativeContext) Recipient() Recipient { chat := c.Chat() if chat != nil { return chat } return c.Sender() } func (c *nativeContext) Text() string { m := c.Message() if m == nil { return "" } if m.Caption != "" { return m.Caption } return m.Text } func (c *nativeContext) Entities() Entities { m := c.Message() if m == nil { return nil } if len(m.CaptionEntities) > 0 { return m.CaptionEntities } return m.Entities } func (c *nativeContext) Data() string { switch { case c.u.Message != nil: return c.u.Message.Payload case c.u.Callback != nil: return c.u.Callback.Data case c.u.Query != nil: return c.u.Query.Text case c.u.InlineResult != nil: return c.u.InlineResult.Query case c.u.ShippingQuery != nil: return c.u.ShippingQuery.Payload case c.u.PreCheckoutQuery != nil: return c.u.PreCheckoutQuery.Payload default: return "" } } func (c *nativeContext) Args() []string { switch { case c.u.Message != nil: payload := strings.Trim(c.u.Message.Payload, " ") if payload != "" { return strings.Fields(payload) } case c.u.Callback != nil: return strings.Split(c.u.Callback.Data, "|") case c.u.Query != nil: return strings.Split(c.u.Query.Text, " ") case c.u.InlineResult != nil: return strings.Split(c.u.InlineResult.Query, " ") } return nil } func (c *nativeContext) Send(what interface{}, opts ...interface{}) error { _, err := c.b.Send(c.Recipient(), what, opts...) return err } func (c *nativeContext) SendAlbum(a Album, opts ...interface{}) error { _, err := c.b.SendAlbum(c.Recipient(), a, opts...) return err } func (c *nativeContext) Reply(what interface{}, opts ...interface{}) error { msg := c.Message() if msg == nil { return ErrBadContext } _, err := c.b.Reply(msg, what, opts...) return err } func (c *nativeContext) Forward(msg Editable, opts ...interface{}) error { _, err := c.b.Forward(c.Recipient(), msg, opts...) return err } func (c *nativeContext) ForwardTo(to Recipient, opts ...interface{}) error { msg := c.Message() if msg == nil { return ErrBadContext } _, err := c.b.Forward(to, msg, opts...) return err } func (c *nativeContext) Edit(what interface{}, opts ...interface{}) error { if c.u.InlineResult != nil { _, err := c.b.Edit(c.u.InlineResult, what, opts...) return err } if c.u.Callback != nil { _, err := c.b.Edit(c.u.Callback, what, opts...) return err } return ErrBadContext } func (c *nativeContext) EditCaption(caption string, opts ...interface{}) error { if c.u.InlineResult != nil { _, err := c.b.EditCaption(c.u.InlineResult, caption, opts...) return err } if c.u.Callback != nil { _, err := c.b.EditCaption(c.u.Callback, caption, opts...) return err } return ErrBadContext } func (c *nativeContext) EditOrSend(what interface{}, opts ...interface{}) error { err := c.Edit(what, opts...) if err == ErrBadContext { return c.Send(what, opts...) } return err } func (c *nativeContext) EditOrReply(what interface{}, opts ...interface{}) error { err := c.Edit(what, opts...) if err == ErrBadContext { return c.Reply(what, opts...) } return err } func (c *nativeContext) Delete() error { msg := c.Message() if msg == nil { return ErrBadContext } return c.b.Delete(msg) } func (c *nativeContext) DeleteAfter(d time.Duration) *time.Timer { return time.AfterFunc(d, func() { if err := c.Delete(); err != nil { c.b.OnError(err, c) } }) } func (c *nativeContext) Notify(action ChatAction) error { return c.b.Notify(c.Recipient(), action) } func (c *nativeContext) Ship(what ...interface{}) error { if c.u.ShippingQuery == nil { return errors.New("telebot: context shipping query is nil") } return c.b.Ship(c.u.ShippingQuery, what...) } func (c *nativeContext) Accept(errorMessage ...string) error { if c.u.PreCheckoutQuery == nil { return errors.New("telebot: context pre checkout query is nil") } return c.b.Accept(c.u.PreCheckoutQuery, errorMessage...) } func (c *nativeContext) Respond(resp ...*CallbackResponse) error { if c.u.Callback == nil { return errors.New("telebot: context callback is nil") } return c.b.Respond(c.u.Callback, resp...) } func (c *nativeContext) RespondText(text string) error { return c.Respond(&CallbackResponse{Text: text}) } func (c *nativeContext) RespondAlert(text string) error { return c.Respond(&CallbackResponse{Text: text, ShowAlert: true}) } func (c *nativeContext) Answer(resp *QueryResponse) error { if c.u.Query == nil { return errors.New("telebot: context inline query is nil") } return c.b.Answer(c.u.Query, resp) } func (c *nativeContext) Set(key string, value interface{}) { c.lock.Lock() defer c.lock.Unlock() if c.store == nil { c.store = make(map[string]interface{}) } c.store[key] = value } func (c *nativeContext) Get(key string) interface{} { c.lock.RLock() defer c.lock.RUnlock() return c.store[key] } telebot-3.3.8/context_test.go000066400000000000000000000004661465437035100162530ustar00rootroot00000000000000package telebot import ( "testing" "github.com/stretchr/testify/assert" ) var _ Context = (*nativeContext)(nil) func TestContext(t *testing.T) { t.Run("Get,Set", func(t *testing.T) { var c Context c = new(nativeContext) c.Set("name", "Jon Snow") assert.Equal(t, "Jon Snow", c.Get("name")) }) } telebot-3.3.8/editable.go000066400000000000000000000017301465437035100152740ustar00rootroot00000000000000package telebot // Editable is an interface for all objects that // provide "message signature", a pair of 32-bit // message ID and 64-bit chat ID, both required // for edit operations. // // Use case: DB model struct for messages to-be // edited with, say two columns: msg_id,chat_id // could easily implement MessageSig() making // instances of stored messages editable. type Editable interface { // MessageSig is a "message signature". // // For inline messages, return chatID = 0. MessageSig() (messageID string, chatID int64) } // StoredMessage is an example struct suitable for being // stored in the database as-is or being embedded into // a larger struct, which is often the case (you might // want to store some metadata alongside, or might not.) type StoredMessage struct { MessageID string `sql:"message_id" json:"message_id"` ChatID int64 `sql:"chat_id" json:"chat_id"` } func (x StoredMessage) MessageSig() (string, int64) { return x.MessageID, x.ChatID } telebot-3.3.8/errors.go000066400000000000000000000243321465437035100150420ustar00rootroot00000000000000package telebot import ( "errors" "fmt" "strings" ) type ( Error struct { Code int Description string Message string } FloodError struct { err *Error RetryAfter int } GroupError struct { err *Error MigratedTo int64 } ) // ʔ returns description of error. // A tiny shortcut to make code clearer. func (err *Error) ʔ() string { return err.Description } // Error implements error interface. func (err *Error) Error() string { msg := err.Message if msg == "" { split := strings.Split(err.Description, ": ") if len(split) == 2 { msg = split[1] } else { msg = err.Description } } return fmt.Sprintf("telegram: %s (%d)", msg, err.Code) } // Error implements error interface. func (err FloodError) Error() string { return err.err.Error() } // Error implements error interface. func (err GroupError) Error() string { return err.err.Error() } // NewError returns new Error instance with given description. // First element of msgs is Description. The second is optional Message. func NewError(code int, msgs ...string) *Error { err := &Error{Code: code} if len(msgs) >= 1 { err.Description = msgs[0] } if len(msgs) >= 2 { err.Message = msgs[1] } return err } // General errors var ( ErrTooLarge = NewError(400, "Request Entity Too Large") ErrUnauthorized = NewError(401, "Unauthorized") ErrNotFound = NewError(404, "Not Found") ErrInternal = NewError(500, "Internal Server Error") ) // Bad request errors var ( ErrBadButtonData = NewError(400, "Bad Request: BUTTON_DATA_INVALID") ErrBadUserID = NewError(400, "Bad Request: USER_ID_INVALID") ErrBadPollOptions = NewError(400, "Bad Request: expected an Array of String as options") ErrBadURLContent = NewError(400, "Bad Request: failed to get HTTP URL content") ErrCantEditMessage = NewError(400, "Bad Request: message can't be edited") ErrCantRemoveOwner = NewError(400, "Bad Request: can't remove chat owner") ErrCantUploadFile = NewError(400, "Bad Request: can't upload file by URL") ErrCantUseMediaInAlbum = NewError(400, "Bad Request: can't use the media of the specified type in the album") ErrChatAboutNotModified = NewError(400, "Bad Request: chat description is not modified") ErrChatNotFound = NewError(400, "Bad Request: chat not found") ErrEmptyChatID = NewError(400, "Bad Request: chat_id is empty") ErrEmptyMessage = NewError(400, "Bad Request: message must be non-empty") ErrEmptyText = NewError(400, "Bad Request: text is empty") ErrFailedImageProcess = NewError(400, "Bad Request: IMAGE_PROCESS_FAILED", "Image process failed") ErrGroupMigrated = NewError(400, "Bad Request: group chat was upgraded to a supergroup chat") ErrMessageNotModified = NewError(400, "Bad Request: message is not modified") ErrNoRightsToDelete = NewError(400, "Bad Request: message can't be deleted") ErrNoRightsToRestrict = NewError(400, "Bad Request: not enough rights to restrict/unrestrict chat member") ErrNoRightsToSend = NewError(400, "Bad Request: have no rights to send a message") ErrNoRightsToSendGifs = NewError(400, "Bad Request: CHAT_SEND_GIFS_FORBIDDEN", "sending GIFS is not allowed in this chat") ErrNoRightsToSendPhoto = NewError(400, "Bad Request: not enough rights to send photos to the chat") ErrNoRightsToSendStickers = NewError(400, "Bad Request: not enough rights to send stickers to the chat") ErrNotFoundToDelete = NewError(400, "Bad Request: message to delete not found") ErrNotFoundToForward = NewError(400, "Bad Request: message to forward not found") ErrNotFoundToReply = NewError(400, "Bad Request: reply message not found") ErrQueryTooOld = NewError(400, "Bad Request: query is too old and response timeout expired or query ID is invalid") ErrSameMessageContent = NewError(400, "Bad Request: message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message") ErrStickerEmojisInvalid = NewError(400, "Bad Request: invalid sticker emojis") ErrStickerSetInvalid = NewError(400, "Bad Request: STICKERSET_INVALID", "Stickerset is invalid") ErrStickerSetInvalidName = NewError(400, "Bad Request: invalid sticker set name is specified") ErrStickerSetNameOccupied = NewError(400, "Bad Request: sticker set name is already occupied") ErrTooLongMarkup = NewError(400, "Bad Request: reply markup is too long") ErrTooLongMessage = NewError(400, "Bad Request: message is too long") ErrUserIsAdmin = NewError(400, "Bad Request: user is an administrator of the chat") ErrWrongFileID = NewError(400, "Bad Request: wrong file identifier/HTTP URL specified") ErrWrongFileIDCharacter = NewError(400, "Bad Request: wrong remote file id specified: Wrong character in the string") ErrWrongFileIDLength = NewError(400, "Bad Request: wrong remote file id specified: Wrong string length") ErrWrongFileIDPadding = NewError(400, "Bad Request: wrong remote file id specified: Wrong padding in the string") ErrWrongFileIDSymbol = NewError(400, "Bad Request: wrong remote file id specified: can't unserialize it. Wrong last symbol") ErrWrongTypeOfContent = NewError(400, "Bad Request: wrong type of the web page content") ErrWrongURL = NewError(400, "Bad Request: wrong HTTP URL specified") ErrForwardMessage = NewError(400, "Bad Request: administrators of the chat restricted message forwarding") ErrUserAlreadyParticipant = NewError(400, "Bad Request: USER_ALREADY_PARTICIPANT", "User is already a participant") ErrHideRequesterMissing = NewError(400, "Bad Request: HIDE_REQUESTER_MISSING") ErrChannelsTooMuch = NewError(400, "Bad Request: CHANNELS_TOO_MUCH") ErrChannelsTooMuchUser = NewError(400, "Bad Request: USER_CHANNELS_TOO_MUCH") ) // Forbidden errors var ( ErrBlockedByUser = NewError(403, "Forbidden: bot was blocked by the user") ErrKickedFromGroup = NewError(403, "Forbidden: bot was kicked from the group chat") ErrKickedFromSuperGroup = NewError(403, "Forbidden: bot was kicked from the supergroup chat") ErrKickedFromChannel = NewError(403, "Forbidden: bot was kicked from the channel chat") ErrNotStartedByUser = NewError(403, "Forbidden: bot can't initiate conversation with a user") ErrUserIsDeactivated = NewError(403, "Forbidden: user is deactivated") ErrNotChannelMember = NewError(403, "Forbidden: bot is not a member of the channel chat") ) // Err returns Error instance by given description. func Err(s string) error { switch s { case ErrTooLarge.ʔ(): return ErrTooLarge case ErrUnauthorized.ʔ(): return ErrUnauthorized case ErrNotFound.ʔ(): return ErrNotFound case ErrInternal.ʔ(): return ErrInternal case ErrBadButtonData.ʔ(): return ErrBadButtonData case ErrBadUserID.ʔ(): return ErrBadUserID case ErrBadPollOptions.ʔ(): return ErrBadPollOptions case ErrBadURLContent.ʔ(): return ErrBadURLContent case ErrCantEditMessage.ʔ(): return ErrCantEditMessage case ErrCantRemoveOwner.ʔ(): return ErrCantRemoveOwner case ErrCantUploadFile.ʔ(): return ErrCantUploadFile case ErrCantUseMediaInAlbum.ʔ(): return ErrCantUseMediaInAlbum case ErrChatAboutNotModified.ʔ(): return ErrChatAboutNotModified case ErrChatNotFound.ʔ(): return ErrChatNotFound case ErrEmptyChatID.ʔ(): return ErrEmptyChatID case ErrEmptyMessage.ʔ(): return ErrEmptyMessage case ErrEmptyText.ʔ(): return ErrEmptyText case ErrFailedImageProcess.ʔ(): return ErrFailedImageProcess case ErrGroupMigrated.ʔ(): return ErrGroupMigrated case ErrMessageNotModified.ʔ(): return ErrMessageNotModified case ErrNoRightsToDelete.ʔ(): return ErrNoRightsToDelete case ErrNoRightsToRestrict.ʔ(): return ErrNoRightsToRestrict case ErrNoRightsToSend.ʔ(): return ErrNoRightsToSend case ErrNoRightsToSendGifs.ʔ(): return ErrNoRightsToSendGifs case ErrNoRightsToSendPhoto.ʔ(): return ErrNoRightsToSendPhoto case ErrNoRightsToSendStickers.ʔ(): return ErrNoRightsToSendStickers case ErrNotFoundToDelete.ʔ(): return ErrNotFoundToDelete case ErrNotFoundToForward.ʔ(): return ErrNotFoundToForward case ErrNotFoundToReply.ʔ(): return ErrNotFoundToReply case ErrQueryTooOld.ʔ(): return ErrQueryTooOld case ErrSameMessageContent.ʔ(): return ErrSameMessageContent case ErrStickerEmojisInvalid.ʔ(): return ErrStickerEmojisInvalid case ErrStickerSetInvalid.ʔ(): return ErrStickerSetInvalid case ErrStickerSetInvalidName.ʔ(): return ErrStickerSetInvalidName case ErrStickerSetNameOccupied.ʔ(): return ErrStickerSetNameOccupied case ErrTooLongMarkup.ʔ(): return ErrTooLongMarkup case ErrTooLongMessage.ʔ(): return ErrTooLongMessage case ErrUserIsAdmin.ʔ(): return ErrUserIsAdmin case ErrWrongFileID.ʔ(): return ErrWrongFileID case ErrWrongFileIDCharacter.ʔ(): return ErrWrongFileIDCharacter case ErrWrongFileIDLength.ʔ(): return ErrWrongFileIDLength case ErrWrongFileIDPadding.ʔ(): return ErrWrongFileIDPadding case ErrWrongFileIDSymbol.ʔ(): return ErrWrongFileIDSymbol case ErrWrongTypeOfContent.ʔ(): return ErrWrongTypeOfContent case ErrWrongURL.ʔ(): return ErrWrongURL case ErrBlockedByUser.ʔ(): return ErrBlockedByUser case ErrKickedFromGroup.ʔ(): return ErrKickedFromGroup case ErrKickedFromSuperGroup.ʔ(): return ErrKickedFromSuperGroup case ErrKickedFromChannel.ʔ(): return ErrKickedFromChannel case ErrNotStartedByUser.ʔ(): return ErrNotStartedByUser case ErrUserIsDeactivated.ʔ(): return ErrUserIsDeactivated case ErrForwardMessage.ʔ(): return ErrForwardMessage case ErrUserAlreadyParticipant.ʔ(): return ErrUserAlreadyParticipant case ErrHideRequesterMissing.ʔ(): return ErrHideRequesterMissing case ErrChannelsTooMuch.ʔ(): return ErrChannelsTooMuch case ErrChannelsTooMuchUser.ʔ(): return ErrChannelsTooMuchUser case ErrNotChannelMember.ʔ(): return ErrNotChannelMember default: return nil } } // ErrIs checks if the error with given description matches an error err. func ErrIs(s string, err error) bool { return errors.Is(err, Err(s)) } // wrapError returns new wrapped telebot-related error. func wrapError(err error) error { return fmt.Errorf("telebot: %w", err) } telebot-3.3.8/file.go000066400000000000000000000044311465437035100144430ustar00rootroot00000000000000package telebot import ( "io" "os" ) // File object represents any sort of file. type File struct { FileID string `json:"file_id"` UniqueID string `json:"file_unique_id"` FileSize int64 `json:"file_size"` // FilePath is used for files on Telegram server. FilePath string `json:"file_path"` // FileLocal is used for files on local file system. FileLocal string `json:"file_local"` // FileURL is used for file on the internet. FileURL string `json:"file_url"` // FileReader is used for file backed with io.Reader. FileReader io.Reader `json:"-"` fileName string } // FromDisk constructs a new local (on-disk) file object. // // Note, it returns File, not *File for a very good reason: // in telebot, File is pretty much an embeddable struct, // so upon uploading media you'll need to set embedded File // with something. NewFile() returning File makes it a one-liner. // // photo := &tele.Photo{File: tele.FromDisk("chicken.jpg")} // func FromDisk(filename string) File { return File{FileLocal: filename} } // FromURL constructs a new file on provided HTTP URL. // // Note, it returns File, not *File for a very good reason: // in telebot, File is pretty much an embeddable struct, // so upon uploading media you'll need to set embedded File // with something. NewFile() returning File makes it a one-liner. // // photo := &tele.Photo{File: tele.FromURL("https://site.com/picture.jpg")} // func FromURL(url string) File { return File{FileURL: url} } // FromReader constructs a new file from io.Reader. // // Note, it returns File, not *File for a very good reason: // in telebot, File is pretty much an embeddable struct, // so upon uploading media you'll need to set embedded File // with something. NewFile() returning File makes it a one-liner. // // photo := &tele.Photo{File: tele.FromReader(bytes.NewReader(...))} // func FromReader(reader io.Reader) File { return File{FileReader: reader} } func (f *File) stealRef(g *File) { if g.OnDisk() { f.FileLocal = g.FileLocal } if g.FileURL != "" { f.FileURL = g.FileURL } } // InCloud tells whether the file is present on Telegram servers. func (f *File) InCloud() bool { return f.FileID != "" } // OnDisk will return true if file is present on disk. func (f *File) OnDisk() bool { _, err := os.Stat(f.FileLocal) return err == nil } telebot-3.3.8/file_test.go000066400000000000000000000010331465437035100154750ustar00rootroot00000000000000package telebot import ( "io" "testing" "github.com/stretchr/testify/assert" ) func TestFile(t *testing.T) { f := FromDisk("telebot.go") g := FromURL("http://") assert.True(t, f.OnDisk()) assert.True(t, (&File{FileID: "1"}).InCloud()) assert.Equal(t, File{FileLocal: "telebot.go"}, f) assert.Equal(t, File{FileURL: "http://"}, g) assert.Equal(t, File{FileReader: io.Reader(nil)}, FromReader(io.Reader(nil))) g.stealRef(&f) f.stealRef(&g) assert.Equal(t, g.FileLocal, f.FileLocal) assert.Equal(t, f.FileURL, g.FileURL) } telebot-3.3.8/game.go000066400000000000000000000047571465437035100144500ustar00rootroot00000000000000package telebot import ( "encoding/json" "strconv" ) // Game object represents a game. // Their short names acts as unique identifiers. type Game struct { Name string `json:"game_short_name"` Title string `json:"title"` Description string `json:"description"` Photo *Photo `json:"photo"` // (Optional) Text string `json:"text"` Entities []MessageEntity `json:"text_entities"` Animation *Animation `json:"animation"` } // GameHighScore object represents one row // of the high scores table for a game. type GameHighScore struct { User *User `json:"user"` Position int `json:"position"` Score int `json:"score"` Force bool `json:"force"` NoEdit bool `json:"disable_edit_message"` } // GameScores returns the score of the specified user // and several of their neighbors in a game. // // This function will panic upon nil Editable. // // Currently, it returns scores for the target user, // plus two of their closest neighbors on each side. // Will also return the top three users // if the user and his neighbors are not among them. // func (b *Bot) GameScores(user Recipient, msg Editable) ([]GameHighScore, error) { msgID, chatID := msg.MessageSig() params := map[string]string{ "user_id": user.Recipient(), } if chatID == 0 { // if inline message params["inline_message_id"] = msgID } else { params["chat_id"] = strconv.FormatInt(chatID, 10) params["message_id"] = msgID } data, err := b.Raw("getGameHighScores", params) if err != nil { return nil, err } var resp struct { Result []GameHighScore } if err := json.Unmarshal(data, &resp); err != nil { return nil, err } return resp.Result, nil } // SetGameScore sets the score of the specified user in a game. // // If the message was sent by the bot, returns the edited Message, // otherwise returns nil and ErrTrueResult. // func (b *Bot) SetGameScore(user Recipient, msg Editable, score GameHighScore) (*Message, error) { msgID, chatID := msg.MessageSig() params := map[string]string{ "user_id": user.Recipient(), "score": strconv.Itoa(score.Score), "force": strconv.FormatBool(score.Force), "disable_edit_message": strconv.FormatBool(score.NoEdit), } if chatID == 0 { // if inline message params["inline_message_id"] = msgID } else { params["chat_id"] = strconv.FormatInt(chatID, 10) params["message_id"] = msgID } data, err := b.Raw("setGameScore", params) if err != nil { return nil, err } return extractMessage(data) } telebot-3.3.8/giveaway.go000066400000000000000000000075221465437035100153440ustar00rootroot00000000000000package telebot import "time" // Giveaway represents a message about a scheduled giveaway. type Giveaway struct { // The list of chats which the user must join to participate in the giveaway. Chats []Chat `json:"chats"` // Point in time (Unix timestamp) when winners of the giveaway will be selected. SelectionUnixtime int64 `json:"winners_selection_date"` // The number of users which are supposed to be selected as winners of the giveaway. WinnerCount int `json:"winner_count"` // (Optional) True, if only users who join the chats after the giveaway // started should be eligible to win. OnlyNewMembers bool `json:"only_new_members"` // (Optional) True, if the list of giveaway winners will be visible to everyone. HasPublicWinners bool `json:"has_public_winners"` // (Optional) Description of additional giveaway prize. PrizeDescription string `json:"prize_description"` // (Optional) A list of two-letter ISO 3166-1 alpha-2 country codes indicating // the countries from which eligible users for the giveaway must come. // If empty, then all users can participate in the giveaway. Users with a phone number // that was bought on Fragment can always participate in giveaways. CountryCodes []string `json:"country_codes"` // (Optional) The number of months the Telegram Premium subscription won from // the giveaway will be active for. PremiumMonthCount int `json:"premium_subscription_month_count"` } // SelectionDate returns the moment of when winners of the giveaway were selected in local time. func (g *Giveaway) SelectionDate() time.Time { return time.Unix(g.SelectionUnixtime, 0) } // GiveawayWinners object represents a message about the completion of a // giveaway with public winners. type GiveawayWinners struct { // The chat that created the giveaway. Chat *Chat `json:"chat"` // Identifier of the message with the giveaway in the chat. MessageID int `json:"message_id"` // Point in time (Unix timestamp) when winners of the giveaway were selected. SelectionUnixtime int64 `json:"winners_selection_date"` // The number of users which are supposed to be selected as winners of the giveaway. WinnerCount int `json:"winner_count"` // List of up to 100 winners of the giveaway. Winners []User `json:"winners"` // (Optional) The number of other chats the user had to join in order // to be eligible for the giveaway. AdditionalChats int `json:"additional_chat_count"` // (Optional) The number of months the Telegram Premium subscription won from // the giveaway will be active for. PremiumMonthCount int `json:"premium_subscription_month_count"` // (Optional) Number of undistributed prizes. UnclaimedPrizes int `json:"unclaimed_prize_count"` // (Optional) True, if only users who had joined the chats after the giveaway started // were eligible to win. OnlyNewMembers bool `json:"only_new_members"` // (Optional) True, if the giveaway was canceled because the payment for it was refunded. Refunded bool `json:"was_refunded"` // (Optional) Description of additional giveaway prize. PrizeDescription string `json:"prize_description"` } // SelectionDate returns the moment of when winners of the giveaway // were selected in local time. func (g *GiveawayWinners) SelectionDate() time.Time { return time.Unix(g.SelectionUnixtime, 0) } // GiveawayCreated represents a service message about the creation of a scheduled giveaway. // Currently holds no information. type GiveawayCreated struct{} // GiveawayCompleted represents a service message about the completion of a // giveaway without public winners. type GiveawayCompleted struct { // Number of winners in the giveaway. WinnerCount int `json:"winner_count"` // (Optional) Number of undistributed prizes. UnclaimedPrizes int `json:"unclaimed_prize_count"` // (Optional) Message with the giveaway that was completed, if it wasn't deleted. Message *Message `json:"giveaway_message"` } telebot-3.3.8/go.mod000066400000000000000000000002261465437035100143010ustar00rootroot00000000000000module gopkg.in/telebot.v3 go 1.16 require ( github.com/goccy/go-yaml v1.9.5 github.com/spf13/viper v1.13.0 github.com/stretchr/testify v1.8.0 ) telebot-3.3.8/go.sum000066400000000000000000002537401465437035100143410ustar00rootroot00000000000000cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY= cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-yaml v1.9.5 h1:Eh/+3uk9kLxG4koCX6lRMAPS1OaMSAi+FJcya0INdB0= github.com/goccy/go-yaml v1.9.5/go.mod h1:U/jl18uSupI5rdI2jmuCswEA2htH9eXfferR3KfscvA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/hashicorp/consul/api v1.12.0/go.mod h1:6pVBMo0ebnYdt2S3H87XhekM/HHrUoTD2XXb/VrZVy0= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v1.2.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc= github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/hashicorp/serf v0.9.7/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sagikazarmark/crypt v0.6.0/go.mod h1:U8+INwJo3nBv1m6A/8OBXAq7Jnpspk5AxSgDyEQcea8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v2 v2.305.4/go.mod h1:Ud+VUwIi9/uQHOMA+4ekToJ12lTxlv0zB/+DHwTGEbU= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4 h1:kUhD7nTDoI3fVd9G4ORWrbV5NY0liEs/Jg2pv5f+bBA= golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8= golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220520000938-2e3eb7b945c2/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211005180243-6b3c2da341f1/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220513210516-0976fa681c29/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df h1:5Pf6pFKu98ODmgnpvkJ3kFUOQGGLIzLIkbzUHp47618= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= google.golang.org/api v0.59.0/go.mod h1:sT2boj7M9YJxZzgeZqXogmhfmRWDtPzT31xkieUbuZU= google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorOCko= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211008145708-270636b82663/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211028162531-8db9c33dc351/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/genproto v0.0.0-20220519153652-3a47de7e79bd/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= telebot-3.3.8/inline.go000066400000000000000000000130561465437035100150050ustar00rootroot00000000000000package telebot import ( "encoding/json" "fmt" ) // Query is an incoming inline query. When the user sends // an empty query, your bot could return some default or // trending results. type Query struct { // Unique identifier for this query. 1-64 bytes. ID string `json:"id"` // Sender. Sender *User `json:"from"` // Sender location, only for bots that request user location. Location *Location `json:"location"` // Text of the query (up to 512 characters). Text string `json:"query"` // Offset of the results to be returned, can be controlled by the bot. Offset string `json:"offset"` // ChatType of the type of the chat, from which the inline query was sent. ChatType string `json:"chat_type"` } // QueryResponse builds a response to an inline Query. type QueryResponse struct { // The ID of the query to which this is a response. // // Note: Telebot sets this field automatically! QueryID string `json:"inline_query_id"` // The results for the inline query. Results Results `json:"results"` // (Optional) The maximum amount of time in seconds that the result // of the inline query may be cached on the server. CacheTime int `json:"cache_time,omitempty"` // (Optional) Pass True, if results may be cached on the server side // only for the user that sent the query. By default, results may // be returned to any user who sends the same query. IsPersonal bool `json:"is_personal"` // (Optional) Pass the offset that a client should send in the next // query with the same text to receive more results. Pass an empty // string if there are no more results or if you don‘t support // pagination. Offset length can’t exceed 64 bytes. NextOffset string `json:"next_offset"` // (Optional) If passed, clients will display a button with specified // text that switches the user to a private chat with the bot and sends // the bot a start message with the parameter switch_pm_parameter. SwitchPMText string `json:"switch_pm_text,omitempty"` // (Optional) Parameter for the start message sent to the bot when user // presses the switch button. SwitchPMParameter string `json:"switch_pm_parameter,omitempty"` // (Optional) A JSON-serialized object describing a button to be shown // above inline query results. Button *QueryResponseButton `json:"button,omitempty"` } // QueryResponseButton represents a button to be shown above inline query results. // You must use exactly one of the optional fields. type QueryResponseButton struct { // Label text on the button Text string `json:"text"` // (Optional) Description of the Web App that will be launched when the // user presses the button. The Web App will be able to switch back to the // inline mode using the method switchInlineQuery inside the Web App. WebApp *WebApp `json:"web_app"` // (Optional) Deep-linking parameter for the /start message sent to the bot // when a user presses the button. 1-64 characters, only A-Z, a-z, 0-9, _ and - are allowed. Start string `json:"start_parameter"` } // SwitchInlineQuery represents an inline button that switches the current // user to inline mode in a chosen chat, with an optional default inline query. type SwitchInlineQuery struct { // (Optional) The default inline query to be inserted in the input field. // If left empty, only the bot's username will be inserted. Query string `json:"query"` // (Optional) True, if private chats with users can be chosen. AllowUserChats bool `json:"allow_user_chats"` // (Optional) True, if private chats with bots can be chosen. AllowBotChats bool `json:"allow_bot_chats"` // (Optional) True, if group and supergroup chats can be chosen. AllowGroupChats bool `json:"allow_group_chats"` // (Optional) True, if channel chats can be chosen. AllowChannelChats bool `json:"allow_channel_chats"` } // InlineResult represents a result of an inline query that was chosen // by the user and sent to their chat partner. type InlineResult struct { Sender *User `json:"from"` Location *Location `json:"location,omitempty"` ResultID string `json:"result_id"` Query string `json:"query"` MessageID string `json:"inline_message_id"` // inline messages only! } // MessageSig satisfies Editable interface. func (ir *InlineResult) MessageSig() (string, int64) { return ir.MessageID, 0 } // Result represents one result of an inline query. type Result interface { ResultID() string SetResultID(string) SetParseMode(ParseMode) SetContent(InputMessageContent) SetReplyMarkup(*ReplyMarkup) Process(*Bot) } // Results is a slice wrapper for convenient marshalling. type Results []Result // MarshalJSON makes sure IQRs have proper IDs and Type variables set. func (results Results) MarshalJSON() ([]byte, error) { for i, result := range results { if result.ResultID() == "" { result.SetResultID(fmt.Sprintf("%d", &results[i])) } if err := inferIQR(result); err != nil { return nil, err } } return json.Marshal([]Result(results)) } func inferIQR(result Result) error { switch r := result.(type) { case *ArticleResult: r.Type = "article" case *AudioResult: r.Type = "audio" case *ContactResult: r.Type = "contact" case *DocumentResult: r.Type = "document" case *GifResult: r.Type = "gif" case *LocationResult: r.Type = "location" case *Mpeg4GifResult: r.Type = "mpeg4_gif" case *PhotoResult: r.Type = "photo" case *VenueResult: r.Type = "venue" case *VideoResult: r.Type = "video" case *VoiceResult: r.Type = "voice" case *StickerResult: r.Type = "sticker" case *GameResult: r.Type = "game" default: return fmt.Errorf("telebot: result %v is not supported", result) } return nil } telebot-3.3.8/inline_types.go000066400000000000000000000244361465437035100162350ustar00rootroot00000000000000package telebot // ResultBase must be embedded into all IQRs. type ResultBase struct { // Unique identifier for this result, 1-64 Bytes. // If left unspecified, a 64-bit FNV-1 hash will be calculated ID string `json:"id"` // Ignore. This field gets set automatically. Type string `json:"type"` // Optional. Send Markdown or HTML, if you want Telegram apps to show // bold, italic, fixed-width text or inline URLs in the media caption. ParseMode ParseMode `json:"parse_mode,omitempty"` // Optional. Content of the message to be sent. Content InputMessageContent `json:"input_message_content,omitempty"` // Optional. Inline keyboard attached to the message. ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` } // ResultID returns ResultBase.ID. func (r *ResultBase) ResultID() string { return r.ID } // SetResultID sets ResultBase.ID. func (r *ResultBase) SetResultID(id string) { r.ID = id } // SetParseMode sets ResultBase.ParseMode. func (r *ResultBase) SetParseMode(mode ParseMode) { r.ParseMode = mode } // SetContent sets ResultBase.Content. func (r *ResultBase) SetContent(content InputMessageContent) { r.Content = content } // SetReplyMarkup sets ResultBase.ReplyMarkup. func (r *ResultBase) SetReplyMarkup(markup *ReplyMarkup) { r.ReplyMarkup = markup } func (r *ResultBase) Process(b *Bot) { if r.ParseMode == ModeDefault { r.ParseMode = b.parseMode } if r.Content != nil { c, ok := r.Content.(*InputTextMessageContent) if ok && c.ParseMode == ModeDefault { c.ParseMode = r.ParseMode } } if r.ReplyMarkup != nil { processButtons(r.ReplyMarkup.InlineKeyboard) } } // GameResult represents a game. Game is a content type // supported by Telegram, which can be sent back to the // user as a result for an inline query. type GameResult struct { ResultBase // ShortName is a unique identifier of the game. ShortName string `json:"game_short_name"` } // ArticleResult represents a link to an article or web page. type ArticleResult struct { ResultBase // Title of the result. Title string `json:"title"` // Message text. Shortcut (and mutually exclusive to) specifying // InputMessageContent. Text string `json:"message_text,omitempty"` // Optional. URL of the result. URL string `json:"url,omitempty"` // Optional. Pass True, if you don't want the URL to be shown in the message. HideURL bool `json:"hide_url,omitempty"` // Optional. Short description of the result. Description string `json:"description,omitempty"` // Optional. URL of the thumbnail for the result. ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. ThumbHeight int `json:"thumbnail_height,omitempty"` } // AudioResult represents a link to an mp3 audio file. type AudioResult struct { ResultBase // Title. Title string `json:"title"` // A valid URL for the audio file. URL string `json:"audio_url"` // Optional. Performer. Performer string `json:"performer,omitempty"` // Optional. Audio duration in seconds. Duration int `json:"audio_duration,omitempty"` // Optional. Caption, 0-1024 characters. Caption string `json:"caption,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"audio_file_id,omitempty"` } // ContactResult represents a contact with a phone number. type ContactResult struct { ResultBase // Contact's phone number. PhoneNumber string `json:"phone_number"` // Optional. Additional data about the contact in the form of a vCard, 0-2048 bytes. VCard string `json:"vcard,omitempty"` // Contact's first name. FirstName string `json:"first_name"` // Optional. Contact's last name. LastName string `json:"last_name,omitempty"` // Optional. URL of the thumbnail for the result. ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. ThumbHeight int `json:"thumbnail_height,omitempty"` } // DocumentResult represents a link to a file. type DocumentResult struct { ResultBase // Title for the result. Title string `json:"title"` // A valid URL for the file URL string `json:"document_url"` // Mime type of the content of the file, either “application/pdf” or // “application/zip”. MIME string `json:"mime_type"` // Optional. Caption of the document to be sent, 0-200 characters. Caption string `json:"caption,omitempty"` // Optional. Short description of the result. Description string `json:"description,omitempty"` // Optional. URL of the thumbnail (jpeg only) for the file. ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. ThumbHeight int `json:"thumbnail_height,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"document_file_id,omitempty"` } // GifResult represents a link to an animated GIF file. type GifResult struct { ResultBase // A valid URL for the GIF file. File size must not exceed 1MB. URL string `json:"gif_url"` // Optional. Width of the GIF. Width int `json:"gif_width,omitempty"` // Optional. Height of the GIF. Height int `json:"gif_height,omitempty"` // Optional. Duration of the GIF. Duration int `json:"gif_duration,omitempty"` // URL of the static thumbnail for the result (jpeg or gif). ThumbURL string `json:"thumbnail_url"` // Optional. MIME type of the thumbnail, must be one of // “image/jpeg”, “image/gif”, or “video/mp4”. ThumbMIME string `json:"thumbnail_mime_type,omitempty"` // Optional. Title for the result. Title string `json:"title,omitempty"` // Optional. Caption of the GIF file to be sent, 0-200 characters. Caption string `json:"caption,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"gif_file_id,omitempty"` } // LocationResult represents a location on a map. type LocationResult struct { ResultBase Location // Location title. Title string `json:"title"` // Optional. Url of the thumbnail for the result. ThumbURL string `json:"thumbnail_url,omitempty"` } // Mpeg4GifResult represents a link to a video animation // (H.264/MPEG-4 AVC video without sound). type Mpeg4GifResult struct { ResultBase // A valid URL for the MP4 file. URL string `json:"mpeg4_url"` // Optional. Video width. Width int `json:"mpeg4_width,omitempty"` // Optional. Video height. Height int `json:"mpeg4_height,omitempty"` // Optional. Video duration. Duration int `json:"mpeg4_duration,omitempty"` // URL of the static thumbnail (jpeg or gif) for the result. ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. MIME type of the thumbnail, must be one of // “image/jpeg”, “image/gif”, or “video/mp4”. ThumbMIME string `json:"thumbnail_mime_type,omitempty"` // Optional. Title for the result. Title string `json:"title,omitempty"` // Optional. Caption of the MPEG-4 file to be sent, 0-200 characters. Caption string `json:"caption,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"mpeg4_file_id,omitempty"` } // PhotoResult represents a link to a photo. type PhotoResult struct { ResultBase // A valid URL of the photo. Photo must be in jpeg format. // Photo size must not exceed 5MB. URL string `json:"photo_url"` // Optional. Width of the photo. Width int `json:"photo_width,omitempty"` // Optional. Height of the photo. Height int `json:"photo_height,omitempty"` // Optional. Title for the result. Title string `json:"title,omitempty"` // Optional. Short description of the result. Description string `json:"description,omitempty"` // Optional. Caption of the photo to be sent, 0-200 characters. Caption string `json:"caption,omitempty"` // URL of the thumbnail for the photo. ThumbURL string `json:"thumbnail_url"` // If Cache != "", it'll be used instead Cache string `json:"photo_file_id,omitempty"` } // VenueResult represents a venue. type VenueResult struct { ResultBase Location // Title of the venue. Title string `json:"title"` // Address of the venue. Address string `json:"address"` // Optional. Foursquare identifier of the venue if known. FoursquareID string `json:"foursquare_id,omitempty"` // Optional. URL of the thumbnail for the result. ThumbURL string `json:"thumbnail_url,omitempty"` // Optional. Width of the thumbnail for the result. ThumbWidth int `json:"thumbnail_width,omitempty"` // Optional. Height of the thumbnail for the result. ThumbHeight int `json:"thumbnail_height,omitempty"` } // VideoResult represents a link to a page containing an embedded // video player or a video file. type VideoResult struct { ResultBase // A valid URL for the embedded video player or video file. URL string `json:"video_url"` // Mime type of the content of video url, “text/html” or “video/mp4”. MIME string `json:"mime_type"` // URL of the thumbnail (jpeg only) for the video. ThumbURL string `json:"thumbnail_url"` // Title for the result. Title string `json:"title"` // Optional. Caption of the video to be sent, 0-200 characters. Caption string `json:"caption,omitempty"` // Optional. Video width. Width int `json:"video_width,omitempty"` // Optional. Video height. Height int `json:"video_height,omitempty"` // Optional. Video duration in seconds. Duration int `json:"video_duration,omitempty"` // Optional. Short description of the result. Description string `json:"description,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"video_file_id,omitempty"` } // VoiceResult represents a link to a voice recording in an .ogg // container encoded with OPUS. type VoiceResult struct { ResultBase // A valid URL for the voice recording. URL string `json:"voice_url"` // Recording title. Title string `json:"title"` // Optional. Recording duration in seconds. Duration int `json:"voice_duration"` // Optional. Caption, 0-1024 characters. Caption string `json:"caption,omitempty"` // If Cache != "", it'll be used instead Cache string `json:"voice_file_id,omitempty"` } // StickerResult represents an inline cached sticker response. type StickerResult struct { ResultBase // If Cache != "", it'll be used instead Cache string `json:"sticker_file_id,omitempty"` } telebot-3.3.8/input_types.go000066400000000000000000000042161465437035100161100ustar00rootroot00000000000000package telebot // InputMessageContent objects represent the content of a message to be sent // as a result of an inline query. type InputMessageContent interface { IsInputMessageContent() bool } // InputTextMessageContent represents the content of a text message to be // sent as the result of an inline query. type InputTextMessageContent struct { // Text of the message to be sent, 1-4096 characters. Text string `json:"message_text"` // (Optional) Send Markdown or HTML, if you want Telegram apps to show // bold, italic, fixed-width text or inline URLs in your bot's message. ParseMode string `json:"parse_mode,omitempty"` // (Optional) Link preview generation options for the message. PreviewOptions *PreviewOptions `json:"link_preview_options,omitempty"` } func (input *InputTextMessageContent) IsInputMessageContent() bool { return true } // InputLocationMessageContent represents the content of a location message // to be sent as the result of an inline query. type InputLocationMessageContent struct { Lat float32 `json:"latitude"` Lng float32 `json:"longitude"` } func (input *InputLocationMessageContent) IsInputMessageContent() bool { return true } // InputVenueMessageContent represents the content of a venue message to // be sent as the result of an inline query. type InputVenueMessageContent struct { Lat float32 `json:"latitude"` Lng float32 `json:"longitude"` // Name of the venue. Title string `json:"title"` // Address of the venue. Address string `json:"address"` // Optional. Foursquare identifier of the venue, if known. FoursquareID string `json:"foursquare_id,omitempty"` } func (input *InputVenueMessageContent) IsInputMessageContent() bool { return true } // InputContactMessageContent represents the content of a contact // message to be sent as the result of an inline query. type InputContactMessageContent struct { // Contact's phone number. PhoneNumber string `json:"phone_number"` // Contact's first name. FirstName string `json:"first_name"` // Optional. Contact's last name. LastName string `json:"last_name,omitempty"` } func (input *InputContactMessageContent) IsInputMessageContent() bool { return true } telebot-3.3.8/layout/000077500000000000000000000000001465437035100145105ustar00rootroot00000000000000telebot-3.3.8/layout/config.go000066400000000000000000000055231465437035100163110ustar00rootroot00000000000000package layout import ( "strconv" "time" "github.com/spf13/viper" tele "gopkg.in/telebot.v3" ) // Config represents typed map interface related to the "config" section in layout. type Config struct { v *viper.Viper } // Unmarshal parses the whole config into the out value. It's useful when you want to // describe and to pre-define the fields in your custom configuration struct. func (c *Config) Unmarshal(v interface{}) error { return c.v.Unmarshal(v) } // UnmarshalKey parses the specific key in the config into the out value. func (c *Config) UnmarshalKey(k string, v interface{}) error { return c.v.UnmarshalKey(k, v) } // Get returns a child map field wrapped into Config. // If the field isn't a map, returns nil. func (c *Config) Get(k string) *Config { v := c.v.Sub(k) if v == nil { return nil } return &Config{v: v} } // Slice returns a child slice of objects wrapped into Config. // If the field isn't a slice, returns nil. func (c *Config) Slice(k string) (slice []*Config) { a, ok := c.v.Get(k).([]interface{}) if !ok { return nil } for i := range a { m, ok := a[i].(map[string]interface{}) if !ok { return nil } v := viper.New() v.MergeConfigMap(m) slice = append(slice, &Config{v: v}) } return } // String returns a field casted to the string. func (c *Config) String(k string) string { return c.v.GetString(k) } // Int returns a field casted to the int. func (c *Config) Int(k string) int { return c.v.GetInt(k) } // Int64 returns a field casted to the int64. func (c *Config) Int64(k string) int64 { return c.v.GetInt64(k) } // Float returns a field casted to the float64. func (c *Config) Float(k string) float64 { return c.v.GetFloat64(k) } // Bool returns a field casted to the bool. func (c *Config) Bool(k string) bool { return c.v.GetBool(k) } // Duration returns a field casted to the time.Duration. // Accepts number-represented duration or a string in 0nsuµmh format. func (c *Config) Duration(k string) time.Duration { return c.v.GetDuration(k) } // ChatID returns a field casted to the ChatID. // The value must be an integer. func (c *Config) ChatID(k string) tele.ChatID { return tele.ChatID(c.Int64(k)) } // Strings returns a field casted to the string slice. func (c *Config) Strings(k string) []string { return c.v.GetStringSlice(k) } // Ints returns a field casted to the int slice. func (c *Config) Ints(k string) []int { return c.v.GetIntSlice(k) } // Int64s returns a field casted to the int64 slice. func (c *Config) Int64s(k string) (ints []int64) { for _, s := range c.Strings(k) { i, _ := strconv.ParseInt(s, 10, 64) ints = append(ints, i) } return ints } // Floats returns a field casted to the float slice. func (c *Config) Floats(k string) (floats []float64) { for _, s := range c.Strings(k) { i, _ := strconv.ParseFloat(s, 64) floats = append(floats, i) } return floats } telebot-3.3.8/layout/default.go000066400000000000000000000024501465437035100164640ustar00rootroot00000000000000package layout import ( tele "gopkg.in/telebot.v3" ) // DefaultLayout is a simplified layout instance with pre-defined locale by default. type DefaultLayout struct { locale string lt *Layout Config } // Settings returns layout settings. func (dlt *DefaultLayout) Settings() tele.Settings { return dlt.lt.Settings() } // Text wraps localized layout function Text using your default locale. func (dlt *DefaultLayout) Text(k string, args ...interface{}) string { return dlt.lt.TextLocale(dlt.locale, k, args...) } // Callback returns a callback endpoint used to handle buttons. func (dlt *DefaultLayout) Callback(k string) tele.CallbackEndpoint { return dlt.lt.Callback(k) } // Button wraps localized layout function Button using your default locale. func (dlt *DefaultLayout) Button(k string, args ...interface{}) *tele.Btn { return dlt.lt.ButtonLocale(dlt.locale, k, args...) } // Markup wraps localized layout function Markup using your default locale. func (dlt *DefaultLayout) Markup(k string, args ...interface{}) *tele.ReplyMarkup { return dlt.lt.MarkupLocale(dlt.locale, k, args...) } // Result wraps localized layout function Result using your default locale. func (dlt *DefaultLayout) Result(k string, args ...interface{}) tele.Result { return dlt.lt.ResultLocale(dlt.locale, k, args...) } telebot-3.3.8/layout/example.yml000066400000000000000000000021771465437035100166750ustar00rootroot00000000000000settings: token_env: TOKEN parse_mode: HTML long_poller: {} commands: /start: Start the bot /help: How to use the bot config: str: string num: 123 strs: - abc - def nums: - 123 - 456 obj: &obj dur: 10m arr: - <<: *obj - <<: *obj buttons: # Shortened reply buttons help: Help settings: Settings # Extended reply button contact: text: Send a contact request_contact: true # Inline button stop: unique: stop text: Stop data: '{{.}}' # Callback data pay: unique: pay text: Pay data: - '{{ .UserID }}' - '{{ .Amount }}' - '{{ .Currency }}' web_app: text: This is a web app web_app: url: https://google.com markups: reply_shortened: - [ help ] - [ settings ] reply_extended: keyboard: - [ contact ] one_time_keyboard: true inline: - [ stop ] web_app: - [ web_app ] results: article: type: article id: '{{ .ID }}' title: '{{ .Title }}' description: '{{ .Description }}' thumbnail_url: '{{ .PreviewURL }}' message_text: '{{ text `article_message` }}' telebot-3.3.8/layout/layout.go000066400000000000000000000342441465437035100163630ustar00rootroot00000000000000package layout import ( "bytes" "encoding/json" "io/fs" "log" "os" "strings" "sync" "text/template" "github.com/goccy/go-yaml" tele "gopkg.in/telebot.v3" ) type ( // Layout provides an interface to interact with the layout, // parsed from the config file and locales. Layout struct { pref *tele.Settings mu sync.RWMutex // protects ctxs ctxs map[tele.Context]string funcs template.FuncMap commands map[string]string buttons map[string]Button markups map[string]Markup results map[string]Result locales map[string]*template.Template Config } // Button is a shortcut for tele.Btn. Button struct { tele.Btn `yaml:",inline"` Data interface{} `yaml:"data"` IsReply bool `yaml:"reply"` } // Markup represents layout-specific markup to be parsed. Markup struct { inline *bool keyboard *template.Template ResizeKeyboard *bool `yaml:"resize_keyboard,omitempty"` // nil == true ForceReply bool `yaml:"force_reply,omitempty"` OneTimeKeyboard bool `yaml:"one_time_keyboard,omitempty"` RemoveKeyboard bool `yaml:"remove_keyboard,omitempty"` Selective bool `yaml:"selective,omitempty"` } // Result represents layout-specific result to be parsed. Result struct { result *template.Template tele.ResultBase `yaml:",inline"` Content ResultContent `yaml:"content"` Markup string `yaml:"markup"` } // ResultBase represents layout-specific result's base to be parsed. ResultBase struct { tele.ResultBase `yaml:",inline"` Content ResultContent `yaml:"content"` } // ResultContent represents any kind of InputMessageContent and implements it. ResultContent map[string]interface{} ) // New parses the given layout file. func New(path string, funcs ...template.FuncMap) (*Layout, error) { return NewFromFS(os.DirFS("."), path, funcs...) } // NewFromFS parses the layout from the given fs.FS. It allows to read layout // from the go:embed filesystem. func NewFromFS(fsys fs.FS, path string, funcs ...template.FuncMap) (*Layout, error) { data, err := fs.ReadFile(fsys, path) if err != nil { return nil, err } lt := Layout{ ctxs: make(map[tele.Context]string), funcs: make(template.FuncMap), } for k, v := range builtinFuncs { lt.funcs[k] = v } for i := range funcs { for k, v := range funcs[i] { lt.funcs[k] = v } } return <, yaml.Unmarshal(data, <) } // NewDefault parses the given layout file without localization features. // See Layout.Default for more details. func NewDefault(path, locale string, funcs ...template.FuncMap) (*DefaultLayout, error) { lt, err := New(path, funcs...) if err != nil { return nil, err } return lt.Default(locale), nil } var builtinFuncs = template.FuncMap{ // Built-in blank and helper functions. "locale": func() string { return "" }, "config": func(string) string { return "" }, "text": func(string, ...interface{}) string { return "" }, } // Settings returns built telebot Settings required for bot initializing. // // settings: // url: (custom url if needed) // token: (not recommended) // updates: (chan capacity) // locales_dir: (optional) // token_env: (token env var name, example: TOKEN) // parse_mode: (default parse mode) // long_poller: (long poller settings) // webhook: (or webhook settings) // // Usage: // // lt, err := layout.New("bot.yml") // b, err := tele.NewBot(lt.Settings()) // // That's all! func (lt *Layout) Settings() tele.Settings { if lt.pref == nil { panic("telebot/layout: settings is empty") } return *lt.pref } // Default returns a simplified layout instance with the pre-defined locale. // It's useful when you have no need for localization and don't want to pass // context each time you use layout functions. func (lt *Layout) Default(locale string) *DefaultLayout { return &DefaultLayout{ locale: locale, lt: lt, Config: lt.Config, } } // Locales returns all presented locales. func (lt *Layout) Locales() []string { var keys []string for k := range lt.locales { keys = append(keys, k) } return keys } // Locale returns the context locale. func (lt *Layout) Locale(c tele.Context) (string, bool) { lt.mu.RLock() defer lt.mu.RUnlock() locale, ok := lt.ctxs[c] return locale, ok } // SetLocale allows you to change a locale for the passed context. func (lt *Layout) SetLocale(c tele.Context, locale string) { lt.mu.Lock() lt.ctxs[c] = locale lt.mu.Unlock() } // Commands returns a list of telebot commands, which can be // used in b.SetCommands later. func (lt *Layout) Commands() (cmds []tele.Command) { for k, v := range lt.commands { cmds = append(cmds, tele.Command{ Text: strings.TrimLeft(k, "/"), Description: v, }) } return } // CommandsLocale returns a list of telebot commands and localized description, which can be // used in b.SetCommands later. // // Example of bot.yml: // // commands: // /start: '{{ text `cmdStart` }}' // // en.yml: // // cmdStart: Start the bot // // ru.yml: // // cmdStart: Запуск бота // // Usage: // // b.SetCommands(lt.CommandsLocale("en"), "en") // b.SetCommands(lt.CommandsLocale("ru"), "ru") func (lt *Layout) CommandsLocale(locale string, args ...interface{}) (cmds []tele.Command) { var arg interface{} if len(args) > 0 { arg = args[0] } for k, v := range lt.commands { tmpl, err := lt.template(template.New(k).Funcs(lt.funcs), locale).Parse(v) if err != nil { log.Println("telebot/layout:", err) return nil } var buf bytes.Buffer if err := tmpl.Execute(&buf, arg); err != nil { log.Println("telebot/layout:", err) return nil } cmds = append(cmds, tele.Command{ Text: strings.TrimLeft(k, "/"), Description: buf.String(), }) } return } // Text returns a text, which locale is dependent on the context. // The given optional argument will be passed to the template engine. // // Example of en.yml: // // start: Hi, {{.FirstName}}! // // Usage: // // func onStart(c tele.Context) error { // return c.Send(lt.Text(c, "start", c.Sender())) // } func (lt *Layout) Text(c tele.Context, k string, args ...interface{}) string { locale, ok := lt.Locale(c) if !ok { return "" } return lt.TextLocale(locale, k, args...) } // TextLocale returns a localized text processed with text/template engine. // See Text for more details. func (lt *Layout) TextLocale(locale, k string, args ...interface{}) string { tmpl, ok := lt.locales[locale] if !ok { return "" } var arg interface{} if len(args) > 0 { arg = args[0] } var buf bytes.Buffer if err := lt.template(tmpl, locale).ExecuteTemplate(&buf, k, arg); err != nil { log.Println("telebot/layout:", err) } return buf.String() } // Callback returns a callback endpoint used to handle buttons. // // Example: // // // Handling settings button // b.Handle(lt.Callback("settings"), onSettings) func (lt *Layout) Callback(k string) tele.CallbackEndpoint { btn, ok := lt.buttons[k] if !ok { return nil } return &btn } // Button returns a button, which locale is dependent on the context. // The given optional argument will be passed to the template engine. // // buttons: // item: // unique: item // callback_data: {{.ID}} // text: Item #{{.Number}} // // Usage: // // btns := make([]tele.Btn, len(items)) // for i, item := range items { // btns[i] = lt.Button(c, "item", struct { // Number int // Item Item // }{ // Number: i, // Item: item, // }) // } // // m := b.NewMarkup() // m.Inline(m.Row(btns...)) // // Your generated markup is ready. func (lt *Layout) Button(c tele.Context, k string, args ...interface{}) *tele.Btn { locale, ok := lt.Locale(c) if !ok { return nil } return lt.ButtonLocale(locale, k, args...) } // ButtonLocale returns a localized button processed with text/template engine. // See Button for more details. func (lt *Layout) ButtonLocale(locale, k string, args ...interface{}) *tele.Btn { btn, ok := lt.buttons[k] if !ok { return nil } var arg interface{} if len(args) > 0 { arg = args[0] } data, err := yaml.Marshal(btn) if err != nil { log.Println("telebot/layout:", err) return nil } tmpl, err := lt.template(template.New(k).Funcs(lt.funcs), locale).Parse(string(data)) if err != nil { log.Println("telebot/layout:", err) return nil } var buf bytes.Buffer if err := tmpl.Execute(&buf, arg); err != nil { log.Println("telebot/layout:", err) return nil } if err := yaml.Unmarshal(buf.Bytes(), &btn); err != nil { log.Println("telebot/layout:", err) return nil } return &btn.Btn } // Markup returns a markup, which locale is dependent on the context. // The given optional argument will be passed to the template engine. // // buttons: // settings: 'Settings' // markups: // menu: // - [settings] // // Usage: // // func onStart(c tele.Context) error { // return c.Send( // lt.Text(c, "start"), // lt.Markup(c, "menu"), // ) // } func (lt *Layout) Markup(c tele.Context, k string, args ...interface{}) *tele.ReplyMarkup { locale, ok := lt.Locale(c) if !ok { return nil } return lt.MarkupLocale(locale, k, args...) } // MarkupLocale returns a localized markup processed with text/template engine. // See Markup for more details. func (lt *Layout) MarkupLocale(locale, k string, args ...interface{}) *tele.ReplyMarkup { markup, ok := lt.markups[k] if !ok { return nil } var arg interface{} if len(args) > 0 { arg = args[0] } var buf bytes.Buffer if err := lt.template(markup.keyboard, locale).Execute(&buf, arg); err != nil { log.Println("telebot/layout:", err) } r := &tele.ReplyMarkup{} if *markup.inline { if err := yaml.Unmarshal(buf.Bytes(), &r.InlineKeyboard); err != nil { log.Println("telebot/layout:", err) } } else { r.ResizeKeyboard = markup.ResizeKeyboard == nil || *markup.ResizeKeyboard r.ForceReply = markup.ForceReply r.OneTimeKeyboard = markup.OneTimeKeyboard r.RemoveKeyboard = markup.RemoveKeyboard r.Selective = markup.Selective if err := yaml.Unmarshal(buf.Bytes(), &r.ReplyKeyboard); err != nil { log.Println("telebot/layout:", err) } } return r } // Result returns an inline result, which locale is dependent on the context. // The given optional argument will be passed to the template engine. // // results: // article: // type: article // id: '{{ .ID }}' // title: '{{ .Title }}' // description: '{{ .Description }}' // message_text: '{{ .Content }}' // thumb_url: '{{ .PreviewURL }}' // // Usage: // // func onQuery(c tele.Context) error { // results := make(tele.Results, len(articles)) // for i, article := range articles { // results[i] = lt.Result(c, "article", article) // } // return c.Answer(&tele.QueryResponse{ // Results: results, // CacheTime: 100, // }) // } func (lt *Layout) Result(c tele.Context, k string, args ...interface{}) tele.Result { locale, ok := lt.Locale(c) if !ok { return nil } return lt.ResultLocale(locale, k, args...) } // ResultLocale returns a localized result processed with text/template engine. // See Result for more details. func (lt *Layout) ResultLocale(locale, k string, args ...interface{}) tele.Result { result, ok := lt.results[k] if !ok { return nil } var arg interface{} if len(args) > 0 { arg = args[0] } var buf bytes.Buffer if err := lt.template(result.result, locale).Execute(&buf, arg); err != nil { log.Println("telebot/layout:", err) } var ( data = buf.Bytes() base Result r tele.Result ) if err := yaml.Unmarshal(data, &base); err != nil { log.Println("telebot/layout:", err) } switch base.Type { case "article": r = &tele.ArticleResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "audio": r = &tele.AudioResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "contact": r = &tele.ContactResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "document": r = &tele.DocumentResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "gif": r = &tele.GifResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "location": r = &tele.LocationResult{ResultBase: base.ResultBase} if err := json.Unmarshal(data, &r); err != nil { log.Println("telebot/layout:", err) } case "mpeg4_gif": r = &tele.Mpeg4GifResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "photo": r = &tele.PhotoResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "venue": r = &tele.VenueResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "video": r = &tele.VideoResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "voice": r = &tele.VoiceResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } case "sticker": r = &tele.StickerResult{ResultBase: base.ResultBase} if err := yaml.Unmarshal(data, r); err != nil { log.Println("telebot/layout:", err) } default: log.Println("telebot/layout: unsupported inline result type") return nil } if base.Content != nil { r.SetContent(base.Content) } if result.Markup != "" { markup := lt.MarkupLocale(locale, result.Markup, args...) if markup == nil { log.Printf("telebot/layout: markup with name %s was not found\n", result.Markup) } else { r.SetReplyMarkup(markup) } } return r } func (lt *Layout) template(tmpl *template.Template, locale string) *template.Template { funcs := make(template.FuncMap) // Redefining built-in blank functions funcs["config"] = lt.String funcs["text"] = func(k string, args ...interface{}) string { return lt.TextLocale(locale, k, args...) } funcs["locale"] = func() string { return locale } return tmpl.Funcs(funcs) } // IsInputMessageContent implements telebot.InputMessageContent. func (ResultContent) IsInputMessageContent() bool { return true } telebot-3.3.8/layout/layout_test.go000066400000000000000000000065221465437035100174200ustar00rootroot00000000000000package layout import ( "embed" "os" "testing" "time" "github.com/stretchr/testify/assert" tele "gopkg.in/telebot.v3" ) //go:embed * var fsys embed.FS func TestLayout(t *testing.T) { os.Setenv("TOKEN", "TEST") lt, err := New("example.yml") if err != nil { t.Fatal(err) } ltfs, err := NewFromFS(fsys, "example.yml") if err != nil { t.Fatal(err) } pref := lt.Settings() assert.Equal(t, "TEST", pref.Token) assert.Equal(t, tele.ModeHTML, pref.ParseMode) assert.Equal(t, &tele.LongPoller{}, pref.Poller) assert.Equal(t, pref, ltfs.Settings()) assert.ElementsMatch(t, []tele.Command{{ Text: "start", Description: "Start the bot", }, { Text: "help", Description: "How to use the bot", }}, lt.Commands()) assert.Equal(t, "string", lt.String("str")) assert.Equal(t, 123, lt.Int("num")) assert.Equal(t, int64(123), lt.Int64("num")) assert.Equal(t, float64(123), lt.Float("num")) assert.Equal(t, tele.ChatID(123), lt.ChatID("num")) assert.Equal(t, []string{"abc", "def"}, lt.Strings("strs")) assert.Equal(t, []int{123, 456}, lt.Ints("nums")) assert.Equal(t, []int64{123, 456}, lt.Int64s("nums")) assert.Equal(t, []float64{123, 456}, lt.Floats("nums")) obj := lt.Get("obj") assert.NotNil(t, obj) const dur = 10 * time.Minute assert.Equal(t, dur, obj.Duration("dur")) assert.True(t, lt.Duration("obj.dur") == obj.Duration("dur")) arr := lt.Slice("arr") assert.Len(t, arr, 2) for _, v := range arr { assert.Equal(t, dur, v.Duration("dur")) } assert.Equal(t, &tele.Btn{ Unique: "pay", Text: "Pay", Data: "1|100.00|USD", }, lt.ButtonLocale("en", "pay", struct { UserID int Amount string Currency string }{ UserID: 1, Amount: "100.00", Currency: "USD", })) assert.Equal(t, &tele.ReplyMarkup{ ReplyKeyboard: [][]tele.ReplyButton{ {{Text: "Help"}}, {{Text: "Settings"}}, }, ResizeKeyboard: true, }, lt.MarkupLocale("en", "reply_shortened")) assert.Equal(t, &tele.ReplyMarkup{ ReplyKeyboard: [][]tele.ReplyButton{{{Text: "Send a contact", Contact: true}}}, ResizeKeyboard: true, OneTimeKeyboard: true, }, lt.MarkupLocale("en", "reply_extended")) assert.Equal(t, &tele.ReplyMarkup{ InlineKeyboard: [][]tele.InlineButton{{ { Unique: "stop", Text: "Stop", Data: "1", }, }}, }, lt.MarkupLocale("en", "inline", 1)) assert.Equal(t, &tele.ReplyMarkup{ InlineKeyboard: [][]tele.InlineButton{{ { Text: "This is a web app", WebApp: &tele.WebApp{URL: "https://google.com"}, }, }}, }, lt.MarkupLocale("en", "web_app")) assert.Equal(t, &tele.ArticleResult{ ResultBase: tele.ResultBase{ ID: "1853", Type: "article", }, Title: "Some title", Description: "Some description", ThumbURL: "https://preview.picture", Text: "This is an article.", }, lt.ResultLocale("en", "article", struct { ID int Title string Description string PreviewURL string }{ ID: 1853, Title: "Some title", Description: "Some description", PreviewURL: "https://preview.picture", })) assert.Equal(t, "This is an article.", lt.TextLocale("en", "article_message"), ) assert.Equal(t, lt.TextLocale("en", "nested.example", "an example"), "This is an example.", ) assert.Equal(t, lt.TextLocale("en", "nested.another.example", "another example"), "This is another example.", ) } telebot-3.3.8/layout/locales/000077500000000000000000000000001465437035100161325ustar00rootroot00000000000000telebot-3.3.8/layout/locales/en.yml000066400000000000000000000002031465437035100172520ustar00rootroot00000000000000article_message: This is an article. nested: example: |- This is {{ . }}. another: example: |- This is {{ . }}. telebot-3.3.8/layout/middleware.go000066400000000000000000000022001465437035100171460ustar00rootroot00000000000000package layout import ( tele "gopkg.in/telebot.v3" ) // LocaleFunc is the function used to fetch the locale of the recipient. // Returned locale will be remembered and linked to the corresponding context. type LocaleFunc func(tele.Recipient) string // Middleware builds a telebot middleware to make localization work. // // Usage: // b.Use(lt.Middleware("en", func(r tele.Recipient) string { // loc, _ := db.UserLocale(r.Recipient()) // return loc // })) // func (lt *Layout) Middleware(defaultLocale string, localeFunc ...LocaleFunc) tele.MiddlewareFunc { var f LocaleFunc if len(localeFunc) > 0 { f = localeFunc[0] } return func(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { locale := defaultLocale if f != nil { if l := f(c.Sender()); l != "" { locale = l } } lt.SetLocale(c, locale) defer func() { lt.mu.Lock() delete(lt.ctxs, c) lt.mu.Unlock() }() return next(c) } } } // Middleware wraps ordinary layout middleware with your default locale. func (dlt *DefaultLayout) Middleware() tele.MiddlewareFunc { return dlt.lt.Middleware(dlt.locale) } telebot-3.3.8/layout/parser.go000066400000000000000000000133401465437035100163340ustar00rootroot00000000000000package layout import ( "fmt" "io/ioutil" "os" "path/filepath" "strings" "text/template" "github.com/goccy/go-yaml" "github.com/spf13/viper" tele "gopkg.in/telebot.v3" ) type Settings struct { URL string Token string Updates int LocalesDir string `yaml:"locales_dir"` TokenEnv string `yaml:"token_env"` ParseMode string `yaml:"parse_mode"` Webhook *tele.Webhook `yaml:"webhook"` LongPoller *tele.LongPoller `yaml:"long_poller"` } func (lt *Layout) UnmarshalYAML(data []byte) error { var aux struct { Settings *Settings Config map[string]interface{} Commands map[string]string Buttons yaml.MapSlice Markups yaml.MapSlice Results yaml.MapSlice Locales map[string]map[string]string } if err := yaml.Unmarshal(data, &aux); err != nil { return err } v := viper.New() if err := v.MergeConfigMap(aux.Config); err != nil { return err } lt.Config = Config{v: v} lt.commands = aux.Commands if pref := aux.Settings; pref != nil { lt.pref = &tele.Settings{ URL: pref.URL, Token: pref.Token, Updates: pref.Updates, ParseMode: pref.ParseMode, } if pref.TokenEnv != "" { lt.pref.Token = os.Getenv(pref.TokenEnv) } if pref.Webhook != nil { lt.pref.Poller = pref.Webhook } else if pref.LongPoller != nil { lt.pref.Poller = pref.LongPoller } } lt.buttons = make(map[string]Button, len(aux.Buttons)) for _, item := range aux.Buttons { k, v := item.Key.(string), item.Value // 1. Shortened reply button if v, ok := v.(string); ok { btn := tele.Btn{Text: v} lt.buttons[k] = Button{Btn: btn} continue } // 2. Extended reply or inline button data, err := yaml.MarshalWithOptions(v, yaml.JSON()) if err != nil { return err } var btn Button if err := yaml.Unmarshal(data, &btn); err != nil { return err } if !btn.IsReply && btn.Data != nil { if a, ok := btn.Data.([]interface{}); ok { s := make([]string, len(a)) for i, v := range a { s[i] = fmt.Sprint(v) } btn.Btn.Data = strings.Join(s, "|") } else if s, ok := btn.Data.(string); ok { btn.Btn.Data = s } else { return fmt.Errorf("telebot/layout: invalid callback_data for %s button", k) } } lt.buttons[k] = btn } lt.markups = make(map[string]Markup, len(aux.Markups)) for _, item := range aux.Markups { k, v := item.Key.(string), item.Value data, err := yaml.Marshal(v) if err != nil { return err } var shortenedMarkup [][]string if yaml.Unmarshal(data, &shortenedMarkup) == nil { // 1. Shortened reply or inline markup kb := make([][]Button, len(shortenedMarkup)) for i, btns := range shortenedMarkup { row := make([]Button, len(btns)) for j, btn := range btns { b, ok := lt.buttons[btn] if !ok { return fmt.Errorf("telebot/layout: no %s button for %s markup", btn, k) } row[j] = b } kb[i] = row } data, err := yaml.Marshal(kb) if err != nil { return err } tmpl, err := template.New(k).Funcs(lt.funcs).Parse(string(data)) if err != nil { return err } markup := Markup{keyboard: tmpl} for _, row := range kb { for _, btn := range row { inline := btn.URL != "" || btn.Unique != "" || btn.InlineQuery != "" || btn.InlineQueryChat != "" || btn.Login != nil || btn.WebApp != nil inline = !btn.IsReply && inline if markup.inline == nil { markup.inline = &inline } else if *markup.inline != inline { return fmt.Errorf("telebot/layout: mixed reply and inline buttons in %s markup", k) } } } lt.markups[k] = markup } else { // 2. Extended reply markup var markup struct { Markup `yaml:",inline"` Keyboard [][]string `yaml:"keyboard"` } if err := yaml.Unmarshal(data, &markup); err != nil { return err } kb := make([][]tele.ReplyButton, len(markup.Keyboard)) for i, btns := range markup.Keyboard { row := make([]tele.ReplyButton, len(btns)) for j, btn := range btns { row[j] = *lt.buttons[btn].Reply() } kb[i] = row } data, err := yaml.Marshal(kb) if err != nil { return err } tmpl, err := template.New(k).Funcs(lt.funcs).Parse(string(data)) if err != nil { return err } markup.inline = new(bool) markup.Markup.keyboard = tmpl lt.markups[k] = markup.Markup } } lt.results = make(map[string]Result, len(aux.Results)) for _, item := range aux.Results { k, v := item.Key.(string), item.Value data, err := yaml.Marshal(v) if err != nil { return err } tmpl, err := template.New(k).Funcs(lt.funcs).Parse(string(data)) if err != nil { return err } var result Result if err := yaml.Unmarshal(data, &result); err != nil { return err } result.result = tmpl lt.results[k] = result } if aux.Locales == nil { if aux.Settings.LocalesDir == "" { aux.Settings.LocalesDir = "locales" } return lt.parseLocales(aux.Settings.LocalesDir) } return nil } func (lt *Layout) parseLocales(dir string) error { lt.locales = make(map[string]*template.Template) return filepath.Walk(dir, func(path string, fi os.FileInfo, _ error) error { if fi == nil || fi.IsDir() { return nil } data, err := ioutil.ReadFile(path) if err != nil { return err } var texts map[string]interface{} if err := yaml.Unmarshal(data, &texts); err != nil { return err } v := viper.New() if err := v.MergeConfigMap(texts); err != nil { return err } name := fi.Name() name = strings.TrimSuffix(name, filepath.Ext(name)) tmpl := template.New(name).Funcs(lt.funcs) for _, key := range v.AllKeys() { text := strings.Trim(v.GetString(key), "\r\n") if _, err := tmpl.New(key).Parse(text); err != nil { return err } } lt.locales[name] = tmpl return nil }) } telebot-3.3.8/markup.go000066400000000000000000000270641465437035100150320ustar00rootroot00000000000000package telebot import ( "encoding/json" "fmt" "strings" ) // ReplyMarkup controls two convenient options for bot-user communications // such as reply keyboard and inline "keyboard" (a grid of buttons as a part // of the message). type ReplyMarkup struct { // InlineKeyboard is a grid of InlineButtons displayed in the message. // // Note: DO NOT confuse with ReplyKeyboard and other keyboard properties! InlineKeyboard [][]InlineButton `json:"inline_keyboard,omitempty"` // ReplyKeyboard is a grid, consisting of keyboard buttons. // // Note: you don't need to set HideCustomKeyboard field to show custom keyboard. ReplyKeyboard [][]ReplyButton `json:"keyboard,omitempty"` // ForceReply forces Telegram clients to display // a reply interface to the user (act as if the user // has selected the bot‘s message and tapped "Reply"). ForceReply bool `json:"force_reply,omitempty"` // Requests clients to resize the keyboard vertically for optimal fit // (e.g. make the keyboard smaller if there are just two rows of buttons). // // Defaults to false, in which case the custom keyboard is always of the // same height as the app's standard keyboard. ResizeKeyboard bool `json:"resize_keyboard,omitempty"` // Requests clients to hide the reply keyboard as soon as it's been used. // // Defaults to false. OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"` // Requests clients to remove the reply keyboard. // // Defaults to false. RemoveKeyboard bool `json:"remove_keyboard,omitempty"` // Use this param if you want to force reply from // specific users only. // // Targets: // 1) Users that are @mentioned in the text of the Message object; // 2) If the bot's message is a reply (has SendOptions.ReplyTo), // sender of the original message. Selective bool `json:"selective,omitempty"` // Placeholder will be shown in the input field when the reply is active. Placeholder string `json:"input_field_placeholder,omitempty"` // IsPersistent allows to control when the keyboard is shown. IsPersistent bool `json:"is_persistent,omitempty"` } func (r *ReplyMarkup) copy() *ReplyMarkup { cp := *r if len(r.ReplyKeyboard) > 0 { cp.ReplyKeyboard = make([][]ReplyButton, len(r.ReplyKeyboard)) for i, row := range r.ReplyKeyboard { cp.ReplyKeyboard[i] = make([]ReplyButton, len(row)) copy(cp.ReplyKeyboard[i], row) } } if len(r.InlineKeyboard) > 0 { cp.InlineKeyboard = make([][]InlineButton, len(r.InlineKeyboard)) for i, row := range r.InlineKeyboard { cp.InlineKeyboard[i] = make([]InlineButton, len(row)) copy(cp.InlineKeyboard[i], row) } } return &cp } // Btn is a constructor button, which will later become either a reply, or an inline button. type Btn struct { Unique string `json:"unique,omitempty"` Text string `json:"text,omitempty"` URL string `json:"url,omitempty"` Data string `json:"callback_data,omitempty"` InlineQuery string `json:"switch_inline_query,omitempty"` InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` Login *Login `json:"login_url,omitempty"` WebApp *WebApp `json:"web_app,omitempty"` Contact bool `json:"request_contact,omitempty"` Location bool `json:"request_location,omitempty"` Poll PollType `json:"request_poll,omitempty"` User *ReplyRecipient `json:"request_user,omitempty"` Chat *ReplyRecipient `json:"request_chat,omitempty"` } // Row represents an array of buttons, a row. type Row []Btn // Row creates a row of buttons. func (r *ReplyMarkup) Row(many ...Btn) Row { return many } // Split splits the keyboard into the rows with N maximum number of buttons. // For example, if you pass six buttons and 3 as the max, you get two rows with // three buttons in each. // // `Split(3, []Btn{six buttons...}) -> [[1, 2, 3], [4, 5, 6]]` // `Split(2, []Btn{six buttons...}) -> [[1, 2],[3, 4],[5, 6]]` func (r *ReplyMarkup) Split(max int, btns []Btn) []Row { rows := make([]Row, (max-1+len(btns))/max) for i, b := range btns { i /= max rows[i] = append(rows[i], b) } return rows } func (r *ReplyMarkup) Inline(rows ...Row) { inlineKeys := make([][]InlineButton, 0, len(rows)) for i, row := range rows { keys := make([]InlineButton, 0, len(row)) for j, btn := range row { btn := btn.Inline() if btn == nil { panic(fmt.Sprintf( "telebot: button row %d column %d is not an inline button", i, j)) } keys = append(keys, *btn) } inlineKeys = append(inlineKeys, keys) } r.InlineKeyboard = inlineKeys } func (r *ReplyMarkup) Reply(rows ...Row) { replyKeys := make([][]ReplyButton, 0, len(rows)) for i, row := range rows { keys := make([]ReplyButton, 0, len(row)) for j, btn := range row { btn := btn.Reply() if btn == nil { panic(fmt.Sprintf( "telebot: button row %d column %d is not a reply button", i, j)) } keys = append(keys, *btn) } replyKeys = append(replyKeys, keys) } r.ReplyKeyboard = replyKeys } func (r *ReplyMarkup) Text(text string) Btn { return Btn{Text: text} } func (r *ReplyMarkup) Data(text, unique string, data ...string) Btn { return Btn{ Unique: unique, Text: text, Data: strings.Join(data, "|"), } } func (r *ReplyMarkup) URL(text, url string) Btn { return Btn{Text: text, URL: url} } func (r *ReplyMarkup) Query(text, query string) Btn { return Btn{Text: text, InlineQuery: query} } func (r *ReplyMarkup) QueryChat(text, query string) Btn { return Btn{Text: text, InlineQueryChat: query} } func (r *ReplyMarkup) Contact(text string) Btn { return Btn{Contact: true, Text: text} } func (r *ReplyMarkup) Location(text string) Btn { return Btn{Location: true, Text: text} } func (r *ReplyMarkup) Poll(text string, poll PollType) Btn { return Btn{Poll: poll, Text: text} } func (r *ReplyMarkup) User(text string, user *ReplyRecipient) Btn { return Btn{Text: text, User: user} } func (r *ReplyMarkup) Chat(text string, chat *ReplyRecipient) Btn { return Btn{Text: text, Chat: chat} } func (r *ReplyMarkup) Login(text string, login *Login) Btn { return Btn{Login: login, Text: text} } func (r *ReplyMarkup) WebApp(text string, app *WebApp) Btn { return Btn{Text: text, WebApp: app} } // ReplyButton represents a button displayed in reply-keyboard. // // Set either Contact or Location to true in order to request // sensitive info, such as user's phone number or current location. type ReplyButton struct { Text string `json:"text"` Contact bool `json:"request_contact,omitempty"` Location bool `json:"request_location,omitempty"` Poll PollType `json:"request_poll,omitempty"` User *ReplyRecipient `json:"request_users,omitempty"` Chat *ReplyRecipient `json:"request_chat,omitempty"` WebApp *WebApp `json:"web_app,omitempty"` } // MarshalJSON implements json.Marshaler. It allows passing PollType as a // keyboard's poll type instead of KeyboardButtonPollType object. func (pt PollType) MarshalJSON() ([]byte, error) { return json.Marshal(&struct { Type string `json:"type"` }{ Type: string(pt), }) } // ReplyRecipient combines both KeyboardButtonRequestUser // and KeyboardButtonRequestChat objects. Use inside ReplyButton // to request the user or chat sharing with respective settings. // // To pass the pointers to bool use a special tele.Flag function, // that way you will be able to reflect the three-state bool (nil, false, true). type ReplyRecipient struct { ID int32 `json:"request_id"` Bot *bool `json:"user_is_bot,omitempty"` // user only, optional Premium *bool `json:"user_is_premium,omitempty"` // user only, optional Quantity int `json:"max_quantity,omitempty"` // user only, optional Channel bool `json:"chat_is_channel,omitempty"` // chat only, required Forum *bool `json:"chat_is_forum,omitempty"` // chat only, optional WithUsername *bool `json:"chat_has_username,omitempty"` // chat only, optional Created *bool `json:"chat_is_created,omitempty"` // chat only, optional UserRights *Rights `json:"user_administrator_rights,omitempty"` // chat only, optional BotRights *Rights `json:"bot_administrator_rights,omitempty"` // chat only, optional BotMember *bool `json:"bot_is_member,omitempty"` // chat only, optional } // RecipientShared combines both UserShared and ChatShared objects. type RecipientShared struct { ID int32 `json:"request_id"` UserID int64 `json:"user_id"` ChatID int64 `json:"chat_id"` } // InlineButton represents a button displayed in the message. type InlineButton struct { // Unique slagish name for this kind of button, // try to be as specific as possible. // // It will be used as a callback endpoint. Unique string `json:"unique,omitempty"` Text string `json:"text"` URL string `json:"url,omitempty"` Data string `json:"callback_data,omitempty"` InlineQuery string `json:"switch_inline_query,omitempty"` InlineQueryChat string `json:"switch_inline_query_current_chat"` InlineQueryChosenChat *SwitchInlineQuery `json:"switch_inline_query_chosen_chat,omitempty"` Login *Login `json:"login_url,omitempty"` WebApp *WebApp `json:"web_app,omitempty"` } // MarshalJSON implements json.Marshaler interface. // It needed to avoid InlineQueryChat and Login or WebApp fields conflict. // If you have Login or WebApp field in your button, InlineQueryChat must be skipped. func (t *InlineButton) MarshalJSON() ([]byte, error) { type IB InlineButton if t.Login != nil || t.WebApp != nil { return json.Marshal(struct { IB InlineQueryChat string `json:"switch_inline_query_current_chat,omitempty"` }{ IB: IB(*t), }) } return json.Marshal(IB(*t)) } // With returns a copy of the button with data. func (t *InlineButton) With(data string) *InlineButton { return &InlineButton{ Unique: t.Unique, Text: t.Text, URL: t.URL, InlineQuery: t.InlineQuery, InlineQueryChat: t.InlineQueryChat, Login: t.Login, Data: data, } } func (b Btn) Reply() *ReplyButton { if b.Unique != "" { return nil } return &ReplyButton{ Text: b.Text, Contact: b.Contact, Location: b.Location, Poll: b.Poll, User: b.User, Chat: b.Chat, WebApp: b.WebApp, } } func (b Btn) Inline() *InlineButton { return &InlineButton{ Unique: b.Unique, Text: b.Text, URL: b.URL, Data: b.Data, InlineQuery: b.InlineQuery, InlineQueryChat: b.InlineQueryChat, Login: b.Login, WebApp: b.WebApp, } } // Login represents a parameter of the inline keyboard button // used to automatically authorize a user. Serves as a great replacement // for the Telegram Login Widget when the user is coming from Telegram. type Login struct { URL string `json:"url"` Text string `json:"forward_text,omitempty"` Username string `json:"bot_username,omitempty"` WriteAccess bool `json:"request_write_access,omitempty"` } // MenuButton describes the bot's menu button in a private chat. type MenuButton struct { Type MenuButtonType `json:"type"` Text string `json:"text,omitempty"` WebApp *WebApp `json:"web_app,omitempty"` } type MenuButtonType = string const ( MenuButtonDefault MenuButtonType = "default" MenuButtonCommands MenuButtonType = "commands" MenuButtonWebApp MenuButtonType = "web_app" ) telebot-3.3.8/markup_test.go000066400000000000000000000037261465437035100160700ustar00rootroot00000000000000package telebot import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestBtn(t *testing.T) { r := &ReplyMarkup{} assert.Equal(t, &ReplyButton{Text: "T"}, r.Text("T").Reply()) assert.Equal(t, &ReplyButton{Text: "T", Contact: true}, r.Contact("T").Reply()) assert.Equal(t, &ReplyButton{Text: "T", Location: true}, r.Location("T").Reply()) assert.Equal(t, &ReplyButton{Text: "T", Poll: PollAny}, r.Poll("T", PollAny).Reply()) assert.Nil(t, r.Data("T", "u").Reply()) assert.Equal(t, &InlineButton{Unique: "u", Text: "T"}, r.Data("T", "u").Inline()) assert.Equal(t, &InlineButton{Unique: "u", Text: "T", Data: "1|2"}, r.Data("T", "u", "1", "2").Inline()) assert.Equal(t, &InlineButton{Text: "T", URL: "url"}, r.URL("T", "url").Inline()) assert.Equal(t, &InlineButton{Text: "T", InlineQuery: "q"}, r.Query("T", "q").Inline()) assert.Equal(t, &InlineButton{Text: "T", InlineQueryChat: "q"}, r.QueryChat("T", "q").Inline()) assert.Equal(t, &InlineButton{Text: "T", Login: &Login{Text: "T"}}, r.Login("T", &Login{Text: "T"}).Inline()) assert.Equal(t, &InlineButton{Text: "T", WebApp: &WebApp{URL: "url"}}, r.WebApp("T", &WebApp{URL: "url"}).Inline()) } func TestOptions(t *testing.T) { r := &ReplyMarkup{} r.Reply( r.Row(r.Text("Menu")), r.Row(r.Text("Settings")), ) assert.Equal(t, [][]ReplyButton{ {{Text: "Menu"}}, {{Text: "Settings"}}, }, r.ReplyKeyboard) i := &ReplyMarkup{} i.Inline(i.Row( i.Data("Previous", "prev"), i.Data("Next", "next"), )) assert.Equal(t, [][]InlineButton{{ {Unique: "prev", Text: "Previous"}, {Unique: "next", Text: "Next"}, }}, i.InlineKeyboard) assert.Panics(t, func() { r.Reply(r.Row(r.Data("T", "u"))) i.Inline(i.Row(i.Text("T"))) }) assert.Equal(t, r.copy(), r) assert.Equal(t, i.copy(), i) o := &SendOptions{ReplyMarkup: r} assert.Equal(t, o.copy(), o) data, err := PollQuiz.MarshalJSON() require.NoError(t, err) assert.Equal(t, []byte(`{"type":"quiz"}`), data) } telebot-3.3.8/media.go000066400000000000000000000222631465437035100146060ustar00rootroot00000000000000package telebot import ( "encoding/json" ) // Media is a generic type for all kinds of media that includes File. type Media interface { // MediaType returns string-represented media type. MediaType() string // MediaFile returns a pointer to the media file. MediaFile() *File } // InputMedia represents a composite InputMedia struct that is // used by Telebot in sending and editing media methods. type InputMedia struct { Type string `json:"type"` Media string `json:"media"` Caption string `json:"caption"` Thumbnail string `json:"thumbnail,omitempty"` ParseMode string `json:"parse_mode,omitempty"` Entities Entities `json:"caption_entities,omitempty"` Width int `json:"width,omitempty"` Height int `json:"height,omitempty"` Duration int `json:"duration,omitempty"` Title string `json:"title,omitempty"` Performer string `json:"performer,omitempty"` Streaming bool `json:"supports_streaming,omitempty"` DisableTypeDetection bool `json:"disable_content_type_detection,omitempty"` HasSpoiler bool `json:"is_spoiler,omitempty"` } // Inputtable is a generic type for all kinds of media you // can put into an album. type Inputtable interface { Media // InputMedia returns already marshalled InputMedia type // ready to be used in sending and editing media methods. InputMedia() InputMedia } // Album lets you group multiple media into a single message. type Album []Inputtable func (a Album) SetCaption(caption string) { if len(a) < 1 { return } switch a[0].MediaType() { case "audio": a[0].(*Audio).Caption = caption case "video": a[0].(*Video).Caption = caption case "document": a[0].(*Document).Caption = caption case "photo": a[0].(*Photo).Caption = caption case "animation": a[0].(*Animation).Caption = caption } } // Photo object represents a single photo file. type Photo struct { File Width int `json:"width"` Height int `json:"height"` Caption string `json:"caption,omitempty"` } type photoSize struct { File Width int `json:"width"` Height int `json:"height"` Caption string `json:"caption,omitempty"` } func (p *Photo) MediaType() string { return "photo" } func (p *Photo) MediaFile() *File { return &p.File } func (p *Photo) InputMedia() InputMedia { return InputMedia{ Type: p.MediaType(), Caption: p.Caption, } } // UnmarshalJSON is custom unmarshaller required to abstract // away the hassle of treating different thumbnail sizes. // Instead, Telebot chooses the hi-res one and just sticks to it. // // I really do find it a beautiful solution. func (p *Photo) UnmarshalJSON(data []byte) error { var hq photoSize if data[0] == '{' { if err := json.Unmarshal(data, &hq); err != nil { return err } } else { var sizes []photoSize if err := json.Unmarshal(data, &sizes); err != nil { return err } hq = sizes[len(sizes)-1] } p.File = hq.File p.Width = hq.Width p.Height = hq.Height return nil } // Audio object represents an audio file. type Audio struct { File Duration int `json:"duration,omitempty"` // (Optional) Caption string `json:"caption,omitempty"` Thumbnail *Photo `json:"thumbnail,omitempty"` Title string `json:"title,omitempty"` Performer string `json:"performer,omitempty"` MIME string `json:"mime_type,omitempty"` FileName string `json:"file_name,omitempty"` } func (a *Audio) MediaType() string { return "audio" } func (a *Audio) MediaFile() *File { a.fileName = a.FileName return &a.File } func (a *Audio) InputMedia() InputMedia { return InputMedia{ Type: a.MediaType(), Caption: a.Caption, Duration: a.Duration, Title: a.Title, Performer: a.Performer, } } // Document object represents a general file (as opposed to Photo or Audio). // Telegram users can send files of any type of up to 1.5 GB in size. type Document struct { File // (Optional) Thumbnail *Photo `json:"thumbnail,omitempty"` Caption string `json:"caption,omitempty"` MIME string `json:"mime_type"` FileName string `json:"file_name,omitempty"` DisableTypeDetection bool `json:"disable_content_type_detection,omitempty"` } func (d *Document) MediaType() string { return "document" } func (d *Document) MediaFile() *File { d.fileName = d.FileName return &d.File } func (d *Document) InputMedia() InputMedia { return InputMedia{ Type: d.MediaType(), Caption: d.Caption, DisableTypeDetection: d.DisableTypeDetection, } } // Video object represents a video file. type Video struct { File Width int `json:"width"` Height int `json:"height"` Duration int `json:"duration,omitempty"` // (Optional) Caption string `json:"caption,omitempty"` Thumbnail *Photo `json:"thumbnail,omitempty"` Streaming bool `json:"supports_streaming,omitempty"` MIME string `json:"mime_type,omitempty"` FileName string `json:"file_name,omitempty"` } func (v *Video) MediaType() string { return "video" } func (v *Video) MediaFile() *File { v.fileName = v.FileName return &v.File } func (v *Video) InputMedia() InputMedia { return InputMedia{ Type: v.MediaType(), Caption: v.Caption, Width: v.Width, Height: v.Height, Duration: v.Duration, Streaming: v.Streaming, } } // Animation object represents a animation file. type Animation struct { File Width int `json:"width"` Height int `json:"height"` Duration int `json:"duration,omitempty"` // (Optional) Caption string `json:"caption,omitempty"` Thumbnail *Photo `json:"thumbnail,omitempty"` MIME string `json:"mime_type,omitempty"` FileName string `json:"file_name,omitempty"` } func (a *Animation) MediaType() string { return "animation" } func (a *Animation) MediaFile() *File { a.fileName = a.FileName return &a.File } func (a *Animation) InputMedia() InputMedia { return InputMedia{ Type: a.MediaType(), Caption: a.Caption, Width: a.Width, Height: a.Height, Duration: a.Duration, } } // Voice object represents a voice note. type Voice struct { File Duration int `json:"duration"` // (Optional) Caption string `json:"caption,omitempty"` MIME string `json:"mime_type,omitempty"` } func (v *Voice) MediaType() string { return "voice" } func (v *Voice) MediaFile() *File { return &v.File } // VideoNote represents a video message. type VideoNote struct { File Duration int `json:"duration"` // (Optional) Thumbnail *Photo `json:"thumbnail,omitempty"` Length int `json:"length,omitempty"` } func (v *VideoNote) MediaType() string { return "videoNote" } func (v *VideoNote) MediaFile() *File { return &v.File } // Sticker object represents a WebP image, so-called sticker. type Sticker struct { File Type StickerSetType `json:"type"` Width int `json:"width"` Height int `json:"height"` Animated bool `json:"is_animated"` Video bool `json:"is_video"` Thumbnail *Photo `json:"thumbnail"` Emoji string `json:"emoji"` SetName string `json:"set_name"` PremiumAnimation *File `json:"premium_animation"` MaskPosition *MaskPosition `json:"mask_position"` CustomEmoji string `json:"custom_emoji_id"` Repaint bool `json:"needs_repainting"` } func (s *Sticker) MediaType() string { return "sticker" } func (s *Sticker) MediaFile() *File { return &s.File } // Contact object represents a contact to Telegram user. type Contact struct { PhoneNumber string `json:"phone_number"` FirstName string `json:"first_name"` // (Optional) LastName string `json:"last_name"` UserID int64 `json:"user_id,omitempty"` } // Location object represents geographic position. type Location struct { Lat float32 `json:"latitude"` Lng float32 `json:"longitude"` HorizontalAccuracy *float32 `json:"horizontal_accuracy,omitempty"` Heading int `json:"heading,omitempty"` AlertRadius int `json:"proximity_alert_radius,omitempty"` // Period in seconds for which the location will be updated // (see Live Locations, should be between 60 and 86400.) LivePeriod int `json:"live_period,omitempty"` } // Venue object represents a venue location with name, address and // optional foursquare ID. type Venue struct { Location Location `json:"location"` Title string `json:"title"` Address string `json:"address"` // (Optional) FoursquareID string `json:"foursquare_id,omitempty"` FoursquareType string `json:"foursquare_type,omitempty"` GooglePlaceID string `json:"google_place_id,omitempty"` GooglePlaceType string `json:"google_place_type,omitempty"` } // Dice object represents a dice with a random value // from 1 to 6 for currently supported base emoji. type Dice struct { Type DiceType `json:"emoji"` Value int `json:"value"` } // DiceType defines dice types. type DiceType string var ( Cube = &Dice{Type: "🎲"} Dart = &Dice{Type: "🎯"} Ball = &Dice{Type: "🏀"} Goal = &Dice{Type: "⚽"} Slot = &Dice{Type: "🎰"} Bowl = &Dice{Type: "🎳"} ) telebot-3.3.8/media_test.go000066400000000000000000000015711465437035100156440ustar00rootroot00000000000000package telebot import ( "github.com/stretchr/testify/assert" "testing" ) func TestAlbumSetCaption(t *testing.T) { tests := []struct { name string media Inputtable }{ { name: "photo", media: &Photo{Caption: "wrong_caption"}, }, { name: "animation", media: &Animation{Caption: "wrong_caption"}, }, { name: "video", media: &Video{Caption: "wrong_caption"}, }, { name: "audio", media: &Audio{Caption: "wrong_caption"}, }, { name: "document", media: &Document{Caption: "wrong_caption"}, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { var a Album a = append(a, tt.media) a = append(a, &Photo{Caption: "random_caption"}) a.SetCaption("correct_caption") assert.Equal(t, "correct_caption", a[0].InputMedia().Caption) assert.Equal(t, "random_caption", a[1].InputMedia().Caption) }) } } telebot-3.3.8/message.go000066400000000000000000000567511465437035100151640ustar00rootroot00000000000000package telebot import ( "strconv" "time" "unicode/utf16" ) // Message object represents a message. type Message struct { ID int `json:"message_id"` // (Optional) Unique identifier of a message thread to which the message belongs; for supergroups only ThreadID int `json:"message_thread_id"` // For message sent to channels, Sender will be nil Sender *User `json:"from"` // Unixtime, use Message.Time() to get time.Time Unixtime int64 `json:"date"` // Conversation the message belongs to. Chat *Chat `json:"chat"` // Sender of the message, sent on behalf of a chat. SenderChat *Chat `json:"sender_chat"` // For forwarded messages, sender of the original message. OriginalSender *User `json:"forward_from"` // For forwarded messages, chat of the original message when // forwarded from a channel. OriginalChat *Chat `json:"forward_from_chat"` // For forwarded messages, identifier of the original message // when forwarded from a channel. OriginalMessageID int `json:"forward_from_message_id"` // For forwarded messages, signature of the post author. OriginalSignature string `json:"forward_signature"` // For forwarded messages, sender's name from users who // disallow adding a link to their account. OriginalSenderName string `json:"forward_sender_name"` // For forwarded messages, unixtime of the original message. OriginalUnixtime int `json:"forward_date"` // For information about the original message for forwarded messages. Origin *MessageOrigin `json:"forward_origin"` // Message is a channel post that was automatically forwarded to the connected discussion group. AutomaticForward bool `json:"is_automatic_forward"` // For replies, ReplyTo represents the original message. // // Note that the Message object in this field will not // contain further ReplyTo fields even if it // itself is a reply. ReplyTo *Message `json:"reply_to_message"` // (Optional) For replies to a story, the original story Story *Story `json:"story"` // (Optional) Information about the message that is being replied to, // which may come from another chat or forum topic. ExternalReplyInfo *ExternalReplyInfo `json:"external_reply"` // (Optional) For replies that quote part of the original message, // the quoted part of the message. Quote *TextQuote `json:"quote"` // Shows through which bot the message was sent. Via *User `json:"via_bot"` // For replies to a story, the original story. ReplyToStory *Story `json:"reply_to_story"` // (Optional) Time of last edit in Unix. LastEdit int64 `json:"edit_date"` // (Optional) True, if the message is sent to a forum topic. TopicMessage bool `json:"is_topic_message"` // (Optional) Message can't be forwarded. Protected bool `json:"has_protected_content,omitempty"` // AlbumID is the unique identifier of a media message group // this message belongs to. AlbumID string `json:"media_group_id"` // Author signature (in channels). Signature string `json:"author_signature"` // For a text message, the actual UTF-8 text of the message. Text string `json:"text"` // For registered commands, will contain the string payload: // // Ex: `/command ` or `/command@botname ` Payload string `json:"-"` // For text messages, special entities like usernames, URLs, bot commands, // etc. that appear in the text. Entities Entities `json:"entities,omitempty"` // (Optional) PreviewOptions used for link preview generation for the message, // if it is a text message and link preview options were changed. PreviewOptions *PreviewOptions `json:"link_preview_options,omitempty"` // Some messages containing media, may as well have a caption. Caption string `json:"caption,omitempty"` // For messages with a caption, special entities like usernames, URLs, // bot commands, etc. that appear in the caption. CaptionEntities Entities `json:"caption_entities,omitempty"` // For an audio recording, information about it. Audio *Audio `json:"audio"` // For a general file, information about it. Document *Document `json:"document"` // For a photo, all available sizes (thumbnails). Photo *Photo `json:"photo"` // For a sticker, information about it. Sticker *Sticker `json:"sticker"` // For a voice message, information about it. Voice *Voice `json:"voice"` // For a video note, information about it. VideoNote *VideoNote `json:"video_note"` // For a video, information about it. Video *Video `json:"video"` // For an animation, information about it. Animation *Animation `json:"animation"` // For a contact, contact information itself. Contact *Contact `json:"contact"` // For a location, its longitude and latitude. Location *Location `json:"location"` // For a venue, information about it. Venue *Venue `json:"venue"` // For a poll, information the native poll. Poll *Poll `json:"poll"` // For a game, information about it. Game *Game `json:"game"` // For a dice, information about it. Dice *Dice `json:"dice"` // (Optional) The message is a scheduled giveaway message. Giveaway *Giveaway `json:"giveaway"` // (Optional) A giveaway with public winners was completed. GiveawayWinners *GiveawayWinners `json:"giveaway_winners"` // (Optional) Service message: a scheduled giveaway was created. GiveawayCreated *GiveawayCreated `json:"giveaway_created"` // (Optional) Service message: a giveaway without public winners was completed. GiveawayCompleted *GiveawayCompleted `json:"giveaway_completed"` // For a service message, represents a user, // that just got added to chat, this message came from. // // Sender leads to User, capable of invite. // // UserJoined might be the Bot itself. UserJoined *User `json:"new_chat_member"` // For a service message, represents a user, // that just left chat, this message came from. // // If user was kicked, Sender leads to a User, // capable of this kick. // // UserLeft might be the Bot itself. UserLeft *User `json:"left_chat_member"` // For a service message, represents a new title // for chat this message came from. // // Sender would lead to a User, capable of change. NewGroupTitle string `json:"new_chat_title"` // For a service message, represents all available // thumbnails of the new chat photo. // // Sender would lead to a User, capable of change. NewGroupPhoto *Photo `json:"new_chat_photo"` // For a service message, new members that were added to // the group or supergroup and information about them // (the bot itself may be one of these members). UsersJoined []User `json:"new_chat_members"` // For a service message, true if chat photo just // got removed. // // Sender would lead to a User, capable of change. GroupPhotoDeleted bool `json:"delete_chat_photo"` // For a service message, true if group has been created. // // You would receive such a message if you are one of // initial group chat members. // // Sender would lead to creator of the chat. GroupCreated bool `json:"group_chat_created"` // For a service message, true if supergroup has been created. // // You would receive such a message if you are one of // initial group chat members. // // Sender would lead to creator of the chat. SuperGroupCreated bool `json:"supergroup_chat_created"` // For a service message, true if channel has been created. // // You would receive such a message if you are one of // initial channel administrators. // // Sender would lead to creator of the chat. ChannelCreated bool `json:"channel_chat_created"` // For a service message, the destination (supergroup) you // migrated to. // // You would receive such a message when your chat has migrated // to a supergroup. // // Sender would lead to creator of the migration. MigrateTo int64 `json:"migrate_to_chat_id"` // For a service message, the Origin (normal group) you migrated // from. // // You would receive such a message when your chat has migrated // to a supergroup. // // Sender would lead to creator of the migration. MigrateFrom int64 `json:"migrate_from_chat_id"` // Specified message was pinned. Note that the Message object // in this field will not contain further ReplyTo fields even // if it is itself a reply. PinnedMessage *Message `json:"pinned_message"` // Message is an invoice for a payment. Invoice *Invoice `json:"invoice"` // Message is a service message about a successful payment. Payment *Payment `json:"successful_payment"` // For a service message, a user was shared with the bot. UserShared *RecipientShared `json:"users_shared,omitempty"` // For a service message, a chat was shared with the bot. ChatShared *RecipientShared `json:"chat_shared,omitempty"` // The domain name of the website on which the user has logged in. ConnectedWebsite string `json:"connected_website,omitempty"` // For a service message, a video chat started in the chat. VideoChatStarted *VideoChatStarted `json:"video_chat_started,omitempty"` // For a service message, a video chat ended in the chat. VideoChatEnded *VideoChatEnded `json:"video_chat_ended,omitempty"` // For a service message, some users were invited in the video chat. VideoChatParticipants *VideoChatParticipants `json:"video_chat_participants_invited,omitempty"` // For a service message, a video chat schedule in the chat. VideoChatScheduled *VideoChatScheduled `json:"video_chat_scheduled,omitempty"` // For a data sent by a Web App. WebAppData *WebAppData `json:"web_app_data,omitempty"` // For a service message, represents the content of a service message, // sent whenever a user in the chat triggers a proximity alert set by another user. ProximityAlert *ProximityAlert `json:"proximity_alert_triggered,omitempty"` // For a service message, represents about a change in auto-delete timer settings. AutoDeleteTimer *AutoDeleteTimer `json:"message_auto_delete_timer_changed,omitempty"` // Inline keyboard attached to the message. ReplyMarkup *ReplyMarkup `json:"reply_markup,omitempty"` // Service message: user boosted the chat. BoostAdded *BoostAdded `json:"boost_added"` // If the sender of the message boosted the chat, the number of boosts // added by the user. SenderBoostCount int `json:"sender_boost_count"` // Service message: forum topic created TopicCreated *Topic `json:"forum_topic_created,omitempty"` // Service message: forum topic closed TopicClosed *struct{} `json:"forum_topic_closed,omitempty"` // Service message: forum topic reopened TopicReopened *Topic `json:"forum_topic_reopened,omitempty"` // Service message: forum topic deleted TopicEdited *Topic `json:"forum_topic_edited,omitempty"` // Service message: general forum topic hidden GeneralTopicHidden *struct{} `json:"general_topic_hidden,omitempty"` // Service message: general forum topic unhidden GeneralTopicUnhidden *struct{} `json:"general_topic_unhidden,omitempty"` // Service message: represents spoiler information about the message. HasMediaSpoiler bool `json:"has_media_spoiler,omitempty"` // Service message: the user allowed the bot added to the attachment menu to write messages WriteAccessAllowed *WriteAccessAllowed `json:"write_access_allowed,omitempty"` } // MessageEntity object represents "special" parts of text messages, // including hashtags, usernames, URLs, etc. type MessageEntity struct { // Specifies entity type. Type EntityType `json:"type"` // Offset in UTF-16 code units to the start of the entity. Offset int `json:"offset"` // Length of the entity in UTF-16 code units. Length int `json:"length"` // (Optional) For EntityTextLink entity type only. // // URL will be opened after user taps on the text. URL string `json:"url,omitempty"` // (Optional) For EntityTMention entity type only. User *User `json:"user,omitempty"` // (Optional) For EntityCodeBlock entity type only. Language string `json:"language,omitempty"` // (Optional) For EntityCustomEmoji entity type only. CustomEmoji string `json:"custom_emoji_id"` } // EntityType is a MessageEntity type. type EntityType string const ( EntityMention EntityType = "mention" EntityTMention EntityType = "text_mention" EntityHashtag EntityType = "hashtag" EntityCashtag EntityType = "cashtag" EntityCommand EntityType = "bot_command" EntityURL EntityType = "url" EntityEmail EntityType = "email" EntityPhone EntityType = "phone_number" EntityBold EntityType = "bold" EntityItalic EntityType = "italic" EntityUnderline EntityType = "underline" EntityStrikethrough EntityType = "strikethrough" EntityCode EntityType = "code" EntityCodeBlock EntityType = "pre" EntityTextLink EntityType = "text_link" EntitySpoiler EntityType = "spoiler" EntityCustomEmoji EntityType = "custom_emoji" EntityBlockquote EntityType = "blockquote" ) // Entities are used to set message's text entities as a send option. type Entities []MessageEntity // ProximityAlert sent whenever a user in the chat triggers // a proximity alert set by another user. type ProximityAlert struct { Traveler *User `json:"traveler,omitempty"` Watcher *User `json:"watcher,omitempty"` Distance int `json:"distance"` } // AutoDeleteTimer represents a service message about a change in auto-delete timer settings. type AutoDeleteTimer struct { Unixtime int `json:"message_auto_delete_time"` } // Inaccessible shows whether the message is InaccessibleMessage object. func (m *Message) Inaccessible() bool { return m.Sender == nil } // MessageSig satisfies Editable interface (see Editable.) func (m *Message) MessageSig() (string, int64) { return strconv.Itoa(m.ID), m.Chat.ID } // Time returns the moment of message creation in local time. func (m *Message) Time() time.Time { return time.Unix(m.Unixtime, 0) } // LastEdited returns time.Time of last edit. func (m *Message) LastEdited() time.Time { return time.Unix(m.LastEdit, 0) } // IsForwarded says whether message is forwarded copy of another // message or not. func (m *Message) IsForwarded() bool { return m.OriginalSender != nil || m.OriginalChat != nil } // IsReply says whether message is a reply to another message. func (m *Message) IsReply() bool { return m.ReplyTo != nil } // Private returns true, if it's a personal message. func (m *Message) Private() bool { return m.Chat.Type == ChatPrivate } // FromGroup returns true, if message came from a group OR a supergroup. func (m *Message) FromGroup() bool { return m.Chat.Type == ChatGroup || m.Chat.Type == ChatSuperGroup } // FromChannel returns true, if message came from a channel. func (m *Message) FromChannel() bool { return m.Chat.Type == ChatChannel } // IsService returns true, if message is a service message, // returns false otherwise. // // Service messages are automatically sent messages, which // typically occur on some global action. For instance, when // anyone leaves the chat or chat title changes. func (m *Message) IsService() bool { fact := false fact = fact || m.UserJoined != nil fact = fact || len(m.UsersJoined) > 0 fact = fact || m.UserLeft != nil fact = fact || m.NewGroupTitle != "" fact = fact || m.NewGroupPhoto != nil fact = fact || m.GroupPhotoDeleted fact = fact || m.GroupCreated || m.SuperGroupCreated fact = fact || (m.MigrateTo != m.MigrateFrom) return fact } // EntityText returns the substring of the message identified by the // given MessageEntity. // // It's safer than manually slicing Text because Telegram uses // UTF-16 indices whereas Go string are []byte. func (m *Message) EntityText(e MessageEntity) string { text := m.Text if text == "" { text = m.Caption } a := utf16.Encode([]rune(text)) off, end := e.Offset, e.Offset+e.Length if off < 0 || end > len(a) { return "" } return string(utf16.Decode(a[off:end])) } // Media returns the message's media if it contains either photo, // voice, audio, animation, sticker, document, video or video note. func (m *Message) Media() Media { switch { case m.Photo != nil: return m.Photo case m.Voice != nil: return m.Voice case m.Audio != nil: return m.Audio case m.Animation != nil: return m.Animation case m.Sticker != nil: return m.Sticker case m.Document != nil: return m.Document case m.Video != nil: return m.Video case m.VideoNote != nil: return m.VideoNote default: return nil } } // MessageReaction object represents a change of a reaction on a message performed by a user. type MessageReaction struct { // The chat containing the message the user reacted to. Chat *Chat `json:"chat"` // Unique identifier of the message inside the chat. MessageID int `json:"message_id"` // (Optional) The user that changed the reaction, // if the user isn't anonymous User *User `json:"user"` // (Optional) The chat on behalf of which the reaction was changed, // if the user is anonymous. ActorChat *Chat `json:"actor_chat"` // Date of the change in Unix time. DateUnixtime int64 `json:"date"` // Previous list of reaction types that were set by the user. OldReaction []Reaction `json:"old_reaction"` // New list of reaction types that have been set by the user. NewReaction []Reaction `json:"new_reaction"` } func (mu *MessageReaction) Time() time.Time { return time.Unix(mu.DateUnixtime, 0) } // MessageReactionCount represents reaction changes on a message with // anonymous reactions. type MessageReactionCount struct { // The chat containing the message. Chat *Chat `json:"chat"` // Unique message identifier inside the chat. MessageID int `json:"message_id"` // Date of the change in Unix time. DateUnixtime int64 `json:"date"` // List of reactions that are present on the message. Reactions *ReactionCount `json:"reactions"` } // Time returns the moment of change in local time. func (mc *MessageReactionCount) Time() time.Time { return time.Unix(mc.DateUnixtime, 0) } // TextQuote contains information about the quoted part of a message that is // replied to by the given message. type TextQuote struct { // Text of the quoted part of a message that is replied to by the given message. Text string `json:"text"` // (Optional) Special entities that appear in the quote. // Currently, only bold, italic, underline, strikethrough, spoiler, // and custom_emoji entities are kept in quotes. Entities []MessageEntity `json:"entities"` // Approximate quote position in the original message in UTF-16 code units // as specified by the sender. Position int `json:"position"` // (Optional) True, if the quote was chosen manually by the message sender. // Otherwise, the quote was added automatically by the server. Manual bool `json:"is_manual"` } // MessageOrigin a message reference that has been sent originally by a known user. type MessageOrigin struct { // Type of the message origin, always “channel”. Type string `json:"type"` // Date the message was sent originally in Unix time. DateUnixtime int64 `json:"date"` // User that sent the message originally. Sender *User `json:"sender_user,omitempty"` // Name of the user that sent the message originally. SenderUsername string `json:"sender_user_name,omitempty"` // Chat that sent the message originally. SenderChat *Chat `json:"sender_chat,omitempty"` // Channel chat to which the message was originally sent. Chat *Chat `json:"chat,omitempty"` // Unique message identifier inside the chat. MessageID int `json:"message_id,omitempty"` // (Optional) For messages originally sent by an anonymous chat administrator, // original message author signature. Signature string `json:"author_signature,omitempty"` } // Time returns the moment of message that was sent originally in local time. func (mo *MessageOrigin) Time() time.Time { return time.Unix(mo.DateUnixtime, 0) } // ExternalReplyInfo contains information about a message that is being replied to, // which may come from another chat or forum topic. type ExternalReplyInfo struct { // Origin of the message replied to by the given message. Origin *MessageOrigin `json:"origin"` // (Optional) Chat the original message belongs to. // Available only if the chat is a supergroup or a channel. Chat *Chat `json:"chat"` // (Optional) Unique message identifier inside the original chat. // Available only if the original chat is a supergroup or a channel. MessageID int `json:"message_id"` // (Optional) ReactionOptions used for link preview generation for the original message, // if it is a text message. PreviewOptions *PreviewOptions `json:"link_preview_options"` // (Optional) Message is an animation, information about the animation. Animation *Animation `json:"animation"` // (Optional) Message is an audio file, information about the file. Audio *Audio `json:"audio"` // (Optional) Message is a general file, information about the file. Document *Document `json:"document"` // (Optional) Message is a photo, available sizes of the photo. Photo []Photo `json:"photo"` // (Optional) Message is a sticker, information about the sticker. Sticker *Sticker `json:"sticker"` // (Optional) Message is a forwarded story. Story *Story `json:"story"` // (Optional) Message is a video, information about the video. Video *Video `json:"video"` // (Optional) Message is a video note, information about the video message. Note *VideoNote `json:"video_note"` // (Optional) Message is a voice message, information about the file. Voice *Voice `json:"voice"` // (Optional) True, if the message media is covered by a spoiler animation. HasMediaSpoiler bool `json:"has_media_spoiler"` // (Optional) Message is a shared contact, information about the contact. Contact *Contact `json:"contact"` // (Optional) Message is a dice with random value. Dice *Dice `json:"dice"` //( Optional) Message is a game, information about the game. Game *Game `json:"game"` // (Optional) Message is a venue, information about the venue. Venue *Venue `json:"venue"` // (Optional) Message is a native poll, information about the poll. Poll *Poll `json:"poll"` // (Optional) Message is a shared location, information about the location. Location *Location `json:"location"` // (Optional) Message is an invoice for a payment, information about the invoice. Invoice *Invoice `json:"invoice"` // (Optional) Message is a scheduled giveaway, information about the giveaway. Giveaway *Giveaway `json:"giveaway"` // (Optional) A giveaway with public winners was completed. GiveawayWinners *GiveawayWinners `json:"giveaway_winners"` } // ReplyParams describes reply parameters for the message that is being sent. type ReplyParams struct { // Identifier of the message that will be replied to in the current chat, // or in the chat chat_id if it is specified. MessageID int `json:"message_id"` // (Optional) If the message to be replied to is from a different chat, // unique identifier for the chat or username of the channel. ChatID int64 `json:"chat_id"` // Optional. Pass True if the message should be sent even if the specified message // to be replied to is not found; can be used only for replies in the // same chat and forum topic. AllowWithoutReply bool `json:"allow_sending_without_reply"` // (Optional) Quoted part of the message to be replied to; 0-1024 characters after // entities parsing. The quote must be an exact substring of the message to be replied to, // including bold, italic, underline, strikethrough, spoiler, and custom_emoji entities. // The message will fail to send if the quote isn't found in the original message. Quote string `json:"quote"` // (Optional) Mode for parsing entities in the quote. QuoteParseMode ParseMode `json:"quote_parse_mode"` // (Optional) A JSON-serialized list of special entities that appear in the quote. // It can be specified instead of quote_parse_mode. QuoteEntities []MessageEntity `json:"quote_entities"` // (Optional) Position of the quote in the original message in UTF-16 code units. QuotePosition int `json:"quote_position"` } telebot-3.3.8/middleware.go000066400000000000000000000020561465437035100156420ustar00rootroot00000000000000package telebot // MiddlewareFunc represents a middleware processing function, // which get called before the endpoint group or specific handler. type MiddlewareFunc func(HandlerFunc) HandlerFunc func appendMiddleware(a, b []MiddlewareFunc) []MiddlewareFunc { if len(a) == 0 { return b } m := make([]MiddlewareFunc, 0, len(a)+len(b)) return append(m, append(a, b...)...) } func applyMiddleware(h HandlerFunc, m ...MiddlewareFunc) HandlerFunc { for i := len(m) - 1; i >= 0; i-- { h = m[i](h) } return h } // Group is a separated group of handlers, united by the general middleware. type Group struct { b *Bot middleware []MiddlewareFunc } // Use adds middleware to the chain. func (g *Group) Use(middleware ...MiddlewareFunc) { g.middleware = append(g.middleware, middleware...) } // Handle adds endpoint handler to the bot, combining group's middleware // with the optional given middleware. func (g *Group) Handle(endpoint interface{}, h HandlerFunc, m ...MiddlewareFunc) { g.b.Handle(endpoint, h, appendMiddleware(g.middleware, m)...) } telebot-3.3.8/middleware/000077500000000000000000000000001465437035100153105ustar00rootroot00000000000000telebot-3.3.8/middleware/logger.go000066400000000000000000000010531465437035100171150ustar00rootroot00000000000000package middleware import ( "encoding/json" "log" tele "gopkg.in/telebot.v3" ) // Logger returns a middleware that logs incoming updates. // If no custom logger provided, log.Default() will be used. func Logger(logger ...*log.Logger) tele.MiddlewareFunc { var l *log.Logger if len(logger) > 0 { l = logger[0] } else { l = log.Default() } return func(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { data, _ := json.MarshalIndent(c.Update(), "", " ") l.Println(string(data)) return next(c) } } } telebot-3.3.8/middleware/middleware.go000066400000000000000000000025041465437035100177550ustar00rootroot00000000000000package middleware import ( "errors" tele "gopkg.in/telebot.v3" ) // AutoRespond returns a middleware that automatically responds // to every callback. func AutoRespond() tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { if c.Callback() != nil { defer c.Respond() } return next(c) } } } // IgnoreVia returns a middleware that ignores all the // "sent via" messages. func IgnoreVia() tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { if msg := c.Message(); msg != nil && msg.Via != nil { return nil } return next(c) } } } type RecoverFunc = func(error, tele.Context) // Recover returns a middleware that recovers a panic happened in // the handler. func Recover(onError ...RecoverFunc) tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { return func(c tele.Context) error { var f RecoverFunc if len(onError) > 0 { f = onError[0] } else { f = func(err error, c tele.Context) { c.Bot().OnError(err, c) } } defer func() { if r := recover(); r != nil { if err, ok := r.(error); ok { f(err, c) } else if s, ok := r.(string); ok { f(errors.New(s), c) } } }() return next(c) } } } telebot-3.3.8/middleware/middleware_test.go000066400000000000000000000007561465437035100210230ustar00rootroot00000000000000package middleware import ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" tele "gopkg.in/telebot.v3" ) var b, _ = tele.NewBot(tele.Settings{Offline: true}) func TestRecover(t *testing.T) { onError := func(err error, c tele.Context) { require.Error(t, err, "recover test") } h := func(c tele.Context) error { panic("recover test") } assert.Panics(t, func() { h(nil) }) assert.NotPanics(t, func() { Recover(onError)(h)(nil) }) } telebot-3.3.8/middleware/restrict.go000066400000000000000000000034041465437035100174770ustar00rootroot00000000000000package middleware import tele "gopkg.in/telebot.v3" // RestrictConfig defines config for Restrict middleware. type RestrictConfig struct { // Chats is a list of chats that are going to be affected // by either In or Out function. Chats []int64 // In defines a function that will be called if the chat // of an update will be found in the Chats list. In tele.HandlerFunc // Out defines a function that will be called if the chat // of an update will NOT be found in the Chats list. Out tele.HandlerFunc } // Restrict returns a middleware that handles a list of provided // chats with the logic defined by In and Out functions. // If the chat is found in the Chats field, In function will be called, // otherwise Out function will be called. func Restrict(v RestrictConfig) tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { if v.In == nil { v.In = next } if v.Out == nil { v.Out = next } return func(c tele.Context) error { for _, chat := range v.Chats { if chat == c.Sender().ID { return v.In(c) } } return v.Out(c) } } } // Blacklist returns a middleware that skips the update for users // specified in the chats field. func Blacklist(chats ...int64) tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { return Restrict(RestrictConfig{ Chats: chats, Out: next, In: func(c tele.Context) error { return nil }, })(next) } } // Whitelist returns a middleware that skips the update for users // NOT specified in the chats field. func Whitelist(chats ...int64) tele.MiddlewareFunc { return func(next tele.HandlerFunc) tele.HandlerFunc { return Restrict(RestrictConfig{ Chats: chats, In: next, Out: func(c tele.Context) error { return nil }, })(next) } } telebot-3.3.8/options.go000066400000000000000000000152111465437035100152150ustar00rootroot00000000000000package telebot import ( "encoding/json" "strconv" ) // Option is a shortcut flag type for certain message features // (so-called options). It means that instead of passing // fully-fledged SendOptions* to Send(), you can use these // flags instead. // // Supported options are defined as iota-constants. type Option int const ( // NoPreview = SendOptions.DisableWebPagePreview NoPreview Option = iota // Silent = SendOptions.DisableNotification Silent // AllowWithoutReply = SendOptions.AllowWithoutReply AllowWithoutReply // Protected = SendOptions.Protected Protected // ForceReply = ReplyMarkup.ForceReply ForceReply // OneTimeKeyboard = ReplyMarkup.OneTimeKeyboard OneTimeKeyboard // RemoveKeyboard = ReplyMarkup.RemoveKeyboard RemoveKeyboard ) // Placeholder is used to set input field placeholder as a send option. func Placeholder(text string) *SendOptions { return &SendOptions{ ReplyMarkup: &ReplyMarkup{ ForceReply: true, Placeholder: text, }, } } // SendOptions has most complete control over in what way the message // must be sent, providing an API-complete set of custom properties // and options. // // Despite its power, SendOptions is rather inconvenient to use all // the way through bot logic, so you might want to consider storing // and re-using it somewhere or be using Option flags instead. type SendOptions struct { // If the message is a reply, original message. ReplyTo *Message // See ReplyMarkup struct definition. ReplyMarkup *ReplyMarkup // For text messages, disables previews for links in this message. DisableWebPagePreview bool // Sends the message silently. iOS users will not receive a notification, Android users will receive a notification with no sound. DisableNotification bool // ParseMode controls how client apps render your message. ParseMode ParseMode // Entities is a list of special entities that appear in message text, which can be specified instead of parse_mode. Entities Entities // AllowWithoutReply allows sending messages not a as reply if the replied-to message has already been deleted. AllowWithoutReply bool // Protected protects the contents of sent message from forwarding and saving. Protected bool // ThreadID supports sending messages to a thread. ThreadID int // HasSpoiler marks the message as containing a spoiler. HasSpoiler bool // ReplyParams Describes the message to reply to ReplyParams *ReplyParams } func (og *SendOptions) copy() *SendOptions { cp := *og if cp.ReplyMarkup != nil { cp.ReplyMarkup = cp.ReplyMarkup.copy() } return &cp } func (b *Bot) extractOptions(how []interface{}) *SendOptions { opts := &SendOptions{ ParseMode: b.parseMode, } for _, prop := range how { switch opt := prop.(type) { case *SendOptions: opts = opt.copy() case *ReplyMarkup: if opt != nil { opts.ReplyMarkup = opt.copy() } case *ReplyParams: opts.ReplyParams = opt case Option: switch opt { case NoPreview: opts.DisableWebPagePreview = true case Silent: opts.DisableNotification = true case AllowWithoutReply: opts.AllowWithoutReply = true case ForceReply: if opts.ReplyMarkup == nil { opts.ReplyMarkup = &ReplyMarkup{} } opts.ReplyMarkup.ForceReply = true case OneTimeKeyboard: if opts.ReplyMarkup == nil { opts.ReplyMarkup = &ReplyMarkup{} } opts.ReplyMarkup.OneTimeKeyboard = true case RemoveKeyboard: if opts.ReplyMarkup == nil { opts.ReplyMarkup = &ReplyMarkup{} } opts.ReplyMarkup.RemoveKeyboard = true case Protected: opts.Protected = true default: panic("telebot: unsupported flag-option") } case ParseMode: opts.ParseMode = opt case Entities: opts.Entities = opt default: panic("telebot: unsupported send-option") } } return opts } func (b *Bot) embedSendOptions(params map[string]string, opt *SendOptions) { if opt == nil { return } if opt.ReplyTo != nil && opt.ReplyTo.ID != 0 { params["reply_to_message_id"] = strconv.Itoa(opt.ReplyTo.ID) } if opt.DisableWebPagePreview { params["disable_web_page_preview"] = "true" } if opt.DisableNotification { params["disable_notification"] = "true" } if opt.ParseMode != ModeDefault { params["parse_mode"] = opt.ParseMode } if len(opt.Entities) > 0 { delete(params, "parse_mode") entities, _ := json.Marshal(opt.Entities) if params["caption"] != "" { params["caption_entities"] = string(entities) } else { params["entities"] = string(entities) } } if opt.AllowWithoutReply { params["allow_sending_without_reply"] = "true" } if opt.ReplyMarkup != nil { processButtons(opt.ReplyMarkup.InlineKeyboard) replyMarkup, _ := json.Marshal(opt.ReplyMarkup) params["reply_markup"] = string(replyMarkup) } if opt.Protected { params["protect_content"] = "true" } if opt.ThreadID != 0 { params["message_thread_id"] = strconv.Itoa(opt.ThreadID) } if opt.HasSpoiler { params["spoiler"] = "true" } } func processButtons(keys [][]InlineButton) { if keys == nil || len(keys) < 1 || len(keys[0]) < 1 { return } for i := range keys { for j := range keys[i] { key := &keys[i][j] if key.Unique != "" { // Format: "\f|" data := key.Data if data == "" { key.Data = "\f" + key.Unique } else { key.Data = "\f" + key.Unique + "|" + data } } } } } // PreviewOptions describes the options used for link preview generation. type PreviewOptions struct { // (Optional) True, if the link preview is disabled. Disabled bool `json:"is_disabled"` // (Optional) URL to use for the link preview. If empty, then the first URL // found in the message text will be used. URL string `json:"url"` // (Optional) True, if the media in the link preview is supposed to be shrunk; // ignored if the URL isn't explicitly specified or media size change. // isn't supported for the preview. SmallMedia bool `json:"prefer_small_media"` // (Optional) True, if the media in the link preview is supposed to be enlarged; // ignored if the URL isn't explicitly specified or media size change. // isn't supported for the preview. LargeMedia bool `json:"prefer_large_media"` // (Optional) True, if the link preview must be shown above the message text; // otherwise, the link preview will be shown below the message text. AboveText bool `json:"show_above_text"` } func embedMessages(params map[string]string, msgs []Editable) { ids := make([]string, 0, len(msgs)) _, chatID := msgs[0].MessageSig() for _, msg := range msgs { msgID, _ := msg.MessageSig() ids = append(ids, msgID) } data, err := json.Marshal(ids) if err != nil { return } params["message_ids"] = string(data) params["chat_id"] = strconv.FormatInt(chatID, 10) } telebot-3.3.8/payments.go000066400000000000000000000137521465437035100153720ustar00rootroot00000000000000package telebot import ( "encoding/json" "math" "strconv" ) // ShippingQuery contains information about an incoming shipping query. type ShippingQuery struct { Sender *User `json:"from"` ID string `json:"id"` Payload string `json:"invoice_payload"` Address ShippingAddress `json:"shipping_address"` } // ShippingAddress represents a shipping address. type ShippingAddress struct { CountryCode string `json:"country_code"` State string `json:"state"` City string `json:"city"` StreetLine1 string `json:"street_line1"` StreetLine2 string `json:"street_line2"` PostCode string `json:"post_code"` } // ShippingOption represents one shipping option. type ShippingOption struct { ID string `json:"id"` Title string `json:"title"` Prices []Price `json:"prices"` } // Payment contains basic information about a successful payment. type Payment struct { Currency string `json:"currency"` Total int `json:"total_amount"` Payload string `json:"invoice_payload"` OptionID string `json:"shipping_option_id"` Order Order `json:"order_info"` TelegramChargeID string `json:"telegram_payment_charge_id"` ProviderChargeID string `json:"provider_payment_charge_id"` } // PreCheckoutQuery contains information about an incoming pre-checkout query. type PreCheckoutQuery struct { Sender *User `json:"from"` ID string `json:"id"` Currency string `json:"currency"` Payload string `json:"invoice_payload"` Total int `json:"total_amount"` OptionID string `json:"shipping_option_id"` Order Order `json:"order_info"` } // Order represents information about an order. type Order struct { Name string `json:"name"` PhoneNumber string `json:"phone_number"` Email string `json:"email"` Address ShippingAddress `json:"shipping_address"` } // Invoice contains basic information about an invoice. type Invoice struct { Title string `json:"title"` Description string `json:"description"` Payload string `json:"payload"` Currency string `json:"currency"` Prices []Price `json:"prices"` Token string `json:"provider_token"` Data string `json:"provider_data"` Photo *Photo `json:"photo"` PhotoSize int `json:"photo_size"` // Unique deep-linking parameter that can be used to // generate this invoice when used as a start parameter (0). Start string `json:"start_parameter"` // Shows the total price in the smallest units of the currency. // For example, for a price of US$ 1.45 pass amount = 145. Total int `json:"total_amount"` MaxTipAmount int `json:"max_tip_amount"` SuggestedTipAmounts []int `json:"suggested_tip_amounts"` NeedName bool `json:"need_name"` NeedPhoneNumber bool `json:"need_phone_number"` NeedEmail bool `json:"need_email"` NeedShippingAddress bool `json:"need_shipping_address"` SendPhoneNumber bool `json:"send_phone_number_to_provider"` SendEmail bool `json:"send_email_to_provider"` Flexible bool `json:"is_flexible"` } func (i Invoice) params() map[string]string { params := map[string]string{ "title": i.Title, "description": i.Description, "start_parameter": i.Start, "payload": i.Payload, "provider_token": i.Token, "provider_data": i.Data, "currency": i.Currency, "max_tip_amount": strconv.Itoa(i.MaxTipAmount), "need_name": strconv.FormatBool(i.NeedName), "need_phone_number": strconv.FormatBool(i.NeedPhoneNumber), "need_email": strconv.FormatBool(i.NeedEmail), "need_shipping_address": strconv.FormatBool(i.NeedShippingAddress), "send_phone_number_to_provider": strconv.FormatBool(i.SendPhoneNumber), "send_email_to_provider": strconv.FormatBool(i.SendEmail), "is_flexible": strconv.FormatBool(i.Flexible), } if i.Photo != nil { if i.Photo.FileURL != "" { params["photo_url"] = i.Photo.FileURL } if i.PhotoSize > 0 { params["photo_size"] = strconv.Itoa(i.PhotoSize) } if i.Photo.Width > 0 { params["photo_width"] = strconv.Itoa(i.Photo.Width) } if i.Photo.Height > 0 { params["photo_height"] = strconv.Itoa(i.Photo.Height) } } if len(i.Prices) > 0 { data, _ := json.Marshal(i.Prices) params["prices"] = string(data) } if len(i.SuggestedTipAmounts) > 0 { var amounts []string for _, n := range i.SuggestedTipAmounts { amounts = append(amounts, strconv.Itoa(n)) } data, _ := json.Marshal(amounts) params["suggested_tip_amounts"] = string(data) } return params } // Price represents a portion of the price for goods or services. type Price struct { Label string `json:"label"` Amount int `json:"amount"` } // Currency contains information about supported currency for payments. type Currency struct { Code string `json:"code"` Title string `json:"title"` Symbol string `json:"symbol"` Native string `json:"native"` ThousandsSep string `json:"thousands_sep"` DecimalSep string `json:"decimal_sep"` SymbolLeft bool `json:"symbol_left"` SpaceBetween bool `json:"space_between"` Exp int `json:"exp"` MinAmount interface{} `json:"min_amount"` MaxAmount interface{} `json:"max_amount"` } func (c Currency) FromTotal(total int) float64 { return float64(total) / math.Pow(10, float64(c.Exp)) } func (c Currency) ToTotal(total float64) int { return int(total) * int(math.Pow(10, float64(c.Exp))) } // CreateInvoiceLink creates a link for a payment invoice. func (b *Bot) CreateInvoiceLink(i Invoice) (string, error) { data, err := b.Raw("createInvoiceLink", i.params()) if err != nil { return "", err } var resp struct { Result string } if err := json.Unmarshal(data, &resp); err != nil { return "", wrapError(err) } return resp.Result, nil } telebot-3.3.8/payments_data.go000066400000000000000000000432411465437035100163570ustar00rootroot00000000000000package telebot import "encoding/json" const dataCurrencies = `{"AED":{"code":"AED","title":"United Arab Emirates Dirham","symbol":"AED","native":"\u062f.\u0625.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"367","max_amount":"3673200"},"AFN":{"code":"AFN","title":"Afghan Afghani","symbol":"AFN","native":"\u060b","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"7554","max_amount":"75540495"},"ALL":{"code":"ALL","title":"Albanian Lek","symbol":"ALL","native":"Lek","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"10908","max_amount":"109085036"},"AMD":{"code":"AMD","title":"Armenian Dram","symbol":"AMD","native":"\u0564\u0580.","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"48398","max_amount":"483984962"},"ARS":{"code":"ARS","title":"Argentine Peso","symbol":"ARS","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3720","max_amount":"37202998"},"AUD":{"code":"AUD","title":"Australian Dollar","symbol":"AU$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"139","max_amount":"1392750"},"AZN":{"code":"AZN","title":"Azerbaijani Manat","symbol":"AZN","native":"\u043c\u0430\u043d.","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"170","max_amount":"1702500"},"BAM":{"code":"BAM","title":"Bosnia & Herzegovina Convertible Mark","symbol":"BAM","native":"KM","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"171","max_amount":"1715550"},"BDT":{"code":"BDT","title":"Bangladeshi Taka","symbol":"BDT","native":"\u09f3","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"8336","max_amount":"83367500"},"BGN":{"code":"BGN","title":"Bulgarian Lev","symbol":"BGN","native":"\u043b\u0432.","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"171","max_amount":"1716850"},"BND":{"code":"BND","title":"Brunei Dollar","symbol":"BND","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"134","max_amount":"1349850"},"BOB":{"code":"BOB","title":"Bolivian Boliviano","symbol":"BOB","native":"Bs","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"687","max_amount":"6877150"},"BRL":{"code":"BRL","title":"Brazilian Real","symbol":"R$","native":"R$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"377","max_amount":"3775397"},"CAD":{"code":"CAD","title":"Canadian Dollar","symbol":"CA$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"132","max_amount":"1321950"},"CHF":{"code":"CHF","title":"Swiss Franc","symbol":"CHF","native":"CHF","thousands_sep":"'","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"99","max_amount":"993220"},"CLP":{"code":"CLP","title":"Chilean Peso","symbol":"CLP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"666","max_amount":"6665199"},"CNY":{"code":"CNY","title":"Chinese Renminbi Yuan","symbol":"CN\u00a5","native":"CN\u00a5","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"674","max_amount":"6747298"},"COP":{"code":"COP","title":"Colombian Peso","symbol":"COP","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"315595","max_amount":"3155950000"},"CRC":{"code":"CRC","title":"Costa Rican Col\u00f3n","symbol":"CRC","native":"\u20a1","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"60113","max_amount":"601130282"},"CZK":{"code":"CZK","title":"Czech Koruna","symbol":"CZK","native":"K\u010d","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"2251","max_amount":"22510978"},"DKK":{"code":"DKK","title":"Danish Krone","symbol":"DKK","native":"kr","thousands_sep":"","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"654","max_amount":"6545403"},"DOP":{"code":"DOP","title":"Dominican Peso","symbol":"DOP","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5032","max_amount":"50329504"},"DZD":{"code":"DZD","title":"Algerian Dinar","symbol":"DZD","native":"\u062f.\u062c.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"11872","max_amount":"118729869"},"EGP":{"code":"EGP","title":"Egyptian Pound","symbol":"EGP","native":"\u062c.\u0645.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1791","max_amount":"17912012"},"EUR":{"code":"EUR","title":"Euro","symbol":"\u20ac","native":"\u20ac","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"87","max_amount":"877155"},"GBP":{"code":"GBP","title":"British Pound","symbol":"\u00a3","native":"\u00a3","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"75","max_amount":"757605"},"GEL":{"code":"GEL","title":"Georgian Lari","symbol":"GEL","native":"GEL","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"266","max_amount":"2663750"},"GTQ":{"code":"GTQ","title":"Guatemalan Quetzal","symbol":"GTQ","native":"Q","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"768","max_amount":"7689850"},"HKD":{"code":"HKD","title":"Hong Kong Dollar","symbol":"HK$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"784","max_amount":"7845505"},"HNL":{"code":"HNL","title":"Honduran Lempira","symbol":"HNL","native":"L","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"2427","max_amount":"24277502"},"HRK":{"code":"HRK","title":"Croatian Kuna","symbol":"HRK","native":"kn","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"650","max_amount":"6506302"},"HUF":{"code":"HUF","title":"Hungarian Forint","symbol":"HUF","native":"Ft","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"27844","max_amount":"278440341"},"IDR":{"code":"IDR","title":"Indonesian Rupiah","symbol":"IDR","native":"Rp","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1406555","max_amount":"14065550000"},"ILS":{"code":"ILS","title":"Israeli New Sheqel","symbol":"\u20aa","native":"\u20aa","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"366","max_amount":"3668230"},"INR":{"code":"INR","title":"Indian Rupee","symbol":"\u20b9","native":"\u20b9","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"7090","max_amount":"70900503"},"ISK":{"code":"ISK","title":"Icelandic Kr\u00f3na","symbol":"ISK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"119","max_amount":"1195599"},"JMD":{"code":"JMD","title":"Jamaican Dollar","symbol":"JMD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"13153","max_amount":"131539958"},"JPY":{"code":"JPY","title":"Japanese Yen","symbol":"\u00a5","native":"\uffe5","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"109","max_amount":"1095549"},"KES":{"code":"KES","title":"Kenyan Shilling","symbol":"KES","native":"Ksh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"10032","max_amount":"100322011"},"KGS":{"code":"KGS","title":"Kyrgyzstani Som","symbol":"KGS","native":"KGS","thousands_sep":"\u00a0","decimal_sep":"-","symbol_left":false,"space_between":true,"exp":2,"min_amount":"6982","max_amount":"69820300"},"KRW":{"code":"KRW","title":"South Korean Won","symbol":"\u20a9","native":"\u20a9","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"1119","max_amount":"11190001"},"KZT":{"code":"KZT","title":"Kazakhstani Tenge","symbol":"KZT","native":"\u20b8","thousands_sep":"\u00a0","decimal_sep":"-","symbol_left":true,"space_between":false,"exp":2,"min_amount":"37767","max_amount":"377674954"},"LBP":{"code":"LBP","title":"Lebanese Pound","symbol":"LBP","native":"\u0644.\u0644.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"150080","max_amount":"1500802255"},"LKR":{"code":"LKR","title":"Sri Lankan Rupee","symbol":"LKR","native":"\u0dbb\u0dd4.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"18078","max_amount":"180789638"},"MAD":{"code":"MAD","title":"Moroccan Dirham","symbol":"MAD","native":"\u062f.\u0645.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"955","max_amount":"9554850"},"MDL":{"code":"MDL","title":"Moldovan Leu","symbol":"MDL","native":"MDL","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1703","max_amount":"17038967"},"MNT":{"code":"MNT","title":"Mongolian T\u00f6gr\u00f6g","symbol":"MNT","native":"MNT","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":true,"space_between":false,"exp":2,"min_amount":"261750","max_amount":"2617500000"},"MUR":{"code":"MUR","title":"Mauritian Rupee","symbol":"MUR","native":"MUR","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3438","max_amount":"34384499"},"MVR":{"code":"MVR","title":"Maldivian Rufiyaa","symbol":"MVR","native":"MVR","thousands_sep":",","decimal_sep":".","symbol_left":false,"space_between":true,"exp":2,"min_amount":"1550","max_amount":"15501063"},"MXN":{"code":"MXN","title":"Mexican Peso","symbol":"MX$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"1898","max_amount":"18988704"},"MYR":{"code":"MYR","title":"Malaysian Ringgit","symbol":"MYR","native":"RM","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"412","max_amount":"4124501"},"MZN":{"code":"MZN","title":"Mozambican Metical","symbol":"MZN","native":"MTn","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"6188","max_amount":"61889913"},"NGN":{"code":"NGN","title":"Nigerian Naira","symbol":"NGN","native":"\u20a6","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"36174","max_amount":"361749532"},"NIO":{"code":"NIO","title":"Nicaraguan C\u00f3rdoba","symbol":"NIO","native":"C$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3241","max_amount":"32415503"},"NOK":{"code":"NOK","title":"Norwegian Krone","symbol":"NOK","native":"kr","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"851","max_amount":"8510100"},"NPR":{"code":"NPR","title":"Nepalese Rupee","symbol":"NPR","native":"\u0928\u0947\u0930\u0942","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"11299","max_amount":"112995016"},"NZD":{"code":"NZD","title":"New Zealand Dollar","symbol":"NZ$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"146","max_amount":"1461850"},"PAB":{"code":"PAB","title":"Panamanian Balboa","symbol":"PAB","native":"B\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"99","max_amount":"995290"},"PEN":{"code":"PEN","title":"Peruvian Nuevo Sol","symbol":"PEN","native":"S\/.","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"333","max_amount":"3331250"},"PHP":{"code":"PHP","title":"Philippine Peso","symbol":"PHP","native":"\u20b1","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"5260","max_amount":"52602981"},"PKR":{"code":"PKR","title":"Pakistani Rupee","symbol":"PKR","native":"\u20a8","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"13921","max_amount":"139214990"},"PLN":{"code":"PLN","title":"Polish Z\u0142oty","symbol":"PLN","native":"z\u0142","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"376","max_amount":"3764026"},"PYG":{"code":"PYG","title":"Paraguayan Guaran\u00ed","symbol":"PYG","native":"\u20b2","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":0,"min_amount":"6013","max_amount":"60134502"},"QAR":{"code":"QAR","title":"Qatari Riyal","symbol":"QAR","native":"\u0631.\u0642.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"364","max_amount":"3641101"},"RON":{"code":"RON","title":"Romanian Leu","symbol":"RON","native":"RON","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"417","max_amount":"4172003"},"RSD":{"code":"RSD","title":"Serbian Dinar","symbol":"RSD","native":"\u0434\u0438\u043d.","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"10391","max_amount":"103910127"},"RUB":{"code":"RUB","title":"Russian Ruble","symbol":"RUB","native":"\u0440\u0443\u0431.","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"6598","max_amount":"65986027"},"SAR":{"code":"SAR","title":"Saudi Riyal","symbol":"SAR","native":"\u0631.\u0633.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"373","max_amount":"3732650"},"SEK":{"code":"SEK","title":"Swedish Krona","symbol":"SEK","native":"kr","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"904","max_amount":"9047896"},"SGD":{"code":"SGD","title":"Singapore Dollar","symbol":"SGD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"135","max_amount":"1353897"},"THB":{"code":"THB","title":"Thai Baht","symbol":"\u0e3f","native":"\u0e3f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3156","max_amount":"31563499"},"TJS":{"code":"TJS","title":"Tajikistani Somoni","symbol":"TJS","native":"TJS","thousands_sep":"\u00a0","decimal_sep":";","symbol_left":false,"space_between":true,"exp":2,"min_amount":"938","max_amount":"9389950"},"TRY":{"code":"TRY","title":"Turkish Lira","symbol":"TRY","native":"TL","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"526","max_amount":"5267200"},"TTD":{"code":"TTD","title":"Trinidad and Tobago Dollar","symbol":"TTD","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"675","max_amount":"6757850"},"TWD":{"code":"TWD","title":"New Taiwan Dollar","symbol":"NT$","native":"NT$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"3072","max_amount":"30722993"},"TZS":{"code":"TZS","title":"Tanzanian Shilling","symbol":"TZS","native":"TSh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"230200","max_amount":"2302000188"},"UAH":{"code":"UAH","title":"Ukrainian Hryvnia","symbol":"UAH","native":"\u20b4","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":false,"exp":2,"min_amount":"2764","max_amount":"27648991"},"UGX":{"code":"UGX","title":"Ugandan Shilling","symbol":"UGX","native":"USh","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":0,"min_amount":"3657","max_amount":"36575502"},"USD":{"code":"USD","title":"United States Dollar","symbol":"$","native":"$","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":false,"exp":2,"min_amount":"100","max_amount":1000000},"UYU":{"code":"UYU","title":"Uruguayan Peso","symbol":"UYU","native":"$","thousands_sep":".","decimal_sep":",","symbol_left":true,"space_between":true,"exp":2,"min_amount":"3246","max_amount":"32469503"},"UZS":{"code":"UZS","title":"Uzbekistani Som","symbol":"UZS","native":"UZS","thousands_sep":"\u00a0","decimal_sep":",","symbol_left":false,"space_between":true,"exp":2,"min_amount":"832759","max_amount":"8327599915"},"VND":{"code":"VND","title":"Vietnamese \u0110\u1ed3ng","symbol":"\u20ab","native":"\u20ab","thousands_sep":".","decimal_sep":",","symbol_left":false,"space_between":true,"exp":0,"min_amount":"23084","max_amount":"230840500"},"YER":{"code":"YER","title":"Yemeni Rial","symbol":"YER","native":"\u0631.\u064a.\u200f","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"25030","max_amount":"250301249"},"ZAR":{"code":"ZAR","title":"South African Rand","symbol":"ZAR","native":"R","thousands_sep":",","decimal_sep":".","symbol_left":true,"space_between":true,"exp":2,"min_amount":"1362","max_amount":"13620106"}}` var SupportedCurrencies = make(map[string]Currency) func init() { err := json.Unmarshal([]byte(dataCurrencies), &SupportedCurrencies) if err != nil { panic(err) } } telebot-3.3.8/poll.go000066400000000000000000000041571465437035100144770ustar00rootroot00000000000000package telebot import "time" // PollType defines poll types. type PollType string const ( // NOTE: // Despite "any" type isn't described in documentation, // it needed for proper KeyboardButtonPollType marshaling. PollAny PollType = "any" PollQuiz PollType = "quiz" PollRegular PollType = "regular" ) // Poll contains information about a poll. type Poll struct { ID string `json:"id"` Type PollType `json:"type"` Question string `json:"question"` Options []PollOption `json:"options"` VoterCount int `json:"total_voter_count"` // (Optional) Closed bool `json:"is_closed,omitempty"` CorrectOption int `json:"correct_option_id,omitempty"` MultipleAnswers bool `json:"allows_multiple_answers,omitempty"` Explanation string `json:"explanation,omitempty"` ParseMode ParseMode `json:"explanation_parse_mode,omitempty"` Entities []MessageEntity `json:"explanation_entities"` // True by default, shouldn't be omitted. Anonymous bool `json:"is_anonymous"` // (Mutually exclusive) OpenPeriod int `json:"open_period,omitempty"` CloseUnixdate int64 `json:"close_date,omitempty"` } // PollOption contains information about one answer option in a poll. type PollOption struct { Text string `json:"text"` VoterCount int `json:"voter_count"` } // PollAnswer represents an answer of a user in a non-anonymous poll. type PollAnswer struct { PollID string `json:"poll_id"` Sender *User `json:"user"` Chat *Chat `json:"voter_chat"` Options []int `json:"option_ids"` } // IsRegular says whether poll is a regular. func (p *Poll) IsRegular() bool { return p.Type == PollRegular } // IsQuiz says whether poll is a quiz. func (p *Poll) IsQuiz() bool { return p.Type == PollQuiz } // CloseDate returns the close date of poll in local time. func (p *Poll) CloseDate() time.Time { return time.Unix(p.CloseUnixdate, 0) } // AddOptions adds text options to the poll. func (p *Poll) AddOptions(opts ...string) { for _, t := range opts { p.Options = append(p.Options, PollOption{Text: t}) } } telebot-3.3.8/poll_test.go000066400000000000000000000024751465437035100155370ustar00rootroot00000000000000package telebot import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestPoll(t *testing.T) { assert.True(t, (&Poll{Type: PollRegular}).IsRegular()) assert.True(t, (&Poll{Type: PollQuiz}).IsQuiz()) p := &Poll{} opts := []PollOption{{Text: "Option 1"}, {Text: "Option 2"}} p.AddOptions(opts[0].Text, opts[1].Text) assert.Equal(t, opts, p.Options) } func TestPollSend(t *testing.T) { if b == nil { t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)") } if userID == 0 { t.Skip("USER_ID is required for Poll methods test") } _, err := b.Send(user, &Poll{}) // empty poll assert.Equal(t, ErrBadPollOptions, err) poll := &Poll{ Type: PollQuiz, Question: "Test Poll", CloseUnixdate: time.Now().Unix() + 60, Explanation: "Explanation", } poll.AddOptions("1", "2") msg, err := b.Send(user, poll) require.NoError(t, err) assert.Equal(t, poll.Type, msg.Poll.Type) assert.Equal(t, poll.Question, msg.Poll.Question) assert.Equal(t, poll.Options, msg.Poll.Options) assert.Equal(t, poll.CloseUnixdate, msg.Poll.CloseUnixdate) assert.Equal(t, poll.CloseDate(), msg.Poll.CloseDate()) p, err := b.StopPoll(msg) require.NoError(t, err) assert.Equal(t, poll.Options, p.Options) assert.Equal(t, 0, p.VoterCount) } telebot-3.3.8/poller.go000066400000000000000000000054551465437035100150300ustar00rootroot00000000000000package telebot import "time" var AllowedUpdates = []string{ "message", "edited_message", "channel_post", "edited_channel_post", "message_reaction", "message_reaction_count", "inline_query", "chosen_inline_result", "callback_query", "shipping_query", "pre_checkout_query", "poll", "poll_answer", "my_chat_member", "chat_member", "chat_join_request", "chat_boost", "removed_chat_boost", } // Poller is a provider of Updates. // // All pollers must implement Poll(), which accepts bot // pointer and subscription channel and start polling // synchronously straight away. type Poller interface { // Poll is supposed to take the bot object // subscription channel and start polling // for Updates immediately. // // Poller must listen for stop constantly and close // it as soon as it's done polling. Poll(b *Bot, updates chan Update, stop chan struct{}) } // LongPoller is a classic LongPoller with timeout. type LongPoller struct { Limit int Timeout time.Duration LastUpdateID int // AllowedUpdates contains the update types // you want your bot to receive. // // Possible values: // message // edited_message // channel_post // edited_channel_post // inline_query // chosen_inline_result // callback_query // shipping_query // pre_checkout_query // poll // poll_answer // AllowedUpdates []string `yaml:"allowed_updates"` } // Poll does long polling. func (p *LongPoller) Poll(b *Bot, dest chan Update, stop chan struct{}) { for { select { case <-stop: return default: } updates, err := b.getUpdates(p.LastUpdateID+1, p.Limit, p.Timeout, p.AllowedUpdates) if err != nil { b.debug(err) continue } for _, update := range updates { p.LastUpdateID = update.ID dest <- update } } } // MiddlewarePoller is a special kind of poller that acts // like a filter for updates. It could be used for spam // handling, banning or whatever. // // For heavy middleware, use increased capacity. type MiddlewarePoller struct { Capacity int // Default: 1 Poller Poller Filter func(*Update) bool } // NewMiddlewarePoller wait for it... constructs a new middleware poller. func NewMiddlewarePoller(original Poller, filter func(*Update) bool) *MiddlewarePoller { return &MiddlewarePoller{ Poller: original, Filter: filter, } } // Poll sieves updates through middleware filter. func (p *MiddlewarePoller) Poll(b *Bot, dest chan Update, stop chan struct{}) { if p.Capacity < 1 { p.Capacity = 1 } middle := make(chan Update, p.Capacity) stopPoller := make(chan struct{}) stopConfirm := make(chan struct{}) go func() { p.Poller.Poll(b, middle, stopPoller) close(stopConfirm) }() for { select { case <-stop: close(stopPoller) <-stopConfirm return case upd := <-middle: if p.Filter(&upd) { dest <- upd } } } } telebot-3.3.8/poller_test.go000066400000000000000000000020021465437035100160500ustar00rootroot00000000000000package telebot import ( "testing" "github.com/stretchr/testify/assert" ) type testPoller struct { updates chan Update done chan struct{} } func newTestPoller() *testPoller { return &testPoller{ updates: make(chan Update, 1), done: make(chan struct{}, 1), } } func (p *testPoller) Poll(b *Bot, updates chan Update, stop chan struct{}) { for { select { case upd := <-p.updates: updates <- upd case <-stop: return default: } } } func TestMiddlewarePoller(t *testing.T) { tp := newTestPoller() var ids []int pref := defaultSettings() pref.Offline = true b, err := NewBot(pref) if err != nil { t.Fatal(err) } b.Poller = NewMiddlewarePoller(tp, func(u *Update) bool { if u.ID > 0 { ids = append(ids, u.ID) return true } tp.done <- struct{}{} return false }) go func() { tp.updates <- Update{ID: 1} tp.updates <- Update{ID: 2} tp.updates <- Update{ID: 0} }() go b.Start() <-tp.done b.Stop() assert.Contains(t, ids, 1) assert.Contains(t, ids, 2) } telebot-3.3.8/react.go000066400000000000000000000032231465437035100146200ustar00rootroot00000000000000package telebot import ( "encoding/json" ) // Reaction describes the type of reaction. // Describes an instance of ReactionTypeCustomEmoji and ReactionTypeEmoji. type Reaction struct { // Type of the reaction, always “emoji” Type string `json:"type"` // Reaction emoji. Emoji string `json:"emoji,omitempty"` // Custom emoji identifier. CustomEmoji string `json:"custom_emoji_id,omitempty"` } // ReactionCount represents a reaction added to a message along // with the number of times it was added. type ReactionCount struct { // Type of the reaction. Type Reaction `json:"type"` // Number of times the reaction was added. Count int `json:"total_count"` } // ReactionOptions represents an object of reaction options. type ReactionOptions struct { // List of reaction types to set on the message. Reactions []Reaction `json:"reaction"` // Pass True to set the reaction with a big animation. Big bool `json:"is_big"` } // React changes the chosen reactions on a message. Service messages can't be // reacted to. Automatically forwarded messages from a channel to its discussion group have // the same available reactions as messages in the channel. func (b *Bot) React(to Recipient, msg Editable, opts ...ReactionOptions) error { if to == nil { return ErrBadRecipient } msgID, _ := msg.MessageSig() params := map[string]string{ "chat_id": to.Recipient(), "message_id": msgID, } if len(opts) > 0 { opt := opts[0] if len(opt.Reactions) > 0 { data, _ := json.Marshal(opt.Reactions) params["reaction"] = string(data) } if opt.Big { params["is_big"] = "true" } } _, err := b.Raw("setMessageReaction", params) return err } telebot-3.3.8/react/000077500000000000000000000000001465437035100142715ustar00rootroot00000000000000telebot-3.3.8/react/react.go000066400000000000000000000077371465437035100157340ustar00rootroot00000000000000package react import ( tele "gopkg.in/telebot.v3" ) type Reaction = tele.Reaction func React(r ...Reaction) tele.ReactionOptions { return tele.ReactionOptions{Reactions: r} } // Currently available emojis. var ( ThumbUp = Reaction{Emoji: "👍"} ThumbDown = Reaction{Emoji: "👎"} Heart = Reaction{Emoji: "❤"} Fire = Reaction{Emoji: "🔥"} HeartEyes = Reaction{Emoji: "😍"} ClappingHands = Reaction{Emoji: "👏"} GrinningFace = Reaction{Emoji: "😁"} ThinkingFace = Reaction{Emoji: "🤔"} ExplodingHead = Reaction{Emoji: "🤯"} ScreamingFace = Reaction{Emoji: "😱"} SwearingFace = Reaction{Emoji: "🤬"} CryingFace = Reaction{Emoji: "😢"} PartyPopper = Reaction{Emoji: "🎉"} StarStruck = Reaction{Emoji: "🤩"} VomitingFace = Reaction{Emoji: "🤮"} PileOfPoo = Reaction{Emoji: "💩"} PrayingHands = Reaction{Emoji: "🙏"} OkHand = Reaction{Emoji: "👌"} DoveOfPeace = Reaction{Emoji: "🕊"} ClownFace = Reaction{Emoji: "🤡"} YawningFace = Reaction{Emoji: "🥱"} WoozyFace = Reaction{Emoji: "🥴"} Whale = Reaction{Emoji: "🐳"} HeartOnFire = Reaction{Emoji: "❤‍🔥"} MoonFace = Reaction{Emoji: "🌚"} HotDog = Reaction{Emoji: "🌭"} HundredPoints = Reaction{Emoji: "💯"} RollingOnTheFloorLaughing = Reaction{Emoji: "🤣"} Lightning = Reaction{Emoji: "⚡"} Banana = Reaction{Emoji: "🍌"} Trophy = Reaction{Emoji: "🏆"} BrokenHeart = Reaction{Emoji: "💔"} FaceWithRaisedEyebrow = Reaction{Emoji: "🤨"} NeutralFace = Reaction{Emoji: "😐"} Strawberry = Reaction{Emoji: "🍓"} Champagne = Reaction{Emoji: "🍾"} KissMark = Reaction{Emoji: "💋"} MiddleFinger = Reaction{Emoji: "🖕"} EvilFace = Reaction{Emoji: "😈"} SleepingFace = Reaction{Emoji: "😴"} LoudlyCryingFace = Reaction{Emoji: "😭"} NerdFace = Reaction{Emoji: "🤓"} Ghost = Reaction{Emoji: "👻"} Engineer = Reaction{Emoji: "👨‍💻"} Eyes = Reaction{Emoji: "👀"} JackOLantern = Reaction{Emoji: "🎃"} NoMonkey = Reaction{Emoji: "🙈"} SmilingFaceWithHalo = Reaction{Emoji: "😇"} FearfulFace = Reaction{Emoji: "😨"} Handshake = Reaction{Emoji: "🤝"} WritingHand = Reaction{Emoji: "✍"} HuggingFace = Reaction{Emoji: "🤗"} Brain = Reaction{Emoji: "🫡"} SantaClaus = Reaction{Emoji: "🎅"} ChristmasTree = Reaction{Emoji: "🎄"} Snowman = Reaction{Emoji: "☃"} NailPolish = Reaction{Emoji: "💅"} ZanyFace = Reaction{Emoji: "🤪"} Moai = Reaction{Emoji: "🗿"} Cool = Reaction{Emoji: "🆒"} HeartWithArrow = Reaction{Emoji: "💘"} HearMonkey = Reaction{Emoji: "🙉"} Unicorn = Reaction{Emoji: "🦄"} FaceBlowingKiss = Reaction{Emoji: "😘"} Pill = Reaction{Emoji: "💊"} SpeaklessMonkey = Reaction{Emoji: "🙊"} Sunglasses = Reaction{Emoji: "😎"} AlienMonster = Reaction{Emoji: "👾"} ManShrugging = Reaction{Emoji: "🤷‍♂️"} PersonShrugging = Reaction{Emoji: "🤷"} WomanShrugging = Reaction{Emoji: "🤷‍♀️"} PoutingFace = Reaction{Emoji: "😡"} ) telebot-3.3.8/sendable.go000066400000000000000000000233231465437035100153020ustar00rootroot00000000000000package telebot import ( "encoding/json" "fmt" "path/filepath" "strconv" ) // Recipient is any possible endpoint you can send // messages to: either user, group or a channel. type Recipient interface { Recipient() string // must return legit Telegram chat_id or username } // Sendable is any object that can send itself. // // This is pretty cool, since it lets bots implement // custom Sendables for complex kind of media or // chat objects spanning across multiple messages. type Sendable interface { Send(*Bot, Recipient, *SendOptions) (*Message, error) } // Send delivers media through bot b to recipient. func (p *Photo) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "caption": p.Caption, } b.embedSendOptions(params, opt) msg, err := b.sendMedia(p, params, nil) if err != nil { return nil, err } msg.Photo.File.stealRef(&p.File) *p = *msg.Photo p.Caption = msg.Caption return msg, nil } // Send delivers media through bot b to recipient. func (a *Audio) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "caption": a.Caption, "performer": a.Performer, "title": a.Title, "file_name": a.FileName, } b.embedSendOptions(params, opt) if a.Duration != 0 { params["duration"] = strconv.Itoa(a.Duration) } msg, err := b.sendMedia(a, params, thumbnailToFilemap(a.Thumbnail)) if err != nil { return nil, err } if msg.Audio != nil { msg.Audio.File.stealRef(&a.File) *a = *msg.Audio a.Caption = msg.Caption } if msg.Document != nil { msg.Document.File.stealRef(&a.File) a.File = msg.Document.File } return msg, nil } // Send delivers media through bot b to recipient. func (d *Document) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "caption": d.Caption, "file_name": d.FileName, } b.embedSendOptions(params, opt) if d.FileSize != 0 { params["file_size"] = strconv.FormatInt(d.FileSize, 10) } if d.DisableTypeDetection { params["disable_content_type_detection"] = "true" } msg, err := b.sendMedia(d, params, thumbnailToFilemap(d.Thumbnail)) if err != nil { return nil, err } if doc := msg.Document; doc != nil { doc.File.stealRef(&d.File) *d = *doc d.Caption = msg.Caption } else if vid := msg.Video; vid != nil { vid.File.stealRef(&d.File) d.Caption = vid.Caption d.MIME = vid.MIME d.Thumbnail = vid.Thumbnail } return msg, nil } // Send delivers media through bot b to recipient. func (s *Sticker) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "emoji": s.Emoji, } b.embedSendOptions(params, opt) msg, err := b.sendMedia(s, params, nil) if err != nil { return nil, err } msg.Sticker.File.stealRef(&s.File) *s = *msg.Sticker return msg, nil } // Send delivers media through bot b to recipient. func (v *Video) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "caption": v.Caption, "file_name": v.FileName, } b.embedSendOptions(params, opt) if v.Duration != 0 { params["duration"] = strconv.Itoa(v.Duration) } if v.Width != 0 { params["width"] = strconv.Itoa(v.Width) } if v.Height != 0 { params["height"] = strconv.Itoa(v.Height) } if v.Streaming { params["supports_streaming"] = "true" } msg, err := b.sendMedia(v, params, thumbnailToFilemap(v.Thumbnail)) if err != nil { return nil, err } if vid := msg.Video; vid != nil { vid.File.stealRef(&v.File) *v = *vid v.Caption = msg.Caption } else if doc := msg.Document; doc != nil { // If video has no sound, Telegram can turn it into Document (GIF) doc.File.stealRef(&v.File) v.Caption = doc.Caption v.MIME = doc.MIME v.Thumbnail = doc.Thumbnail } return msg, nil } // Send delivers animation through bot b to recipient. func (a *Animation) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "caption": a.Caption, "file_name": a.FileName, } b.embedSendOptions(params, opt) if a.Duration != 0 { params["duration"] = strconv.Itoa(a.Duration) } if a.Width != 0 { params["width"] = strconv.Itoa(a.Width) } if a.Height != 0 { params["height"] = strconv.Itoa(a.Height) } // file_name is required, without it animation sends as a document if params["file_name"] == "" && a.File.OnDisk() { params["file_name"] = filepath.Base(a.File.FileLocal) } msg, err := b.sendMedia(a, params, thumbnailToFilemap(a.Thumbnail)) if err != nil { return nil, err } if anim := msg.Animation; anim != nil { anim.File.stealRef(&a.File) *a = *msg.Animation } else if doc := msg.Document; doc != nil { *a = Animation{ File: doc.File, Thumbnail: doc.Thumbnail, MIME: doc.MIME, FileName: doc.FileName, } } a.Caption = msg.Caption return msg, nil } // Send delivers media through bot b to recipient. func (v *Voice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "caption": v.Caption, } b.embedSendOptions(params, opt) if v.Duration != 0 { params["duration"] = strconv.Itoa(v.Duration) } msg, err := b.sendMedia(v, params, nil) if err != nil { return nil, err } msg.Voice.File.stealRef(&v.File) *v = *msg.Voice return msg, nil } // Send delivers media through bot b to recipient. func (v *VideoNote) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), } b.embedSendOptions(params, opt) if v.Duration != 0 { params["duration"] = strconv.Itoa(v.Duration) } if v.Length != 0 { params["length"] = strconv.Itoa(v.Length) } msg, err := b.sendMedia(v, params, thumbnailToFilemap(v.Thumbnail)) if err != nil { return nil, err } msg.VideoNote.File.stealRef(&v.File) *v = *msg.VideoNote return msg, nil } // Send delivers media through bot b to recipient. func (x *Location) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "latitude": fmt.Sprintf("%f", x.Lat), "longitude": fmt.Sprintf("%f", x.Lng), "live_period": strconv.Itoa(x.LivePeriod), } if x.HorizontalAccuracy != nil { params["horizontal_accuracy"] = fmt.Sprintf("%f", *x.HorizontalAccuracy) } if x.Heading != 0 { params["heading"] = strconv.Itoa(x.Heading) } if x.AlertRadius != 0 { params["proximity_alert_radius"] = strconv.Itoa(x.Heading) } b.embedSendOptions(params, opt) data, err := b.Raw("sendLocation", params) if err != nil { return nil, err } return extractMessage(data) } // Send delivers media through bot b to recipient. func (v *Venue) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "latitude": fmt.Sprintf("%f", v.Location.Lat), "longitude": fmt.Sprintf("%f", v.Location.Lng), "title": v.Title, "address": v.Address, "foursquare_id": v.FoursquareID, "foursquare_type": v.FoursquareType, "google_place_id": v.GooglePlaceID, "google_place_type": v.GooglePlaceType, } b.embedSendOptions(params, opt) data, err := b.Raw("sendVenue", params) if err != nil { return nil, err } return extractMessage(data) } // Send delivers invoice through bot b to recipient. func (i *Invoice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := i.params() params["chat_id"] = to.Recipient() b.embedSendOptions(params, opt) data, err := b.Raw("sendInvoice", params) if err != nil { return nil, err } return extractMessage(data) } // Send delivers poll through bot b to recipient. func (p *Poll) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "question": p.Question, "type": string(p.Type), "is_closed": strconv.FormatBool(p.Closed), "is_anonymous": strconv.FormatBool(p.Anonymous), "allows_multiple_answers": strconv.FormatBool(p.MultipleAnswers), "correct_option_id": strconv.Itoa(p.CorrectOption), } if p.Explanation != "" { params["explanation"] = p.Explanation params["explanation_parse_mode"] = p.ParseMode } if p.OpenPeriod != 0 { params["open_period"] = strconv.Itoa(p.OpenPeriod) } else if p.CloseUnixdate != 0 { params["close_date"] = strconv.FormatInt(p.CloseUnixdate, 10) } b.embedSendOptions(params, opt) var options []string for _, o := range p.Options { options = append(options, o.Text) } opts, _ := json.Marshal(options) params["options"] = string(opts) data, err := b.Raw("sendPoll", params) if err != nil { return nil, err } return extractMessage(data) } // Send delivers dice through bot b to recipient. func (d *Dice) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "emoji": string(d.Type), } b.embedSendOptions(params, opt) data, err := b.Raw("sendDice", params) if err != nil { return nil, err } return extractMessage(data) } // Send delivers game through bot b to recipient. func (g *Game) Send(b *Bot, to Recipient, opt *SendOptions) (*Message, error) { params := map[string]string{ "chat_id": to.Recipient(), "game_short_name": g.Name, } b.embedSendOptions(params, opt) data, err := b.Raw("sendGame", params) if err != nil { return nil, err } return extractMessage(data) } func thumbnailToFilemap(thumb *Photo) map[string]File { if thumb != nil { return map[string]File{"thumbnail": thumb.File} } return nil } telebot-3.3.8/sticker.go000066400000000000000000000175431465437035100152000ustar00rootroot00000000000000package telebot import ( "encoding/json" "errors" "fmt" "strconv" ) type ( StickerSetType = string StickerSetFormat = string MaskFeature = string ) const ( StickerRegular StickerSetType = "regular" StickerMask StickerSetType = "mask" StickerCustomEmoji StickerSetType = "custom_emoji" ) const ( StickerStatic StickerSetFormat = "static" StickerAnimated StickerSetFormat = "animated" StickerVideo StickerSetFormat = "video" ) const ( MaskForehead MaskFeature = "forehead" MaskEyes MaskFeature = "eyes" MaskMouth MaskFeature = "mouth" MaskChin MaskFeature = "chin" ) // StickerSet represents a sticker set. type StickerSet struct { Type StickerSetType `json:"sticker_type"` Format StickerSetFormat `json:"sticker_format"` Name string `json:"name"` Title string `json:"title"` Animated bool `json:"is_animated"` Video bool `json:"is_video"` Stickers []Sticker `json:"stickers"` Thumbnail *Photo `json:"thumbnail"` Emojis string `json:"emojis"` ContainsMasks bool `json:"contains_masks"` // FIXME: can be removed MaskPosition *MaskPosition `json:"mask_position"` Repaint bool `json:"needs_repainting"` // Input is a field used in createNewStickerSet method to specify a list // of pre-defined stickers of type InputSticker to add to the set. Input []InputSticker } type InputSticker struct { File Sticker string `json:"sticker"` MaskPosition *MaskPosition `json:"mask_position"` Emojis []string `json:"emoji_list"` Keywords []string `json:"keywords"` } // MaskPosition describes the position on faces where // a mask should be placed by default. type MaskPosition struct { Feature MaskFeature `json:"point"` XShift float32 `json:"x_shift"` YShift float32 `json:"y_shift"` Scale float32 `json:"scale"` } // UploadSticker uploads a sticker file for later use. func (b *Bot) UploadSticker(to Recipient, format StickerSetFormat, f File) (*File, error) { params := map[string]string{ "user_id": to.Recipient(), "sticker_format": format, } data, err := b.sendFiles("uploadStickerFile", map[string]File{"0": f}, params) if err != nil { return nil, err } var resp struct { Result File } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return &resp.Result, nil } // StickerSet returns a sticker set on success. func (b *Bot) StickerSet(name string) (*StickerSet, error) { data, err := b.Raw("getStickerSet", map[string]string{"name": name}) if err != nil { return nil, err } var resp struct { Result *StickerSet } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // CreateStickerSet creates a new sticker set. func (b *Bot) CreateStickerSet(of Recipient, set *StickerSet) error { files := make(map[string]File) for i, s := range set.Input { repr := s.File.process(strconv.Itoa(i), files) if repr == "" { return fmt.Errorf("telebot: sticker #%d does not exist", i+1) } set.Input[i].Sticker = repr } data, _ := json.Marshal(set.Input) params := map[string]string{ "user_id": of.Recipient(), "name": set.Name, "title": set.Title, "sticker_format": set.Format, "stickers": string(data), } if set.Type != "" { params["sticker_type"] = set.Type } if set.Repaint { params["needs_repainting"] = "true" } _, err := b.sendFiles("createNewStickerSet", files, params) return err } // AddStickerToSet adds a new sticker to the existing sticker set. func (b *Bot) AddStickerToSet(of Recipient, name string, sticker InputSticker) error { files := make(map[string]File) repr := sticker.File.process("0", files) if repr == "" { return errors.New("telebot: sticker does not exist") } sticker.Sticker = repr data, _ := json.Marshal(sticker) params := map[string]string{ "user_id": of.Recipient(), "name": name, "sticker": string(data), } _, err := b.sendFiles("addStickerToSet", files, params) return err } // SetStickerPosition moves a sticker in set to a specific position. func (b *Bot) SetStickerPosition(sticker string, position int) error { params := map[string]string{ "sticker": sticker, "position": strconv.Itoa(position), } _, err := b.Raw("setStickerPositionInSet", params) return err } // DeleteSticker deletes a sticker from a set created by the bot. func (b *Bot) DeleteSticker(sticker string) error { _, err := b.Raw("deleteStickerFromSet", map[string]string{"sticker": sticker}) return err } // SetStickerSetThumb sets a thumbnail of the sticker set. // Animated thumbnails can be set for animated sticker sets only. // // Thumbnail must be a PNG image, up to 128 kilobytes in size // and have width and height exactly 100px, or a TGS animation // up to 32 kilobytes in size. // // Animated sticker set thumbnail can't be uploaded via HTTP URL. func (b *Bot) SetStickerSetThumb(of Recipient, set *StickerSet) error { if set.Thumbnail == nil { return errors.New("telebot: thumbnail is required") } files := make(map[string]File) repr := set.Thumbnail.File.process("thumb", files) if repr == "" { return errors.New("telebot: thumbnail does not exist") } params := map[string]string{ "user_id": of.Recipient(), "name": set.Name, "thumbnail": repr, } _, err := b.sendFiles("setStickerSetThumbnail", files, params) return err } // SetStickerSetTitle sets the title of a created sticker set. func (b *Bot) SetStickerSetTitle(s StickerSet) error { params := map[string]string{ "name": s.Name, "title": s.Title, } _, err := b.Raw("setStickerSetTitle", params) return err } // DeleteStickerSet deletes a sticker set that was created by the bot. func (b *Bot) DeleteStickerSet(name string) error { params := map[string]string{"name": name} _, err := b.Raw("deleteStickerSet", params) return err } // SetStickerEmojis changes the list of emoji assigned to a regular or custom emoji sticker. func (b *Bot) SetStickerEmojis(sticker string, emojis []string) error { data, err := json.Marshal(emojis) if err != nil { return err } params := map[string]string{ "sticker": sticker, "emoji_list": string(data), } _, err = b.Raw("setStickerEmojiList", params) return err } // SetStickerKeywords changes search keywords assigned to a regular or custom emoji sticker. func (b *Bot) SetStickerKeywords(sticker string, keywords []string) error { mk, err := json.Marshal(keywords) if err != nil { return err } params := map[string]string{ "sticker": sticker, "keywords": string(mk), } _, err = b.Raw("setStickerKeywords", params) return err } // SetStickerMaskPosition changes the mask position of a mask sticker. func (b *Bot) SetStickerMaskPosition(sticker string, mask MaskPosition) error { data, err := json.Marshal(mask) if err != nil { return err } params := map[string]string{ "sticker": sticker, "mask_position": string(data), } _, err = b.Raw("setStickerMaskPosition", params) return err } // CustomEmojiStickers returns the information about custom emoji stickers by their ids. func (b *Bot) CustomEmojiStickers(ids []string) ([]Sticker, error) { data, _ := json.Marshal(ids) params := map[string]string{ "custom_emoji_ids": string(data), } data, err := b.Raw("getCustomEmojiStickers", params) if err != nil { return nil, err } var resp struct { Result []Sticker } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // SetCustomEmojiStickerSetThumb sets the thumbnail of a custom emoji sticker set. func (b *Bot) SetCustomEmojiStickerSetThumb(name, id string) error { params := map[string]string{ "name": name, "custom_emoji_id": id, } _, err := b.Raw("setCustomEmojiStickerSetThumbnail", params) return err } telebot-3.3.8/sticker_test.go000066400000000000000000000026321465437035100162300ustar00rootroot00000000000000package telebot import ( "fmt" "testing" "time" "github.com/stretchr/testify/require" ) func TestStickerSet(t *testing.T) { if b == nil { t.Skip("Cached bot instance is bad (probably wrong or empty TELEBOT_SECRET)") } if userID == 0 { t.Skip("USER_ID is required for StickerSet methods test") } input := []InputSticker{ { File: FromURL("https://placehold.co/512/000000/FFFFFF/png"), Emojis: []string{"🤖"}, Keywords: []string{"telebot", "robot", "bot"}, }, { File: FromURL("https://placehold.co/512/000000/999999/png"), Emojis: []string{"🤖"}, Keywords: []string{"telebot", "robot", "bot"}, }, } original := &StickerSet{ Name: fmt.Sprintf("telebot_%d_by_%s", time.Now().Unix(), b.Me.Username), Type: StickerRegular, Format: StickerStatic, Title: "Telebot Stickers", Input: input[:1], } // 1 err := b.CreateStickerSet(user, original) require.NoError(t, err) // 2 err = b.AddStickerToSet(user, original.Name, input[1]) require.NoError(t, err) original.Thumbnail = &Photo{File: thumb} err = b.SetStickerSetThumb(user, original) require.NoError(t, err) set, err := b.StickerSet(original.Name) require.NoError(t, err) require.Equal(t, original.Name, set.Name) require.Equal(t, len(input), len(set.Stickers)) _, err = b.Send(user, &set.Stickers[0]) require.NoError(t, err) _, err = b.Send(user, &set.Stickers[1]) require.NoError(t, err) } telebot-3.3.8/telebot.go000066400000000000000000000114371465437035100151660ustar00rootroot00000000000000// Package telebot is a framework for Telegram bots. // // Example: // // package main // // import ( // "time" // tele "gopkg.in/telebot.v3" // ) // // func main() { // b, err := tele.NewBot(tele.Settings{ // Token: "...", // Poller: &tele.LongPoller{Timeout: 10 * time.Second}, // }) // if err != nil { // return // } // // b.Handle("/start", func(c tele.Context) error { // return c.Send("Hello world!") // }) // // b.Start() // } package telebot import "errors" var ( ErrBadRecipient = errors.New("telebot: recipient is nil") ErrUnsupportedWhat = errors.New("telebot: unsupported what argument") ErrCouldNotUpdate = errors.New("telebot: could not fetch new updates") ErrTrueResult = errors.New("telebot: result is True") ErrBadContext = errors.New("telebot: context does not contain message") ) const DefaultApiURL = "https://api.telegram.org" // These are one of the possible events Handle() can deal with. // // For convenience, all Telebot-provided endpoints start with // an "alert" character \a. const ( // Basic message handlers. OnText = "\atext" OnEdited = "\aedited" OnPhoto = "\aphoto" OnAudio = "\aaudio" OnAnimation = "\aanimation" OnDocument = "\adocument" OnSticker = "\asticker" OnVideo = "\avideo" OnVoice = "\avoice" OnVideoNote = "\avideo_note" OnContact = "\acontact" OnLocation = "\alocation" OnVenue = "\avenue" OnDice = "\adice" OnInvoice = "\ainvoice" OnPayment = "\apayment" OnGame = "\agame" OnPoll = "\apoll" OnPollAnswer = "\apoll_answer" OnPinned = "\apinned" OnChannelPost = "\achannel_post" OnEditedChannelPost = "\aedited_channel_post" OnTopicCreated = "\atopic_created" OnTopicReopened = "\atopic_reopened" OnTopicClosed = "\atopic_closed" OnTopicEdited = "\atopic_edited" OnGeneralTopicHidden = "\ageneral_topic_hidden" OnGeneralTopicUnhidden = "\ageneral_topic_unhidden" OnWriteAccessAllowed = "\awrite_access_allowed" OnAddedToGroup = "\aadded_to_group" OnUserJoined = "\auser_joined" OnUserLeft = "\auser_left" OnUserShared = "\auser_shared" OnChatShared = "\achat_shared" OnNewGroupTitle = "\anew_chat_title" OnNewGroupPhoto = "\anew_chat_photo" OnGroupPhotoDeleted = "\achat_photo_deleted" OnGroupCreated = "\agroup_created" OnSuperGroupCreated = "\asupergroup_created" OnChannelCreated = "\achannel_created" // OnMigration happens when group switches to // a supergroup. You might want to update // your internal references to this chat // upon switching as its ID will change. OnMigration = "\amigration" OnMedia = "\amedia" OnCallback = "\acallback" OnQuery = "\aquery" OnInlineResult = "\ainline_result" OnShipping = "\ashipping_query" OnCheckout = "\apre_checkout_query" OnMyChatMember = "\amy_chat_member" OnChatMember = "\achat_member" OnChatJoinRequest = "\achat_join_request" OnProximityAlert = "\aproximity_alert_triggered" OnAutoDeleteTimer = "\amessage_auto_delete_timer_changed" OnWebApp = "\aweb_app" OnVideoChatStarted = "\avideo_chat_started" OnVideoChatEnded = "\avideo_chat_ended" OnVideoChatParticipants = "\avideo_chat_participants_invited" OnVideoChatScheduled = "\avideo_chat_scheduled" OnBoost = "\aboost_updated" OnBoostRemoved = "\aboost_removed" ) // ChatAction is a client-side status indicating bot activity. type ChatAction string const ( Typing ChatAction = "typing" UploadingPhoto ChatAction = "upload_photo" UploadingVideo ChatAction = "upload_video" UploadingAudio ChatAction = "upload_audio" UploadingDocument ChatAction = "upload_document" UploadingVNote ChatAction = "upload_video_note" RecordingVideo ChatAction = "record_video" RecordingAudio ChatAction = "record_audio" RecordingVNote ChatAction = "record_video_note" FindingLocation ChatAction = "find_location" ChoosingSticker ChatAction = "choose_sticker" ) // ParseMode determines the way client applications treat the text of the message type ParseMode = string const ( ModeDefault ParseMode = "" ModeMarkdown ParseMode = "Markdown" ModeMarkdownV2 ParseMode = "MarkdownV2" ModeHTML ParseMode = "HTML" ) // M is a shortcut for map[string]interface{}. // Useful for passing arguments to the layout functions. type M = map[string]interface{} // Flag returns a pointer to the given bool. // Useful for passing the three-state flags to a Bot API. // For example, see ReplyRecipient type. func Flag(b bool) *bool { return &b } telebot-3.3.8/topic.go000066400000000000000000000116471465437035100146510ustar00rootroot00000000000000package telebot import ( "encoding/json" "strconv" ) type Topic struct { Name string `json:"name"` IconColor int `json:"icon_color"` IconCustomEmoji string `json:"icon_custom_emoji_id"` ThreadID int `json:"message_thread_id"` } // CreateTopic creates a topic in a forum supergroup chat. func (b *Bot) CreateTopic(chat *Chat, topic *Topic) (*Topic, error) { params := map[string]string{ "chat_id": chat.Recipient(), "name": topic.Name, } if topic.IconColor != 0 { params["icon_color"] = strconv.Itoa(topic.IconColor) } if topic.IconCustomEmoji != "" { params["icon_custom_emoji_id"] = topic.IconCustomEmoji } data, err := b.Raw("createForumTopic", params) if err != nil { return nil, err } var resp struct { Result *Topic } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, err } // EditTopic edits name and icon of a topic in a forum supergroup chat. func (b *Bot) EditTopic(chat *Chat, topic *Topic) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "message_thread_id": topic.ThreadID, } if topic.Name != "" { params["name"] = topic.Name } if topic.IconCustomEmoji != "" { params["icon_custom_emoji_id"] = topic.IconCustomEmoji } _, err := b.Raw("editForumTopic", params) return err } // CloseTopic closes an open topic in a forum supergroup chat. func (b *Bot) CloseTopic(chat *Chat, topic *Topic) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "message_thread_id": topic.ThreadID, } _, err := b.Raw("closeForumTopic", params) return err } // ReopenTopic reopens a closed topic in a forum supergroup chat. func (b *Bot) ReopenTopic(chat *Chat, topic *Topic) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "message_thread_id": topic.ThreadID, } _, err := b.Raw("reopenForumTopic", params) return err } // DeleteTopic deletes a forum topic along with all its messages in a forum supergroup chat. func (b *Bot) DeleteTopic(chat *Chat, topic *Topic) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "message_thread_id": topic.ThreadID, } _, err := b.Raw("deleteForumTopic", params) return err } // UnpinAllTopicMessages clears the list of pinned messages in a forum topic. The bot must be an administrator in the chat for this to work and must have the can_pin_messages administrator right in the supergroup. func (b *Bot) UnpinAllTopicMessages(chat *Chat, topic *Topic) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "message_thread_id": topic.ThreadID, } _, err := b.Raw("unpinAllForumTopicMessages", params) return err } // TopicIconStickers gets custom emoji stickers, which can be used as a forum topic icon by any user. func (b *Bot) TopicIconStickers() ([]Sticker, error) { params := map[string]string{} data, err := b.Raw("getForumTopicIconStickers", params) if err != nil { return nil, err } var resp struct { Result []Sticker } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return resp.Result, nil } // EditGeneralTopic edits name of the 'General' topic in a forum supergroup chat. func (b *Bot) EditGeneralTopic(chat *Chat, topic *Topic) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), "name": topic.Name, } _, err := b.Raw("editGeneralForumTopic", params) return err } // CloseGeneralTopic closes an open 'General' topic in a forum supergroup chat. func (b *Bot) CloseGeneralTopic(chat *Chat) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), } _, err := b.Raw("closeGeneralForumTopic", params) return err } // ReopenGeneralTopic reopens a closed 'General' topic in a forum supergroup chat. func (b *Bot) ReopenGeneralTopic(chat *Chat) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), } _, err := b.Raw("reopenGeneralForumTopic", params) return err } // HideGeneralTopic hides the 'General' topic in a forum supergroup chat. func (b *Bot) HideGeneralTopic(chat *Chat) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), } _, err := b.Raw("hideGeneralForumTopic", params) return err } // UnhideGeneralTopic unhides the 'General' topic in a forum supergroup chat. func (b *Bot) UnhideGeneralTopic(chat *Chat) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), } _, err := b.Raw("unhideGeneralForumTopic", params) return err } // UnpinAllGeneralTopicMessages clears the list of pinned messages in a General forum topic. // The bot must be an administrator in the chat for this to work and must have the // can_pin_messages administrator right in the supergroup. func (b *Bot) UnpinAllGeneralTopicMessages(chat *Chat) error { params := map[string]interface{}{ "chat_id": chat.Recipient(), } _, err := b.Raw("unpinAllGeneralForumTopicMessages", params) return err } telebot-3.3.8/update.go000066400000000000000000000170361465437035100150130ustar00rootroot00000000000000package telebot import "strings" // Update object represents an incoming update. type Update struct { ID int `json:"update_id"` Message *Message `json:"message,omitempty"` EditedMessage *Message `json:"edited_message,omitempty"` ChannelPost *Message `json:"channel_post,omitempty"` EditedChannelPost *Message `json:"edited_channel_post,omitempty"` MessageReaction *MessageReaction `json:"message_reaction"` MessageReactionCount *MessageReactionCount `json:"message_reaction_count"` Callback *Callback `json:"callback_query,omitempty"` Query *Query `json:"inline_query,omitempty"` InlineResult *InlineResult `json:"chosen_inline_result,omitempty"` ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"` PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"` Poll *Poll `json:"poll,omitempty"` PollAnswer *PollAnswer `json:"poll_answer,omitempty"` MyChatMember *ChatMemberUpdate `json:"my_chat_member,omitempty"` ChatMember *ChatMemberUpdate `json:"chat_member,omitempty"` ChatJoinRequest *ChatJoinRequest `json:"chat_join_request,omitempty"` Boost *BoostUpdated `json:"chat_boost"` BoostRemoved *BoostRemoved `json:"removed_chat_boost"` } // ProcessUpdate processes a single incoming update. // A started bot calls this function automatically. func (b *Bot) ProcessUpdate(u Update) { c := b.NewContext(u) if u.Message != nil { m := u.Message if m.PinnedMessage != nil { b.handle(OnPinned, c) return } // Commands if m.Text != "" { // Filtering malicious messages if m.Text[0] == '\a' { return } match := cmdRx.FindAllStringSubmatch(m.Text, -1) if match != nil { // Syntax: "@ " command, botName := match[0][1], match[0][3] if botName != "" && !strings.EqualFold(b.Me.Username, botName) { return } m.Payload = match[0][5] if b.handle(command, c) { return } } // 1:1 satisfaction if b.handle(m.Text, c) { return } b.handle(OnText, c) return } if b.handleMedia(c) { return } if m.Contact != nil { b.handle(OnContact, c) return } if m.Location != nil { b.handle(OnLocation, c) return } if m.Venue != nil { b.handle(OnVenue, c) return } if m.Game != nil { b.handle(OnGame, c) return } if m.Dice != nil { b.handle(OnDice, c) return } if m.Invoice != nil { b.handle(OnInvoice, c) return } if m.Payment != nil { b.handle(OnPayment, c) return } if m.TopicCreated != nil { b.handle(OnTopicCreated, c) return } if m.TopicReopened != nil { b.handle(OnTopicReopened, c) return } if m.TopicClosed != nil { b.handle(OnTopicClosed, c) return } if m.TopicEdited != nil { b.handle(OnTopicEdited, c) return } if m.GeneralTopicHidden != nil { b.handle(OnGeneralTopicHidden, c) return } if m.GeneralTopicUnhidden != nil { b.handle(OnGeneralTopicUnhidden, c) return } if m.WriteAccessAllowed != nil { b.handle(OnWriteAccessAllowed, c) return } wasAdded := (m.UserJoined != nil && m.UserJoined.ID == b.Me.ID) || (m.UsersJoined != nil && isUserInList(b.Me, m.UsersJoined)) if m.GroupCreated || m.SuperGroupCreated || wasAdded { b.handle(OnAddedToGroup, c) return } if m.UserJoined != nil { b.handle(OnUserJoined, c) return } if m.UsersJoined != nil { for _, user := range m.UsersJoined { m.UserJoined = &user b.handle(OnUserJoined, c) } return } if m.UserLeft != nil { b.handle(OnUserLeft, c) return } if m.UserShared != nil { b.handle(OnUserShared, c) return } if m.ChatShared != nil { b.handle(OnChatShared, c) return } if m.NewGroupTitle != "" { b.handle(OnNewGroupTitle, c) return } if m.NewGroupPhoto != nil { b.handle(OnNewGroupPhoto, c) return } if m.GroupPhotoDeleted { b.handle(OnGroupPhotoDeleted, c) return } if m.GroupCreated { b.handle(OnGroupCreated, c) return } if m.SuperGroupCreated { b.handle(OnSuperGroupCreated, c) return } if m.ChannelCreated { b.handle(OnChannelCreated, c) return } if m.MigrateTo != 0 { m.MigrateFrom = m.Chat.ID b.handle(OnMigration, c) return } if m.VideoChatStarted != nil { b.handle(OnVideoChatStarted, c) return } if m.VideoChatEnded != nil { b.handle(OnVideoChatEnded, c) return } if m.VideoChatParticipants != nil { b.handle(OnVideoChatParticipants, c) return } if m.VideoChatScheduled != nil { b.handle(OnVideoChatScheduled, c) return } if m.WebAppData != nil { b.handle(OnWebApp, c) return } if m.ProximityAlert != nil { b.handle(OnProximityAlert, c) return } if m.AutoDeleteTimer != nil { b.handle(OnAutoDeleteTimer, c) return } } if u.EditedMessage != nil { b.handle(OnEdited, c) return } if u.ChannelPost != nil { m := u.ChannelPost if m.PinnedMessage != nil { b.handle(OnPinned, c) return } b.handle(OnChannelPost, c) return } if u.EditedChannelPost != nil { b.handle(OnEditedChannelPost, c) return } if u.Callback != nil { if data := u.Callback.Data; data != "" && data[0] == '\f' { match := cbackRx.FindAllStringSubmatch(data, -1) if match != nil { unique, payload := match[0][1], match[0][3] if handler, ok := b.handlers["\f"+unique]; ok { u.Callback.Unique = unique u.Callback.Data = payload b.runHandler(handler, c) return } } } b.handle(OnCallback, c) return } if u.Query != nil { b.handle(OnQuery, c) return } if u.InlineResult != nil { b.handle(OnInlineResult, c) return } if u.ShippingQuery != nil { b.handle(OnShipping, c) return } if u.PreCheckoutQuery != nil { b.handle(OnCheckout, c) return } if u.Poll != nil { b.handle(OnPoll, c) return } if u.PollAnswer != nil { b.handle(OnPollAnswer, c) return } if u.MyChatMember != nil { b.handle(OnMyChatMember, c) return } if u.ChatMember != nil { b.handle(OnChatMember, c) return } if u.ChatJoinRequest != nil { b.handle(OnChatJoinRequest, c) return } if u.Boost != nil { b.handle(OnBoost, c) return } if u.BoostRemoved != nil { b.handle(OnBoostRemoved, c) return } } func (b *Bot) handle(end string, c Context) bool { if handler, ok := b.handlers[end]; ok { b.runHandler(handler, c) return true } return false } func (b *Bot) handleMedia(c Context) bool { var ( m = c.Message() fired = true ) switch { case m.Photo != nil: fired = b.handle(OnPhoto, c) case m.Voice != nil: fired = b.handle(OnVoice, c) case m.Audio != nil: fired = b.handle(OnAudio, c) case m.Animation != nil: fired = b.handle(OnAnimation, c) case m.Document != nil: fired = b.handle(OnDocument, c) case m.Sticker != nil: fired = b.handle(OnSticker, c) case m.Video != nil: fired = b.handle(OnVideo, c) case m.VideoNote != nil: fired = b.handle(OnVideoNote, c) default: return false } if !fired { return b.handle(OnMedia, c) } return true } func (b *Bot) runHandler(h HandlerFunc, c Context) { f := func() { if err := h(c); err != nil { b.OnError(err, c) } } if b.synchronous { f() } else { go f() } } func isUserInList(user *User, list []User) bool { for _, user2 := range list { if user.ID == user2.ID { return true } } return false } telebot-3.3.8/video_chat.go000066400000000000000000000015201465437035100156250ustar00rootroot00000000000000package telebot import "time" type ( // VideoChatStarted represents a service message about a video chat // started in the chat. VideoChatStarted struct{} // VideoChatEnded represents a service message about a video chat // ended in the chat. VideoChatEnded struct { Duration int `json:"duration"` // in seconds } // VideoChatParticipants represents a service message about new // members invited to a video chat VideoChatParticipants struct { Users []User `json:"users"` } // VideoChatScheduled represents a service message about a video chat scheduled in the chat. VideoChatScheduled struct { Unixtime int64 `json:"start_date"` } ) // StartsAt returns the point when the video chat is supposed to be started by a chat administrator. func (v *VideoChatScheduled) StartsAt() time.Time { return time.Unix(v.Unixtime, 0) } telebot-3.3.8/web_app.go000066400000000000000000000016111465437035100151360ustar00rootroot00000000000000package telebot // WebApp represents a parameter of the inline keyboard button // or the keyboard button used to launch Web App. type WebApp struct { URL string `json:"url"` } // WebAppMessage describes an inline message sent by a Web App on behalf of a user. type WebAppMessage struct { InlineMessageID string `json:"inline_message_id"` } // WebAppData object represents a data sent from a Web App to the bot type WebAppData struct { Data string `json:"data"` Text string `json:"button_text"` } // WebAppAccessAllowed represents a service message about a user allowing // a bot to write messages after adding the bot to the attachment menu or launching a Web App from a link. type WriteAccessAllowed struct { WebAppName string `json:"web_app_name,omitempty"` FromRequest bool `json:"from_request,omitempty"` FromAttachmentMenu bool `json:"from_attachment_menu,omitempty"` } telebot-3.3.8/webhook.go000066400000000000000000000132241465437035100151620ustar00rootroot00000000000000package telebot import ( "context" "encoding/json" "fmt" "net/http" "strconv" ) // A WebhookTLS specifies the path to a key and a cert so the poller can open // a TLS listener. type WebhookTLS struct { Key string `json:"key"` Cert string `json:"cert"` } // A WebhookEndpoint describes the endpoint to which telegram will send its requests. // This must be a public URL and can be a loadbalancer or something similar. If the // endpoint uses TLS and the certificate is self-signed you have to add the certificate // path of this certificate so telegram will trust it. This field can be ignored if you // have a trusted certificate (letsencrypt, ...). type WebhookEndpoint struct { PublicURL string `json:"public_url"` Cert string `json:"cert"` } // A Webhook configures the poller for webhooks. It opens a port on the given // listen address. If TLS is filled, the listener will use the key and cert to open // a secure port. Otherwise it will use plain HTTP. // // If you have a loadbalancer ore other infrastructure in front of your service, you // must fill the Endpoint structure so this poller will send this data to telegram. If // you leave these values empty, your local address will be sent to telegram which is mostly // not what you want (at least while developing). If you have a single instance of your // bot you should consider to use the LongPoller instead of a WebHook. // // You can also leave the Listen field empty. In this case it is up to the caller to // add the Webhook to a http-mux. // type Webhook struct { Listen string `json:"url"` MaxConnections int `json:"max_connections"` AllowedUpdates []string `json:"allowed_updates"` IP string `json:"ip_address"` DropUpdates bool `json:"drop_pending_updates"` SecretToken string `json:"secret_token"` // (WebhookInfo) HasCustomCert bool `json:"has_custom_certificate"` PendingUpdates int `json:"pending_update_count"` ErrorUnixtime int64 `json:"last_error_date"` ErrorMessage string `json:"last_error_message"` SyncErrorUnixtime int64 `json:"last_synchronization_error_date"` TLS *WebhookTLS Endpoint *WebhookEndpoint dest chan<- Update bot *Bot } func (h *Webhook) getFiles() map[string]File { m := make(map[string]File) if h.TLS != nil { m["certificate"] = FromDisk(h.TLS.Cert) } // check if it is overwritten by an endpoint if h.Endpoint != nil { if h.Endpoint.Cert == "" { // this can be the case if there is a loadbalancer or reverseproxy in // front with a public cert. in this case we do not need to upload it // to telegram. we delete the certificate from the map, because someone // can have an internal TLS listener with a private cert delete(m, "certificate") } else { // someone configured a certificate m["certificate"] = FromDisk(h.Endpoint.Cert) } } return m } func (h *Webhook) getParams() map[string]string { params := make(map[string]string) if h.MaxConnections != 0 { params["max_connections"] = strconv.Itoa(h.MaxConnections) } if len(h.AllowedUpdates) > 0 { data, _ := json.Marshal(h.AllowedUpdates) params["allowed_updates"] = string(data) } if h.IP != "" { params["ip_address"] = h.IP } if h.DropUpdates { params["drop_pending_updates"] = strconv.FormatBool(h.DropUpdates) } if h.SecretToken != "" { params["secret_token"] = h.SecretToken } if h.TLS != nil { params["url"] = "https://" + h.Listen } else { // this will not work with telegram, they want TLS // but i allow this because telegram will send an error // when you register this hook. in their docs they write // that port 80/http is allowed ... params["url"] = "http://" + h.Listen } if h.Endpoint != nil { params["url"] = h.Endpoint.PublicURL } return params } func (h *Webhook) Poll(b *Bot, dest chan Update, stop chan struct{}) { if err := b.SetWebhook(h); err != nil { b.OnError(err, nil) close(stop) return } // store the variables so the HTTP-handler can use 'em h.dest = dest h.bot = b if h.Listen == "" { h.waitForStop(stop) return } s := &http.Server{ Addr: h.Listen, Handler: h, } go func(stop chan struct{}) { h.waitForStop(stop) s.Shutdown(context.Background()) }(stop) if h.TLS != nil { s.ListenAndServeTLS(h.TLS.Cert, h.TLS.Key) } else { s.ListenAndServe() } } func (h *Webhook) waitForStop(stop chan struct{}) { <-stop close(stop) } // The handler simply reads the update from the body of the requests // and writes them to the update channel. func (h *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) { if h.SecretToken != "" && r.Header.Get("X-Telegram-Bot-Api-Secret-Token") != h.SecretToken { h.bot.debug(fmt.Errorf("invalid secret token in request")) return } var update Update if err := json.NewDecoder(r.Body).Decode(&update); err != nil { h.bot.debug(fmt.Errorf("cannot decode update: %v", err)) return } h.dest <- update } // Webhook returns the current webhook status. func (b *Bot) Webhook() (*Webhook, error) { data, err := b.Raw("getWebhookInfo", nil) if err != nil { return nil, err } var resp struct { Result Webhook } if err := json.Unmarshal(data, &resp); err != nil { return nil, wrapError(err) } return &resp.Result, nil } // SetWebhook configures a bot to receive incoming // updates via an outgoing webhook. func (b *Bot) SetWebhook(w *Webhook) error { _, err := b.sendFiles("setWebhook", w.getFiles(), w.getParams()) return err } // RemoveWebhook removes webhook integration. func (b *Bot) RemoveWebhook(dropPending ...bool) error { drop := false if len(dropPending) > 0 { drop = dropPending[0] } _, err := b.Raw("deleteWebhook", map[string]bool{ "drop_pending_updates": drop, }) return err }