commit 87a18f36b6957c3fae730d0c25acf68fb7c465c2
Author: bsandro <email@bsandro.tech>
Date: Sat, 5 Nov 2022 08:31:52 +0200
init
Diffstat:
34 files changed, 11628 insertions(+), 0 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1,3 @@
+leobot
+leobot.exe
+logs
diff --git a/config.ini b/config.ini
@@ -0,0 +1 @@
+token = 123
diff --git a/go.mod b/go.mod
@@ -0,0 +1,10 @@
+module leobot
+
+go 1.18
+
+require (
+ github.com/go-ini/ini v1.67.0
+ github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
+)
+
+require github.com/stretchr/testify v1.8.1 // indirect
diff --git a/go.sum b/go.sum
@@ -0,0 +1,20 @@
+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/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
+github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
+github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc=
+github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8=
+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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
+github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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=
diff --git a/main.go b/main.go
@@ -0,0 +1,85 @@
+package main
+
+import (
+ "fmt"
+ ini "github.com/go-ini/ini"
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+ "log"
+ "os"
+ "strings"
+ "sync"
+ "time"
+)
+
+func main() {
+ cfg, err := ini.Load("config.ini")
+ if err != nil {
+ log.Fatal("Invalid config.ini: ", err)
+ }
+
+ token := cfg.Section("").Key("token").String()
+ bot, err := tgbotapi.NewBotAPI(token)
+ if err != nil {
+ log.Fatal("Cannot connect to Telegram: ", err)
+ }
+ bot.Debug = false
+ log.Printf("Authorized on account %s", bot.Self.UserName)
+ var writeMutex sync.Mutex
+ u := tgbotapi.NewUpdate(0)
+ u.Timeout = 60
+ updates := bot.GetUpdatesChan(u)
+
+ for update := range updates {
+ if update.Message != nil {
+ go writeLog(update.Message, &writeMutex)
+ }
+ }
+}
+
+func clearName(name *string) {
+ *name = strings.ReplaceAll(*name, string(os.PathSeparator), "-")
+ *name = strings.ReplaceAll(*name, string(os.PathListSeparator), "-")
+ *name = strings.ReplaceAll(*name, ".", "")
+}
+
+func writeLog(msg *tgbotapi.Message, mtx *sync.Mutex) {
+ mtx.Lock()
+ defer mtx.Unlock()
+
+ var chat string
+ if msg.Chat.Type == "group" {
+ chat = msg.Chat.Title
+ } else {
+ chat = "private"
+ }
+ clearName(&chat)
+
+ var fromName string
+ if msg.From.UserName != "" {
+ fromName = msg.From.UserName
+ } else {
+ fromName = msg.From.FirstName + " " + msg.From.LastName
+ }
+
+ ts := time.Unix(int64(msg.Date), 0)
+ ps := string(os.PathSeparator)
+ fpath := "logs" + ps + chat + ps + ts.Format("2006"+ps+"01"+ps+"02")
+ fname := fpath + ps + "log.txt"
+
+ if err := os.MkdirAll(fpath, 0750); err != nil {
+ log.Println(err)
+ return
+ }
+
+ logfile, err := os.OpenFile(fname, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640)
+ if err != nil {
+ log.Println(err)
+ return
+ }
+ defer logfile.Close()
+ t := ts.Format("2006-01-02 15:04:05")
+ logstr := fmt.Sprintf("[%s][%s] %s\n", t, fromName, msg.Text)
+ if _, err := logfile.WriteString(logstr); err != nil {
+ log.Println(err)
+ }
+}
diff --git a/vendor/github.com/go-ini/ini/.editorconfig b/vendor/github.com/go-ini/ini/.editorconfig
@@ -0,0 +1,12 @@
+# http://editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+
+[*_test.go]
+trim_trailing_whitespace = false
diff --git a/vendor/github.com/go-ini/ini/.gitignore b/vendor/github.com/go-ini/ini/.gitignore
@@ -0,0 +1,7 @@
+testdata/conf_out.ini
+ini.sublime-project
+ini.sublime-workspace
+testdata/conf_reflect.ini
+.idea
+/.vscode
+.DS_Store
diff --git a/vendor/github.com/go-ini/ini/.golangci.yml b/vendor/github.com/go-ini/ini/.golangci.yml
@@ -0,0 +1,27 @@
+linters-settings:
+ staticcheck:
+ checks: [
+ "all",
+ "-SA1019" # There are valid use cases of strings.Title
+ ]
+ nakedret:
+ max-func-lines: 0 # Disallow any unnamed return statement
+
+linters:
+ enable:
+ - deadcode
+ - errcheck
+ - gosimple
+ - govet
+ - ineffassign
+ - staticcheck
+ - structcheck
+ - typecheck
+ - unused
+ - varcheck
+ - nakedret
+ - gofmt
+ - rowserrcheck
+ - unconvert
+ - goimports
+ - unparam
diff --git a/vendor/github.com/go-ini/ini/LICENSE b/vendor/github.com/go-ini/ini/LICENSE
@@ -0,0 +1,191 @@
+Apache License
+Version 2.0, January 2004
+http://www.apache.org/licenses/
+
+TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+1. Definitions.
+
+"License" shall mean the terms and conditions for use, reproduction, and
+distribution as defined by Sections 1 through 9 of this document.
+
+"Licensor" shall mean the copyright owner or entity authorized by the copyright
+owner that is granting the License.
+
+"Legal Entity" shall mean the union of the acting entity and all other entities
+that control, are controlled by, or are under common control with that entity.
+For the purposes of this definition, "control" means (i) the power, direct or
+indirect, to cause the direction or management of such entity, whether by
+contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the
+outstanding shares, or (iii) beneficial ownership of such entity.
+
+"You" (or "Your") shall mean an individual or Legal Entity exercising
+permissions granted by this License.
+
+"Source" form shall mean the preferred form for making modifications, including
+but not limited to software source code, documentation source, and configuration
+files.
+
+"Object" form shall mean any form resulting from mechanical transformation or
+translation of a Source form, including but not limited to compiled object code,
+generated documentation, and conversions to other media types.
+
+"Work" shall mean the work of authorship, whether in Source or Object form, made
+available under the License, as indicated by a copyright notice that is included
+in or attached to the work (an example is provided in the Appendix below).
+
+"Derivative Works" shall mean any work, whether in Source or Object form, that
+is based on (or derived from) the Work and for which the editorial revisions,
+annotations, elaborations, or other modifications represent, as a whole, an
+original work of authorship. For the purposes of this License, Derivative Works
+shall not include works that remain separable from, or merely link (or bind by
+name) to the interfaces of, the Work and Derivative Works thereof.
+
+"Contribution" shall mean any work of authorship, including the original version
+of the Work and any modifications or additions to that Work or Derivative Works
+thereof, that is intentionally submitted to Licensor for inclusion in the Work
+by the copyright owner or by an individual or Legal Entity authorized to submit
+on behalf of the copyright owner. For the purposes of this definition,
+"submitted" means any form of electronic, verbal, or written communication sent
+to the Licensor or its representatives, including but not limited to
+communication on electronic mailing lists, source code control systems, and
+issue tracking systems that are managed by, or on behalf of, the Licensor for
+the purpose of discussing and improving the Work, but excluding communication
+that is conspicuously marked or otherwise designated in writing by the copyright
+owner as "Not a Contribution."
+
+"Contributor" shall mean Licensor and any individual or Legal Entity on behalf
+of whom a Contribution has been received by Licensor and subsequently
+incorporated within the Work.
+
+2. Grant of Copyright License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable copyright license to reproduce, prepare Derivative Works of,
+publicly display, publicly perform, sublicense, and distribute the Work and such
+Derivative Works in Source or Object form.
+
+3. Grant of Patent License.
+
+Subject to the terms and conditions of this License, each Contributor hereby
+grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free,
+irrevocable (except as stated in this section) patent license to make, have
+made, use, offer to sell, sell, import, and otherwise transfer the Work, where
+such license applies only to those patent claims licensable by such Contributor
+that are necessarily infringed by their Contribution(s) alone or by combination
+of their Contribution(s) with the Work to which such Contribution(s) was
+submitted. If You institute patent litigation against any entity (including a
+cross-claim or counterclaim in a lawsuit) alleging that the Work or a
+Contribution incorporated within the Work constitutes direct or contributory
+patent infringement, then any patent licenses granted to You under this License
+for that Work shall terminate as of the date such litigation is filed.
+
+4. Redistribution.
+
+You may reproduce and distribute copies of the Work or Derivative Works thereof
+in any medium, with or without modifications, and in Source or Object form,
+provided that You meet the following conditions:
+
+You must give any other recipients of the Work or Derivative Works a copy of
+this License; and
+You must cause any modified files to carry prominent notices stating that You
+changed the files; and
+You must retain, in the Source form of any Derivative Works that You distribute,
+all copyright, patent, trademark, and attribution notices from the Source form
+of the Work, excluding those notices that do not pertain to any part of the
+Derivative Works; and
+If the Work includes a "NOTICE" text file as part of its distribution, then any
+Derivative Works that You distribute must include a readable copy of the
+attribution notices contained within such NOTICE file, excluding those notices
+that do not pertain to any part of the Derivative Works, in at least one of the
+following places: within a NOTICE text file distributed as part of the
+Derivative Works; within the Source form or documentation, if provided along
+with the Derivative Works; or, within a display generated by the Derivative
+Works, if and wherever such third-party notices normally appear. The contents of
+the NOTICE file are for informational purposes only and do not modify the
+License. You may add Your own attribution notices within Derivative Works that
+You distribute, alongside or as an addendum to the NOTICE text from the Work,
+provided that such additional attribution notices cannot be construed as
+modifying the License.
+You may add Your own copyright statement to Your modifications and may provide
+additional or different license terms and conditions for use, reproduction, or
+distribution of Your modifications, or for any such Derivative Works as a whole,
+provided Your use, reproduction, and distribution of the Work otherwise complies
+with the conditions stated in this License.
+
+5. Submission of Contributions.
+
+Unless You explicitly state otherwise, any Contribution intentionally submitted
+for inclusion in the Work by You to the Licensor shall be under the terms and
+conditions of this License, without any additional terms or conditions.
+Notwithstanding the above, nothing herein shall supersede or modify the terms of
+any separate license agreement you may have executed with Licensor regarding
+such Contributions.
+
+6. Trademarks.
+
+This License does not grant permission to use the trade names, trademarks,
+service marks, or product names of the Licensor, except as required for
+reasonable and customary use in describing the origin of the Work and
+reproducing the content of the NOTICE file.
+
+7. Disclaimer of Warranty.
+
+Unless required by applicable law or agreed to in writing, Licensor provides the
+Work (and each Contributor provides its Contributions) on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied,
+including, without limitation, any warranties or conditions of TITLE,
+NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are
+solely responsible for determining the appropriateness of using or
+redistributing the Work and assume any risks associated with Your exercise of
+permissions under this License.
+
+8. Limitation of Liability.
+
+In no event and under no legal theory, whether in tort (including negligence),
+contract, or otherwise, unless required by applicable law (such as deliberate
+and grossly negligent acts) or agreed to in writing, shall any Contributor be
+liable to You for damages, including any direct, indirect, special, incidental,
+or consequential damages of any character arising as a result of this License or
+out of the use or inability to use the Work (including but not limited to
+damages for loss of goodwill, work stoppage, computer failure or malfunction, or
+any and all other commercial damages or losses), even if such Contributor has
+been advised of the possibility of such damages.
+
+9. Accepting Warranty or Additional Liability.
+
+While redistributing the Work or Derivative Works thereof, You may choose to
+offer, and charge a fee for, acceptance of support, warranty, indemnity, or
+other liability obligations and/or rights consistent with this License. However,
+in accepting such obligations, You may act only on Your own behalf and on Your
+sole responsibility, not on behalf of any other Contributor, and only if You
+agree to indemnify, defend, and hold each Contributor harmless for any liability
+incurred by, or claims asserted against, such Contributor by reason of your
+accepting any such warranty or additional liability.
+
+END OF TERMS AND CONDITIONS
+
+APPENDIX: How to apply the Apache License to your work
+
+To apply the Apache License to your work, attach the following boilerplate
+notice, with the fields enclosed by brackets "[]" replaced with your own
+identifying information. (Don't include the brackets!) The text should be
+enclosed in the appropriate comment syntax for the file format. We also
+recommend that a file or class name and description of purpose be included on
+the same "printed page" as the copyright notice for easier identification within
+third-party archives.
+
+ Copyright 2014 Unknwon
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/github.com/go-ini/ini/Makefile b/vendor/github.com/go-ini/ini/Makefile
@@ -0,0 +1,15 @@
+.PHONY: build test bench vet coverage
+
+build: vet bench
+
+test:
+ go test -v -cover -race
+
+bench:
+ go test -v -cover -test.bench=. -test.benchmem
+
+vet:
+ go vet
+
+coverage:
+ go test -coverprofile=c.out && go tool cover -html=c.out && rm c.out
diff --git a/vendor/github.com/go-ini/ini/README.md b/vendor/github.com/go-ini/ini/README.md
@@ -0,0 +1,43 @@
+# INI
+
+[![GitHub Workflow Status](https://img.shields.io/github/checks-status/go-ini/ini/main?logo=github&style=for-the-badge)](https://github.com/go-ini/ini/actions?query=branch%3Amain)
+[![codecov](https://img.shields.io/codecov/c/github/go-ini/ini/master?logo=codecov&style=for-the-badge)](https://codecov.io/gh/go-ini/ini)
+[![GoDoc](https://img.shields.io/badge/GoDoc-Reference-blue?style=for-the-badge&logo=go)](https://pkg.go.dev/github.com/go-ini/ini?tab=doc)
+[![Sourcegraph](https://img.shields.io/badge/view%20on-Sourcegraph-brightgreen.svg?style=for-the-badge&logo=sourcegraph)](https://sourcegraph.com/github.com/go-ini/ini)
+
+![](https://avatars0.githubusercontent.com/u/10216035?v=3&s=200)
+
+Package ini provides INI file read and write functionality in Go.
+
+## Features
+
+- Load from multiple data sources(file, `[]byte`, `io.Reader` and `io.ReadCloser`) with overwrites.
+- Read with recursion values.
+- Read with parent-child sections.
+- Read with auto-increment key names.
+- Read with multiple-line values.
+- Read with tons of helper methods.
+- Read and convert values to Go types.
+- Read and **WRITE** comments of sections and keys.
+- Manipulate sections, keys and comments with ease.
+- Keep sections and keys in order as you parse and save.
+
+## Installation
+
+The minimum requirement of Go is **1.13**.
+
+```sh
+$ go get gopkg.in/ini.v1
+```
+
+Please add `-u` flag to update in the future.
+
+## Getting Help
+
+- [Getting Started](https://ini.unknwon.io/docs/intro/getting_started)
+- [API Documentation](https://gowalker.org/gopkg.in/ini.v1)
+- 中国大陆镜像:https://ini.unknwon.cn
+
+## License
+
+This project is under Apache v2 License. See the [LICENSE](LICENSE) file for the full license text.
diff --git a/vendor/github.com/go-ini/ini/codecov.yml b/vendor/github.com/go-ini/ini/codecov.yml
@@ -0,0 +1,16 @@
+coverage:
+ range: "60...95"
+ status:
+ project:
+ default:
+ threshold: 1%
+ informational: true
+ patch:
+ defualt:
+ only_pulls: true
+ informational: true
+
+comment:
+ layout: 'diff'
+
+github_checks: false
diff --git a/vendor/github.com/go-ini/ini/data_source.go b/vendor/github.com/go-ini/ini/data_source.go
@@ -0,0 +1,76 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+)
+
+var (
+ _ dataSource = (*sourceFile)(nil)
+ _ dataSource = (*sourceData)(nil)
+ _ dataSource = (*sourceReadCloser)(nil)
+)
+
+// dataSource is an interface that returns object which can be read and closed.
+type dataSource interface {
+ ReadCloser() (io.ReadCloser, error)
+}
+
+// sourceFile represents an object that contains content on the local file system.
+type sourceFile struct {
+ name string
+}
+
+func (s sourceFile) ReadCloser() (_ io.ReadCloser, err error) {
+ return os.Open(s.name)
+}
+
+// sourceData represents an object that contains content in memory.
+type sourceData struct {
+ data []byte
+}
+
+func (s *sourceData) ReadCloser() (io.ReadCloser, error) {
+ return ioutil.NopCloser(bytes.NewReader(s.data)), nil
+}
+
+// sourceReadCloser represents an input stream with Close method.
+type sourceReadCloser struct {
+ reader io.ReadCloser
+}
+
+func (s *sourceReadCloser) ReadCloser() (io.ReadCloser, error) {
+ return s.reader, nil
+}
+
+func parseDataSource(source interface{}) (dataSource, error) {
+ switch s := source.(type) {
+ case string:
+ return sourceFile{s}, nil
+ case []byte:
+ return &sourceData{s}, nil
+ case io.ReadCloser:
+ return &sourceReadCloser{s}, nil
+ case io.Reader:
+ return &sourceReadCloser{ioutil.NopCloser(s)}, nil
+ default:
+ return nil, fmt.Errorf("error parsing data source: unknown type %q", s)
+ }
+}
diff --git a/vendor/github.com/go-ini/ini/deprecated.go b/vendor/github.com/go-ini/ini/deprecated.go
@@ -0,0 +1,22 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+var (
+ // Deprecated: Use "DefaultSection" instead.
+ DEFAULT_SECTION = DefaultSection
+ // Deprecated: AllCapsUnderscore converts to format ALL_CAPS_UNDERSCORE.
+ AllCapsUnderscore = SnackCase
+)
diff --git a/vendor/github.com/go-ini/ini/error.go b/vendor/github.com/go-ini/ini/error.go
@@ -0,0 +1,49 @@
+// Copyright 2016 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "fmt"
+)
+
+// ErrDelimiterNotFound indicates the error type of no delimiter is found which there should be one.
+type ErrDelimiterNotFound struct {
+ Line string
+}
+
+// IsErrDelimiterNotFound returns true if the given error is an instance of ErrDelimiterNotFound.
+func IsErrDelimiterNotFound(err error) bool {
+ _, ok := err.(ErrDelimiterNotFound)
+ return ok
+}
+
+func (err ErrDelimiterNotFound) Error() string {
+ return fmt.Sprintf("key-value delimiter not found: %s", err.Line)
+}
+
+// ErrEmptyKeyName indicates the error type of no key name is found which there should be one.
+type ErrEmptyKeyName struct {
+ Line string
+}
+
+// IsErrEmptyKeyName returns true if the given error is an instance of ErrEmptyKeyName.
+func IsErrEmptyKeyName(err error) bool {
+ _, ok := err.(ErrEmptyKeyName)
+ return ok
+}
+
+func (err ErrEmptyKeyName) Error() string {
+ return fmt.Sprintf("empty key name: %s", err.Line)
+}
diff --git a/vendor/github.com/go-ini/ini/file.go b/vendor/github.com/go-ini/ini/file.go
@@ -0,0 +1,541 @@
+// Copyright 2017 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "os"
+ "strings"
+ "sync"
+)
+
+// File represents a combination of one or more INI files in memory.
+type File struct {
+ options LoadOptions
+ dataSources []dataSource
+
+ // Should make things safe, but sometimes doesn't matter.
+ BlockMode bool
+ lock sync.RWMutex
+
+ // To keep data in order.
+ sectionList []string
+ // To keep track of the index of a section with same name.
+ // This meta list is only used with non-unique section names are allowed.
+ sectionIndexes []int
+
+ // Actual data is stored here.
+ sections map[string][]*Section
+
+ NameMapper
+ ValueMapper
+}
+
+// newFile initializes File object with given data sources.
+func newFile(dataSources []dataSource, opts LoadOptions) *File {
+ if len(opts.KeyValueDelimiters) == 0 {
+ opts.KeyValueDelimiters = "=:"
+ }
+ if len(opts.KeyValueDelimiterOnWrite) == 0 {
+ opts.KeyValueDelimiterOnWrite = "="
+ }
+ if len(opts.ChildSectionDelimiter) == 0 {
+ opts.ChildSectionDelimiter = "."
+ }
+
+ return &File{
+ BlockMode: true,
+ dataSources: dataSources,
+ sections: make(map[string][]*Section),
+ options: opts,
+ }
+}
+
+// Empty returns an empty file object.
+func Empty(opts ...LoadOptions) *File {
+ var opt LoadOptions
+ if len(opts) > 0 {
+ opt = opts[0]
+ }
+
+ // Ignore error here, we are sure our data is good.
+ f, _ := LoadSources(opt, []byte(""))
+ return f
+}
+
+// NewSection creates a new section.
+func (f *File) NewSection(name string) (*Section, error) {
+ if len(name) == 0 {
+ return nil, errors.New("empty section name")
+ }
+
+ if (f.options.Insensitive || f.options.InsensitiveSections) && name != DefaultSection {
+ name = strings.ToLower(name)
+ }
+
+ if f.BlockMode {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+ }
+
+ if !f.options.AllowNonUniqueSections && inSlice(name, f.sectionList) {
+ return f.sections[name][0], nil
+ }
+
+ f.sectionList = append(f.sectionList, name)
+
+ // NOTE: Append to indexes must happen before appending to sections,
+ // otherwise index will have off-by-one problem.
+ f.sectionIndexes = append(f.sectionIndexes, len(f.sections[name]))
+
+ sec := newSection(f, name)
+ f.sections[name] = append(f.sections[name], sec)
+
+ return sec, nil
+}
+
+// NewRawSection creates a new section with an unparseable body.
+func (f *File) NewRawSection(name, body string) (*Section, error) {
+ section, err := f.NewSection(name)
+ if err != nil {
+ return nil, err
+ }
+
+ section.isRawSection = true
+ section.rawBody = body
+ return section, nil
+}
+
+// NewSections creates a list of sections.
+func (f *File) NewSections(names ...string) (err error) {
+ for _, name := range names {
+ if _, err = f.NewSection(name); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// GetSection returns section by given name.
+func (f *File) GetSection(name string) (*Section, error) {
+ secs, err := f.SectionsByName(name)
+ if err != nil {
+ return nil, err
+ }
+
+ return secs[0], err
+}
+
+// HasSection returns true if the file contains a section with given name.
+func (f *File) HasSection(name string) bool {
+ section, _ := f.GetSection(name)
+ return section != nil
+}
+
+// SectionsByName returns all sections with given name.
+func (f *File) SectionsByName(name string) ([]*Section, error) {
+ if len(name) == 0 {
+ name = DefaultSection
+ }
+ if f.options.Insensitive || f.options.InsensitiveSections {
+ name = strings.ToLower(name)
+ }
+
+ if f.BlockMode {
+ f.lock.RLock()
+ defer f.lock.RUnlock()
+ }
+
+ secs := f.sections[name]
+ if len(secs) == 0 {
+ return nil, fmt.Errorf("section %q does not exist", name)
+ }
+
+ return secs, nil
+}
+
+// Section assumes named section exists and returns a zero-value when not.
+func (f *File) Section(name string) *Section {
+ sec, err := f.GetSection(name)
+ if err != nil {
+ if name == "" {
+ name = DefaultSection
+ }
+ sec, _ = f.NewSection(name)
+ return sec
+ }
+ return sec
+}
+
+// SectionWithIndex assumes named section exists and returns a new section when not.
+func (f *File) SectionWithIndex(name string, index int) *Section {
+ secs, err := f.SectionsByName(name)
+ if err != nil || len(secs) <= index {
+ // NOTE: It's OK here because the only possible error is empty section name,
+ // but if it's empty, this piece of code won't be executed.
+ newSec, _ := f.NewSection(name)
+ return newSec
+ }
+
+ return secs[index]
+}
+
+// Sections returns a list of Section stored in the current instance.
+func (f *File) Sections() []*Section {
+ if f.BlockMode {
+ f.lock.RLock()
+ defer f.lock.RUnlock()
+ }
+
+ sections := make([]*Section, len(f.sectionList))
+ for i, name := range f.sectionList {
+ sections[i] = f.sections[name][f.sectionIndexes[i]]
+ }
+ return sections
+}
+
+// ChildSections returns a list of child sections of given section name.
+func (f *File) ChildSections(name string) []*Section {
+ return f.Section(name).ChildSections()
+}
+
+// SectionStrings returns list of section names.
+func (f *File) SectionStrings() []string {
+ list := make([]string, len(f.sectionList))
+ copy(list, f.sectionList)
+ return list
+}
+
+// DeleteSection deletes a section or all sections with given name.
+func (f *File) DeleteSection(name string) {
+ secs, err := f.SectionsByName(name)
+ if err != nil {
+ return
+ }
+
+ for i := 0; i < len(secs); i++ {
+ // For non-unique sections, it is always needed to remove the first one so
+ // in the next iteration, the subsequent section continue having index 0.
+ // Ignoring the error as index 0 never returns an error.
+ _ = f.DeleteSectionWithIndex(name, 0)
+ }
+}
+
+// DeleteSectionWithIndex deletes a section with given name and index.
+func (f *File) DeleteSectionWithIndex(name string, index int) error {
+ if !f.options.AllowNonUniqueSections && index != 0 {
+ return fmt.Errorf("delete section with non-zero index is only allowed when non-unique sections is enabled")
+ }
+
+ if len(name) == 0 {
+ name = DefaultSection
+ }
+ if f.options.Insensitive || f.options.InsensitiveSections {
+ name = strings.ToLower(name)
+ }
+
+ if f.BlockMode {
+ f.lock.Lock()
+ defer f.lock.Unlock()
+ }
+
+ // Count occurrences of the sections
+ occurrences := 0
+
+ sectionListCopy := make([]string, len(f.sectionList))
+ copy(sectionListCopy, f.sectionList)
+
+ for i, s := range sectionListCopy {
+ if s != name {
+ continue
+ }
+
+ if occurrences == index {
+ if len(f.sections[name]) <= 1 {
+ delete(f.sections, name) // The last one in the map
+ } else {
+ f.sections[name] = append(f.sections[name][:index], f.sections[name][index+1:]...)
+ }
+
+ // Fix section lists
+ f.sectionList = append(f.sectionList[:i], f.sectionList[i+1:]...)
+ f.sectionIndexes = append(f.sectionIndexes[:i], f.sectionIndexes[i+1:]...)
+
+ } else if occurrences > index {
+ // Fix the indices of all following sections with this name.
+ f.sectionIndexes[i-1]--
+ }
+
+ occurrences++
+ }
+
+ return nil
+}
+
+func (f *File) reload(s dataSource) error {
+ r, err := s.ReadCloser()
+ if err != nil {
+ return err
+ }
+ defer r.Close()
+
+ return f.parse(r)
+}
+
+// Reload reloads and parses all data sources.
+func (f *File) Reload() (err error) {
+ for _, s := range f.dataSources {
+ if err = f.reload(s); err != nil {
+ // In loose mode, we create an empty default section for nonexistent files.
+ if os.IsNotExist(err) && f.options.Loose {
+ _ = f.parse(bytes.NewBuffer(nil))
+ continue
+ }
+ return err
+ }
+ if f.options.ShortCircuit {
+ return nil
+ }
+ }
+ return nil
+}
+
+// Append appends one or more data sources and reloads automatically.
+func (f *File) Append(source interface{}, others ...interface{}) error {
+ ds, err := parseDataSource(source)
+ if err != nil {
+ return err
+ }
+ f.dataSources = append(f.dataSources, ds)
+ for _, s := range others {
+ ds, err = parseDataSource(s)
+ if err != nil {
+ return err
+ }
+ f.dataSources = append(f.dataSources, ds)
+ }
+ return f.Reload()
+}
+
+func (f *File) writeToBuffer(indent string) (*bytes.Buffer, error) {
+ equalSign := DefaultFormatLeft + f.options.KeyValueDelimiterOnWrite + DefaultFormatRight
+
+ if PrettyFormat || PrettyEqual {
+ equalSign = fmt.Sprintf(" %s ", f.options.KeyValueDelimiterOnWrite)
+ }
+
+ // Use buffer to make sure target is safe until finish encoding.
+ buf := bytes.NewBuffer(nil)
+ lastSectionIdx := len(f.sectionList) - 1
+ for i, sname := range f.sectionList {
+ sec := f.SectionWithIndex(sname, f.sectionIndexes[i])
+ if len(sec.Comment) > 0 {
+ // Support multiline comments
+ lines := strings.Split(sec.Comment, LineBreak)
+ for i := range lines {
+ if lines[i][0] != '#' && lines[i][0] != ';' {
+ lines[i] = "; " + lines[i]
+ } else {
+ lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+ }
+
+ if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if i > 0 || DefaultHeader || (i == 0 && strings.ToUpper(sec.name) != DefaultSection) {
+ if _, err := buf.WriteString("[" + sname + "]" + LineBreak); err != nil {
+ return nil, err
+ }
+ } else {
+ // Write nothing if default section is empty
+ if len(sec.keyList) == 0 {
+ continue
+ }
+ }
+
+ isLastSection := i == lastSectionIdx
+ if sec.isRawSection {
+ if _, err := buf.WriteString(sec.rawBody); err != nil {
+ return nil, err
+ }
+
+ if PrettySection && !isLastSection {
+ // Put a line between sections
+ if _, err := buf.WriteString(LineBreak); err != nil {
+ return nil, err
+ }
+ }
+ continue
+ }
+
+ // Count and generate alignment length and buffer spaces using the
+ // longest key. Keys may be modified if they contain certain characters so
+ // we need to take that into account in our calculation.
+ alignLength := 0
+ if PrettyFormat {
+ for _, kname := range sec.keyList {
+ keyLength := len(kname)
+ // First case will surround key by ` and second by """
+ if strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters) {
+ keyLength += 2
+ } else if strings.Contains(kname, "`") {
+ keyLength += 6
+ }
+
+ if keyLength > alignLength {
+ alignLength = keyLength
+ }
+ }
+ }
+ alignSpaces := bytes.Repeat([]byte(" "), alignLength)
+
+ KeyList:
+ for _, kname := range sec.keyList {
+ key := sec.Key(kname)
+ if len(key.Comment) > 0 {
+ if len(indent) > 0 && sname != DefaultSection {
+ buf.WriteString(indent)
+ }
+
+ // Support multiline comments
+ lines := strings.Split(key.Comment, LineBreak)
+ for i := range lines {
+ if lines[i][0] != '#' && lines[i][0] != ';' {
+ lines[i] = "; " + strings.TrimSpace(lines[i])
+ } else {
+ lines[i] = lines[i][:1] + " " + strings.TrimSpace(lines[i][1:])
+ }
+
+ if _, err := buf.WriteString(lines[i] + LineBreak); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if len(indent) > 0 && sname != DefaultSection {
+ buf.WriteString(indent)
+ }
+
+ switch {
+ case key.isAutoIncrement:
+ kname = "-"
+ case strings.Contains(kname, "\"") || strings.ContainsAny(kname, f.options.KeyValueDelimiters):
+ kname = "`" + kname + "`"
+ case strings.Contains(kname, "`"):
+ kname = `"""` + kname + `"""`
+ }
+
+ writeKeyValue := func(val string) (bool, error) {
+ if _, err := buf.WriteString(kname); err != nil {
+ return false, err
+ }
+
+ if key.isBooleanType {
+ buf.WriteString(LineBreak)
+ return true, nil
+ }
+
+ // Write out alignment spaces before "=" sign
+ if PrettyFormat {
+ buf.Write(alignSpaces[:alignLength-len(kname)])
+ }
+
+ // In case key value contains "\n", "`", "\"", "#" or ";"
+ if strings.ContainsAny(val, "\n`") {
+ val = `"""` + val + `"""`
+ } else if !f.options.IgnoreInlineComment && strings.ContainsAny(val, "#;") {
+ val = "`" + val + "`"
+ } else if len(strings.TrimSpace(val)) != len(val) {
+ val = `"` + val + `"`
+ }
+ if _, err := buf.WriteString(equalSign + val + LineBreak); err != nil {
+ return false, err
+ }
+ return false, nil
+ }
+
+ shadows := key.ValueWithShadows()
+ if len(shadows) == 0 {
+ if _, err := writeKeyValue(""); err != nil {
+ return nil, err
+ }
+ }
+
+ for _, val := range shadows {
+ exitLoop, err := writeKeyValue(val)
+ if err != nil {
+ return nil, err
+ } else if exitLoop {
+ continue KeyList
+ }
+ }
+
+ for _, val := range key.nestedValues {
+ if _, err := buf.WriteString(indent + " " + val + LineBreak); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ if PrettySection && !isLastSection {
+ // Put a line between sections
+ if _, err := buf.WriteString(LineBreak); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ return buf, nil
+}
+
+// WriteToIndent writes content into io.Writer with given indention.
+// If PrettyFormat has been set to be true,
+// it will align "=" sign with spaces under each section.
+func (f *File) WriteToIndent(w io.Writer, indent string) (int64, error) {
+ buf, err := f.writeToBuffer(indent)
+ if err != nil {
+ return 0, err
+ }
+ return buf.WriteTo(w)
+}
+
+// WriteTo writes file content into io.Writer.
+func (f *File) WriteTo(w io.Writer) (int64, error) {
+ return f.WriteToIndent(w, "")
+}
+
+// SaveToIndent writes content to file system with given value indention.
+func (f *File) SaveToIndent(filename, indent string) error {
+ // Note: Because we are truncating with os.Create,
+ // so it's safer to save to a temporary file location and rename after done.
+ buf, err := f.writeToBuffer(indent)
+ if err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile(filename, buf.Bytes(), 0666)
+}
+
+// SaveTo writes content to file system.
+func (f *File) SaveTo(filename string) error {
+ return f.SaveToIndent(filename, "")
+}
diff --git a/vendor/github.com/go-ini/ini/helper.go b/vendor/github.com/go-ini/ini/helper.go
@@ -0,0 +1,24 @@
+// Copyright 2019 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+func inSlice(str string, s []string) bool {
+ for _, v := range s {
+ if str == v {
+ return true
+ }
+ }
+ return false
+}
diff --git a/vendor/github.com/go-ini/ini/ini.go b/vendor/github.com/go-ini/ini/ini.go
@@ -0,0 +1,176 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+// Package ini provides INI file read and write functionality in Go.
+package ini
+
+import (
+ "os"
+ "regexp"
+ "runtime"
+ "strings"
+)
+
+const (
+ // Maximum allowed depth when recursively substituing variable names.
+ depthValues = 99
+)
+
+var (
+ // DefaultSection is the name of default section. You can use this var or the string literal.
+ // In most of cases, an empty string is all you need to access the section.
+ DefaultSection = "DEFAULT"
+
+ // LineBreak is the delimiter to determine or compose a new line.
+ // This variable will be changed to "\r\n" automatically on Windows at package init time.
+ LineBreak = "\n"
+
+ // Variable regexp pattern: %(variable)s
+ varPattern = regexp.MustCompile(`%\(([^)]+)\)s`)
+
+ // DefaultHeader explicitly writes default section header.
+ DefaultHeader = false
+
+ // PrettySection indicates whether to put a line between sections.
+ PrettySection = true
+ // PrettyFormat indicates whether to align "=" sign with spaces to produce pretty output
+ // or reduce all possible spaces for compact format.
+ PrettyFormat = true
+ // PrettyEqual places spaces around "=" sign even when PrettyFormat is false.
+ PrettyEqual = false
+ // DefaultFormatLeft places custom spaces on the left when PrettyFormat and PrettyEqual are both disabled.
+ DefaultFormatLeft = ""
+ // DefaultFormatRight places custom spaces on the right when PrettyFormat and PrettyEqual are both disabled.
+ DefaultFormatRight = ""
+)
+
+var inTest = len(os.Args) > 0 && strings.HasSuffix(strings.TrimSuffix(os.Args[0], ".exe"), ".test")
+
+func init() {
+ if runtime.GOOS == "windows" && !inTest {
+ LineBreak = "\r\n"
+ }
+}
+
+// LoadOptions contains all customized options used for load data source(s).
+type LoadOptions struct {
+ // Loose indicates whether the parser should ignore nonexistent files or return error.
+ Loose bool
+ // Insensitive indicates whether the parser forces all section and key names to lowercase.
+ Insensitive bool
+ // InsensitiveSections indicates whether the parser forces all section to lowercase.
+ InsensitiveSections bool
+ // InsensitiveKeys indicates whether the parser forces all key names to lowercase.
+ InsensitiveKeys bool
+ // IgnoreContinuation indicates whether to ignore continuation lines while parsing.
+ IgnoreContinuation bool
+ // IgnoreInlineComment indicates whether to ignore comments at the end of value and treat it as part of value.
+ IgnoreInlineComment bool
+ // SkipUnrecognizableLines indicates whether to skip unrecognizable lines that do not conform to key/value pairs.
+ SkipUnrecognizableLines bool
+ // ShortCircuit indicates whether to ignore other configuration sources after loaded the first available configuration source.
+ ShortCircuit bool
+ // AllowBooleanKeys indicates whether to allow boolean type keys or treat as value is missing.
+ // This type of keys are mostly used in my.cnf.
+ AllowBooleanKeys bool
+ // AllowShadows indicates whether to keep track of keys with same name under same section.
+ AllowShadows bool
+ // AllowNestedValues indicates whether to allow AWS-like nested values.
+ // Docs: http://docs.aws.amazon.com/cli/latest/topic/config-vars.html#nested-values
+ AllowNestedValues bool
+ // AllowPythonMultilineValues indicates whether to allow Python-like multi-line values.
+ // Docs: https://docs.python.org/3/library/configparser.html#supported-ini-file-structure
+ // Relevant quote: Values can also span multiple lines, as long as they are indented deeper
+ // than the first line of the value.
+ AllowPythonMultilineValues bool
+ // SpaceBeforeInlineComment indicates whether to allow comment symbols (\# and \;) inside value.
+ // Docs: https://docs.python.org/2/library/configparser.html
+ // Quote: Comments may appear on their own in an otherwise empty line, or may be entered in lines holding values or section names.
+ // In the latter case, they need to be preceded by a whitespace character to be recognized as a comment.
+ SpaceBeforeInlineComment bool
+ // UnescapeValueDoubleQuotes indicates whether to unescape double quotes inside value to regular format
+ // when value is surrounded by double quotes, e.g. key="a \"value\"" => key=a "value"
+ UnescapeValueDoubleQuotes bool
+ // UnescapeValueCommentSymbols indicates to unescape comment symbols (\# and \;) inside value to regular format
+ // when value is NOT surrounded by any quotes.
+ // Note: UNSTABLE, behavior might change to only unescape inside double quotes but may noy necessary at all.
+ UnescapeValueCommentSymbols bool
+ // UnparseableSections stores a list of blocks that are allowed with raw content which do not otherwise
+ // conform to key/value pairs. Specify the names of those blocks here.
+ UnparseableSections []string
+ // KeyValueDelimiters is the sequence of delimiters that are used to separate key and value. By default, it is "=:".
+ KeyValueDelimiters string
+ // KeyValueDelimiterOnWrite is the delimiter that are used to separate key and value output. By default, it is "=".
+ KeyValueDelimiterOnWrite string
+ // ChildSectionDelimiter is the delimiter that is used to separate child sections. By default, it is ".".
+ ChildSectionDelimiter string
+ // PreserveSurroundedQuote indicates whether to preserve surrounded quote (single and double quotes).
+ PreserveSurroundedQuote bool
+ // DebugFunc is called to collect debug information (currently only useful to debug parsing Python-style multiline values).
+ DebugFunc DebugFunc
+ // ReaderBufferSize is the buffer size of the reader in bytes.
+ ReaderBufferSize int
+ // AllowNonUniqueSections indicates whether to allow sections with the same name multiple times.
+ AllowNonUniqueSections bool
+ // AllowDuplicateShadowValues indicates whether values for shadowed keys should be deduplicated.
+ AllowDuplicateShadowValues bool
+}
+
+// DebugFunc is the type of function called to log parse events.
+type DebugFunc func(message string)
+
+// LoadSources allows caller to apply customized options for loading from data source(s).
+func LoadSources(opts LoadOptions, source interface{}, others ...interface{}) (_ *File, err error) {
+ sources := make([]dataSource, len(others)+1)
+ sources[0], err = parseDataSource(source)
+ if err != nil {
+ return nil, err
+ }
+ for i := range others {
+ sources[i+1], err = parseDataSource(others[i])
+ if err != nil {
+ return nil, err
+ }
+ }
+ f := newFile(sources, opts)
+ if err = f.Reload(); err != nil {
+ return nil, err
+ }
+ return f, nil
+}
+
+// Load loads and parses from INI data sources.
+// Arguments can be mixed of file name with string type, or raw data in []byte.
+// It will return error if list contains nonexistent files.
+func Load(source interface{}, others ...interface{}) (*File, error) {
+ return LoadSources(LoadOptions{}, source, others...)
+}
+
+// LooseLoad has exactly same functionality as Load function
+// except it ignores nonexistent files instead of returning error.
+func LooseLoad(source interface{}, others ...interface{}) (*File, error) {
+ return LoadSources(LoadOptions{Loose: true}, source, others...)
+}
+
+// InsensitiveLoad has exactly same functionality as Load function
+// except it forces all section and key names to be lowercased.
+func InsensitiveLoad(source interface{}, others ...interface{}) (*File, error) {
+ return LoadSources(LoadOptions{Insensitive: true}, source, others...)
+}
+
+// ShadowLoad has exactly same functionality as Load function
+// except it allows have shadow keys.
+func ShadowLoad(source interface{}, others ...interface{}) (*File, error) {
+ return LoadSources(LoadOptions{AllowShadows: true}, source, others...)
+}
diff --git a/vendor/github.com/go-ini/ini/key.go b/vendor/github.com/go-ini/ini/key.go
@@ -0,0 +1,837 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+ "time"
+)
+
+// Key represents a key under a section.
+type Key struct {
+ s *Section
+ Comment string
+ name string
+ value string
+ isAutoIncrement bool
+ isBooleanType bool
+
+ isShadow bool
+ shadows []*Key
+
+ nestedValues []string
+}
+
+// newKey simply return a key object with given values.
+func newKey(s *Section, name, val string) *Key {
+ return &Key{
+ s: s,
+ name: name,
+ value: val,
+ }
+}
+
+func (k *Key) addShadow(val string) error {
+ if k.isShadow {
+ return errors.New("cannot add shadow to another shadow key")
+ } else if k.isAutoIncrement || k.isBooleanType {
+ return errors.New("cannot add shadow to auto-increment or boolean key")
+ }
+
+ if !k.s.f.options.AllowDuplicateShadowValues {
+ // Deduplicate shadows based on their values.
+ if k.value == val {
+ return nil
+ }
+ for i := range k.shadows {
+ if k.shadows[i].value == val {
+ return nil
+ }
+ }
+ }
+
+ shadow := newKey(k.s, k.name, val)
+ shadow.isShadow = true
+ k.shadows = append(k.shadows, shadow)
+ return nil
+}
+
+// AddShadow adds a new shadow key to itself.
+func (k *Key) AddShadow(val string) error {
+ if !k.s.f.options.AllowShadows {
+ return errors.New("shadow key is not allowed")
+ }
+ return k.addShadow(val)
+}
+
+func (k *Key) addNestedValue(val string) error {
+ if k.isAutoIncrement || k.isBooleanType {
+ return errors.New("cannot add nested value to auto-increment or boolean key")
+ }
+
+ k.nestedValues = append(k.nestedValues, val)
+ return nil
+}
+
+// AddNestedValue adds a nested value to the key.
+func (k *Key) AddNestedValue(val string) error {
+ if !k.s.f.options.AllowNestedValues {
+ return errors.New("nested value is not allowed")
+ }
+ return k.addNestedValue(val)
+}
+
+// ValueMapper represents a mapping function for values, e.g. os.ExpandEnv
+type ValueMapper func(string) string
+
+// Name returns name of key.
+func (k *Key) Name() string {
+ return k.name
+}
+
+// Value returns raw value of key for performance purpose.
+func (k *Key) Value() string {
+ return k.value
+}
+
+// ValueWithShadows returns raw values of key and its shadows if any. Shadow
+// keys with empty values are ignored from the returned list.
+func (k *Key) ValueWithShadows() []string {
+ if len(k.shadows) == 0 {
+ if k.value == "" {
+ return []string{}
+ }
+ return []string{k.value}
+ }
+
+ vals := make([]string, 0, len(k.shadows)+1)
+ if k.value != "" {
+ vals = append(vals, k.value)
+ }
+ for _, s := range k.shadows {
+ if s.value != "" {
+ vals = append(vals, s.value)
+ }
+ }
+ return vals
+}
+
+// NestedValues returns nested values stored in the key.
+// It is possible returned value is nil if no nested values stored in the key.
+func (k *Key) NestedValues() []string {
+ return k.nestedValues
+}
+
+// transformValue takes a raw value and transforms to its final string.
+func (k *Key) transformValue(val string) string {
+ if k.s.f.ValueMapper != nil {
+ val = k.s.f.ValueMapper(val)
+ }
+
+ // Fail-fast if no indicate char found for recursive value
+ if !strings.Contains(val, "%") {
+ return val
+ }
+ for i := 0; i < depthValues; i++ {
+ vr := varPattern.FindString(val)
+ if len(vr) == 0 {
+ break
+ }
+
+ // Take off leading '%(' and trailing ')s'.
+ noption := vr[2 : len(vr)-2]
+
+ // Search in the same section.
+ // If not found or found the key itself, then search again in default section.
+ nk, err := k.s.GetKey(noption)
+ if err != nil || k == nk {
+ nk, _ = k.s.f.Section("").GetKey(noption)
+ if nk == nil {
+ // Stop when no results found in the default section,
+ // and returns the value as-is.
+ break
+ }
+ }
+
+ // Substitute by new value and take off leading '%(' and trailing ')s'.
+ val = strings.Replace(val, vr, nk.value, -1)
+ }
+ return val
+}
+
+// String returns string representation of value.
+func (k *Key) String() string {
+ return k.transformValue(k.value)
+}
+
+// Validate accepts a validate function which can
+// return modifed result as key value.
+func (k *Key) Validate(fn func(string) string) string {
+ return fn(k.String())
+}
+
+// parseBool returns the boolean value represented by the string.
+//
+// It accepts 1, t, T, TRUE, true, True, YES, yes, Yes, y, ON, on, On,
+// 0, f, F, FALSE, false, False, NO, no, No, n, OFF, off, Off.
+// Any other value returns an error.
+func parseBool(str string) (value bool, err error) {
+ switch str {
+ case "1", "t", "T", "true", "TRUE", "True", "YES", "yes", "Yes", "y", "ON", "on", "On":
+ return true, nil
+ case "0", "f", "F", "false", "FALSE", "False", "NO", "no", "No", "n", "OFF", "off", "Off":
+ return false, nil
+ }
+ return false, fmt.Errorf("parsing \"%s\": invalid syntax", str)
+}
+
+// Bool returns bool type value.
+func (k *Key) Bool() (bool, error) {
+ return parseBool(k.String())
+}
+
+// Float64 returns float64 type value.
+func (k *Key) Float64() (float64, error) {
+ return strconv.ParseFloat(k.String(), 64)
+}
+
+// Int returns int type value.
+func (k *Key) Int() (int, error) {
+ v, err := strconv.ParseInt(k.String(), 0, 64)
+ return int(v), err
+}
+
+// Int64 returns int64 type value.
+func (k *Key) Int64() (int64, error) {
+ return strconv.ParseInt(k.String(), 0, 64)
+}
+
+// Uint returns uint type valued.
+func (k *Key) Uint() (uint, error) {
+ u, e := strconv.ParseUint(k.String(), 0, 64)
+ return uint(u), e
+}
+
+// Uint64 returns uint64 type value.
+func (k *Key) Uint64() (uint64, error) {
+ return strconv.ParseUint(k.String(), 0, 64)
+}
+
+// Duration returns time.Duration type value.
+func (k *Key) Duration() (time.Duration, error) {
+ return time.ParseDuration(k.String())
+}
+
+// TimeFormat parses with given format and returns time.Time type value.
+func (k *Key) TimeFormat(format string) (time.Time, error) {
+ return time.Parse(format, k.String())
+}
+
+// Time parses with RFC3339 format and returns time.Time type value.
+func (k *Key) Time() (time.Time, error) {
+ return k.TimeFormat(time.RFC3339)
+}
+
+// MustString returns default value if key value is empty.
+func (k *Key) MustString(defaultVal string) string {
+ val := k.String()
+ if len(val) == 0 {
+ k.value = defaultVal
+ return defaultVal
+ }
+ return val
+}
+
+// MustBool always returns value without error,
+// it returns false if error occurs.
+func (k *Key) MustBool(defaultVal ...bool) bool {
+ val, err := k.Bool()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = strconv.FormatBool(defaultVal[0])
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustFloat64 always returns value without error,
+// it returns 0.0 if error occurs.
+func (k *Key) MustFloat64(defaultVal ...float64) float64 {
+ val, err := k.Float64()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = strconv.FormatFloat(defaultVal[0], 'f', -1, 64)
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustInt always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt(defaultVal ...int) int {
+ val, err := k.Int()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = strconv.FormatInt(int64(defaultVal[0]), 10)
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustInt64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustInt64(defaultVal ...int64) int64 {
+ val, err := k.Int64()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = strconv.FormatInt(defaultVal[0], 10)
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustUint always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint(defaultVal ...uint) uint {
+ val, err := k.Uint()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = strconv.FormatUint(uint64(defaultVal[0]), 10)
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustUint64 always returns value without error,
+// it returns 0 if error occurs.
+func (k *Key) MustUint64(defaultVal ...uint64) uint64 {
+ val, err := k.Uint64()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = strconv.FormatUint(defaultVal[0], 10)
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustDuration always returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustDuration(defaultVal ...time.Duration) time.Duration {
+ val, err := k.Duration()
+ if len(defaultVal) > 0 && err != nil {
+ k.value = defaultVal[0].String()
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustTimeFormat always parses with given format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTimeFormat(format string, defaultVal ...time.Time) time.Time {
+ val, err := k.TimeFormat(format)
+ if len(defaultVal) > 0 && err != nil {
+ k.value = defaultVal[0].Format(format)
+ return defaultVal[0]
+ }
+ return val
+}
+
+// MustTime always parses with RFC3339 format and returns value without error,
+// it returns zero value if error occurs.
+func (k *Key) MustTime(defaultVal ...time.Time) time.Time {
+ return k.MustTimeFormat(time.RFC3339, defaultVal...)
+}
+
+// In always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) In(defaultVal string, candidates []string) string {
+ val := k.String()
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InFloat64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InFloat64(defaultVal float64, candidates []float64) float64 {
+ val := k.MustFloat64()
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InInt always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt(defaultVal int, candidates []int) int {
+ val := k.MustInt()
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InInt64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InInt64(defaultVal int64, candidates []int64) int64 {
+ val := k.MustInt64()
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InUint always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint(defaultVal uint, candidates []uint) uint {
+ val := k.MustUint()
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InUint64 always returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InUint64(defaultVal uint64, candidates []uint64) uint64 {
+ val := k.MustUint64()
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InTimeFormat always parses with given format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTimeFormat(format string, defaultVal time.Time, candidates []time.Time) time.Time {
+ val := k.MustTimeFormat(format)
+ for _, cand := range candidates {
+ if val == cand {
+ return val
+ }
+ }
+ return defaultVal
+}
+
+// InTime always parses with RFC3339 format and returns value without error,
+// it returns default value if error occurs or doesn't fit into candidates.
+func (k *Key) InTime(defaultVal time.Time, candidates []time.Time) time.Time {
+ return k.InTimeFormat(time.RFC3339, defaultVal, candidates)
+}
+
+// RangeFloat64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeFloat64(defaultVal, min, max float64) float64 {
+ val := k.MustFloat64()
+ if val < min || val > max {
+ return defaultVal
+ }
+ return val
+}
+
+// RangeInt checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt(defaultVal, min, max int) int {
+ val := k.MustInt()
+ if val < min || val > max {
+ return defaultVal
+ }
+ return val
+}
+
+// RangeInt64 checks if value is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeInt64(defaultVal, min, max int64) int64 {
+ val := k.MustInt64()
+ if val < min || val > max {
+ return defaultVal
+ }
+ return val
+}
+
+// RangeTimeFormat checks if value with given format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTimeFormat(format string, defaultVal, min, max time.Time) time.Time {
+ val := k.MustTimeFormat(format)
+ if val.Unix() < min.Unix() || val.Unix() > max.Unix() {
+ return defaultVal
+ }
+ return val
+}
+
+// RangeTime checks if value with RFC3339 format is in given range inclusively,
+// and returns default value if it's not.
+func (k *Key) RangeTime(defaultVal, min, max time.Time) time.Time {
+ return k.RangeTimeFormat(time.RFC3339, defaultVal, min, max)
+}
+
+// Strings returns list of string divided by given delimiter.
+func (k *Key) Strings(delim string) []string {
+ str := k.String()
+ if len(str) == 0 {
+ return []string{}
+ }
+
+ runes := []rune(str)
+ vals := make([]string, 0, 2)
+ var buf bytes.Buffer
+ escape := false
+ idx := 0
+ for {
+ if escape {
+ escape = false
+ if runes[idx] != '\\' && !strings.HasPrefix(string(runes[idx:]), delim) {
+ buf.WriteRune('\\')
+ }
+ buf.WriteRune(runes[idx])
+ } else {
+ if runes[idx] == '\\' {
+ escape = true
+ } else if strings.HasPrefix(string(runes[idx:]), delim) {
+ idx += len(delim) - 1
+ vals = append(vals, strings.TrimSpace(buf.String()))
+ buf.Reset()
+ } else {
+ buf.WriteRune(runes[idx])
+ }
+ }
+ idx++
+ if idx == len(runes) {
+ break
+ }
+ }
+
+ if buf.Len() > 0 {
+ vals = append(vals, strings.TrimSpace(buf.String()))
+ }
+
+ return vals
+}
+
+// StringsWithShadows returns list of string divided by given delimiter.
+// Shadows will also be appended if any.
+func (k *Key) StringsWithShadows(delim string) []string {
+ vals := k.ValueWithShadows()
+ results := make([]string, 0, len(vals)*2)
+ for i := range vals {
+ if len(vals) == 0 {
+ continue
+ }
+
+ results = append(results, strings.Split(vals[i], delim)...)
+ }
+
+ for i := range results {
+ results[i] = k.transformValue(strings.TrimSpace(results[i]))
+ }
+ return results
+}
+
+// Float64s returns list of float64 divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Float64s(delim string) []float64 {
+ vals, _ := k.parseFloat64s(k.Strings(delim), true, false)
+ return vals
+}
+
+// Ints returns list of int divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Ints(delim string) []int {
+ vals, _ := k.parseInts(k.Strings(delim), true, false)
+ return vals
+}
+
+// Int64s returns list of int64 divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Int64s(delim string) []int64 {
+ vals, _ := k.parseInt64s(k.Strings(delim), true, false)
+ return vals
+}
+
+// Uints returns list of uint divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Uints(delim string) []uint {
+ vals, _ := k.parseUints(k.Strings(delim), true, false)
+ return vals
+}
+
+// Uint64s returns list of uint64 divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Uint64s(delim string) []uint64 {
+ vals, _ := k.parseUint64s(k.Strings(delim), true, false)
+ return vals
+}
+
+// Bools returns list of bool divided by given delimiter. Any invalid input will be treated as zero value.
+func (k *Key) Bools(delim string) []bool {
+ vals, _ := k.parseBools(k.Strings(delim), true, false)
+ return vals
+}
+
+// TimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
+func (k *Key) TimesFormat(format, delim string) []time.Time {
+ vals, _ := k.parseTimesFormat(format, k.Strings(delim), true, false)
+ return vals
+}
+
+// Times parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+// Any invalid input will be treated as zero value (0001-01-01 00:00:00 +0000 UTC).
+func (k *Key) Times(delim string) []time.Time {
+ return k.TimesFormat(time.RFC3339, delim)
+}
+
+// ValidFloat64s returns list of float64 divided by given delimiter. If some value is not float, then
+// it will not be included to result list.
+func (k *Key) ValidFloat64s(delim string) []float64 {
+ vals, _ := k.parseFloat64s(k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidInts returns list of int divided by given delimiter. If some value is not integer, then it will
+// not be included to result list.
+func (k *Key) ValidInts(delim string) []int {
+ vals, _ := k.parseInts(k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidInt64s returns list of int64 divided by given delimiter. If some value is not 64-bit integer,
+// then it will not be included to result list.
+func (k *Key) ValidInt64s(delim string) []int64 {
+ vals, _ := k.parseInt64s(k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidUints returns list of uint divided by given delimiter. If some value is not unsigned integer,
+// then it will not be included to result list.
+func (k *Key) ValidUints(delim string) []uint {
+ vals, _ := k.parseUints(k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidUint64s returns list of uint64 divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidUint64s(delim string) []uint64 {
+ vals, _ := k.parseUint64s(k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidBools returns list of bool divided by given delimiter. If some value is not 64-bit unsigned
+// integer, then it will not be included to result list.
+func (k *Key) ValidBools(delim string) []bool {
+ vals, _ := k.parseBools(k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidTimesFormat parses with given format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimesFormat(format, delim string) []time.Time {
+ vals, _ := k.parseTimesFormat(format, k.Strings(delim), false, false)
+ return vals
+}
+
+// ValidTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter.
+func (k *Key) ValidTimes(delim string) []time.Time {
+ return k.ValidTimesFormat(time.RFC3339, delim)
+}
+
+// StrictFloat64s returns list of float64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictFloat64s(delim string) ([]float64, error) {
+ return k.parseFloat64s(k.Strings(delim), false, true)
+}
+
+// StrictInts returns list of int divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInts(delim string) ([]int, error) {
+ return k.parseInts(k.Strings(delim), false, true)
+}
+
+// StrictInt64s returns list of int64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictInt64s(delim string) ([]int64, error) {
+ return k.parseInt64s(k.Strings(delim), false, true)
+}
+
+// StrictUints returns list of uint divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUints(delim string) ([]uint, error) {
+ return k.parseUints(k.Strings(delim), false, true)
+}
+
+// StrictUint64s returns list of uint64 divided by given delimiter or error on first invalid input.
+func (k *Key) StrictUint64s(delim string) ([]uint64, error) {
+ return k.parseUint64s(k.Strings(delim), false, true)
+}
+
+// StrictBools returns list of bool divided by given delimiter or error on first invalid input.
+func (k *Key) StrictBools(delim string) ([]bool, error) {
+ return k.parseBools(k.Strings(delim), false, true)
+}
+
+// StrictTimesFormat parses with given format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimesFormat(format, delim string) ([]time.Time, error) {
+ return k.parseTimesFormat(format, k.Strings(delim), false, true)
+}
+
+// StrictTimes parses with RFC3339 format and returns list of time.Time divided by given delimiter
+// or error on first invalid input.
+func (k *Key) StrictTimes(delim string) ([]time.Time, error) {
+ return k.StrictTimesFormat(time.RFC3339, delim)
+}
+
+// parseBools transforms strings to bools.
+func (k *Key) parseBools(strs []string, addInvalid, returnOnInvalid bool) ([]bool, error) {
+ vals := make([]bool, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := parseBool(str)
+ return val, err
+ }
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, val.(bool))
+ }
+ }
+ return vals, err
+}
+
+// parseFloat64s transforms strings to float64s.
+func (k *Key) parseFloat64s(strs []string, addInvalid, returnOnInvalid bool) ([]float64, error) {
+ vals := make([]float64, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := strconv.ParseFloat(str, 64)
+ return val, err
+ }
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, val.(float64))
+ }
+ }
+ return vals, err
+}
+
+// parseInts transforms strings to ints.
+func (k *Key) parseInts(strs []string, addInvalid, returnOnInvalid bool) ([]int, error) {
+ vals := make([]int, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := strconv.ParseInt(str, 0, 64)
+ return val, err
+ }
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, int(val.(int64)))
+ }
+ }
+ return vals, err
+}
+
+// parseInt64s transforms strings to int64s.
+func (k *Key) parseInt64s(strs []string, addInvalid, returnOnInvalid bool) ([]int64, error) {
+ vals := make([]int64, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := strconv.ParseInt(str, 0, 64)
+ return val, err
+ }
+
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, val.(int64))
+ }
+ }
+ return vals, err
+}
+
+// parseUints transforms strings to uints.
+func (k *Key) parseUints(strs []string, addInvalid, returnOnInvalid bool) ([]uint, error) {
+ vals := make([]uint, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := strconv.ParseUint(str, 0, 64)
+ return val, err
+ }
+
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, uint(val.(uint64)))
+ }
+ }
+ return vals, err
+}
+
+// parseUint64s transforms strings to uint64s.
+func (k *Key) parseUint64s(strs []string, addInvalid, returnOnInvalid bool) ([]uint64, error) {
+ vals := make([]uint64, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := strconv.ParseUint(str, 0, 64)
+ return val, err
+ }
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, val.(uint64))
+ }
+ }
+ return vals, err
+}
+
+type Parser func(str string) (interface{}, error)
+
+// parseTimesFormat transforms strings to times in given format.
+func (k *Key) parseTimesFormat(format string, strs []string, addInvalid, returnOnInvalid bool) ([]time.Time, error) {
+ vals := make([]time.Time, 0, len(strs))
+ parser := func(str string) (interface{}, error) {
+ val, err := time.Parse(format, str)
+ return val, err
+ }
+ rawVals, err := k.doParse(strs, addInvalid, returnOnInvalid, parser)
+ if err == nil {
+ for _, val := range rawVals {
+ vals = append(vals, val.(time.Time))
+ }
+ }
+ return vals, err
+}
+
+// doParse transforms strings to different types
+func (k *Key) doParse(strs []string, addInvalid, returnOnInvalid bool, parser Parser) ([]interface{}, error) {
+ vals := make([]interface{}, 0, len(strs))
+ for _, str := range strs {
+ val, err := parser(str)
+ if err != nil && returnOnInvalid {
+ return nil, err
+ }
+ if err == nil || addInvalid {
+ vals = append(vals, val)
+ }
+ }
+ return vals, nil
+}
+
+// SetValue changes key value.
+func (k *Key) SetValue(v string) {
+ if k.s.f.BlockMode {
+ k.s.f.lock.Lock()
+ defer k.s.f.lock.Unlock()
+ }
+
+ k.value = v
+ k.s.keysHash[k.name] = v
+}
diff --git a/vendor/github.com/go-ini/ini/parser.go b/vendor/github.com/go-ini/ini/parser.go
@@ -0,0 +1,520 @@
+// Copyright 2015 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "bufio"
+ "bytes"
+ "fmt"
+ "io"
+ "regexp"
+ "strconv"
+ "strings"
+ "unicode"
+)
+
+const minReaderBufferSize = 4096
+
+var pythonMultiline = regexp.MustCompile(`^([\t\f ]+)(.*)`)
+
+type parserOptions struct {
+ IgnoreContinuation bool
+ IgnoreInlineComment bool
+ AllowPythonMultilineValues bool
+ SpaceBeforeInlineComment bool
+ UnescapeValueDoubleQuotes bool
+ UnescapeValueCommentSymbols bool
+ PreserveSurroundedQuote bool
+ DebugFunc DebugFunc
+ ReaderBufferSize int
+}
+
+type parser struct {
+ buf *bufio.Reader
+ options parserOptions
+
+ isEOF bool
+ count int
+ comment *bytes.Buffer
+}
+
+func (p *parser) debug(format string, args ...interface{}) {
+ if p.options.DebugFunc != nil {
+ p.options.DebugFunc(fmt.Sprintf(format, args...))
+ }
+}
+
+func newParser(r io.Reader, opts parserOptions) *parser {
+ size := opts.ReaderBufferSize
+ if size < minReaderBufferSize {
+ size = minReaderBufferSize
+ }
+
+ return &parser{
+ buf: bufio.NewReaderSize(r, size),
+ options: opts,
+ count: 1,
+ comment: &bytes.Buffer{},
+ }
+}
+
+// BOM handles header of UTF-8, UTF-16 LE and UTF-16 BE's BOM format.
+// http://en.wikipedia.org/wiki/Byte_order_mark#Representations_of_byte_order_marks_by_encoding
+func (p *parser) BOM() error {
+ mask, err := p.buf.Peek(2)
+ if err != nil && err != io.EOF {
+ return err
+ } else if len(mask) < 2 {
+ return nil
+ }
+
+ switch {
+ case mask[0] == 254 && mask[1] == 255:
+ fallthrough
+ case mask[0] == 255 && mask[1] == 254:
+ _, err = p.buf.Read(mask)
+ if err != nil {
+ return err
+ }
+ case mask[0] == 239 && mask[1] == 187:
+ mask, err := p.buf.Peek(3)
+ if err != nil && err != io.EOF {
+ return err
+ } else if len(mask) < 3 {
+ return nil
+ }
+ if mask[2] == 191 {
+ _, err = p.buf.Read(mask)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+}
+
+func (p *parser) readUntil(delim byte) ([]byte, error) {
+ data, err := p.buf.ReadBytes(delim)
+ if err != nil {
+ if err == io.EOF {
+ p.isEOF = true
+ } else {
+ return nil, err
+ }
+ }
+ return data, nil
+}
+
+func cleanComment(in []byte) ([]byte, bool) {
+ i := bytes.IndexAny(in, "#;")
+ if i == -1 {
+ return nil, false
+ }
+ return in[i:], true
+}
+
+func readKeyName(delimiters string, in []byte) (string, int, error) {
+ line := string(in)
+
+ // Check if key name surrounded by quotes.
+ var keyQuote string
+ if line[0] == '"' {
+ if len(line) > 6 && line[0:3] == `"""` {
+ keyQuote = `"""`
+ } else {
+ keyQuote = `"`
+ }
+ } else if line[0] == '`' {
+ keyQuote = "`"
+ }
+
+ // Get out key name
+ var endIdx int
+ if len(keyQuote) > 0 {
+ startIdx := len(keyQuote)
+ // FIXME: fail case -> """"""name"""=value
+ pos := strings.Index(line[startIdx:], keyQuote)
+ if pos == -1 {
+ return "", -1, fmt.Errorf("missing closing key quote: %s", line)
+ }
+ pos += startIdx
+
+ // Find key-value delimiter
+ i := strings.IndexAny(line[pos+startIdx:], delimiters)
+ if i < 0 {
+ return "", -1, ErrDelimiterNotFound{line}
+ }
+ endIdx = pos + i
+ return strings.TrimSpace(line[startIdx:pos]), endIdx + startIdx + 1, nil
+ }
+
+ endIdx = strings.IndexAny(line, delimiters)
+ if endIdx < 0 {
+ return "", -1, ErrDelimiterNotFound{line}
+ }
+ if endIdx == 0 {
+ return "", -1, ErrEmptyKeyName{line}
+ }
+
+ return strings.TrimSpace(line[0:endIdx]), endIdx + 1, nil
+}
+
+func (p *parser) readMultilines(line, val, valQuote string) (string, error) {
+ for {
+ data, err := p.readUntil('\n')
+ if err != nil {
+ return "", err
+ }
+ next := string(data)
+
+ pos := strings.LastIndex(next, valQuote)
+ if pos > -1 {
+ val += next[:pos]
+
+ comment, has := cleanComment([]byte(next[pos:]))
+ if has {
+ p.comment.Write(bytes.TrimSpace(comment))
+ }
+ break
+ }
+ val += next
+ if p.isEOF {
+ return "", fmt.Errorf("missing closing key quote from %q to %q", line, next)
+ }
+ }
+ return val, nil
+}
+
+func (p *parser) readContinuationLines(val string) (string, error) {
+ for {
+ data, err := p.readUntil('\n')
+ if err != nil {
+ return "", err
+ }
+ next := strings.TrimSpace(string(data))
+
+ if len(next) == 0 {
+ break
+ }
+ val += next
+ if val[len(val)-1] != '\\' {
+ break
+ }
+ val = val[:len(val)-1]
+ }
+ return val, nil
+}
+
+// hasSurroundedQuote check if and only if the first and last characters
+// are quotes \" or \'.
+// It returns false if any other parts also contain same kind of quotes.
+func hasSurroundedQuote(in string, quote byte) bool {
+ return len(in) >= 2 && in[0] == quote && in[len(in)-1] == quote &&
+ strings.IndexByte(in[1:], quote) == len(in)-2
+}
+
+func (p *parser) readValue(in []byte, bufferSize int) (string, error) {
+
+ line := strings.TrimLeftFunc(string(in), unicode.IsSpace)
+ if len(line) == 0 {
+ if p.options.AllowPythonMultilineValues && len(in) > 0 && in[len(in)-1] == '\n' {
+ return p.readPythonMultilines(line, bufferSize)
+ }
+ return "", nil
+ }
+
+ var valQuote string
+ if len(line) > 3 && line[0:3] == `"""` {
+ valQuote = `"""`
+ } else if line[0] == '`' {
+ valQuote = "`"
+ } else if p.options.UnescapeValueDoubleQuotes && line[0] == '"' {
+ valQuote = `"`
+ }
+
+ if len(valQuote) > 0 {
+ startIdx := len(valQuote)
+ pos := strings.LastIndex(line[startIdx:], valQuote)
+ // Check for multi-line value
+ if pos == -1 {
+ return p.readMultilines(line, line[startIdx:], valQuote)
+ }
+
+ if p.options.UnescapeValueDoubleQuotes && valQuote == `"` {
+ return strings.Replace(line[startIdx:pos+startIdx], `\"`, `"`, -1), nil
+ }
+ return line[startIdx : pos+startIdx], nil
+ }
+
+ lastChar := line[len(line)-1]
+ // Won't be able to reach here if value only contains whitespace
+ line = strings.TrimSpace(line)
+ trimmedLastChar := line[len(line)-1]
+
+ // Check continuation lines when desired
+ if !p.options.IgnoreContinuation && trimmedLastChar == '\\' {
+ return p.readContinuationLines(line[:len(line)-1])
+ }
+
+ // Check if ignore inline comment
+ if !p.options.IgnoreInlineComment {
+ var i int
+ if p.options.SpaceBeforeInlineComment {
+ i = strings.Index(line, " #")
+ if i == -1 {
+ i = strings.Index(line, " ;")
+ }
+
+ } else {
+ i = strings.IndexAny(line, "#;")
+ }
+
+ if i > -1 {
+ p.comment.WriteString(line[i:])
+ line = strings.TrimSpace(line[:i])
+ }
+
+ }
+
+ // Trim single and double quotes
+ if (hasSurroundedQuote(line, '\'') ||
+ hasSurroundedQuote(line, '"')) && !p.options.PreserveSurroundedQuote {
+ line = line[1 : len(line)-1]
+ } else if len(valQuote) == 0 && p.options.UnescapeValueCommentSymbols {
+ line = strings.ReplaceAll(line, `\;`, ";")
+ line = strings.ReplaceAll(line, `\#`, "#")
+ } else if p.options.AllowPythonMultilineValues && lastChar == '\n' {
+ return p.readPythonMultilines(line, bufferSize)
+ }
+
+ return line, nil
+}
+
+func (p *parser) readPythonMultilines(line string, bufferSize int) (string, error) {
+ parserBufferPeekResult, _ := p.buf.Peek(bufferSize)
+ peekBuffer := bytes.NewBuffer(parserBufferPeekResult)
+
+ for {
+ peekData, peekErr := peekBuffer.ReadBytes('\n')
+ if peekErr != nil && peekErr != io.EOF {
+ p.debug("readPythonMultilines: failed to peek with error: %v", peekErr)
+ return "", peekErr
+ }
+
+ p.debug("readPythonMultilines: parsing %q", string(peekData))
+
+ peekMatches := pythonMultiline.FindStringSubmatch(string(peekData))
+ p.debug("readPythonMultilines: matched %d parts", len(peekMatches))
+ for n, v := range peekMatches {
+ p.debug(" %d: %q", n, v)
+ }
+
+ // Return if not a Python multiline value.
+ if len(peekMatches) != 3 {
+ p.debug("readPythonMultilines: end of value, got: %q", line)
+ return line, nil
+ }
+
+ // Advance the parser reader (buffer) in-sync with the peek buffer.
+ _, err := p.buf.Discard(len(peekData))
+ if err != nil {
+ p.debug("readPythonMultilines: failed to skip to the end, returning error")
+ return "", err
+ }
+
+ line += "\n" + peekMatches[0]
+ }
+}
+
+// parse parses data through an io.Reader.
+func (f *File) parse(reader io.Reader) (err error) {
+ p := newParser(reader, parserOptions{
+ IgnoreContinuation: f.options.IgnoreContinuation,
+ IgnoreInlineComment: f.options.IgnoreInlineComment,
+ AllowPythonMultilineValues: f.options.AllowPythonMultilineValues,
+ SpaceBeforeInlineComment: f.options.SpaceBeforeInlineComment,
+ UnescapeValueDoubleQuotes: f.options.UnescapeValueDoubleQuotes,
+ UnescapeValueCommentSymbols: f.options.UnescapeValueCommentSymbols,
+ PreserveSurroundedQuote: f.options.PreserveSurroundedQuote,
+ DebugFunc: f.options.DebugFunc,
+ ReaderBufferSize: f.options.ReaderBufferSize,
+ })
+ if err = p.BOM(); err != nil {
+ return fmt.Errorf("BOM: %v", err)
+ }
+
+ // Ignore error because default section name is never empty string.
+ name := DefaultSection
+ if f.options.Insensitive || f.options.InsensitiveSections {
+ name = strings.ToLower(DefaultSection)
+ }
+ section, _ := f.NewSection(name)
+
+ // This "last" is not strictly equivalent to "previous one" if current key is not the first nested key
+ var isLastValueEmpty bool
+ var lastRegularKey *Key
+
+ var line []byte
+ var inUnparseableSection bool
+
+ // NOTE: Iterate and increase `currentPeekSize` until
+ // the size of the parser buffer is found.
+ // TODO(unknwon): When Golang 1.10 is the lowest version supported, replace with `parserBufferSize := p.buf.Size()`.
+ parserBufferSize := 0
+ // NOTE: Peek 4kb at a time.
+ currentPeekSize := minReaderBufferSize
+
+ if f.options.AllowPythonMultilineValues {
+ for {
+ peekBytes, _ := p.buf.Peek(currentPeekSize)
+ peekBytesLength := len(peekBytes)
+
+ if parserBufferSize >= peekBytesLength {
+ break
+ }
+
+ currentPeekSize *= 2
+ parserBufferSize = peekBytesLength
+ }
+ }
+
+ for !p.isEOF {
+ line, err = p.readUntil('\n')
+ if err != nil {
+ return err
+ }
+
+ if f.options.AllowNestedValues &&
+ isLastValueEmpty && len(line) > 0 {
+ if line[0] == ' ' || line[0] == '\t' {
+ err = lastRegularKey.addNestedValue(string(bytes.TrimSpace(line)))
+ if err != nil {
+ return err
+ }
+ continue
+ }
+ }
+
+ line = bytes.TrimLeftFunc(line, unicode.IsSpace)
+ if len(line) == 0 {
+ continue
+ }
+
+ // Comments
+ if line[0] == '#' || line[0] == ';' {
+ // Note: we do not care ending line break,
+ // it is needed for adding second line,
+ // so just clean it once at the end when set to value.
+ p.comment.Write(line)
+ continue
+ }
+
+ // Section
+ if line[0] == '[' {
+ // Read to the next ']' (TODO: support quoted strings)
+ closeIdx := bytes.LastIndexByte(line, ']')
+ if closeIdx == -1 {
+ return fmt.Errorf("unclosed section: %s", line)
+ }
+
+ name := string(line[1:closeIdx])
+ section, err = f.NewSection(name)
+ if err != nil {
+ return err
+ }
+
+ comment, has := cleanComment(line[closeIdx+1:])
+ if has {
+ p.comment.Write(comment)
+ }
+
+ section.Comment = strings.TrimSpace(p.comment.String())
+
+ // Reset auto-counter and comments
+ p.comment.Reset()
+ p.count = 1
+ // Nested values can't span sections
+ isLastValueEmpty = false
+
+ inUnparseableSection = false
+ for i := range f.options.UnparseableSections {
+ if f.options.UnparseableSections[i] == name ||
+ ((f.options.Insensitive || f.options.InsensitiveSections) && strings.EqualFold(f.options.UnparseableSections[i], name)) {
+ inUnparseableSection = true
+ continue
+ }
+ }
+ continue
+ }
+
+ if inUnparseableSection {
+ section.isRawSection = true
+ section.rawBody += string(line)
+ continue
+ }
+
+ kname, offset, err := readKeyName(f.options.KeyValueDelimiters, line)
+ if err != nil {
+ switch {
+ // Treat as boolean key when desired, and whole line is key name.
+ case IsErrDelimiterNotFound(err):
+ switch {
+ case f.options.AllowBooleanKeys:
+ kname, err := p.readValue(line, parserBufferSize)
+ if err != nil {
+ return err
+ }
+ key, err := section.NewBooleanKey(kname)
+ if err != nil {
+ return err
+ }
+ key.Comment = strings.TrimSpace(p.comment.String())
+ p.comment.Reset()
+ continue
+
+ case f.options.SkipUnrecognizableLines:
+ continue
+ }
+ case IsErrEmptyKeyName(err) && f.options.SkipUnrecognizableLines:
+ continue
+ }
+ return err
+ }
+
+ // Auto increment.
+ isAutoIncr := false
+ if kname == "-" {
+ isAutoIncr = true
+ kname = "#" + strconv.Itoa(p.count)
+ p.count++
+ }
+
+ value, err := p.readValue(line[offset:], parserBufferSize)
+ if err != nil {
+ return err
+ }
+ isLastValueEmpty = len(value) == 0
+
+ key, err := section.NewKey(kname, value)
+ if err != nil {
+ return err
+ }
+ key.isAutoIncrement = isAutoIncr
+ key.Comment = strings.TrimSpace(p.comment.String())
+ p.comment.Reset()
+ lastRegularKey = key
+ }
+ return nil
+}
diff --git a/vendor/github.com/go-ini/ini/section.go b/vendor/github.com/go-ini/ini/section.go
@@ -0,0 +1,256 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "errors"
+ "fmt"
+ "strings"
+)
+
+// Section represents a config section.
+type Section struct {
+ f *File
+ Comment string
+ name string
+ keys map[string]*Key
+ keyList []string
+ keysHash map[string]string
+
+ isRawSection bool
+ rawBody string
+}
+
+func newSection(f *File, name string) *Section {
+ return &Section{
+ f: f,
+ name: name,
+ keys: make(map[string]*Key),
+ keyList: make([]string, 0, 10),
+ keysHash: make(map[string]string),
+ }
+}
+
+// Name returns name of Section.
+func (s *Section) Name() string {
+ return s.name
+}
+
+// Body returns rawBody of Section if the section was marked as unparseable.
+// It still follows the other rules of the INI format surrounding leading/trailing whitespace.
+func (s *Section) Body() string {
+ return strings.TrimSpace(s.rawBody)
+}
+
+// SetBody updates body content only if section is raw.
+func (s *Section) SetBody(body string) {
+ if !s.isRawSection {
+ return
+ }
+ s.rawBody = body
+}
+
+// NewKey creates a new key to given section.
+func (s *Section) NewKey(name, val string) (*Key, error) {
+ if len(name) == 0 {
+ return nil, errors.New("error creating new key: empty key name")
+ } else if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+ name = strings.ToLower(name)
+ }
+
+ if s.f.BlockMode {
+ s.f.lock.Lock()
+ defer s.f.lock.Unlock()
+ }
+
+ if inSlice(name, s.keyList) {
+ if s.f.options.AllowShadows {
+ if err := s.keys[name].addShadow(val); err != nil {
+ return nil, err
+ }
+ } else {
+ s.keys[name].value = val
+ s.keysHash[name] = val
+ }
+ return s.keys[name], nil
+ }
+
+ s.keyList = append(s.keyList, name)
+ s.keys[name] = newKey(s, name, val)
+ s.keysHash[name] = val
+ return s.keys[name], nil
+}
+
+// NewBooleanKey creates a new boolean type key to given section.
+func (s *Section) NewBooleanKey(name string) (*Key, error) {
+ key, err := s.NewKey(name, "true")
+ if err != nil {
+ return nil, err
+ }
+
+ key.isBooleanType = true
+ return key, nil
+}
+
+// GetKey returns key in section by given name.
+func (s *Section) GetKey(name string) (*Key, error) {
+ if s.f.BlockMode {
+ s.f.lock.RLock()
+ }
+ if s.f.options.Insensitive || s.f.options.InsensitiveKeys {
+ name = strings.ToLower(name)
+ }
+ key := s.keys[name]
+ if s.f.BlockMode {
+ s.f.lock.RUnlock()
+ }
+
+ if key == nil {
+ // Check if it is a child-section.
+ sname := s.name
+ for {
+ if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+ sname = sname[:i]
+ sec, err := s.f.GetSection(sname)
+ if err != nil {
+ continue
+ }
+ return sec.GetKey(name)
+ }
+ break
+ }
+ return nil, fmt.Errorf("error when getting key of section %q: key %q not exists", s.name, name)
+ }
+ return key, nil
+}
+
+// HasKey returns true if section contains a key with given name.
+func (s *Section) HasKey(name string) bool {
+ key, _ := s.GetKey(name)
+ return key != nil
+}
+
+// Deprecated: Use "HasKey" instead.
+func (s *Section) Haskey(name string) bool {
+ return s.HasKey(name)
+}
+
+// HasValue returns true if section contains given raw value.
+func (s *Section) HasValue(value string) bool {
+ if s.f.BlockMode {
+ s.f.lock.RLock()
+ defer s.f.lock.RUnlock()
+ }
+
+ for _, k := range s.keys {
+ if value == k.value {
+ return true
+ }
+ }
+ return false
+}
+
+// Key assumes named Key exists in section and returns a zero-value when not.
+func (s *Section) Key(name string) *Key {
+ key, err := s.GetKey(name)
+ if err != nil {
+ // It's OK here because the only possible error is empty key name,
+ // but if it's empty, this piece of code won't be executed.
+ key, _ = s.NewKey(name, "")
+ return key
+ }
+ return key
+}
+
+// Keys returns list of keys of section.
+func (s *Section) Keys() []*Key {
+ keys := make([]*Key, len(s.keyList))
+ for i := range s.keyList {
+ keys[i] = s.Key(s.keyList[i])
+ }
+ return keys
+}
+
+// ParentKeys returns list of keys of parent section.
+func (s *Section) ParentKeys() []*Key {
+ var parentKeys []*Key
+ sname := s.name
+ for {
+ if i := strings.LastIndex(sname, s.f.options.ChildSectionDelimiter); i > -1 {
+ sname = sname[:i]
+ sec, err := s.f.GetSection(sname)
+ if err != nil {
+ continue
+ }
+ parentKeys = append(parentKeys, sec.Keys()...)
+ } else {
+ break
+ }
+
+ }
+ return parentKeys
+}
+
+// KeyStrings returns list of key names of section.
+func (s *Section) KeyStrings() []string {
+ list := make([]string, len(s.keyList))
+ copy(list, s.keyList)
+ return list
+}
+
+// KeysHash returns keys hash consisting of names and values.
+func (s *Section) KeysHash() map[string]string {
+ if s.f.BlockMode {
+ s.f.lock.RLock()
+ defer s.f.lock.RUnlock()
+ }
+
+ hash := make(map[string]string, len(s.keysHash))
+ for key, value := range s.keysHash {
+ hash[key] = value
+ }
+ return hash
+}
+
+// DeleteKey deletes a key from section.
+func (s *Section) DeleteKey(name string) {
+ if s.f.BlockMode {
+ s.f.lock.Lock()
+ defer s.f.lock.Unlock()
+ }
+
+ for i, k := range s.keyList {
+ if k == name {
+ s.keyList = append(s.keyList[:i], s.keyList[i+1:]...)
+ delete(s.keys, name)
+ delete(s.keysHash, name)
+ return
+ }
+ }
+}
+
+// ChildSections returns a list of child sections of current section.
+// For example, "[parent.child1]" and "[parent.child12]" are child sections
+// of section "[parent]".
+func (s *Section) ChildSections() []*Section {
+ prefix := s.name + s.f.options.ChildSectionDelimiter
+ children := make([]*Section, 0, 3)
+ for _, name := range s.f.sectionList {
+ if strings.HasPrefix(name, prefix) {
+ children = append(children, s.f.sections[name]...)
+ }
+ }
+ return children
+}
diff --git a/vendor/github.com/go-ini/ini/struct.go b/vendor/github.com/go-ini/ini/struct.go
@@ -0,0 +1,747 @@
+// Copyright 2014 Unknwon
+//
+// Licensed under the Apache License, Version 2.0 (the "License"): you may
+// not use this file except in compliance with the License. You may obtain
+// a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+// License for the specific language governing permissions and limitations
+// under the License.
+
+package ini
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "reflect"
+ "strings"
+ "time"
+ "unicode"
+)
+
+// NameMapper represents a ini tag name mapper.
+type NameMapper func(string) string
+
+// Built-in name getters.
+var (
+ // SnackCase converts to format SNACK_CASE.
+ SnackCase NameMapper = func(raw string) string {
+ newstr := make([]rune, 0, len(raw))
+ for i, chr := range raw {
+ if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+ if i > 0 {
+ newstr = append(newstr, '_')
+ }
+ }
+ newstr = append(newstr, unicode.ToUpper(chr))
+ }
+ return string(newstr)
+ }
+ // TitleUnderscore converts to format title_underscore.
+ TitleUnderscore NameMapper = func(raw string) string {
+ newstr := make([]rune, 0, len(raw))
+ for i, chr := range raw {
+ if isUpper := 'A' <= chr && chr <= 'Z'; isUpper {
+ if i > 0 {
+ newstr = append(newstr, '_')
+ }
+ chr -= 'A' - 'a'
+ }
+ newstr = append(newstr, chr)
+ }
+ return string(newstr)
+ }
+)
+
+func (s *Section) parseFieldName(raw, actual string) string {
+ if len(actual) > 0 {
+ return actual
+ }
+ if s.f.NameMapper != nil {
+ return s.f.NameMapper(raw)
+ }
+ return raw
+}
+
+func parseDelim(actual string) string {
+ if len(actual) > 0 {
+ return actual
+ }
+ return ","
+}
+
+var reflectTime = reflect.TypeOf(time.Now()).Kind()
+
+// setSliceWithProperType sets proper values to slice based on its type.
+func setSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+ var strs []string
+ if allowShadow {
+ strs = key.StringsWithShadows(delim)
+ } else {
+ strs = key.Strings(delim)
+ }
+
+ numVals := len(strs)
+ if numVals == 0 {
+ return nil
+ }
+
+ var vals interface{}
+ var err error
+
+ sliceOf := field.Type().Elem().Kind()
+ switch sliceOf {
+ case reflect.String:
+ vals = strs
+ case reflect.Int:
+ vals, err = key.parseInts(strs, true, false)
+ case reflect.Int64:
+ vals, err = key.parseInt64s(strs, true, false)
+ case reflect.Uint:
+ vals, err = key.parseUints(strs, true, false)
+ case reflect.Uint64:
+ vals, err = key.parseUint64s(strs, true, false)
+ case reflect.Float64:
+ vals, err = key.parseFloat64s(strs, true, false)
+ case reflect.Bool:
+ vals, err = key.parseBools(strs, true, false)
+ case reflectTime:
+ vals, err = key.parseTimesFormat(time.RFC3339, strs, true, false)
+ default:
+ return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+ }
+ if err != nil && isStrict {
+ return err
+ }
+
+ slice := reflect.MakeSlice(field.Type(), numVals, numVals)
+ for i := 0; i < numVals; i++ {
+ switch sliceOf {
+ case reflect.String:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]string)[i]))
+ case reflect.Int:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]int)[i]))
+ case reflect.Int64:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]int64)[i]))
+ case reflect.Uint:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]uint)[i]))
+ case reflect.Uint64:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]uint64)[i]))
+ case reflect.Float64:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]float64)[i]))
+ case reflect.Bool:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]bool)[i]))
+ case reflectTime:
+ slice.Index(i).Set(reflect.ValueOf(vals.([]time.Time)[i]))
+ }
+ }
+ field.Set(slice)
+ return nil
+}
+
+func wrapStrictError(err error, isStrict bool) error {
+ if isStrict {
+ return err
+ }
+ return nil
+}
+
+// setWithProperType sets proper value to field based on its type,
+// but it does not return error for failing parsing,
+// because we want to use default value that is already assigned to struct.
+func setWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow, isStrict bool) error {
+ vt := t
+ isPtr := t.Kind() == reflect.Ptr
+ if isPtr {
+ vt = t.Elem()
+ }
+ switch vt.Kind() {
+ case reflect.String:
+ stringVal := key.String()
+ if isPtr {
+ field.Set(reflect.ValueOf(&stringVal))
+ } else if len(stringVal) > 0 {
+ field.SetString(key.String())
+ }
+ case reflect.Bool:
+ boolVal, err := key.Bool()
+ if err != nil {
+ return wrapStrictError(err, isStrict)
+ }
+ if isPtr {
+ field.Set(reflect.ValueOf(&boolVal))
+ } else {
+ field.SetBool(boolVal)
+ }
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ // ParseDuration will not return err for `0`, so check the type name
+ if vt.Name() == "Duration" {
+ durationVal, err := key.Duration()
+ if err != nil {
+ if intVal, err := key.Int64(); err == nil {
+ field.SetInt(intVal)
+ return nil
+ }
+ return wrapStrictError(err, isStrict)
+ }
+ if isPtr {
+ field.Set(reflect.ValueOf(&durationVal))
+ } else if int64(durationVal) > 0 {
+ field.Set(reflect.ValueOf(durationVal))
+ }
+ return nil
+ }
+
+ intVal, err := key.Int64()
+ if err != nil {
+ return wrapStrictError(err, isStrict)
+ }
+ if isPtr {
+ pv := reflect.New(t.Elem())
+ pv.Elem().SetInt(intVal)
+ field.Set(pv)
+ } else {
+ field.SetInt(intVal)
+ }
+ // byte is an alias for uint8, so supporting uint8 breaks support for byte
+ case reflect.Uint, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ durationVal, err := key.Duration()
+ // Skip zero value
+ if err == nil && uint64(durationVal) > 0 {
+ if isPtr {
+ field.Set(reflect.ValueOf(&durationVal))
+ } else {
+ field.Set(reflect.ValueOf(durationVal))
+ }
+ return nil
+ }
+
+ uintVal, err := key.Uint64()
+ if err != nil {
+ return wrapStrictError(err, isStrict)
+ }
+ if isPtr {
+ pv := reflect.New(t.Elem())
+ pv.Elem().SetUint(uintVal)
+ field.Set(pv)
+ } else {
+ field.SetUint(uintVal)
+ }
+
+ case reflect.Float32, reflect.Float64:
+ floatVal, err := key.Float64()
+ if err != nil {
+ return wrapStrictError(err, isStrict)
+ }
+ if isPtr {
+ pv := reflect.New(t.Elem())
+ pv.Elem().SetFloat(floatVal)
+ field.Set(pv)
+ } else {
+ field.SetFloat(floatVal)
+ }
+ case reflectTime:
+ timeVal, err := key.Time()
+ if err != nil {
+ return wrapStrictError(err, isStrict)
+ }
+ if isPtr {
+ field.Set(reflect.ValueOf(&timeVal))
+ } else {
+ field.Set(reflect.ValueOf(timeVal))
+ }
+ case reflect.Slice:
+ return setSliceWithProperType(key, field, delim, allowShadow, isStrict)
+ default:
+ return fmt.Errorf("unsupported type %q", t)
+ }
+ return nil
+}
+
+func parseTagOptions(tag string) (rawName string, omitEmpty bool, allowShadow bool, allowNonUnique bool, extends bool) {
+ opts := strings.SplitN(tag, ",", 5)
+ rawName = opts[0]
+ for _, opt := range opts[1:] {
+ omitEmpty = omitEmpty || (opt == "omitempty")
+ allowShadow = allowShadow || (opt == "allowshadow")
+ allowNonUnique = allowNonUnique || (opt == "nonunique")
+ extends = extends || (opt == "extends")
+ }
+ return rawName, omitEmpty, allowShadow, allowNonUnique, extends
+}
+
+// mapToField maps the given value to the matching field of the given section.
+// The sectionIndex is the index (if non unique sections are enabled) to which the value should be added.
+func (s *Section) mapToField(val reflect.Value, isStrict bool, sectionIndex int, sectionName string) error {
+ if val.Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+ typ := val.Type()
+
+ for i := 0; i < typ.NumField(); i++ {
+ field := val.Field(i)
+ tpField := typ.Field(i)
+
+ tag := tpField.Tag.Get("ini")
+ if tag == "-" {
+ continue
+ }
+
+ rawName, _, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+ fieldName := s.parseFieldName(tpField.Name, rawName)
+ if len(fieldName) == 0 || !field.CanSet() {
+ continue
+ }
+
+ isStruct := tpField.Type.Kind() == reflect.Struct
+ isStructPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct
+ isAnonymousPtr := tpField.Type.Kind() == reflect.Ptr && tpField.Anonymous
+ if isAnonymousPtr {
+ field.Set(reflect.New(tpField.Type.Elem()))
+ }
+
+ if extends && (isAnonymousPtr || (isStruct && tpField.Anonymous)) {
+ if isStructPtr && field.IsNil() {
+ field.Set(reflect.New(tpField.Type.Elem()))
+ }
+ fieldSection := s
+ if rawName != "" {
+ sectionName = s.name + s.f.options.ChildSectionDelimiter + rawName
+ if secs, err := s.f.SectionsByName(sectionName); err == nil && sectionIndex < len(secs) {
+ fieldSection = secs[sectionIndex]
+ }
+ }
+ if err := fieldSection.mapToField(field, isStrict, sectionIndex, sectionName); err != nil {
+ return fmt.Errorf("map to field %q: %v", fieldName, err)
+ }
+ } else if isAnonymousPtr || isStruct || isStructPtr {
+ if secs, err := s.f.SectionsByName(fieldName); err == nil {
+ if len(secs) <= sectionIndex {
+ return fmt.Errorf("there are not enough sections (%d <= %d) for the field %q", len(secs), sectionIndex, fieldName)
+ }
+ // Only set the field to non-nil struct value if we have a section for it.
+ // Otherwise, we end up with a non-nil struct ptr even though there is no data.
+ if isStructPtr && field.IsNil() {
+ field.Set(reflect.New(tpField.Type.Elem()))
+ }
+ if err = secs[sectionIndex].mapToField(field, isStrict, sectionIndex, fieldName); err != nil {
+ return fmt.Errorf("map to field %q: %v", fieldName, err)
+ }
+ continue
+ }
+ }
+
+ // Map non-unique sections
+ if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+ newField, err := s.mapToSlice(fieldName, field, isStrict)
+ if err != nil {
+ return fmt.Errorf("map to slice %q: %v", fieldName, err)
+ }
+
+ field.Set(newField)
+ continue
+ }
+
+ if key, err := s.GetKey(fieldName); err == nil {
+ delim := parseDelim(tpField.Tag.Get("delim"))
+ if err = setWithProperType(tpField.Type, key, field, delim, allowShadow, isStrict); err != nil {
+ return fmt.Errorf("set field %q: %v", fieldName, err)
+ }
+ }
+ }
+ return nil
+}
+
+// mapToSlice maps all sections with the same name and returns the new value.
+// The type of the Value must be a slice.
+func (s *Section) mapToSlice(secName string, val reflect.Value, isStrict bool) (reflect.Value, error) {
+ secs, err := s.f.SectionsByName(secName)
+ if err != nil {
+ return reflect.Value{}, err
+ }
+
+ typ := val.Type().Elem()
+ for i, sec := range secs {
+ elem := reflect.New(typ)
+ if err = sec.mapToField(elem, isStrict, i, sec.name); err != nil {
+ return reflect.Value{}, fmt.Errorf("map to field from section %q: %v", secName, err)
+ }
+
+ val = reflect.Append(val, elem.Elem())
+ }
+ return val, nil
+}
+
+// mapTo maps a section to object v.
+func (s *Section) mapTo(v interface{}, isStrict bool) error {
+ typ := reflect.TypeOf(v)
+ val := reflect.ValueOf(v)
+ if typ.Kind() == reflect.Ptr {
+ typ = typ.Elem()
+ val = val.Elem()
+ } else {
+ return errors.New("not a pointer to a struct")
+ }
+
+ if typ.Kind() == reflect.Slice {
+ newField, err := s.mapToSlice(s.name, val, isStrict)
+ if err != nil {
+ return err
+ }
+
+ val.Set(newField)
+ return nil
+ }
+
+ return s.mapToField(val, isStrict, 0, s.name)
+}
+
+// MapTo maps section to given struct.
+func (s *Section) MapTo(v interface{}) error {
+ return s.mapTo(v, false)
+}
+
+// StrictMapTo maps section to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (s *Section) StrictMapTo(v interface{}) error {
+ return s.mapTo(v, true)
+}
+
+// MapTo maps file to given struct.
+func (f *File) MapTo(v interface{}) error {
+ return f.Section("").MapTo(v)
+}
+
+// StrictMapTo maps file to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func (f *File) StrictMapTo(v interface{}) error {
+ return f.Section("").StrictMapTo(v)
+}
+
+// MapToWithMapper maps data sources to given struct with name mapper.
+func MapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+ cfg, err := Load(source, others...)
+ if err != nil {
+ return err
+ }
+ cfg.NameMapper = mapper
+ return cfg.MapTo(v)
+}
+
+// StrictMapToWithMapper maps data sources to given struct with name mapper in strict mode,
+// which returns all possible error including value parsing error.
+func StrictMapToWithMapper(v interface{}, mapper NameMapper, source interface{}, others ...interface{}) error {
+ cfg, err := Load(source, others...)
+ if err != nil {
+ return err
+ }
+ cfg.NameMapper = mapper
+ return cfg.StrictMapTo(v)
+}
+
+// MapTo maps data sources to given struct.
+func MapTo(v, source interface{}, others ...interface{}) error {
+ return MapToWithMapper(v, nil, source, others...)
+}
+
+// StrictMapTo maps data sources to given struct in strict mode,
+// which returns all possible error including value parsing error.
+func StrictMapTo(v, source interface{}, others ...interface{}) error {
+ return StrictMapToWithMapper(v, nil, source, others...)
+}
+
+// reflectSliceWithProperType does the opposite thing as setSliceWithProperType.
+func reflectSliceWithProperType(key *Key, field reflect.Value, delim string, allowShadow bool) error {
+ slice := field.Slice(0, field.Len())
+ if field.Len() == 0 {
+ return nil
+ }
+ sliceOf := field.Type().Elem().Kind()
+
+ if allowShadow {
+ var keyWithShadows *Key
+ for i := 0; i < field.Len(); i++ {
+ var val string
+ switch sliceOf {
+ case reflect.String:
+ val = slice.Index(i).String()
+ case reflect.Int, reflect.Int64:
+ val = fmt.Sprint(slice.Index(i).Int())
+ case reflect.Uint, reflect.Uint64:
+ val = fmt.Sprint(slice.Index(i).Uint())
+ case reflect.Float64:
+ val = fmt.Sprint(slice.Index(i).Float())
+ case reflect.Bool:
+ val = fmt.Sprint(slice.Index(i).Bool())
+ case reflectTime:
+ val = slice.Index(i).Interface().(time.Time).Format(time.RFC3339)
+ default:
+ return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+ }
+
+ if i == 0 {
+ keyWithShadows = newKey(key.s, key.name, val)
+ } else {
+ _ = keyWithShadows.AddShadow(val)
+ }
+ }
+ *key = *keyWithShadows
+ return nil
+ }
+
+ var buf bytes.Buffer
+ for i := 0; i < field.Len(); i++ {
+ switch sliceOf {
+ case reflect.String:
+ buf.WriteString(slice.Index(i).String())
+ case reflect.Int, reflect.Int64:
+ buf.WriteString(fmt.Sprint(slice.Index(i).Int()))
+ case reflect.Uint, reflect.Uint64:
+ buf.WriteString(fmt.Sprint(slice.Index(i).Uint()))
+ case reflect.Float64:
+ buf.WriteString(fmt.Sprint(slice.Index(i).Float()))
+ case reflect.Bool:
+ buf.WriteString(fmt.Sprint(slice.Index(i).Bool()))
+ case reflectTime:
+ buf.WriteString(slice.Index(i).Interface().(time.Time).Format(time.RFC3339))
+ default:
+ return fmt.Errorf("unsupported type '[]%s'", sliceOf)
+ }
+ buf.WriteString(delim)
+ }
+ key.SetValue(buf.String()[:buf.Len()-len(delim)])
+ return nil
+}
+
+// reflectWithProperType does the opposite thing as setWithProperType.
+func reflectWithProperType(t reflect.Type, key *Key, field reflect.Value, delim string, allowShadow bool) error {
+ switch t.Kind() {
+ case reflect.String:
+ key.SetValue(field.String())
+ case reflect.Bool:
+ key.SetValue(fmt.Sprint(field.Bool()))
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ key.SetValue(fmt.Sprint(field.Int()))
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+ key.SetValue(fmt.Sprint(field.Uint()))
+ case reflect.Float32, reflect.Float64:
+ key.SetValue(fmt.Sprint(field.Float()))
+ case reflectTime:
+ key.SetValue(fmt.Sprint(field.Interface().(time.Time).Format(time.RFC3339)))
+ case reflect.Slice:
+ return reflectSliceWithProperType(key, field, delim, allowShadow)
+ case reflect.Ptr:
+ if !field.IsNil() {
+ return reflectWithProperType(t.Elem(), key, field.Elem(), delim, allowShadow)
+ }
+ default:
+ return fmt.Errorf("unsupported type %q", t)
+ }
+ return nil
+}
+
+// CR: copied from encoding/json/encode.go with modifications of time.Time support.
+// TODO: add more test coverage.
+func isEmptyValue(v reflect.Value) bool {
+ switch v.Kind() {
+ case reflect.Array, reflect.Map, reflect.Slice, reflect.String:
+ return v.Len() == 0
+ case reflect.Bool:
+ return !v.Bool()
+ case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+ return v.Int() == 0
+ case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
+ return v.Uint() == 0
+ case reflect.Float32, reflect.Float64:
+ return v.Float() == 0
+ case reflect.Interface, reflect.Ptr:
+ return v.IsNil()
+ case reflectTime:
+ t, ok := v.Interface().(time.Time)
+ return ok && t.IsZero()
+ }
+ return false
+}
+
+// StructReflector is the interface implemented by struct types that can extract themselves into INI objects.
+type StructReflector interface {
+ ReflectINIStruct(*File) error
+}
+
+func (s *Section) reflectFrom(val reflect.Value) error {
+ if val.Kind() == reflect.Ptr {
+ val = val.Elem()
+ }
+ typ := val.Type()
+
+ for i := 0; i < typ.NumField(); i++ {
+ if !val.Field(i).CanInterface() {
+ continue
+ }
+
+ field := val.Field(i)
+ tpField := typ.Field(i)
+
+ tag := tpField.Tag.Get("ini")
+ if tag == "-" {
+ continue
+ }
+
+ rawName, omitEmpty, allowShadow, allowNonUnique, extends := parseTagOptions(tag)
+ if omitEmpty && isEmptyValue(field) {
+ continue
+ }
+
+ if r, ok := field.Interface().(StructReflector); ok {
+ return r.ReflectINIStruct(s.f)
+ }
+
+ fieldName := s.parseFieldName(tpField.Name, rawName)
+ if len(fieldName) == 0 || !field.CanSet() {
+ continue
+ }
+
+ if extends && tpField.Anonymous && (tpField.Type.Kind() == reflect.Ptr || tpField.Type.Kind() == reflect.Struct) {
+ if err := s.reflectFrom(field); err != nil {
+ return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+ }
+ continue
+ }
+
+ if (tpField.Type.Kind() == reflect.Ptr && tpField.Type.Elem().Kind() == reflect.Struct) ||
+ (tpField.Type.Kind() == reflect.Struct && tpField.Type.Name() != "Time") {
+ // Note: The only error here is section doesn't exist.
+ sec, err := s.f.GetSection(fieldName)
+ if err != nil {
+ // Note: fieldName can never be empty here, ignore error.
+ sec, _ = s.f.NewSection(fieldName)
+ }
+
+ // Add comment from comment tag
+ if len(sec.Comment) == 0 {
+ sec.Comment = tpField.Tag.Get("comment")
+ }
+
+ if err = sec.reflectFrom(field); err != nil {
+ return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+ }
+ continue
+ }
+
+ if allowNonUnique && tpField.Type.Kind() == reflect.Slice {
+ slice := field.Slice(0, field.Len())
+ if field.Len() == 0 {
+ return nil
+ }
+ sliceOf := field.Type().Elem().Kind()
+
+ for i := 0; i < field.Len(); i++ {
+ if sliceOf != reflect.Struct && sliceOf != reflect.Ptr {
+ return fmt.Errorf("field %q is not a slice of pointer or struct", fieldName)
+ }
+
+ sec, err := s.f.NewSection(fieldName)
+ if err != nil {
+ return err
+ }
+
+ // Add comment from comment tag
+ if len(sec.Comment) == 0 {
+ sec.Comment = tpField.Tag.Get("comment")
+ }
+
+ if err := sec.reflectFrom(slice.Index(i)); err != nil {
+ return fmt.Errorf("reflect from field %q: %v", fieldName, err)
+ }
+ }
+ continue
+ }
+
+ // Note: Same reason as section.
+ key, err := s.GetKey(fieldName)
+ if err != nil {
+ key, _ = s.NewKey(fieldName, "")
+ }
+
+ // Add comment from comment tag
+ if len(key.Comment) == 0 {
+ key.Comment = tpField.Tag.Get("comment")
+ }
+
+ delim := parseDelim(tpField.Tag.Get("delim"))
+ if err = reflectWithProperType(tpField.Type, key, field, delim, allowShadow); err != nil {
+ return fmt.Errorf("reflect field %q: %v", fieldName, err)
+ }
+
+ }
+ return nil
+}
+
+// ReflectFrom reflects section from given struct. It overwrites existing ones.
+func (s *Section) ReflectFrom(v interface{}) error {
+ typ := reflect.TypeOf(v)
+ val := reflect.ValueOf(v)
+
+ if s.name != DefaultSection && s.f.options.AllowNonUniqueSections &&
+ (typ.Kind() == reflect.Slice || typ.Kind() == reflect.Ptr) {
+ // Clear sections to make sure none exists before adding the new ones
+ s.f.DeleteSection(s.name)
+
+ if typ.Kind() == reflect.Ptr {
+ sec, err := s.f.NewSection(s.name)
+ if err != nil {
+ return err
+ }
+ return sec.reflectFrom(val.Elem())
+ }
+
+ slice := val.Slice(0, val.Len())
+ sliceOf := val.Type().Elem().Kind()
+ if sliceOf != reflect.Ptr {
+ return fmt.Errorf("not a slice of pointers")
+ }
+
+ for i := 0; i < slice.Len(); i++ {
+ sec, err := s.f.NewSection(s.name)
+ if err != nil {
+ return err
+ }
+
+ err = sec.reflectFrom(slice.Index(i))
+ if err != nil {
+ return fmt.Errorf("reflect from %dth field: %v", i, err)
+ }
+ }
+
+ return nil
+ }
+
+ if typ.Kind() == reflect.Ptr {
+ val = val.Elem()
+ } else {
+ return errors.New("not a pointer to a struct")
+ }
+
+ return s.reflectFrom(val)
+}
+
+// ReflectFrom reflects file from given struct.
+func (f *File) ReflectFrom(v interface{}) error {
+ return f.Section("").ReflectFrom(v)
+}
+
+// ReflectFromWithMapper reflects data sources from given struct with name mapper.
+func ReflectFromWithMapper(cfg *File, v interface{}, mapper NameMapper) error {
+ cfg.NameMapper = mapper
+ return cfg.ReflectFrom(v)
+}
+
+// ReflectFrom reflects data sources from given struct.
+func ReflectFrom(cfg *File, v interface{}) error {
+ return ReflectFromWithMapper(cfg, v, nil)
+}
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/.gitignore b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/.gitignore
@@ -0,0 +1,4 @@
+.idea/
+coverage.out
+tmp/
+book/
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/LICENSE.txt b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/LICENSE.txt
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Syfaro
+
+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.
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/README.md b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/README.md
@@ -0,0 +1,121 @@
+# Golang bindings for the Telegram Bot API
+
+[![Go Reference](https://pkg.go.dev/badge/github.com/go-telegram-bot-api/telegram-bot-api/v5.svg)](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5)
+[![Test](https://github.com/go-telegram-bot-api/telegram-bot-api/actions/workflows/test.yml/badge.svg)](https://github.com/go-telegram-bot-api/telegram-bot-api/actions/workflows/test.yml)
+
+All methods are fairly self-explanatory, and reading the [godoc](https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5) page should
+explain everything. If something isn't clear, open an issue or submit
+a pull request.
+
+There are more tutorials and high-level information on the website, [go-telegram-bot-api.dev](https://go-telegram-bot-api.dev).
+
+The scope of this project is just to provide a wrapper around the API
+without any additional features. There are other projects for creating
+something with plugins and command handlers without having to design
+all that yourself.
+
+Join [the development group](https://telegram.me/go_telegram_bot_api) if
+you want to ask questions or discuss development.
+
+## Example
+
+First, ensure the library is installed and up to date by running
+`go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5`.
+
+This is a very simple bot that just displays any gotten updates,
+then replies it to that chat.
+
+```go
+package main
+
+import (
+ "log"
+
+ tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+func main() {
+ bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
+ if err != nil {
+ log.Panic(err)
+ }
+
+ bot.Debug = true
+
+ log.Printf("Authorized on account %s", bot.Self.UserName)
+
+ u := tgbotapi.NewUpdate(0)
+ u.Timeout = 60
+
+ updates := bot.GetUpdatesChan(u)
+
+ for update := range updates {
+ if update.Message != nil { // If we got a message
+ log.Printf("[%s] %s", update.Message.From.UserName, update.Message.Text)
+
+ msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
+ msg.ReplyToMessageID = update.Message.MessageID
+
+ bot.Send(msg)
+ }
+ }
+}
+```
+
+If you need to use webhooks (if you wish to run on Google App Engine),
+you may use a slightly different method.
+
+```go
+package main
+
+import (
+ "log"
+ "net/http"
+
+ "github.com/go-telegram-bot-api/telegram-bot-api/v5"
+)
+
+func main() {
+ bot, err := tgbotapi.NewBotAPI("MyAwesomeBotToken")
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ bot.Debug = true
+
+ log.Printf("Authorized on account %s", bot.Self.UserName)
+
+ wh, _ := tgbotapi.NewWebhookWithCert("https://www.google.com:8443/"+bot.Token, "cert.pem")
+
+ _, err = bot.SetWebhook(wh)
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ info, err := bot.GetWebhookInfo()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ if info.LastErrorDate != 0 {
+ log.Printf("Telegram callback failed: %s", info.LastErrorMessage)
+ }
+
+ updates := bot.ListenForWebhook("/" + bot.Token)
+ go http.ListenAndServeTLS("0.0.0.0:8443", "cert.pem", "key.pem", nil)
+
+ for update := range updates {
+ log.Printf("%+v\n", update)
+ }
+}
+```
+
+If you need, you may generate a self-signed certificate, as this requires
+HTTPS / TLS. The above example tells Telegram that this is your
+certificate and that it should be trusted, even though it is not
+properly signed.
+
+ openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 3560 -subj "//O=Org\CN=Test" -nodes
+
+Now that [Let's Encrypt](https://letsencrypt.org) is available,
+you may wish to generate your free TLS certificate there.
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/book.toml b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/book.toml
@@ -0,0 +1,9 @@
+[book]
+authors = ["Syfaro"]
+language = "en"
+multilingual = false
+src = "docs"
+title = "Go Telegram Bot API"
+
+[output.html]
+git-repository-url = "https://github.com/go-telegram-bot-api/telegram-bot-api"
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/bot.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/bot.go
@@ -0,0 +1,726 @@
+// Package tgbotapi has functions and types used for interacting with
+// the Telegram Bot API.
+package tgbotapi
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "io/ioutil"
+ "mime/multipart"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// HTTPClient is the type needed for the bot to perform HTTP requests.
+type HTTPClient interface {
+ Do(req *http.Request) (*http.Response, error)
+}
+
+// BotAPI allows you to interact with the Telegram Bot API.
+type BotAPI struct {
+ Token string `json:"token"`
+ Debug bool `json:"debug"`
+ Buffer int `json:"buffer"`
+
+ Self User `json:"-"`
+ Client HTTPClient `json:"-"`
+ shutdownChannel chan interface{}
+
+ apiEndpoint string
+}
+
+// NewBotAPI creates a new BotAPI instance.
+//
+// It requires a token, provided by @BotFather on Telegram.
+func NewBotAPI(token string) (*BotAPI, error) {
+ return NewBotAPIWithClient(token, APIEndpoint, &http.Client{})
+}
+
+// NewBotAPIWithAPIEndpoint creates a new BotAPI instance
+// and allows you to pass API endpoint.
+//
+// It requires a token, provided by @BotFather on Telegram and API endpoint.
+func NewBotAPIWithAPIEndpoint(token, apiEndpoint string) (*BotAPI, error) {
+ return NewBotAPIWithClient(token, apiEndpoint, &http.Client{})
+}
+
+// NewBotAPIWithClient creates a new BotAPI instance
+// and allows you to pass a http.Client.
+//
+// It requires a token, provided by @BotFather on Telegram and API endpoint.
+func NewBotAPIWithClient(token, apiEndpoint string, client HTTPClient) (*BotAPI, error) {
+ bot := &BotAPI{
+ Token: token,
+ Client: client,
+ Buffer: 100,
+ shutdownChannel: make(chan interface{}),
+
+ apiEndpoint: apiEndpoint,
+ }
+
+ self, err := bot.GetMe()
+ if err != nil {
+ return nil, err
+ }
+
+ bot.Self = self
+
+ return bot, nil
+}
+
+// SetAPIEndpoint changes the Telegram Bot API endpoint used by the instance.
+func (bot *BotAPI) SetAPIEndpoint(apiEndpoint string) {
+ bot.apiEndpoint = apiEndpoint
+}
+
+func buildParams(in Params) url.Values {
+ if in == nil {
+ return url.Values{}
+ }
+
+ out := url.Values{}
+
+ for key, value := range in {
+ out.Set(key, value)
+ }
+
+ return out
+}
+
+// MakeRequest makes a request to a specific endpoint with our token.
+func (bot *BotAPI) MakeRequest(endpoint string, params Params) (*APIResponse, error) {
+ if bot.Debug {
+ log.Printf("Endpoint: %s, params: %v\n", endpoint, params)
+ }
+
+ method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
+
+ values := buildParams(params)
+
+ req, err := http.NewRequest("POST", method, strings.NewReader(values.Encode()))
+ if err != nil {
+ return &APIResponse{}, err
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+
+ resp, err := bot.Client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var apiResp APIResponse
+ bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
+ if err != nil {
+ return &apiResp, err
+ }
+
+ if bot.Debug {
+ log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
+ }
+
+ if !apiResp.Ok {
+ var parameters ResponseParameters
+
+ if apiResp.Parameters != nil {
+ parameters = *apiResp.Parameters
+ }
+
+ return &apiResp, &Error{
+ Code: apiResp.ErrorCode,
+ Message: apiResp.Description,
+ ResponseParameters: parameters,
+ }
+ }
+
+ return &apiResp, nil
+}
+
+// decodeAPIResponse decode response and return slice of bytes if debug enabled.
+// If debug disabled, just decode http.Response.Body stream to APIResponse struct
+// for efficient memory usage
+func (bot *BotAPI) decodeAPIResponse(responseBody io.Reader, resp *APIResponse) ([]byte, error) {
+ if !bot.Debug {
+ dec := json.NewDecoder(responseBody)
+ err := dec.Decode(resp)
+ return nil, err
+ }
+
+ // if debug, read response body
+ data, err := ioutil.ReadAll(responseBody)
+ if err != nil {
+ return nil, err
+ }
+
+ err = json.Unmarshal(data, resp)
+ if err != nil {
+ return nil, err
+ }
+
+ return data, nil
+}
+
+// UploadFiles makes a request to the API with files.
+func (bot *BotAPI) UploadFiles(endpoint string, params Params, files []RequestFile) (*APIResponse, error) {
+ r, w := io.Pipe()
+ m := multipart.NewWriter(w)
+
+ // This code modified from the very helpful @HirbodBehnam
+ // https://github.com/go-telegram-bot-api/telegram-bot-api/issues/354#issuecomment-663856473
+ go func() {
+ defer w.Close()
+ defer m.Close()
+
+ for field, value := range params {
+ if err := m.WriteField(field, value); err != nil {
+ w.CloseWithError(err)
+ return
+ }
+ }
+
+ for _, file := range files {
+ if file.Data.NeedsUpload() {
+ name, reader, err := file.Data.UploadData()
+ if err != nil {
+ w.CloseWithError(err)
+ return
+ }
+
+ part, err := m.CreateFormFile(file.Name, name)
+ if err != nil {
+ w.CloseWithError(err)
+ return
+ }
+
+ if _, err := io.Copy(part, reader); err != nil {
+ w.CloseWithError(err)
+ return
+ }
+
+ if closer, ok := reader.(io.ReadCloser); ok {
+ if err = closer.Close(); err != nil {
+ w.CloseWithError(err)
+ return
+ }
+ }
+ } else {
+ value := file.Data.SendData()
+
+ if err := m.WriteField(file.Name, value); err != nil {
+ w.CloseWithError(err)
+ return
+ }
+ }
+ }
+ }()
+
+ if bot.Debug {
+ log.Printf("Endpoint: %s, params: %v, with %d files\n", endpoint, params, len(files))
+ }
+
+ method := fmt.Sprintf(bot.apiEndpoint, bot.Token, endpoint)
+
+ req, err := http.NewRequest("POST", method, r)
+ if err != nil {
+ return nil, err
+ }
+
+ req.Header.Set("Content-Type", m.FormDataContentType())
+
+ resp, err := bot.Client.Do(req)
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ var apiResp APIResponse
+ bytes, err := bot.decodeAPIResponse(resp.Body, &apiResp)
+ if err != nil {
+ return &apiResp, err
+ }
+
+ if bot.Debug {
+ log.Printf("Endpoint: %s, response: %s\n", endpoint, string(bytes))
+ }
+
+ if !apiResp.Ok {
+ var parameters ResponseParameters
+
+ if apiResp.Parameters != nil {
+ parameters = *apiResp.Parameters
+ }
+
+ return &apiResp, &Error{
+ Message: apiResp.Description,
+ ResponseParameters: parameters,
+ }
+ }
+
+ return &apiResp, nil
+}
+
+// GetFileDirectURL returns direct URL to file
+//
+// It requires the FileID.
+func (bot *BotAPI) GetFileDirectURL(fileID string) (string, error) {
+ file, err := bot.GetFile(FileConfig{fileID})
+
+ if err != nil {
+ return "", err
+ }
+
+ return file.Link(bot.Token), nil
+}
+
+// GetMe fetches the currently authenticated bot.
+//
+// This method is called upon creation to validate the token,
+// and so you may get this data from BotAPI.Self without the need for
+// another request.
+func (bot *BotAPI) GetMe() (User, error) {
+ resp, err := bot.MakeRequest("getMe", nil)
+ if err != nil {
+ return User{}, err
+ }
+
+ var user User
+ err = json.Unmarshal(resp.Result, &user)
+
+ return user, err
+}
+
+// IsMessageToMe returns true if message directed to this bot.
+//
+// It requires the Message.
+func (bot *BotAPI) IsMessageToMe(message Message) bool {
+ return strings.Contains(message.Text, "@"+bot.Self.UserName)
+}
+
+func hasFilesNeedingUpload(files []RequestFile) bool {
+ for _, file := range files {
+ if file.Data.NeedsUpload() {
+ return true
+ }
+ }
+
+ return false
+}
+
+// Request sends a Chattable to Telegram, and returns the APIResponse.
+func (bot *BotAPI) Request(c Chattable) (*APIResponse, error) {
+ params, err := c.params()
+ if err != nil {
+ return nil, err
+ }
+
+ if t, ok := c.(Fileable); ok {
+ files := t.files()
+
+ // If we have files that need to be uploaded, we should delegate the
+ // request to UploadFile.
+ if hasFilesNeedingUpload(files) {
+ return bot.UploadFiles(t.method(), params, files)
+ }
+
+ // However, if there are no files to be uploaded, there's likely things
+ // that need to be turned into params instead.
+ for _, file := range files {
+ params[file.Name] = file.Data.SendData()
+ }
+ }
+
+ return bot.MakeRequest(c.method(), params)
+}
+
+// Send will send a Chattable item to Telegram and provides the
+// returned Message.
+func (bot *BotAPI) Send(c Chattable) (Message, error) {
+ resp, err := bot.Request(c)
+ if err != nil {
+ return Message{}, err
+ }
+
+ var message Message
+ err = json.Unmarshal(resp.Result, &message)
+
+ return message, err
+}
+
+// SendMediaGroup sends a media group and returns the resulting messages.
+func (bot *BotAPI) SendMediaGroup(config MediaGroupConfig) ([]Message, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return nil, err
+ }
+
+ var messages []Message
+ err = json.Unmarshal(resp.Result, &messages)
+
+ return messages, err
+}
+
+// GetUserProfilePhotos gets a user's profile photos.
+//
+// It requires UserID.
+// Offset and Limit are optional.
+func (bot *BotAPI) GetUserProfilePhotos(config UserProfilePhotosConfig) (UserProfilePhotos, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return UserProfilePhotos{}, err
+ }
+
+ var profilePhotos UserProfilePhotos
+ err = json.Unmarshal(resp.Result, &profilePhotos)
+
+ return profilePhotos, err
+}
+
+// GetFile returns a File which can download a file from Telegram.
+//
+// Requires FileID.
+func (bot *BotAPI) GetFile(config FileConfig) (File, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return File{}, err
+ }
+
+ var file File
+ err = json.Unmarshal(resp.Result, &file)
+
+ return file, err
+}
+
+// GetUpdates fetches updates.
+// If a WebHook is set, this will not return any data!
+//
+// Offset, Limit, Timeout, and AllowedUpdates are optional.
+// To avoid stale items, set Offset to one higher than the previous item.
+// Set Timeout to a large number to reduce requests, so you can get updates
+// instantly instead of having to wait between requests.
+func (bot *BotAPI) GetUpdates(config UpdateConfig) ([]Update, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return []Update{}, err
+ }
+
+ var updates []Update
+ err = json.Unmarshal(resp.Result, &updates)
+
+ return updates, err
+}
+
+// GetWebhookInfo allows you to fetch information about a webhook and if
+// one currently is set, along with pending update count and error messages.
+func (bot *BotAPI) GetWebhookInfo() (WebhookInfo, error) {
+ resp, err := bot.MakeRequest("getWebhookInfo", nil)
+ if err != nil {
+ return WebhookInfo{}, err
+ }
+
+ var info WebhookInfo
+ err = json.Unmarshal(resp.Result, &info)
+
+ return info, err
+}
+
+// GetUpdatesChan starts and returns a channel for getting updates.
+func (bot *BotAPI) GetUpdatesChan(config UpdateConfig) UpdatesChannel {
+ ch := make(chan Update, bot.Buffer)
+
+ go func() {
+ for {
+ select {
+ case <-bot.shutdownChannel:
+ close(ch)
+ return
+ default:
+ }
+
+ updates, err := bot.GetUpdates(config)
+ if err != nil {
+ log.Println(err)
+ log.Println("Failed to get updates, retrying in 3 seconds...")
+ time.Sleep(time.Second * 3)
+
+ continue
+ }
+
+ for _, update := range updates {
+ if update.UpdateID >= config.Offset {
+ config.Offset = update.UpdateID + 1
+ ch <- update
+ }
+ }
+ }
+ }()
+
+ return ch
+}
+
+// StopReceivingUpdates stops the go routine which receives updates
+func (bot *BotAPI) StopReceivingUpdates() {
+ if bot.Debug {
+ log.Println("Stopping the update receiver routine...")
+ }
+ close(bot.shutdownChannel)
+}
+
+// ListenForWebhook registers a http handler for a webhook.
+func (bot *BotAPI) ListenForWebhook(pattern string) UpdatesChannel {
+ ch := make(chan Update, bot.Buffer)
+
+ http.HandleFunc(pattern, func(w http.ResponseWriter, r *http.Request) {
+ update, err := bot.HandleUpdate(r)
+ if err != nil {
+ errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
+ w.WriteHeader(http.StatusBadRequest)
+ w.Header().Set("Content-Type", "application/json")
+ _, _ = w.Write(errMsg)
+ return
+ }
+
+ ch <- *update
+ })
+
+ return ch
+}
+
+// ListenForWebhookRespReqFormat registers a http handler for a single incoming webhook.
+func (bot *BotAPI) ListenForWebhookRespReqFormat(w http.ResponseWriter, r *http.Request) UpdatesChannel {
+ ch := make(chan Update, bot.Buffer)
+
+ func(w http.ResponseWriter, r *http.Request) {
+ update, err := bot.HandleUpdate(r)
+ if err != nil {
+ errMsg, _ := json.Marshal(map[string]string{"error": err.Error()})
+ w.WriteHeader(http.StatusBadRequest)
+ w.Header().Set("Content-Type", "application/json")
+ _, _ = w.Write(errMsg)
+ return
+ }
+
+ ch <- *update
+ close(ch)
+ }(w, r)
+
+ return ch
+}
+
+// HandleUpdate parses and returns update received via webhook
+func (bot *BotAPI) HandleUpdate(r *http.Request) (*Update, error) {
+ if r.Method != http.MethodPost {
+ err := errors.New("wrong HTTP method required POST")
+ return nil, err
+ }
+
+ var update Update
+ err := json.NewDecoder(r.Body).Decode(&update)
+ if err != nil {
+ return nil, err
+ }
+
+ return &update, nil
+}
+
+// WriteToHTTPResponse writes the request to the HTTP ResponseWriter.
+//
+// It doesn't support uploading files.
+//
+// See https://core.telegram.org/bots/api#making-requests-when-getting-updates
+// for details.
+func WriteToHTTPResponse(w http.ResponseWriter, c Chattable) error {
+ params, err := c.params()
+ if err != nil {
+ return err
+ }
+
+ if t, ok := c.(Fileable); ok {
+ if hasFilesNeedingUpload(t.files()) {
+ return errors.New("unable to use http response to upload files")
+ }
+ }
+
+ values := buildParams(params)
+ values.Set("method", c.method())
+
+ w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
+ _, err = w.Write([]byte(values.Encode()))
+ return err
+}
+
+// GetChat gets information about a chat.
+func (bot *BotAPI) GetChat(config ChatInfoConfig) (Chat, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return Chat{}, err
+ }
+
+ var chat Chat
+ err = json.Unmarshal(resp.Result, &chat)
+
+ return chat, err
+}
+
+// GetChatAdministrators gets a list of administrators in the chat.
+//
+// If none have been appointed, only the creator will be returned.
+// Bots are not shown, even if they are an administrator.
+func (bot *BotAPI) GetChatAdministrators(config ChatAdministratorsConfig) ([]ChatMember, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return []ChatMember{}, err
+ }
+
+ var members []ChatMember
+ err = json.Unmarshal(resp.Result, &members)
+
+ return members, err
+}
+
+// GetChatMembersCount gets the number of users in a chat.
+func (bot *BotAPI) GetChatMembersCount(config ChatMemberCountConfig) (int, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return -1, err
+ }
+
+ var count int
+ err = json.Unmarshal(resp.Result, &count)
+
+ return count, err
+}
+
+// GetChatMember gets a specific chat member.
+func (bot *BotAPI) GetChatMember(config GetChatMemberConfig) (ChatMember, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return ChatMember{}, err
+ }
+
+ var member ChatMember
+ err = json.Unmarshal(resp.Result, &member)
+
+ return member, err
+}
+
+// GetGameHighScores allows you to get the high scores for a game.
+func (bot *BotAPI) GetGameHighScores(config GetGameHighScoresConfig) ([]GameHighScore, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return []GameHighScore{}, err
+ }
+
+ var highScores []GameHighScore
+ err = json.Unmarshal(resp.Result, &highScores)
+
+ return highScores, err
+}
+
+// GetInviteLink get InviteLink for a chat
+func (bot *BotAPI) GetInviteLink(config ChatInviteLinkConfig) (string, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return "", err
+ }
+
+ var inviteLink string
+ err = json.Unmarshal(resp.Result, &inviteLink)
+
+ return inviteLink, err
+}
+
+// GetStickerSet returns a StickerSet.
+func (bot *BotAPI) GetStickerSet(config GetStickerSetConfig) (StickerSet, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return StickerSet{}, err
+ }
+
+ var stickers StickerSet
+ err = json.Unmarshal(resp.Result, &stickers)
+
+ return stickers, err
+}
+
+// StopPoll stops a poll and returns the result.
+func (bot *BotAPI) StopPoll(config StopPollConfig) (Poll, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return Poll{}, err
+ }
+
+ var poll Poll
+ err = json.Unmarshal(resp.Result, &poll)
+
+ return poll, err
+}
+
+// GetMyCommands gets the currently registered commands.
+func (bot *BotAPI) GetMyCommands() ([]BotCommand, error) {
+ return bot.GetMyCommandsWithConfig(GetMyCommandsConfig{})
+}
+
+// GetMyCommandsWithConfig gets the currently registered commands with a config.
+func (bot *BotAPI) GetMyCommandsWithConfig(config GetMyCommandsConfig) ([]BotCommand, error) {
+ resp, err := bot.Request(config)
+ if err != nil {
+ return nil, err
+ }
+
+ var commands []BotCommand
+ err = json.Unmarshal(resp.Result, &commands)
+
+ return commands, err
+}
+
+// CopyMessage copy messages of any kind. The method is analogous to the method
+// forwardMessage, but the copied message doesn't have a link to the original
+// message. Returns the MessageID of the sent message on success.
+func (bot *BotAPI) CopyMessage(config CopyMessageConfig) (MessageID, error) {
+ params, err := config.params()
+ if err != nil {
+ return MessageID{}, err
+ }
+
+ resp, err := bot.MakeRequest(config.method(), params)
+ if err != nil {
+ return MessageID{}, err
+ }
+
+ var messageID MessageID
+ err = json.Unmarshal(resp.Result, &messageID)
+
+ return messageID, err
+}
+
+// EscapeText takes an input text and escape Telegram markup symbols.
+// In this way we can send a text without being afraid of having to escape the characters manually.
+// Note that you don't have to include the formatting style in the input text, or it will be escaped too.
+// If there is an error, an empty string will be returned.
+//
+// parseMode is the text formatting mode (ModeMarkdown, ModeMarkdownV2 or ModeHTML)
+// text is the input string that will be escaped
+func EscapeText(parseMode string, text string) string {
+ var replacer *strings.Replacer
+
+ if parseMode == ModeHTML {
+ replacer = strings.NewReplacer("<", "<", ">", ">", "&", "&")
+ } else if parseMode == ModeMarkdown {
+ replacer = strings.NewReplacer("_", "\\_", "*", "\\*", "`", "\\`", "[", "\\[")
+ } else if parseMode == ModeMarkdownV2 {
+ replacer = strings.NewReplacer(
+ "_", "\\_", "*", "\\*", "[", "\\[", "]", "\\]", "(",
+ "\\(", ")", "\\)", "~", "\\~", "`", "\\`", ">", "\\>",
+ "#", "\\#", "+", "\\+", "-", "\\-", "=", "\\=", "|",
+ "\\|", "{", "\\{", "}", "\\}", ".", "\\.", "!", "\\!",
+ )
+ } else {
+ return ""
+ }
+
+ return replacer.Replace(text)
+}
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/configs.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/configs.go
@@ -0,0 +1,2468 @@
+package tgbotapi
+
+import (
+ "bytes"
+ "fmt"
+ "io"
+ "net/url"
+ "os"
+ "strconv"
+)
+
+// Telegram constants
+const (
+ // APIEndpoint is the endpoint for all API methods,
+ // with formatting for Sprintf.
+ APIEndpoint = "https://api.telegram.org/bot%s/%s"
+ // FileEndpoint is the endpoint for downloading a file from Telegram.
+ FileEndpoint = "https://api.telegram.org/file/bot%s/%s"
+)
+
+// Constant values for ChatActions
+const (
+ ChatTyping = "typing"
+ ChatUploadPhoto = "upload_photo"
+ ChatRecordVideo = "record_video"
+ ChatUploadVideo = "upload_video"
+ ChatRecordVoice = "record_voice"
+ ChatUploadVoice = "upload_voice"
+ ChatUploadDocument = "upload_document"
+ ChatChooseSticker = "choose_sticker"
+ ChatFindLocation = "find_location"
+ ChatRecordVideoNote = "record_video_note"
+ ChatUploadVideoNote = "upload_video_note"
+)
+
+// API errors
+const (
+ // ErrAPIForbidden happens when a token is bad
+ ErrAPIForbidden = "forbidden"
+)
+
+// Constant values for ParseMode in MessageConfig
+const (
+ ModeMarkdown = "Markdown"
+ ModeMarkdownV2 = "MarkdownV2"
+ ModeHTML = "HTML"
+)
+
+// Constant values for update types
+const (
+ // UpdateTypeMessage is new incoming message of any kind — text, photo, sticker, etc.
+ UpdateTypeMessage = "message"
+
+ // UpdateTypeEditedMessage is new version of a message that is known to the bot and was edited
+ UpdateTypeEditedMessage = "edited_message"
+
+ // UpdateTypeChannelPost is new incoming channel post of any kind — text, photo, sticker, etc.
+ UpdateTypeChannelPost = "channel_post"
+
+ // UpdateTypeEditedChannelPost is new version of a channel post that is known to the bot and was edited
+ UpdateTypeEditedChannelPost = "edited_channel_post"
+
+ // UpdateTypeInlineQuery is new incoming inline query
+ UpdateTypeInlineQuery = "inline_query"
+
+ // UpdateTypeChosenInlineResult i the result of an inline query that was chosen by a user and sent to their
+ // chat partner. Please see the documentation on the feedback collecting for
+ // details on how to enable these updates for your bot.
+ UpdateTypeChosenInlineResult = "chosen_inline_result"
+
+ // UpdateTypeCallbackQuery is new incoming callback query
+ UpdateTypeCallbackQuery = "callback_query"
+
+ // UpdateTypeShippingQuery is new incoming shipping query. Only for invoices with flexible price
+ UpdateTypeShippingQuery = "shipping_query"
+
+ // UpdateTypePreCheckoutQuery is new incoming pre-checkout query. Contains full information about checkout
+ UpdateTypePreCheckoutQuery = "pre_checkout_query"
+
+ // UpdateTypePoll is new poll state. Bots receive only updates about stopped polls and polls
+ // which are sent by the bot
+ UpdateTypePoll = "poll"
+
+ // UpdateTypePollAnswer is when user changed their answer in a non-anonymous poll. Bots receive new votes
+ // only in polls that were sent by the bot itself.
+ UpdateTypePollAnswer = "poll_answer"
+
+ // UpdateTypeMyChatMember is when the bot's chat member status was updated in a chat. For private chats, this
+ // update is received only when the bot is blocked or unblocked by the user.
+ UpdateTypeMyChatMember = "my_chat_member"
+
+ // UpdateTypeChatMember is when the bot must be an administrator in the chat and must explicitly specify
+ // this update in the list of allowed_updates to receive these updates.
+ UpdateTypeChatMember = "chat_member"
+)
+
+// Library errors
+const (
+ ErrBadURL = "bad or empty url"
+)
+
+// Chattable is any config type that can be sent.
+type Chattable interface {
+ params() (Params, error)
+ method() string
+}
+
+// Fileable is any config type that can be sent that includes a file.
+type Fileable interface {
+ Chattable
+ files() []RequestFile
+}
+
+// RequestFile represents a file associated with a field name.
+type RequestFile struct {
+ // The file field name.
+ Name string
+ // The file data to include.
+ Data RequestFileData
+}
+
+// RequestFileData represents the data to be used for a file.
+type RequestFileData interface {
+ // NeedsUpload shows if the file needs to be uploaded.
+ NeedsUpload() bool
+
+ // UploadData gets the file name and an `io.Reader` for the file to be uploaded. This
+ // must only be called when the file needs to be uploaded.
+ UploadData() (string, io.Reader, error)
+ // SendData gets the file data to send when a file does not need to be uploaded. This
+ // must only be called when the file does not need to be uploaded.
+ SendData() string
+}
+
+// FileBytes contains information about a set of bytes to upload
+// as a File.
+type FileBytes struct {
+ Name string
+ Bytes []byte
+}
+
+func (fb FileBytes) NeedsUpload() bool {
+ return true
+}
+
+func (fb FileBytes) UploadData() (string, io.Reader, error) {
+ return fb.Name, bytes.NewReader(fb.Bytes), nil
+}
+
+func (fb FileBytes) SendData() string {
+ panic("FileBytes must be uploaded")
+}
+
+// FileReader contains information about a reader to upload as a File.
+type FileReader struct {
+ Name string
+ Reader io.Reader
+}
+
+func (fr FileReader) NeedsUpload() bool {
+ return true
+}
+
+func (fr FileReader) UploadData() (string, io.Reader, error) {
+ return fr.Name, fr.Reader, nil
+}
+
+func (fr FileReader) SendData() string {
+ panic("FileReader must be uploaded")
+}
+
+// FilePath is a path to a local file.
+type FilePath string
+
+func (fp FilePath) NeedsUpload() bool {
+ return true
+}
+
+func (fp FilePath) UploadData() (string, io.Reader, error) {
+ fileHandle, err := os.Open(string(fp))
+ if err != nil {
+ return "", nil, err
+ }
+
+ name := fileHandle.Name()
+ return name, fileHandle, err
+}
+
+func (fp FilePath) SendData() string {
+ panic("FilePath must be uploaded")
+}
+
+// FileURL is a URL to use as a file for a request.
+type FileURL string
+
+func (fu FileURL) NeedsUpload() bool {
+ return false
+}
+
+func (fu FileURL) UploadData() (string, io.Reader, error) {
+ panic("FileURL cannot be uploaded")
+}
+
+func (fu FileURL) SendData() string {
+ return string(fu)
+}
+
+// FileID is an ID of a file already uploaded to Telegram.
+type FileID string
+
+func (fi FileID) NeedsUpload() bool {
+ return false
+}
+
+func (fi FileID) UploadData() (string, io.Reader, error) {
+ panic("FileID cannot be uploaded")
+}
+
+func (fi FileID) SendData() string {
+ return string(fi)
+}
+
+// fileAttach is an internal file type used for processed media groups.
+type fileAttach string
+
+func (fa fileAttach) NeedsUpload() bool {
+ return false
+}
+
+func (fa fileAttach) UploadData() (string, io.Reader, error) {
+ panic("fileAttach cannot be uploaded")
+}
+
+func (fa fileAttach) SendData() string {
+ return string(fa)
+}
+
+// LogOutConfig is a request to log out of the cloud Bot API server.
+//
+// Note that you may not log back in for at least 10 minutes.
+type LogOutConfig struct{}
+
+func (LogOutConfig) method() string {
+ return "logOut"
+}
+
+func (LogOutConfig) params() (Params, error) {
+ return nil, nil
+}
+
+// CloseConfig is a request to close the bot instance on a local server.
+//
+// Note that you may not close an instance for the first 10 minutes after the
+// bot has started.
+type CloseConfig struct{}
+
+func (CloseConfig) method() string {
+ return "close"
+}
+
+func (CloseConfig) params() (Params, error) {
+ return nil, nil
+}
+
+// BaseChat is base type for all chat config types.
+type BaseChat struct {
+ ChatID int64 // required
+ ChannelUsername string
+ ReplyToMessageID int
+ ReplyMarkup interface{}
+ DisableNotification bool
+ AllowSendingWithoutReply bool
+}
+
+func (chat *BaseChat) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", chat.ChatID, chat.ChannelUsername)
+ params.AddNonZero("reply_to_message_id", chat.ReplyToMessageID)
+ params.AddBool("disable_notification", chat.DisableNotification)
+ params.AddBool("allow_sending_without_reply", chat.AllowSendingWithoutReply)
+
+ err := params.AddInterface("reply_markup", chat.ReplyMarkup)
+
+ return params, err
+}
+
+// BaseFile is a base type for all file config types.
+type BaseFile struct {
+ BaseChat
+ File RequestFileData
+}
+
+func (file BaseFile) params() (Params, error) {
+ return file.BaseChat.params()
+}
+
+// BaseEdit is base type of all chat edits.
+type BaseEdit struct {
+ ChatID int64
+ ChannelUsername string
+ MessageID int
+ InlineMessageID string
+ ReplyMarkup *InlineKeyboardMarkup
+}
+
+func (edit BaseEdit) params() (Params, error) {
+ params := make(Params)
+
+ if edit.InlineMessageID != "" {
+ params["inline_message_id"] = edit.InlineMessageID
+ } else {
+ params.AddFirstValid("chat_id", edit.ChatID, edit.ChannelUsername)
+ params.AddNonZero("message_id", edit.MessageID)
+ }
+
+ err := params.AddInterface("reply_markup", edit.ReplyMarkup)
+
+ return params, err
+}
+
+// MessageConfig contains information about a SendMessage request.
+type MessageConfig struct {
+ BaseChat
+ Text string
+ ParseMode string
+ Entities []MessageEntity
+ DisableWebPagePreview bool
+}
+
+func (config MessageConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonEmpty("text", config.Text)
+ params.AddBool("disable_web_page_preview", config.DisableWebPagePreview)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("entities", config.Entities)
+
+ return params, err
+}
+
+func (config MessageConfig) method() string {
+ return "sendMessage"
+}
+
+// ForwardConfig contains information about a ForwardMessage request.
+type ForwardConfig struct {
+ BaseChat
+ FromChatID int64 // required
+ FromChannelUsername string
+ MessageID int // required
+}
+
+func (config ForwardConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonZero64("from_chat_id", config.FromChatID)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
+}
+
+func (config ForwardConfig) method() string {
+ return "forwardMessage"
+}
+
+// CopyMessageConfig contains information about a copyMessage request.
+type CopyMessageConfig struct {
+ BaseChat
+ FromChatID int64
+ FromChannelUsername string
+ MessageID int
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+}
+
+func (config CopyMessageConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddFirstValid("from_chat_id", config.FromChatID, config.FromChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config CopyMessageConfig) method() string {
+ return "copyMessage"
+}
+
+// PhotoConfig contains information about a SendPhoto request.
+type PhotoConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+}
+
+func (config PhotoConfig) params() (Params, error) {
+ params, err := config.BaseFile.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config PhotoConfig) method() string {
+ return "sendPhoto"
+}
+
+func (config PhotoConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "photo",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// AudioConfig contains information about a SendAudio request.
+type AudioConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ Duration int
+ Performer string
+ Title string
+}
+
+func (config AudioConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("performer", config.Performer)
+ params.AddNonEmpty("title", config.Title)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config AudioConfig) method() string {
+ return "sendAudio"
+}
+
+func (config AudioConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "audio",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// DocumentConfig contains information about a SendDocument request.
+type DocumentConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ DisableContentTypeDetection bool
+}
+
+func (config DocumentConfig) params() (Params, error) {
+ params, err := config.BaseFile.params()
+
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ params.AddBool("disable_content_type_detection", config.DisableContentTypeDetection)
+
+ return params, err
+}
+
+func (config DocumentConfig) method() string {
+ return "sendDocument"
+}
+
+func (config DocumentConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "document",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// StickerConfig contains information about a SendSticker request.
+type StickerConfig struct {
+ BaseFile
+}
+
+func (config StickerConfig) params() (Params, error) {
+ return config.BaseChat.params()
+}
+
+func (config StickerConfig) method() string {
+ return "sendSticker"
+}
+
+func (config StickerConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "sticker",
+ Data: config.File,
+ }}
+}
+
+// VideoConfig contains information about a SendVideo request.
+type VideoConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Duration int
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ SupportsStreaming bool
+}
+
+func (config VideoConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ params.AddBool("supports_streaming", config.SupportsStreaming)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config VideoConfig) method() string {
+ return "sendVideo"
+}
+
+func (config VideoConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "video",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// AnimationConfig contains information about a SendAnimation request.
+type AnimationConfig struct {
+ BaseFile
+ Duration int
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+}
+
+func (config AnimationConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config AnimationConfig) method() string {
+ return "sendAnimation"
+}
+
+func (config AnimationConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "animation",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// VideoNoteConfig contains information about a SendVideoNote request.
+type VideoNoteConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Duration int
+ Length int
+}
+
+func (config VideoNoteConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonZero("length", config.Length)
+
+ return params, err
+}
+
+func (config VideoNoteConfig) method() string {
+ return "sendVideoNote"
+}
+
+func (config VideoNoteConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "video_note",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// VoiceConfig contains information about a SendVoice request.
+type VoiceConfig struct {
+ BaseFile
+ Thumb RequestFileData
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+ Duration int
+}
+
+func (config VoiceConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("duration", config.Duration)
+ params.AddNonEmpty("caption", config.Caption)
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config VoiceConfig) method() string {
+ return "sendVoice"
+}
+
+func (config VoiceConfig) files() []RequestFile {
+ files := []RequestFile{{
+ Name: "voice",
+ Data: config.File,
+ }}
+
+ if config.Thumb != nil {
+ files = append(files, RequestFile{
+ Name: "thumb",
+ Data: config.Thumb,
+ })
+ }
+
+ return files
+}
+
+// LocationConfig contains information about a SendLocation request.
+type LocationConfig struct {
+ BaseChat
+ Latitude float64 // required
+ Longitude float64 // required
+ HorizontalAccuracy float64 // optional
+ LivePeriod int // optional
+ Heading int // optional
+ ProximityAlertRadius int // optional
+}
+
+func (config LocationConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params.AddNonZeroFloat("latitude", config.Latitude)
+ params.AddNonZeroFloat("longitude", config.Longitude)
+ params.AddNonZeroFloat("horizontal_accuracy", config.HorizontalAccuracy)
+ params.AddNonZero("live_period", config.LivePeriod)
+ params.AddNonZero("heading", config.Heading)
+ params.AddNonZero("proximity_alert_radius", config.ProximityAlertRadius)
+
+ return params, err
+}
+
+func (config LocationConfig) method() string {
+ return "sendLocation"
+}
+
+// EditMessageLiveLocationConfig allows you to update a live location.
+type EditMessageLiveLocationConfig struct {
+ BaseEdit
+ Latitude float64 // required
+ Longitude float64 // required
+ HorizontalAccuracy float64 // optional
+ Heading int // optional
+ ProximityAlertRadius int // optional
+}
+
+func (config EditMessageLiveLocationConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+
+ params.AddNonZeroFloat("latitude", config.Latitude)
+ params.AddNonZeroFloat("longitude", config.Longitude)
+ params.AddNonZeroFloat("horizontal_accuracy", config.HorizontalAccuracy)
+ params.AddNonZero("heading", config.Heading)
+ params.AddNonZero("proximity_alert_radius", config.ProximityAlertRadius)
+
+ return params, err
+}
+
+func (config EditMessageLiveLocationConfig) method() string {
+ return "editMessageLiveLocation"
+}
+
+// StopMessageLiveLocationConfig stops updating a live location.
+type StopMessageLiveLocationConfig struct {
+ BaseEdit
+}
+
+func (config StopMessageLiveLocationConfig) params() (Params, error) {
+ return config.BaseEdit.params()
+}
+
+func (config StopMessageLiveLocationConfig) method() string {
+ return "stopMessageLiveLocation"
+}
+
+// VenueConfig contains information about a SendVenue request.
+type VenueConfig struct {
+ BaseChat
+ Latitude float64 // required
+ Longitude float64 // required
+ Title string // required
+ Address string // required
+ FoursquareID string
+ FoursquareType string
+ GooglePlaceID string
+ GooglePlaceType string
+}
+
+func (config VenueConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params.AddNonZeroFloat("latitude", config.Latitude)
+ params.AddNonZeroFloat("longitude", config.Longitude)
+ params["title"] = config.Title
+ params["address"] = config.Address
+ params.AddNonEmpty("foursquare_id", config.FoursquareID)
+ params.AddNonEmpty("foursquare_type", config.FoursquareType)
+ params.AddNonEmpty("google_place_id", config.GooglePlaceID)
+ params.AddNonEmpty("google_place_type", config.GooglePlaceType)
+
+ return params, err
+}
+
+func (config VenueConfig) method() string {
+ return "sendVenue"
+}
+
+// ContactConfig allows you to send a contact.
+type ContactConfig struct {
+ BaseChat
+ PhoneNumber string
+ FirstName string
+ LastName string
+ VCard string
+}
+
+func (config ContactConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params["phone_number"] = config.PhoneNumber
+ params["first_name"] = config.FirstName
+
+ params.AddNonEmpty("last_name", config.LastName)
+ params.AddNonEmpty("vcard", config.VCard)
+
+ return params, err
+}
+
+func (config ContactConfig) method() string {
+ return "sendContact"
+}
+
+// SendPollConfig allows you to send a poll.
+type SendPollConfig struct {
+ BaseChat
+ Question string
+ Options []string
+ IsAnonymous bool
+ Type string
+ AllowsMultipleAnswers bool
+ CorrectOptionID int64
+ Explanation string
+ ExplanationParseMode string
+ ExplanationEntities []MessageEntity
+ OpenPeriod int
+ CloseDate int
+ IsClosed bool
+}
+
+func (config SendPollConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params["question"] = config.Question
+ if err = params.AddInterface("options", config.Options); err != nil {
+ return params, err
+ }
+ params["is_anonymous"] = strconv.FormatBool(config.IsAnonymous)
+ params.AddNonEmpty("type", config.Type)
+ params["allows_multiple_answers"] = strconv.FormatBool(config.AllowsMultipleAnswers)
+ params["correct_option_id"] = strconv.FormatInt(config.CorrectOptionID, 10)
+ params.AddBool("is_closed", config.IsClosed)
+ params.AddNonEmpty("explanation", config.Explanation)
+ params.AddNonEmpty("explanation_parse_mode", config.ExplanationParseMode)
+ params.AddNonZero("open_period", config.OpenPeriod)
+ params.AddNonZero("close_date", config.CloseDate)
+ err = params.AddInterface("explanation_entities", config.ExplanationEntities)
+
+ return params, err
+}
+
+func (SendPollConfig) method() string {
+ return "sendPoll"
+}
+
+// GameConfig allows you to send a game.
+type GameConfig struct {
+ BaseChat
+ GameShortName string
+}
+
+func (config GameConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params["game_short_name"] = config.GameShortName
+
+ return params, err
+}
+
+func (config GameConfig) method() string {
+ return "sendGame"
+}
+
+// SetGameScoreConfig allows you to update the game score in a chat.
+type SetGameScoreConfig struct {
+ UserID int64
+ Score int
+ Force bool
+ DisableEditMessage bool
+ ChatID int64
+ ChannelUsername string
+ MessageID int
+ InlineMessageID string
+}
+
+func (config SetGameScoreConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonZero("scrore", config.Score)
+ params.AddBool("disable_edit_message", config.DisableEditMessage)
+
+ if config.InlineMessageID != "" {
+ params["inline_message_id"] = config.InlineMessageID
+ } else {
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+ }
+
+ return params, nil
+}
+
+func (config SetGameScoreConfig) method() string {
+ return "setGameScore"
+}
+
+// GetGameHighScoresConfig allows you to fetch the high scores for a game.
+type GetGameHighScoresConfig struct {
+ UserID int64
+ ChatID int64
+ ChannelUsername string
+ MessageID int
+ InlineMessageID string
+}
+
+func (config GetGameHighScoresConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+
+ if config.InlineMessageID != "" {
+ params["inline_message_id"] = config.InlineMessageID
+ } else {
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+ }
+
+ return params, nil
+}
+
+func (config GetGameHighScoresConfig) method() string {
+ return "getGameHighScores"
+}
+
+// ChatActionConfig contains information about a SendChatAction request.
+type ChatActionConfig struct {
+ BaseChat
+ Action string // required
+}
+
+func (config ChatActionConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+
+ params["action"] = config.Action
+
+ return params, err
+}
+
+func (config ChatActionConfig) method() string {
+ return "sendChatAction"
+}
+
+// EditMessageTextConfig allows you to modify the text in a message.
+type EditMessageTextConfig struct {
+ BaseEdit
+ Text string
+ ParseMode string
+ Entities []MessageEntity
+ DisableWebPagePreview bool
+}
+
+func (config EditMessageTextConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+ if err != nil {
+ return params, err
+ }
+
+ params["text"] = config.Text
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ params.AddBool("disable_web_page_preview", config.DisableWebPagePreview)
+ err = params.AddInterface("entities", config.Entities)
+
+ return params, err
+}
+
+func (config EditMessageTextConfig) method() string {
+ return "editMessageText"
+}
+
+// EditMessageCaptionConfig allows you to modify the caption of a message.
+type EditMessageCaptionConfig struct {
+ BaseEdit
+ Caption string
+ ParseMode string
+ CaptionEntities []MessageEntity
+}
+
+func (config EditMessageCaptionConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+ if err != nil {
+ return params, err
+ }
+
+ params["caption"] = config.Caption
+ params.AddNonEmpty("parse_mode", config.ParseMode)
+ err = params.AddInterface("caption_entities", config.CaptionEntities)
+
+ return params, err
+}
+
+func (config EditMessageCaptionConfig) method() string {
+ return "editMessageCaption"
+}
+
+// EditMessageMediaConfig allows you to make an editMessageMedia request.
+type EditMessageMediaConfig struct {
+ BaseEdit
+
+ Media interface{}
+}
+
+func (EditMessageMediaConfig) method() string {
+ return "editMessageMedia"
+}
+
+func (config EditMessageMediaConfig) params() (Params, error) {
+ params, err := config.BaseEdit.params()
+ if err != nil {
+ return params, err
+ }
+
+ err = params.AddInterface("media", prepareInputMediaParam(config.Media, 0))
+
+ return params, err
+}
+
+func (config EditMessageMediaConfig) files() []RequestFile {
+ return prepareInputMediaFile(config.Media, 0)
+}
+
+// EditMessageReplyMarkupConfig allows you to modify the reply markup
+// of a message.
+type EditMessageReplyMarkupConfig struct {
+ BaseEdit
+}
+
+func (config EditMessageReplyMarkupConfig) params() (Params, error) {
+ return config.BaseEdit.params()
+}
+
+func (config EditMessageReplyMarkupConfig) method() string {
+ return "editMessageReplyMarkup"
+}
+
+// StopPollConfig allows you to stop a poll sent by the bot.
+type StopPollConfig struct {
+ BaseEdit
+}
+
+func (config StopPollConfig) params() (Params, error) {
+ return config.BaseEdit.params()
+}
+
+func (StopPollConfig) method() string {
+ return "stopPoll"
+}
+
+// UserProfilePhotosConfig contains information about a
+// GetUserProfilePhotos request.
+type UserProfilePhotosConfig struct {
+ UserID int64
+ Offset int
+ Limit int
+}
+
+func (UserProfilePhotosConfig) method() string {
+ return "getUserProfilePhotos"
+}
+
+func (config UserProfilePhotosConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonZero("offset", config.Offset)
+ params.AddNonZero("limit", config.Limit)
+
+ return params, nil
+}
+
+// FileConfig has information about a file hosted on Telegram.
+type FileConfig struct {
+ FileID string
+}
+
+func (FileConfig) method() string {
+ return "getFile"
+}
+
+func (config FileConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["file_id"] = config.FileID
+
+ return params, nil
+}
+
+// UpdateConfig contains information about a GetUpdates request.
+type UpdateConfig struct {
+ Offset int
+ Limit int
+ Timeout int
+ AllowedUpdates []string
+}
+
+func (UpdateConfig) method() string {
+ return "getUpdates"
+}
+
+func (config UpdateConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero("offset", config.Offset)
+ params.AddNonZero("limit", config.Limit)
+ params.AddNonZero("timeout", config.Timeout)
+ params.AddInterface("allowed_updates", config.AllowedUpdates)
+
+ return params, nil
+}
+
+// WebhookConfig contains information about a SetWebhook request.
+type WebhookConfig struct {
+ URL *url.URL
+ Certificate RequestFileData
+ IPAddress string
+ MaxConnections int
+ AllowedUpdates []string
+ DropPendingUpdates bool
+}
+
+func (config WebhookConfig) method() string {
+ return "setWebhook"
+}
+
+func (config WebhookConfig) params() (Params, error) {
+ params := make(Params)
+
+ if config.URL != nil {
+ params["url"] = config.URL.String()
+ }
+
+ params.AddNonEmpty("ip_address", config.IPAddress)
+ params.AddNonZero("max_connections", config.MaxConnections)
+ err := params.AddInterface("allowed_updates", config.AllowedUpdates)
+ params.AddBool("drop_pending_updates", config.DropPendingUpdates)
+
+ return params, err
+}
+
+func (config WebhookConfig) files() []RequestFile {
+ if config.Certificate != nil {
+ return []RequestFile{{
+ Name: "certificate",
+ Data: config.Certificate,
+ }}
+ }
+
+ return nil
+}
+
+// DeleteWebhookConfig is a helper to delete a webhook.
+type DeleteWebhookConfig struct {
+ DropPendingUpdates bool
+}
+
+func (config DeleteWebhookConfig) method() string {
+ return "deleteWebhook"
+}
+
+func (config DeleteWebhookConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddBool("drop_pending_updates", config.DropPendingUpdates)
+
+ return params, nil
+}
+
+// InlineConfig contains information on making an InlineQuery response.
+type InlineConfig struct {
+ InlineQueryID string `json:"inline_query_id"`
+ Results []interface{} `json:"results"`
+ CacheTime int `json:"cache_time"`
+ IsPersonal bool `json:"is_personal"`
+ NextOffset string `json:"next_offset"`
+ SwitchPMText string `json:"switch_pm_text"`
+ SwitchPMParameter string `json:"switch_pm_parameter"`
+}
+
+func (config InlineConfig) method() string {
+ return "answerInlineQuery"
+}
+
+func (config InlineConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["inline_query_id"] = config.InlineQueryID
+ params.AddNonZero("cache_time", config.CacheTime)
+ params.AddBool("is_personal", config.IsPersonal)
+ params.AddNonEmpty("next_offset", config.NextOffset)
+ params.AddNonEmpty("switch_pm_text", config.SwitchPMText)
+ params.AddNonEmpty("switch_pm_parameter", config.SwitchPMParameter)
+ err := params.AddInterface("results", config.Results)
+
+ return params, err
+}
+
+// CallbackConfig contains information on making a CallbackQuery response.
+type CallbackConfig struct {
+ CallbackQueryID string `json:"callback_query_id"`
+ Text string `json:"text"`
+ ShowAlert bool `json:"show_alert"`
+ URL string `json:"url"`
+ CacheTime int `json:"cache_time"`
+}
+
+func (config CallbackConfig) method() string {
+ return "answerCallbackQuery"
+}
+
+func (config CallbackConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["callback_query_id"] = config.CallbackQueryID
+ params.AddNonEmpty("text", config.Text)
+ params.AddBool("show_alert", config.ShowAlert)
+ params.AddNonEmpty("url", config.URL)
+ params.AddNonZero("cache_time", config.CacheTime)
+
+ return params, nil
+}
+
+// ChatMemberConfig contains information about a user in a chat for use
+// with administrative functions such as kicking or unbanning a user.
+type ChatMemberConfig struct {
+ ChatID int64
+ SuperGroupUsername string
+ ChannelUsername string
+ UserID int64
+}
+
+// UnbanChatMemberConfig allows you to unban a user.
+type UnbanChatMemberConfig struct {
+ ChatMemberConfig
+ OnlyIfBanned bool
+}
+
+func (config UnbanChatMemberConfig) method() string {
+ return "unbanChatMember"
+}
+
+func (config UnbanChatMemberConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddBool("only_if_banned", config.OnlyIfBanned)
+
+ return params, nil
+}
+
+// BanChatMemberConfig contains extra fields to kick user.
+type BanChatMemberConfig struct {
+ ChatMemberConfig
+ UntilDate int64
+ RevokeMessages bool
+}
+
+func (config BanChatMemberConfig) method() string {
+ return "banChatMember"
+}
+
+func (config BanChatMemberConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonZero64("until_date", config.UntilDate)
+ params.AddBool("revoke_messages", config.RevokeMessages)
+
+ return params, nil
+}
+
+// KickChatMemberConfig contains extra fields to ban user.
+//
+// This was renamed to BanChatMember in later versions of the Telegram Bot API.
+type KickChatMemberConfig = BanChatMemberConfig
+
+// RestrictChatMemberConfig contains fields to restrict members of chat
+type RestrictChatMemberConfig struct {
+ ChatMemberConfig
+ UntilDate int64
+ Permissions *ChatPermissions
+}
+
+func (config RestrictChatMemberConfig) method() string {
+ return "restrictChatMember"
+}
+
+func (config RestrictChatMemberConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+
+ err := params.AddInterface("permissions", config.Permissions)
+ params.AddNonZero64("until_date", config.UntilDate)
+
+ return params, err
+}
+
+// PromoteChatMemberConfig contains fields to promote members of chat
+type PromoteChatMemberConfig struct {
+ ChatMemberConfig
+ IsAnonymous bool
+ CanManageChat bool
+ CanChangeInfo bool
+ CanPostMessages bool
+ CanEditMessages bool
+ CanDeleteMessages bool
+ CanManageVoiceChats bool
+ CanInviteUsers bool
+ CanRestrictMembers bool
+ CanPinMessages bool
+ CanPromoteMembers bool
+}
+
+func (config PromoteChatMemberConfig) method() string {
+ return "promoteChatMember"
+}
+
+func (config PromoteChatMemberConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+
+ params.AddBool("is_anonymous", config.IsAnonymous)
+ params.AddBool("can_manage_chat", config.CanManageChat)
+ params.AddBool("can_change_info", config.CanChangeInfo)
+ params.AddBool("can_post_messages", config.CanPostMessages)
+ params.AddBool("can_edit_messages", config.CanEditMessages)
+ params.AddBool("can_delete_messages", config.CanDeleteMessages)
+ params.AddBool("can_manage_voice_chats", config.CanManageVoiceChats)
+ params.AddBool("can_invite_users", config.CanInviteUsers)
+ params.AddBool("can_restrict_members", config.CanRestrictMembers)
+ params.AddBool("can_pin_messages", config.CanPinMessages)
+ params.AddBool("can_promote_members", config.CanPromoteMembers)
+
+ return params, nil
+}
+
+// SetChatAdministratorCustomTitle sets the title of an administrative user
+// promoted by the bot for a chat.
+type SetChatAdministratorCustomTitle struct {
+ ChatMemberConfig
+ CustomTitle string
+}
+
+func (SetChatAdministratorCustomTitle) method() string {
+ return "setChatAdministratorCustomTitle"
+}
+
+func (config SetChatAdministratorCustomTitle) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername, config.ChannelUsername)
+ params.AddNonZero64("user_id", config.UserID)
+ params.AddNonEmpty("custom_title", config.CustomTitle)
+
+ return params, nil
+}
+
+// BanChatSenderChatConfig bans a channel chat in a supergroup or a channel. The
+// owner of the chat will not be able to send messages and join live streams on
+// behalf of the chat, unless it is unbanned first. The bot must be an
+// administrator in the supergroup or channel for this to work and must have the
+// appropriate administrator rights.
+type BanChatSenderChatConfig struct {
+ ChatID int64
+ ChannelUsername string
+ SenderChatID int64
+ UntilDate int
+}
+
+func (config BanChatSenderChatConfig) method() string {
+ return "banChatSenderChat"
+}
+
+func (config BanChatSenderChatConfig) params() (Params, error) {
+ params := make(Params)
+
+ _ = params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero64("sender_chat_id", config.SenderChatID)
+ params.AddNonZero("until_date", config.UntilDate)
+
+ return params, nil
+}
+
+// UnbanChatSenderChatConfig unbans 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.
+type UnbanChatSenderChatConfig struct {
+ ChatID int64
+ ChannelUsername string
+ SenderChatID int64
+}
+
+func (config UnbanChatSenderChatConfig) method() string {
+ return "unbanChatSenderChat"
+}
+
+func (config UnbanChatSenderChatConfig) params() (Params, error) {
+ params := make(Params)
+
+ _ = params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero64("sender_chat_id", config.SenderChatID)
+
+ return params, nil
+}
+
+// ChatConfig contains information about getting information on a chat.
+type ChatConfig struct {
+ ChatID int64
+ SuperGroupUsername string
+}
+
+func (config ChatConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+
+ return params, nil
+}
+
+// ChatInfoConfig contains information about getting chat information.
+type ChatInfoConfig struct {
+ ChatConfig
+}
+
+func (ChatInfoConfig) method() string {
+ return "getChat"
+}
+
+// ChatMemberCountConfig contains information about getting the number of users in a chat.
+type ChatMemberCountConfig struct {
+ ChatConfig
+}
+
+func (ChatMemberCountConfig) method() string {
+ return "getChatMembersCount"
+}
+
+// ChatAdministratorsConfig contains information about getting chat administrators.
+type ChatAdministratorsConfig struct {
+ ChatConfig
+}
+
+func (ChatAdministratorsConfig) method() string {
+ return "getChatAdministrators"
+}
+
+// SetChatPermissionsConfig allows you to set default permissions for the
+// members in a group. The bot must be an administrator and have rights to
+// restrict members.
+type SetChatPermissionsConfig struct {
+ ChatConfig
+ Permissions *ChatPermissions
+}
+
+func (SetChatPermissionsConfig) method() string {
+ return "setChatPermissions"
+}
+
+func (config SetChatPermissionsConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ err := params.AddInterface("permissions", config.Permissions)
+
+ return params, err
+}
+
+// ChatInviteLinkConfig contains information about getting a chat link.
+//
+// Note that generating a new link will revoke any previous links.
+type ChatInviteLinkConfig struct {
+ ChatConfig
+}
+
+func (ChatInviteLinkConfig) method() string {
+ return "exportChatInviteLink"
+}
+
+func (config ChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+
+ return params, nil
+}
+
+// CreateChatInviteLinkConfig allows you to create an additional invite link for
+// a chat. The bot must be an administrator in the chat for this to work and
+// must have the appropriate admin rights. The link can be revoked using the
+// RevokeChatInviteLinkConfig.
+type CreateChatInviteLinkConfig struct {
+ ChatConfig
+ Name string
+ ExpireDate int
+ MemberLimit int
+ CreatesJoinRequest bool
+}
+
+func (CreateChatInviteLinkConfig) method() string {
+ return "createChatInviteLink"
+}
+
+func (config CreateChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonEmpty("name", config.Name)
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero("expire_date", config.ExpireDate)
+ params.AddNonZero("member_limit", config.MemberLimit)
+ params.AddBool("creates_join_request", config.CreatesJoinRequest)
+
+ return params, nil
+}
+
+// EditChatInviteLinkConfig allows you to edit a non-primary invite link created
+// by the bot. The bot must be an administrator in the chat for this to work and
+// must have the appropriate admin rights.
+type EditChatInviteLinkConfig struct {
+ ChatConfig
+ InviteLink string
+ Name string
+ ExpireDate int
+ MemberLimit int
+ CreatesJoinRequest bool
+}
+
+func (EditChatInviteLinkConfig) method() string {
+ return "editChatInviteLink"
+}
+
+func (config EditChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonEmpty("name", config.Name)
+ params["invite_link"] = config.InviteLink
+ params.AddNonZero("expire_date", config.ExpireDate)
+ params.AddNonZero("member_limit", config.MemberLimit)
+ params.AddBool("creates_join_request", config.CreatesJoinRequest)
+
+ return params, nil
+}
+
+// RevokeChatInviteLinkConfig allows you to revoke an invite link created by the
+// bot. If the primary link is revoked, a new link is automatically generated.
+// The bot must be an administrator in the chat for this to work and must have
+// the appropriate admin rights.
+type RevokeChatInviteLinkConfig struct {
+ ChatConfig
+ InviteLink string
+}
+
+func (RevokeChatInviteLinkConfig) method() string {
+ return "revokeChatInviteLink"
+}
+
+func (config RevokeChatInviteLinkConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params["invite_link"] = config.InviteLink
+
+ return params, nil
+}
+
+// ApproveChatJoinRequestConfig allows you to approve a chat join request.
+type ApproveChatJoinRequestConfig struct {
+ ChatConfig
+ UserID int64
+}
+
+func (ApproveChatJoinRequestConfig) method() string {
+ return "approveChatJoinRequest"
+}
+
+func (config ApproveChatJoinRequestConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero("user_id", int(config.UserID))
+
+ return params, nil
+}
+
+// DeclineChatJoinRequest allows you to decline a chat join request.
+type DeclineChatJoinRequest struct {
+ ChatConfig
+ UserID int64
+}
+
+func (DeclineChatJoinRequest) method() string {
+ return "declineChatJoinRequest"
+}
+
+func (config DeclineChatJoinRequest) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero("user_id", int(config.UserID))
+
+ return params, nil
+}
+
+// LeaveChatConfig allows you to leave a chat.
+type LeaveChatConfig struct {
+ ChatID int64
+ ChannelUsername string
+}
+
+func (config LeaveChatConfig) method() string {
+ return "leaveChat"
+}
+
+func (config LeaveChatConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+
+ return params, nil
+}
+
+// ChatConfigWithUser contains information about a chat and a user.
+type ChatConfigWithUser struct {
+ ChatID int64
+ SuperGroupUsername string
+ UserID int64
+}
+
+func (config ChatConfigWithUser) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params.AddNonZero64("user_id", config.UserID)
+
+ return params, nil
+}
+
+// GetChatMemberConfig is information about getting a specific member in a chat.
+type GetChatMemberConfig struct {
+ ChatConfigWithUser
+}
+
+func (GetChatMemberConfig) method() string {
+ return "getChatMember"
+}
+
+// InvoiceConfig contains information for sendInvoice request.
+type InvoiceConfig struct {
+ BaseChat
+ Title string // required
+ Description string // required
+ Payload string // required
+ ProviderToken string // required
+ Currency string // required
+ Prices []LabeledPrice // required
+ MaxTipAmount int
+ SuggestedTipAmounts []int
+ StartParameter string
+ ProviderData string
+ PhotoURL string
+ PhotoSize int
+ PhotoWidth int
+ PhotoHeight int
+ NeedName bool
+ NeedPhoneNumber bool
+ NeedEmail bool
+ NeedShippingAddress bool
+ SendPhoneNumberToProvider bool
+ SendEmailToProvider bool
+ IsFlexible bool
+}
+
+func (config InvoiceConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params["title"] = config.Title
+ params["description"] = config.Description
+ params["payload"] = config.Payload
+ params["provider_token"] = config.ProviderToken
+ params["currency"] = config.Currency
+ if err = params.AddInterface("prices", config.Prices); err != nil {
+ return params, err
+ }
+
+ params.AddNonZero("max_tip_amount", config.MaxTipAmount)
+ err = params.AddInterface("suggested_tip_amounts", config.SuggestedTipAmounts)
+ params.AddNonEmpty("start_parameter", config.StartParameter)
+ params.AddNonEmpty("provider_data", config.ProviderData)
+ params.AddNonEmpty("photo_url", config.PhotoURL)
+ params.AddNonZero("photo_size", config.PhotoSize)
+ params.AddNonZero("photo_width", config.PhotoWidth)
+ params.AddNonZero("photo_height", config.PhotoHeight)
+ params.AddBool("need_name", config.NeedName)
+ params.AddBool("need_phone_number", config.NeedPhoneNumber)
+ params.AddBool("need_email", config.NeedEmail)
+ params.AddBool("need_shipping_address", config.NeedShippingAddress)
+ params.AddBool("is_flexible", config.IsFlexible)
+ params.AddBool("send_phone_number_to_provider", config.SendPhoneNumberToProvider)
+ params.AddBool("send_email_to_provider", config.SendEmailToProvider)
+
+ return params, err
+}
+
+func (config InvoiceConfig) method() string {
+ return "sendInvoice"
+}
+
+// ShippingConfig contains information for answerShippingQuery request.
+type ShippingConfig struct {
+ ShippingQueryID string // required
+ OK bool // required
+ ShippingOptions []ShippingOption
+ ErrorMessage string
+}
+
+func (config ShippingConfig) method() string {
+ return "answerShippingQuery"
+}
+
+func (config ShippingConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["shipping_query_id"] = config.ShippingQueryID
+ params.AddBool("ok", config.OK)
+ err := params.AddInterface("shipping_options", config.ShippingOptions)
+ params.AddNonEmpty("error_message", config.ErrorMessage)
+
+ return params, err
+}
+
+// PreCheckoutConfig conatins information for answerPreCheckoutQuery request.
+type PreCheckoutConfig struct {
+ PreCheckoutQueryID string // required
+ OK bool // required
+ ErrorMessage string
+}
+
+func (config PreCheckoutConfig) method() string {
+ return "answerPreCheckoutQuery"
+}
+
+func (config PreCheckoutConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["pre_checkout_query_id"] = config.PreCheckoutQueryID
+ params.AddBool("ok", config.OK)
+ params.AddNonEmpty("error_message", config.ErrorMessage)
+
+ return params, nil
+}
+
+// DeleteMessageConfig contains information of a message in a chat to delete.
+type DeleteMessageConfig struct {
+ ChannelUsername string
+ ChatID int64
+ MessageID int
+}
+
+func (config DeleteMessageConfig) method() string {
+ return "deleteMessage"
+}
+
+func (config DeleteMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
+}
+
+// PinChatMessageConfig contains information of a message in a chat to pin.
+type PinChatMessageConfig struct {
+ ChatID int64
+ ChannelUsername string
+ MessageID int
+ DisableNotification bool
+}
+
+func (config PinChatMessageConfig) method() string {
+ return "pinChatMessage"
+}
+
+func (config PinChatMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+ params.AddBool("disable_notification", config.DisableNotification)
+
+ return params, nil
+}
+
+// UnpinChatMessageConfig contains information of a chat message to unpin.
+//
+// If MessageID is not specified, it will unpin the most recent pin.
+type UnpinChatMessageConfig struct {
+ ChatID int64
+ ChannelUsername string
+ MessageID int
+}
+
+func (config UnpinChatMessageConfig) method() string {
+ return "unpinChatMessage"
+}
+
+func (config UnpinChatMessageConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddNonZero("message_id", config.MessageID)
+
+ return params, nil
+}
+
+// UnpinAllChatMessagesConfig contains information of all messages to unpin in
+// a chat.
+type UnpinAllChatMessagesConfig struct {
+ ChatID int64
+ ChannelUsername string
+}
+
+func (config UnpinAllChatMessagesConfig) method() string {
+ return "unpinAllChatMessages"
+}
+
+func (config UnpinAllChatMessagesConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+
+ return params, nil
+}
+
+// SetChatPhotoConfig allows you to set a group, supergroup, or channel's photo.
+type SetChatPhotoConfig struct {
+ BaseFile
+}
+
+func (config SetChatPhotoConfig) method() string {
+ return "setChatPhoto"
+}
+
+func (config SetChatPhotoConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "photo",
+ Data: config.File,
+ }}
+}
+
+// DeleteChatPhotoConfig allows you to delete a group, supergroup, or channel's photo.
+type DeleteChatPhotoConfig struct {
+ ChatID int64
+ ChannelUsername string
+}
+
+func (config DeleteChatPhotoConfig) method() string {
+ return "deleteChatPhoto"
+}
+
+func (config DeleteChatPhotoConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+
+ return params, nil
+}
+
+// SetChatTitleConfig allows you to set the title of something other than a private chat.
+type SetChatTitleConfig struct {
+ ChatID int64
+ ChannelUsername string
+
+ Title string
+}
+
+func (config SetChatTitleConfig) method() string {
+ return "setChatTitle"
+}
+
+func (config SetChatTitleConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params["title"] = config.Title
+
+ return params, nil
+}
+
+// SetChatDescriptionConfig allows you to set the description of a supergroup or channel.
+type SetChatDescriptionConfig struct {
+ ChatID int64
+ ChannelUsername string
+
+ Description string
+}
+
+func (config SetChatDescriptionConfig) method() string {
+ return "setChatDescription"
+}
+
+func (config SetChatDescriptionConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params["description"] = config.Description
+
+ return params, nil
+}
+
+// GetStickerSetConfig allows you to get the stickers in a set.
+type GetStickerSetConfig struct {
+ Name string
+}
+
+func (config GetStickerSetConfig) method() string {
+ return "getStickerSet"
+}
+
+func (config GetStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["name"] = config.Name
+
+ return params, nil
+}
+
+// UploadStickerConfig allows you to upload a sticker for use in a set later.
+type UploadStickerConfig struct {
+ UserID int64
+ PNGSticker RequestFileData
+}
+
+func (config UploadStickerConfig) method() string {
+ return "uploadStickerFile"
+}
+
+func (config UploadStickerConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+
+ return params, nil
+}
+
+func (config UploadStickerConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "png_sticker",
+ Data: config.PNGSticker,
+ }}
+}
+
+// NewStickerSetConfig allows creating a new sticker set.
+//
+// You must set either PNGSticker or TGSSticker.
+type NewStickerSetConfig struct {
+ UserID int64
+ Name string
+ Title string
+ PNGSticker RequestFileData
+ TGSSticker RequestFileData
+ Emojis string
+ ContainsMasks bool
+ MaskPosition *MaskPosition
+}
+
+func (config NewStickerSetConfig) method() string {
+ return "createNewStickerSet"
+}
+
+func (config NewStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params["name"] = config.Name
+ params["title"] = config.Title
+
+ params["emojis"] = config.Emojis
+
+ params.AddBool("contains_masks", config.ContainsMasks)
+
+ err := params.AddInterface("mask_position", config.MaskPosition)
+
+ return params, err
+}
+
+func (config NewStickerSetConfig) files() []RequestFile {
+ if config.PNGSticker != nil {
+ return []RequestFile{{
+ Name: "png_sticker",
+ Data: config.PNGSticker,
+ }}
+ }
+
+ return []RequestFile{{
+ Name: "tgs_sticker",
+ Data: config.TGSSticker,
+ }}
+}
+
+// AddStickerConfig allows you to add a sticker to a set.
+type AddStickerConfig struct {
+ UserID int64
+ Name string
+ PNGSticker RequestFileData
+ TGSSticker RequestFileData
+ Emojis string
+ MaskPosition *MaskPosition
+}
+
+func (config AddStickerConfig) method() string {
+ return "addStickerToSet"
+}
+
+func (config AddStickerConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddNonZero64("user_id", config.UserID)
+ params["name"] = config.Name
+ params["emojis"] = config.Emojis
+
+ err := params.AddInterface("mask_position", config.MaskPosition)
+
+ return params, err
+}
+
+func (config AddStickerConfig) files() []RequestFile {
+ if config.PNGSticker != nil {
+ return []RequestFile{{
+ Name: "png_sticker",
+ Data: config.PNGSticker,
+ }}
+ }
+
+ return []RequestFile{{
+ Name: "tgs_sticker",
+ Data: config.TGSSticker,
+ }}
+
+}
+
+// SetStickerPositionConfig allows you to change the position of a sticker in a set.
+type SetStickerPositionConfig struct {
+ Sticker string
+ Position int
+}
+
+func (config SetStickerPositionConfig) method() string {
+ return "setStickerPositionInSet"
+}
+
+func (config SetStickerPositionConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["sticker"] = config.Sticker
+ params.AddNonZero("position", config.Position)
+
+ return params, nil
+}
+
+// DeleteStickerConfig allows you to delete a sticker from a set.
+type DeleteStickerConfig struct {
+ Sticker string
+}
+
+func (config DeleteStickerConfig) method() string {
+ return "deleteStickerFromSet"
+}
+
+func (config DeleteStickerConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["sticker"] = config.Sticker
+
+ return params, nil
+}
+
+// SetStickerSetThumbConfig allows you to set the thumbnail for a sticker set.
+type SetStickerSetThumbConfig struct {
+ Name string
+ UserID int64
+ Thumb RequestFileData
+}
+
+func (config SetStickerSetThumbConfig) method() string {
+ return "setStickerSetThumb"
+}
+
+func (config SetStickerSetThumbConfig) params() (Params, error) {
+ params := make(Params)
+
+ params["name"] = config.Name
+ params.AddNonZero64("user_id", config.UserID)
+
+ return params, nil
+}
+
+func (config SetStickerSetThumbConfig) files() []RequestFile {
+ return []RequestFile{{
+ Name: "thumb",
+ Data: config.Thumb,
+ }}
+}
+
+// SetChatStickerSetConfig allows you to set the sticker set for a supergroup.
+type SetChatStickerSetConfig struct {
+ ChatID int64
+ SuperGroupUsername string
+
+ StickerSetName string
+}
+
+func (config SetChatStickerSetConfig) method() string {
+ return "setChatStickerSet"
+}
+
+func (config SetChatStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+ params["sticker_set_name"] = config.StickerSetName
+
+ return params, nil
+}
+
+// DeleteChatStickerSetConfig allows you to remove a supergroup's sticker set.
+type DeleteChatStickerSetConfig struct {
+ ChatID int64
+ SuperGroupUsername string
+}
+
+func (config DeleteChatStickerSetConfig) method() string {
+ return "deleteChatStickerSet"
+}
+
+func (config DeleteChatStickerSetConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.SuperGroupUsername)
+
+ return params, nil
+}
+
+// MediaGroupConfig allows you to send a group of media.
+//
+// Media consist of InputMedia items (InputMediaPhoto, InputMediaVideo).
+type MediaGroupConfig struct {
+ ChatID int64
+ ChannelUsername string
+
+ Media []interface{}
+ DisableNotification bool
+ ReplyToMessageID int
+}
+
+func (config MediaGroupConfig) method() string {
+ return "sendMediaGroup"
+}
+
+func (config MediaGroupConfig) params() (Params, error) {
+ params := make(Params)
+
+ params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
+ params.AddBool("disable_notification", config.DisableNotification)
+ params.AddNonZero("reply_to_message_id", config.ReplyToMessageID)
+
+ err := params.AddInterface("media", prepareInputMediaForParams(config.Media))
+
+ return params, err
+}
+
+func (config MediaGroupConfig) files() []RequestFile {
+ return prepareInputMediaForFiles(config.Media)
+}
+
+// DiceConfig contains information about a sendDice request.
+type DiceConfig struct {
+ BaseChat
+ // Emoji on which the dice throw animation is based.
+ // Currently, must be one of 🎲, 🎯, 🏀, ⚽, 🎳, or 🎰.
+ // Dice can have values 1-6 for 🎲, 🎯, and 🎳, values 1-5 for 🏀 and ⚽,
+ // and values 1-64 for 🎰.
+ // Defaults to “🎲”
+ Emoji string
+}
+
+func (config DiceConfig) method() string {
+ return "sendDice"
+}
+
+func (config DiceConfig) params() (Params, error) {
+ params, err := config.BaseChat.params()
+ if err != nil {
+ return params, err
+ }
+
+ params.AddNonEmpty("emoji", config.Emoji)
+
+ return params, err
+}
+
+// GetMyCommandsConfig gets a list of the currently registered commands.
+type GetMyCommandsConfig struct {
+ Scope *BotCommandScope
+ LanguageCode string
+}
+
+func (config GetMyCommandsConfig) method() string {
+ return "getMyCommands"
+}
+
+func (config GetMyCommandsConfig) params() (Params, error) {
+ params := make(Params)
+
+ err := params.AddInterface("scope", config.Scope)
+ params.AddNonEmpty("language_code", config.LanguageCode)
+
+ return params, err
+}
+
+// SetMyCommandsConfig sets a list of commands the bot understands.
+type SetMyCommandsConfig struct {
+ Commands []BotCommand
+ Scope *BotCommandScope
+ LanguageCode string
+}
+
+func (config SetMyCommandsConfig) method() string {
+ return "setMyCommands"
+}
+
+func (config SetMyCommandsConfig) params() (Params, error) {
+ params := make(Params)
+
+ if err := params.AddInterface("commands", config.Commands); err != nil {
+ return params, err
+ }
+ err := params.AddInterface("scope", config.Scope)
+ params.AddNonEmpty("language_code", config.LanguageCode)
+
+ return params, err
+}
+
+type DeleteMyCommandsConfig struct {
+ Scope *BotCommandScope
+ LanguageCode string
+}
+
+func (config DeleteMyCommandsConfig) method() string {
+ return "deleteMyCommands"
+}
+
+func (config DeleteMyCommandsConfig) params() (Params, error) {
+ params := make(Params)
+
+ err := params.AddInterface("scope", config.Scope)
+ params.AddNonEmpty("language_code", config.LanguageCode)
+
+ return params, err
+}
+
+// prepareInputMediaParam evaluates a single InputMedia and determines if it
+// needs to be modified for a successful upload. If it returns nil, then the
+// value does not need to be included in the params. Otherwise, it will return
+// the same type as was originally provided.
+//
+// The idx is used to calculate the file field name. If you only have a single
+// file, 0 may be used. It is formatted into "attach://file-%d" for the primary
+// media and "attach://file-%d-thumb" for thumbnails.
+//
+// It is expected to be used in conjunction with prepareInputMediaFile.
+func prepareInputMediaParam(inputMedia interface{}, idx int) interface{} {
+ switch m := inputMedia.(type) {
+ case InputMediaPhoto:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ return m
+ case InputMediaVideo:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
+ }
+
+ return m
+ case InputMediaAudio:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
+ }
+
+ return m
+ case InputMediaDocument:
+ if m.Media.NeedsUpload() {
+ m.Media = fileAttach(fmt.Sprintf("attach://file-%d", idx))
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ m.Thumb = fileAttach(fmt.Sprintf("attach://file-%d-thumb", idx))
+ }
+
+ return m
+ }
+
+ return nil
+}
+
+// prepareInputMediaFile generates an array of RequestFile to provide for
+// Fileable's files method. It returns an array as a single InputMedia may have
+// multiple files, for the primary media and a thumbnail.
+//
+// The idx parameter is used to generate file field names. It uses the names
+// "file-%d" for the main file and "file-%d-thumb" for the thumbnail.
+//
+// It is expected to be used in conjunction with prepareInputMediaParam.
+func prepareInputMediaFile(inputMedia interface{}, idx int) []RequestFile {
+ files := []RequestFile{}
+
+ switch m := inputMedia.(type) {
+ case InputMediaPhoto:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+ case InputMediaVideo:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Thumb,
+ })
+ }
+ case InputMediaDocument:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Thumb,
+ })
+ }
+ case InputMediaAudio:
+ if m.Media.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Media,
+ })
+ }
+
+ if m.Thumb != nil && m.Thumb.NeedsUpload() {
+ files = append(files, RequestFile{
+ Name: fmt.Sprintf("file-%d", idx),
+ Data: m.Thumb,
+ })
+ }
+ }
+
+ return files
+}
+
+// prepareInputMediaForParams calls prepareInputMediaParam for each item
+// provided and returns a new array with the correct params for a request.
+//
+// It is expected that files will get data from the associated function,
+// prepareInputMediaForFiles.
+func prepareInputMediaForParams(inputMedia []interface{}) []interface{} {
+ newMedia := make([]interface{}, len(inputMedia))
+ copy(newMedia, inputMedia)
+
+ for idx, media := range inputMedia {
+ if param := prepareInputMediaParam(media, idx); param != nil {
+ newMedia[idx] = param
+ }
+ }
+
+ return newMedia
+}
+
+// prepareInputMediaForFiles calls prepareInputMediaFile for each item
+// provided and returns a new array with the correct files for a request.
+//
+// It is expected that params will get data from the associated function,
+// prepareInputMediaForParams.
+func prepareInputMediaForFiles(inputMedia []interface{}) []RequestFile {
+ files := []RequestFile{}
+
+ for idx, media := range inputMedia {
+ if file := prepareInputMediaFile(media, idx); file != nil {
+ files = append(files, file...)
+ }
+ }
+
+ return files
+}
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/helpers.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/helpers.go
@@ -0,0 +1,927 @@
+package tgbotapi
+
+import (
+ "net/url"
+)
+
+// NewMessage creates a new Message.
+//
+// chatID is where to send it, text is the message text.
+func NewMessage(chatID int64, text string) MessageConfig {
+ return MessageConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ ReplyToMessageID: 0,
+ },
+ Text: text,
+ DisableWebPagePreview: false,
+ }
+}
+
+// NewDeleteMessage creates a request to delete a message.
+func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
+ return DeleteMessageConfig{
+ ChatID: chatID,
+ MessageID: messageID,
+ }
+}
+
+// NewMessageToChannel creates a new Message that is sent to a channel
+// by username.
+//
+// username is the username of the channel, text is the message text,
+// and the username should be in the form of `@username`.
+func NewMessageToChannel(username string, text string) MessageConfig {
+ return MessageConfig{
+ BaseChat: BaseChat{
+ ChannelUsername: username,
+ },
+ Text: text,
+ }
+}
+
+// NewForward creates a new forward.
+//
+// chatID is where to send it, fromChatID is the source chat,
+// and messageID is the ID of the original message.
+func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig {
+ return ForwardConfig{
+ BaseChat: BaseChat{ChatID: chatID},
+ FromChatID: fromChatID,
+ MessageID: messageID,
+ }
+}
+
+// NewCopyMessage creates a new copy message.
+//
+// chatID is where to send it, fromChatID is the source chat,
+// and messageID is the ID of the original message.
+func NewCopyMessage(chatID int64, fromChatID int64, messageID int) CopyMessageConfig {
+ return CopyMessageConfig{
+ BaseChat: BaseChat{ChatID: chatID},
+ FromChatID: fromChatID,
+ MessageID: messageID,
+ }
+}
+
+// NewPhoto creates a new sendPhoto request.
+//
+// chatID is where to send it, file is a string path to the file,
+// FileReader, or FileBytes.
+//
+// Note that you must send animated GIFs as a document.
+func NewPhoto(chatID int64, file RequestFileData) PhotoConfig {
+ return PhotoConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewPhotoToChannel creates a new photo uploader to send a photo to a channel.
+//
+// Note that you must send animated GIFs as a document.
+func NewPhotoToChannel(username string, file RequestFileData) PhotoConfig {
+ return PhotoConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{
+ ChannelUsername: username,
+ },
+ File: file,
+ },
+ }
+}
+
+// NewAudio creates a new sendAudio request.
+func NewAudio(chatID int64, file RequestFileData) AudioConfig {
+ return AudioConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewDocument creates a new sendDocument request.
+func NewDocument(chatID int64, file RequestFileData) DocumentConfig {
+ return DocumentConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewSticker creates a new sendSticker request.
+func NewSticker(chatID int64, file RequestFileData) StickerConfig {
+ return StickerConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewVideo creates a new sendVideo request.
+func NewVideo(chatID int64, file RequestFileData) VideoConfig {
+ return VideoConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewAnimation creates a new sendAnimation request.
+func NewAnimation(chatID int64, file RequestFileData) AnimationConfig {
+ return AnimationConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewVideoNote creates a new sendVideoNote request.
+//
+// chatID is where to send it, file is a string path to the file,
+// FileReader, or FileBytes.
+func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig {
+ return VideoNoteConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ Length: length,
+ }
+}
+
+// NewVoice creates a new sendVoice request.
+func NewVoice(chatID int64, file RequestFileData) VoiceConfig {
+ return VoiceConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{ChatID: chatID},
+ File: file,
+ },
+ }
+}
+
+// NewMediaGroup creates a new media group. Files should be an array of
+// two to ten InputMediaPhoto or InputMediaVideo.
+func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
+ return MediaGroupConfig{
+ ChatID: chatID,
+ Media: files,
+ }
+}
+
+// NewInputMediaPhoto creates a new InputMediaPhoto.
+func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto {
+ return InputMediaPhoto{
+ BaseInputMedia{
+ Type: "photo",
+ Media: media,
+ },
+ }
+}
+
+// NewInputMediaVideo creates a new InputMediaVideo.
+func NewInputMediaVideo(media RequestFileData) InputMediaVideo {
+ return InputMediaVideo{
+ BaseInputMedia: BaseInputMedia{
+ Type: "video",
+ Media: media,
+ },
+ }
+}
+
+// NewInputMediaAnimation creates a new InputMediaAnimation.
+func NewInputMediaAnimation(media RequestFileData) InputMediaAnimation {
+ return InputMediaAnimation{
+ BaseInputMedia: BaseInputMedia{
+ Type: "animation",
+ Media: media,
+ },
+ }
+}
+
+// NewInputMediaAudio creates a new InputMediaAudio.
+func NewInputMediaAudio(media RequestFileData) InputMediaAudio {
+ return InputMediaAudio{
+ BaseInputMedia: BaseInputMedia{
+ Type: "audio",
+ Media: media,
+ },
+ }
+}
+
+// NewInputMediaDocument creates a new InputMediaDocument.
+func NewInputMediaDocument(media RequestFileData) InputMediaDocument {
+ return InputMediaDocument{
+ BaseInputMedia: BaseInputMedia{
+ Type: "document",
+ Media: media,
+ },
+ }
+}
+
+// NewContact allows you to send a shared contact.
+func NewContact(chatID int64, phoneNumber, firstName string) ContactConfig {
+ return ContactConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ PhoneNumber: phoneNumber,
+ FirstName: firstName,
+ }
+}
+
+// NewLocation shares your location.
+//
+// chatID is where to send it, latitude and longitude are coordinates.
+func NewLocation(chatID int64, latitude float64, longitude float64) LocationConfig {
+ return LocationConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ Latitude: latitude,
+ Longitude: longitude,
+ }
+}
+
+// NewVenue allows you to send a venue and its location.
+func NewVenue(chatID int64, title, address string, latitude, longitude float64) VenueConfig {
+ return VenueConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ Title: title,
+ Address: address,
+ Latitude: latitude,
+ Longitude: longitude,
+ }
+}
+
+// NewChatAction sets a chat action.
+// Actions last for 5 seconds, or until your next action.
+//
+// chatID is where to send it, action should be set via Chat constants.
+func NewChatAction(chatID int64, action string) ChatActionConfig {
+ return ChatActionConfig{
+ BaseChat: BaseChat{ChatID: chatID},
+ Action: action,
+ }
+}
+
+// NewUserProfilePhotos gets user profile photos.
+//
+// userID is the ID of the user you wish to get profile photos from.
+func NewUserProfilePhotos(userID int64) UserProfilePhotosConfig {
+ return UserProfilePhotosConfig{
+ UserID: userID,
+ Offset: 0,
+ Limit: 0,
+ }
+}
+
+// NewUpdate gets updates since the last Offset.
+//
+// offset is the last Update ID to include.
+// You likely want to set this to the last Update ID plus 1.
+func NewUpdate(offset int) UpdateConfig {
+ return UpdateConfig{
+ Offset: offset,
+ Limit: 0,
+ Timeout: 0,
+ }
+}
+
+// NewWebhook creates a new webhook.
+//
+// link is the url parsable link you wish to get the updates.
+func NewWebhook(link string) (WebhookConfig, error) {
+ u, err := url.Parse(link)
+
+ if err != nil {
+ return WebhookConfig{}, err
+ }
+
+ return WebhookConfig{
+ URL: u,
+ }, nil
+}
+
+// NewWebhookWithCert creates a new webhook with a certificate.
+//
+// link is the url you wish to get webhooks,
+// file contains a string to a file, FileReader, or FileBytes.
+func NewWebhookWithCert(link string, file RequestFileData) (WebhookConfig, error) {
+ u, err := url.Parse(link)
+
+ if err != nil {
+ return WebhookConfig{}, err
+ }
+
+ return WebhookConfig{
+ URL: u,
+ Certificate: file,
+ }, nil
+}
+
+// NewInlineQueryResultArticle creates a new inline query article.
+func NewInlineQueryResultArticle(id, title, messageText string) InlineQueryResultArticle {
+ return InlineQueryResultArticle{
+ Type: "article",
+ ID: id,
+ Title: title,
+ InputMessageContent: InputTextMessageContent{
+ Text: messageText,
+ },
+ }
+}
+
+// NewInlineQueryResultArticleMarkdown creates a new inline query article with Markdown parsing.
+func NewInlineQueryResultArticleMarkdown(id, title, messageText string) InlineQueryResultArticle {
+ return InlineQueryResultArticle{
+ Type: "article",
+ ID: id,
+ Title: title,
+ InputMessageContent: InputTextMessageContent{
+ Text: messageText,
+ ParseMode: "Markdown",
+ },
+ }
+}
+
+// NewInlineQueryResultArticleMarkdownV2 creates a new inline query article with MarkdownV2 parsing.
+func NewInlineQueryResultArticleMarkdownV2(id, title, messageText string) InlineQueryResultArticle {
+ return InlineQueryResultArticle{
+ Type: "article",
+ ID: id,
+ Title: title,
+ InputMessageContent: InputTextMessageContent{
+ Text: messageText,
+ ParseMode: "MarkdownV2",
+ },
+ }
+}
+
+// NewInlineQueryResultArticleHTML creates a new inline query article with HTML parsing.
+func NewInlineQueryResultArticleHTML(id, title, messageText string) InlineQueryResultArticle {
+ return InlineQueryResultArticle{
+ Type: "article",
+ ID: id,
+ Title: title,
+ InputMessageContent: InputTextMessageContent{
+ Text: messageText,
+ ParseMode: "HTML",
+ },
+ }
+}
+
+// NewInlineQueryResultGIF creates a new inline query GIF.
+func NewInlineQueryResultGIF(id, url string) InlineQueryResultGIF {
+ return InlineQueryResultGIF{
+ Type: "gif",
+ ID: id,
+ URL: url,
+ }
+}
+
+// NewInlineQueryResultCachedGIF create a new inline query with cached photo.
+func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF {
+ return InlineQueryResultCachedGIF{
+ Type: "gif",
+ ID: id,
+ GIFID: gifID,
+ }
+}
+
+// NewInlineQueryResultMPEG4GIF creates a new inline query MPEG4 GIF.
+func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
+ return InlineQueryResultMPEG4GIF{
+ Type: "mpeg4_gif",
+ ID: id,
+ URL: url,
+ }
+}
+
+// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF.
+func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF {
+ return InlineQueryResultCachedMPEG4GIF{
+ Type: "mpeg4_gif",
+ ID: id,
+ MPEG4FileID: MPEG4GIFID,
+ }
+}
+
+// NewInlineQueryResultPhoto creates a new inline query photo.
+func NewInlineQueryResultPhoto(id, url string) InlineQueryResultPhoto {
+ return InlineQueryResultPhoto{
+ Type: "photo",
+ ID: id,
+ URL: url,
+ }
+}
+
+// NewInlineQueryResultPhotoWithThumb creates a new inline query photo.
+func NewInlineQueryResultPhotoWithThumb(id, url, thumb string) InlineQueryResultPhoto {
+ return InlineQueryResultPhoto{
+ Type: "photo",
+ ID: id,
+ URL: url,
+ ThumbURL: thumb,
+ }
+}
+
+// NewInlineQueryResultCachedPhoto create a new inline query with cached photo.
+func NewInlineQueryResultCachedPhoto(id, photoID string) InlineQueryResultCachedPhoto {
+ return InlineQueryResultCachedPhoto{
+ Type: "photo",
+ ID: id,
+ PhotoID: photoID,
+ }
+}
+
+// NewInlineQueryResultVideo creates a new inline query video.
+func NewInlineQueryResultVideo(id, url string) InlineQueryResultVideo {
+ return InlineQueryResultVideo{
+ Type: "video",
+ ID: id,
+ URL: url,
+ }
+}
+
+// NewInlineQueryResultCachedVideo create a new inline query with cached video.
+func NewInlineQueryResultCachedVideo(id, videoID, title string) InlineQueryResultCachedVideo {
+ return InlineQueryResultCachedVideo{
+ Type: "video",
+ ID: id,
+ VideoID: videoID,
+ Title: title,
+ }
+}
+
+// NewInlineQueryResultCachedSticker create a new inline query with cached sticker.
+func NewInlineQueryResultCachedSticker(id, stickerID, title string) InlineQueryResultCachedSticker {
+ return InlineQueryResultCachedSticker{
+ Type: "sticker",
+ ID: id,
+ StickerID: stickerID,
+ Title: title,
+ }
+}
+
+// NewInlineQueryResultAudio creates a new inline query audio.
+func NewInlineQueryResultAudio(id, url, title string) InlineQueryResultAudio {
+ return InlineQueryResultAudio{
+ Type: "audio",
+ ID: id,
+ URL: url,
+ Title: title,
+ }
+}
+
+// NewInlineQueryResultCachedAudio create a new inline query with cached photo.
+func NewInlineQueryResultCachedAudio(id, audioID string) InlineQueryResultCachedAudio {
+ return InlineQueryResultCachedAudio{
+ Type: "audio",
+ ID: id,
+ AudioID: audioID,
+ }
+}
+
+// NewInlineQueryResultVoice creates a new inline query voice.
+func NewInlineQueryResultVoice(id, url, title string) InlineQueryResultVoice {
+ return InlineQueryResultVoice{
+ Type: "voice",
+ ID: id,
+ URL: url,
+ Title: title,
+ }
+}
+
+// NewInlineQueryResultCachedVoice create a new inline query with cached photo.
+func NewInlineQueryResultCachedVoice(id, voiceID, title string) InlineQueryResultCachedVoice {
+ return InlineQueryResultCachedVoice{
+ Type: "voice",
+ ID: id,
+ VoiceID: voiceID,
+ Title: title,
+ }
+}
+
+// NewInlineQueryResultDocument creates a new inline query document.
+func NewInlineQueryResultDocument(id, url, title, mimeType string) InlineQueryResultDocument {
+ return InlineQueryResultDocument{
+ Type: "document",
+ ID: id,
+ URL: url,
+ Title: title,
+ MimeType: mimeType,
+ }
+}
+
+// NewInlineQueryResultCachedDocument create a new inline query with cached photo.
+func NewInlineQueryResultCachedDocument(id, documentID, title string) InlineQueryResultCachedDocument {
+ return InlineQueryResultCachedDocument{
+ Type: "document",
+ ID: id,
+ DocumentID: documentID,
+ Title: title,
+ }
+}
+
+// NewInlineQueryResultLocation creates a new inline query location.
+func NewInlineQueryResultLocation(id, title string, latitude, longitude float64) InlineQueryResultLocation {
+ return InlineQueryResultLocation{
+ Type: "location",
+ ID: id,
+ Title: title,
+ Latitude: latitude,
+ Longitude: longitude,
+ }
+}
+
+// NewInlineQueryResultVenue creates a new inline query venue.
+func NewInlineQueryResultVenue(id, title, address string, latitude, longitude float64) InlineQueryResultVenue {
+ return InlineQueryResultVenue{
+ Type: "venue",
+ ID: id,
+ Title: title,
+ Address: address,
+ Latitude: latitude,
+ Longitude: longitude,
+ }
+}
+
+// NewEditMessageText allows you to edit the text of a message.
+func NewEditMessageText(chatID int64, messageID int, text string) EditMessageTextConfig {
+ return EditMessageTextConfig{
+ BaseEdit: BaseEdit{
+ ChatID: chatID,
+ MessageID: messageID,
+ },
+ Text: text,
+ }
+}
+
+// NewEditMessageTextAndMarkup allows you to edit the text and replymarkup of a message.
+func NewEditMessageTextAndMarkup(chatID int64, messageID int, text string, replyMarkup InlineKeyboardMarkup) EditMessageTextConfig {
+ return EditMessageTextConfig{
+ BaseEdit: BaseEdit{
+ ChatID: chatID,
+ MessageID: messageID,
+ ReplyMarkup: &replyMarkup,
+ },
+ Text: text,
+ }
+}
+
+// NewEditMessageCaption allows you to edit the caption of a message.
+func NewEditMessageCaption(chatID int64, messageID int, caption string) EditMessageCaptionConfig {
+ return EditMessageCaptionConfig{
+ BaseEdit: BaseEdit{
+ ChatID: chatID,
+ MessageID: messageID,
+ },
+ Caption: caption,
+ }
+}
+
+// NewEditMessageReplyMarkup allows you to edit the inline
+// keyboard markup.
+func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKeyboardMarkup) EditMessageReplyMarkupConfig {
+ return EditMessageReplyMarkupConfig{
+ BaseEdit: BaseEdit{
+ ChatID: chatID,
+ MessageID: messageID,
+ ReplyMarkup: &replyMarkup,
+ },
+ }
+}
+
+// NewRemoveKeyboard hides the keyboard, with the option for being selective
+// or hiding for everyone.
+func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {
+ return ReplyKeyboardRemove{
+ RemoveKeyboard: true,
+ Selective: selective,
+ }
+}
+
+// NewKeyboardButton creates a regular keyboard button.
+func NewKeyboardButton(text string) KeyboardButton {
+ return KeyboardButton{
+ Text: text,
+ }
+}
+
+// NewKeyboardButtonContact creates a keyboard button that requests
+// user contact information upon click.
+func NewKeyboardButtonContact(text string) KeyboardButton {
+ return KeyboardButton{
+ Text: text,
+ RequestContact: true,
+ }
+}
+
+// NewKeyboardButtonLocation creates a keyboard button that requests
+// user location information upon click.
+func NewKeyboardButtonLocation(text string) KeyboardButton {
+ return KeyboardButton{
+ Text: text,
+ RequestLocation: true,
+ }
+}
+
+// NewKeyboardButtonRow creates a row of keyboard buttons.
+func NewKeyboardButtonRow(buttons ...KeyboardButton) []KeyboardButton {
+ var row []KeyboardButton
+
+ row = append(row, buttons...)
+
+ return row
+}
+
+// NewReplyKeyboard creates a new regular keyboard with sane defaults.
+func NewReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
+ var keyboard [][]KeyboardButton
+
+ keyboard = append(keyboard, rows...)
+
+ return ReplyKeyboardMarkup{
+ ResizeKeyboard: true,
+ Keyboard: keyboard,
+ }
+}
+
+// NewOneTimeReplyKeyboard creates a new one time keyboard.
+func NewOneTimeReplyKeyboard(rows ...[]KeyboardButton) ReplyKeyboardMarkup {
+ markup := NewReplyKeyboard(rows...)
+ markup.OneTimeKeyboard = true
+ return markup
+}
+
+// NewInlineKeyboardButtonData creates an inline keyboard button with text
+// and data for a callback.
+func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
+ return InlineKeyboardButton{
+ Text: text,
+ CallbackData: &data,
+ }
+}
+
+// NewInlineKeyboardButtonLoginURL creates an inline keyboard button with text
+// which goes to a LoginURL.
+func NewInlineKeyboardButtonLoginURL(text string, loginURL LoginURL) InlineKeyboardButton {
+ return InlineKeyboardButton{
+ Text: text,
+ LoginURL: &loginURL,
+ }
+}
+
+// NewInlineKeyboardButtonURL creates an inline keyboard button with text
+// which goes to a URL.
+func NewInlineKeyboardButtonURL(text, url string) InlineKeyboardButton {
+ return InlineKeyboardButton{
+ Text: text,
+ URL: &url,
+ }
+}
+
+// NewInlineKeyboardButtonSwitch creates an inline keyboard button with
+// text which allows the user to switch to a chat or return to a chat.
+func NewInlineKeyboardButtonSwitch(text, sw string) InlineKeyboardButton {
+ return InlineKeyboardButton{
+ Text: text,
+ SwitchInlineQuery: &sw,
+ }
+}
+
+// NewInlineKeyboardRow creates an inline keyboard row with buttons.
+func NewInlineKeyboardRow(buttons ...InlineKeyboardButton) []InlineKeyboardButton {
+ var row []InlineKeyboardButton
+
+ row = append(row, buttons...)
+
+ return row
+}
+
+// NewInlineKeyboardMarkup creates a new inline keyboard.
+func NewInlineKeyboardMarkup(rows ...[]InlineKeyboardButton) InlineKeyboardMarkup {
+ var keyboard [][]InlineKeyboardButton
+
+ keyboard = append(keyboard, rows...)
+
+ return InlineKeyboardMarkup{
+ InlineKeyboard: keyboard,
+ }
+}
+
+// NewCallback creates a new callback message.
+func NewCallback(id, text string) CallbackConfig {
+ return CallbackConfig{
+ CallbackQueryID: id,
+ Text: text,
+ ShowAlert: false,
+ }
+}
+
+// NewCallbackWithAlert creates a new callback message that alerts
+// the user.
+func NewCallbackWithAlert(id, text string) CallbackConfig {
+ return CallbackConfig{
+ CallbackQueryID: id,
+ Text: text,
+ ShowAlert: true,
+ }
+}
+
+// NewInvoice creates a new Invoice request to the user.
+func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig {
+ return InvoiceConfig{
+ BaseChat: BaseChat{ChatID: chatID},
+ Title: title,
+ Description: description,
+ Payload: payload,
+ ProviderToken: providerToken,
+ StartParameter: startParameter,
+ Currency: currency,
+ Prices: prices}
+}
+
+// NewChatTitle allows you to update the title of a chat.
+func NewChatTitle(chatID int64, title string) SetChatTitleConfig {
+ return SetChatTitleConfig{
+ ChatID: chatID,
+ Title: title,
+ }
+}
+
+// NewChatDescription allows you to update the description of a chat.
+func NewChatDescription(chatID int64, description string) SetChatDescriptionConfig {
+ return SetChatDescriptionConfig{
+ ChatID: chatID,
+ Description: description,
+ }
+}
+
+// NewChatPhoto allows you to update the photo for a chat.
+func NewChatPhoto(chatID int64, photo RequestFileData) SetChatPhotoConfig {
+ return SetChatPhotoConfig{
+ BaseFile: BaseFile{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ File: photo,
+ },
+ }
+}
+
+// NewDeleteChatPhoto allows you to delete the photo for a chat.
+func NewDeleteChatPhoto(chatID int64) DeleteChatPhotoConfig {
+ return DeleteChatPhotoConfig{
+ ChatID: chatID,
+ }
+}
+
+// NewPoll allows you to create a new poll.
+func NewPoll(chatID int64, question string, options ...string) SendPollConfig {
+ return SendPollConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ Question: question,
+ Options: options,
+ IsAnonymous: true, // This is Telegram's default.
+ }
+}
+
+// NewStopPoll allows you to stop a poll.
+func NewStopPoll(chatID int64, messageID int) StopPollConfig {
+ return StopPollConfig{
+ BaseEdit{
+ ChatID: chatID,
+ MessageID: messageID,
+ },
+ }
+}
+
+// NewDice allows you to send a random dice roll.
+func NewDice(chatID int64) DiceConfig {
+ return DiceConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ }
+}
+
+// NewDiceWithEmoji allows you to send a random roll of one of many types.
+//
+// Emoji may be 🎲 (1-6), 🎯 (1-6), or 🏀 (1-5).
+func NewDiceWithEmoji(chatID int64, emoji string) DiceConfig {
+ return DiceConfig{
+ BaseChat: BaseChat{
+ ChatID: chatID,
+ },
+ Emoji: emoji,
+ }
+}
+
+// NewBotCommandScopeDefault represents the default scope of bot commands.
+func NewBotCommandScopeDefault() BotCommandScope {
+ return BotCommandScope{Type: "default"}
+}
+
+// NewBotCommandScopeAllPrivateChats represents the scope of bot commands,
+// covering all private chats.
+func NewBotCommandScopeAllPrivateChats() BotCommandScope {
+ return BotCommandScope{Type: "all_private_chats"}
+}
+
+// NewBotCommandScopeAllGroupChats represents the scope of bot commands,
+// covering all group and supergroup chats.
+func NewBotCommandScopeAllGroupChats() BotCommandScope {
+ return BotCommandScope{Type: "all_group_chats"}
+}
+
+// NewBotCommandScopeAllChatAdministrators represents the scope of bot commands,
+// covering all group and supergroup chat administrators.
+func NewBotCommandScopeAllChatAdministrators() BotCommandScope {
+ return BotCommandScope{Type: "all_chat_administrators"}
+}
+
+// NewBotCommandScopeChat represents the scope of bot commands, covering a
+// specific chat.
+func NewBotCommandScopeChat(chatID int64) BotCommandScope {
+ return BotCommandScope{
+ Type: "chat",
+ ChatID: chatID,
+ }
+}
+
+// NewBotCommandScopeChatAdministrators represents the scope of bot commands,
+// covering all administrators of a specific group or supergroup chat.
+func NewBotCommandScopeChatAdministrators(chatID int64) BotCommandScope {
+ return BotCommandScope{
+ Type: "chat_administrators",
+ ChatID: chatID,
+ }
+}
+
+// NewBotCommandScopeChatMember represents the scope of bot commands, covering a
+// specific member of a group or supergroup chat.
+func NewBotCommandScopeChatMember(chatID, userID int64) BotCommandScope {
+ return BotCommandScope{
+ Type: "chat_member",
+ ChatID: chatID,
+ UserID: userID,
+ }
+}
+
+// NewGetMyCommandsWithScope allows you to set the registered commands for a
+// given scope.
+func NewGetMyCommandsWithScope(scope BotCommandScope) GetMyCommandsConfig {
+ return GetMyCommandsConfig{Scope: &scope}
+}
+
+// NewGetMyCommandsWithScopeAndLanguage allows you to set the registered
+// commands for a given scope and language code.
+func NewGetMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string) GetMyCommandsConfig {
+ return GetMyCommandsConfig{Scope: &scope, LanguageCode: languageCode}
+}
+
+// NewSetMyCommands allows you to set the registered commands.
+func NewSetMyCommands(commands ...BotCommand) SetMyCommandsConfig {
+ return SetMyCommandsConfig{Commands: commands}
+}
+
+// NewSetMyCommandsWithScope allows you to set the registered commands for a given scope.
+func NewSetMyCommandsWithScope(scope BotCommandScope, commands ...BotCommand) SetMyCommandsConfig {
+ return SetMyCommandsConfig{Commands: commands, Scope: &scope}
+}
+
+// NewSetMyCommandsWithScopeAndLanguage allows you to set the registered commands for a given scope
+// and language code.
+func NewSetMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string, commands ...BotCommand) SetMyCommandsConfig {
+ return SetMyCommandsConfig{Commands: commands, Scope: &scope, LanguageCode: languageCode}
+}
+
+// NewDeleteMyCommands allows you to delete the registered commands.
+func NewDeleteMyCommands() DeleteMyCommandsConfig {
+ return DeleteMyCommandsConfig{}
+}
+
+// NewDeleteMyCommandsWithScope allows you to delete the registered commands for a given
+// scope.
+func NewDeleteMyCommandsWithScope(scope BotCommandScope) DeleteMyCommandsConfig {
+ return DeleteMyCommandsConfig{Scope: &scope}
+}
+
+// NewDeleteMyCommandsWithScopeAndLanguage allows you to delete the registered commands for a given
+// scope and language code.
+func NewDeleteMyCommandsWithScopeAndLanguage(scope BotCommandScope, languageCode string) DeleteMyCommandsConfig {
+ return DeleteMyCommandsConfig{Scope: &scope, LanguageCode: languageCode}
+}
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/log.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/log.go
@@ -0,0 +1,27 @@
+package tgbotapi
+
+import (
+ "errors"
+ stdlog "log"
+ "os"
+)
+
+// BotLogger is an interface that represents the required methods to log data.
+//
+// Instead of requiring the standard logger, we can just specify the methods we
+// use and allow users to pass anything that implements these.
+type BotLogger interface {
+ Println(v ...interface{})
+ Printf(format string, v ...interface{})
+}
+
+var log BotLogger = stdlog.New(os.Stderr, "", stdlog.LstdFlags)
+
+// SetLogger specifies the logger that the package should use.
+func SetLogger(logger BotLogger) error {
+ if logger == nil {
+ return errors.New("logger is nil")
+ }
+ log = logger
+ return nil
+}
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/params.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/params.go
@@ -0,0 +1,97 @@
+package tgbotapi
+
+import (
+ "encoding/json"
+ "reflect"
+ "strconv"
+)
+
+// Params represents a set of parameters that gets passed to a request.
+type Params map[string]string
+
+// AddNonEmpty adds a value if it not an empty string.
+func (p Params) AddNonEmpty(key, value string) {
+ if value != "" {
+ p[key] = value
+ }
+}
+
+// AddNonZero adds a value if it is not zero.
+func (p Params) AddNonZero(key string, value int) {
+ if value != 0 {
+ p[key] = strconv.Itoa(value)
+ }
+}
+
+// AddNonZero64 is the same as AddNonZero except uses an int64.
+func (p Params) AddNonZero64(key string, value int64) {
+ if value != 0 {
+ p[key] = strconv.FormatInt(value, 10)
+ }
+}
+
+// AddBool adds a value of a bool if it is true.
+func (p Params) AddBool(key string, value bool) {
+ if value {
+ p[key] = strconv.FormatBool(value)
+ }
+}
+
+// AddNonZeroFloat adds a floating point value that is not zero.
+func (p Params) AddNonZeroFloat(key string, value float64) {
+ if value != 0 {
+ p[key] = strconv.FormatFloat(value, 'f', 6, 64)
+ }
+}
+
+// AddInterface adds an interface if it is not nil and can be JSON marshalled.
+func (p Params) AddInterface(key string, value interface{}) error {
+ if value == nil || (reflect.ValueOf(value).Kind() == reflect.Ptr && reflect.ValueOf(value).IsNil()) {
+ return nil
+ }
+
+ b, err := json.Marshal(value)
+ if err != nil {
+ return err
+ }
+
+ p[key] = string(b)
+
+ return nil
+}
+
+// AddFirstValid attempts to add the first item that is not a default value.
+//
+// For example, AddFirstValid(0, "", "test") would add "test".
+func (p Params) AddFirstValid(key string, args ...interface{}) error {
+ for _, arg := range args {
+ switch v := arg.(type) {
+ case int:
+ if v != 0 {
+ p[key] = strconv.Itoa(v)
+ return nil
+ }
+ case int64:
+ if v != 0 {
+ p[key] = strconv.FormatInt(v, 10)
+ return nil
+ }
+ case string:
+ if v != "" {
+ p[key] = v
+ return nil
+ }
+ case nil:
+ default:
+ b, err := json.Marshal(arg)
+ if err != nil {
+ return err
+ }
+
+ p[key] = string(b)
+ return nil
+ }
+ }
+
+ return nil
+}
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/passport.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/passport.go
@@ -0,0 +1,317 @@
+package tgbotapi
+
+// PassportRequestInfoConfig allows you to request passport info
+type PassportRequestInfoConfig struct {
+ BotID int `json:"bot_id"`
+ Scope *PassportScope `json:"scope"`
+ Nonce string `json:"nonce"`
+ PublicKey string `json:"public_key"`
+}
+
+// PassportScopeElement supports using one or one of several elements.
+type PassportScopeElement interface {
+ ScopeType() string
+}
+
+// PassportScope is the requested scopes of data.
+type PassportScope struct {
+ V int `json:"v"`
+ Data []PassportScopeElement `json:"data"`
+}
+
+// PassportScopeElementOneOfSeveral allows you to request any one of the
+// requested documents.
+type PassportScopeElementOneOfSeveral struct {
+}
+
+// ScopeType is the scope type.
+func (eo *PassportScopeElementOneOfSeveral) ScopeType() string {
+ return "one_of"
+}
+
+// PassportScopeElementOne requires the specified element be provided.
+type PassportScopeElementOne struct {
+ Type string `json:"type"` // One of “personal_details”, “passport”, “driver_license”, “identity_card”, “internal_passport”, “address”, “utility_bill”, “bank_statement”, “rental_agreement”, “passport_registration”, “temporary_registration”, “phone_number”, “email”
+ Selfie bool `json:"selfie"`
+ Translation bool `json:"translation"`
+ NativeNames bool `json:"native_name"`
+}
+
+// ScopeType is the scope type.
+func (eo *PassportScopeElementOne) ScopeType() string {
+ return "one"
+}
+
+type (
+ // PassportData contains information about Telegram Passport data shared with
+ // the bot by the user.
+ PassportData struct {
+ // Array with information about documents and other Telegram Passport
+ // elements that was shared with the bot
+ Data []EncryptedPassportElement `json:"data"`
+
+ // Encrypted credentials required to decrypt the data
+ Credentials *EncryptedCredentials `json:"credentials"`
+ }
+
+ // PassportFile represents a file uploaded to Telegram Passport. Currently, all
+ // Telegram Passport files are in JPEG format when decrypted and don't exceed
+ // 10MB.
+ PassportFile struct {
+ // Unique identifier for this file
+ FileID string `json:"file_id"`
+
+ FileUniqueID string `json:"file_unique_id"`
+
+ // File size
+ FileSize int `json:"file_size"`
+
+ // Unix time when the file was uploaded
+ FileDate int64 `json:"file_date"`
+ }
+
+ // EncryptedPassportElement contains information about documents or other
+ // Telegram Passport elements shared with the bot by the user.
+ EncryptedPassportElement struct {
+ // Element type.
+ Type string `json:"type"`
+
+ // Base64-encoded encrypted Telegram Passport element data provided by
+ // the user, available for "personal_details", "passport",
+ // "driver_license", "identity_card", "identity_passport" and "address"
+ // types. Can be decrypted and verified using the accompanying
+ // EncryptedCredentials.
+ Data string `json:"data,omitempty"`
+
+ // User's verified phone number, available only for "phone_number" type
+ PhoneNumber string `json:"phone_number,omitempty"`
+
+ // User's verified email address, available only for "email" type
+ Email string `json:"email,omitempty"`
+
+ // Array of encrypted files with documents provided by the user,
+ // available for "utility_bill", "bank_statement", "rental_agreement",
+ // "passport_registration" and "temporary_registration" types. Files can
+ // be decrypted and verified using the accompanying EncryptedCredentials.
+ Files []PassportFile `json:"files,omitempty"`
+
+ // Encrypted file with the front side of the document, provided by the
+ // user. Available for "passport", "driver_license", "identity_card" and
+ // "internal_passport". The file can be decrypted and verified using the
+ // accompanying EncryptedCredentials.
+ FrontSide *PassportFile `json:"front_side,omitempty"`
+
+ // Encrypted file with the reverse side of the document, provided by the
+ // user. Available for "driver_license" and "identity_card". The file can
+ // be decrypted and verified using the accompanying EncryptedCredentials.
+ ReverseSide *PassportFile `json:"reverse_side,omitempty"`
+
+ // Encrypted file with the selfie of the user holding a document,
+ // provided by the user; available for "passport", "driver_license",
+ // "identity_card" and "internal_passport". The file can be decrypted
+ // and verified using the accompanying EncryptedCredentials.
+ Selfie *PassportFile `json:"selfie,omitempty"`
+ }
+
+ // EncryptedCredentials contains data required for decrypting and
+ // authenticating EncryptedPassportElement. See the Telegram Passport
+ // Documentation for a complete description of the data decryption and
+ // authentication processes.
+ EncryptedCredentials struct {
+ // Base64-encoded encrypted JSON-serialized data with unique user's
+ // payload, data hashes and secrets required for EncryptedPassportElement
+ // decryption and authentication
+ Data string `json:"data"`
+
+ // Base64-encoded data hash for data authentication
+ Hash string `json:"hash"`
+
+ // Base64-encoded secret, encrypted with the bot's public RSA key,
+ // required for data decryption
+ Secret string `json:"secret"`
+ }
+
+ // PassportElementError represents an error in the Telegram Passport element
+ // which was submitted that should be resolved by the user.
+ PassportElementError interface{}
+
+ // PassportElementErrorDataField represents an issue in one of the data
+ // fields that was provided by the user. The error is considered resolved
+ // when the field's value changes.
+ PassportElementErrorDataField struct {
+ // Error source, must be data
+ Source string `json:"source"`
+
+ // The section of the user's Telegram Passport which has the error, one
+ // of "personal_details", "passport", "driver_license", "identity_card",
+ // "internal_passport", "address"
+ Type string `json:"type"`
+
+ // Name of the data field which has the error
+ FieldName string `json:"field_name"`
+
+ // Base64-encoded data hash
+ DataHash string `json:"data_hash"`
+
+ // Error message
+ Message string `json:"message"`
+ }
+
+ // PassportElementErrorFrontSide represents an issue with the front side of
+ // a document. The error is considered resolved when the file with the front
+ // side of the document changes.
+ PassportElementErrorFrontSide struct {
+ // Error source, must be front_side
+ Source string `json:"source"`
+
+ // The section of the user's Telegram Passport which has the issue, one
+ // of "passport", "driver_license", "identity_card", "internal_passport"
+ Type string `json:"type"`
+
+ // Base64-encoded hash of the file with the front side of the document
+ FileHash string `json:"file_hash"`
+
+ // Error message
+ Message string `json:"message"`
+ }
+
+ // PassportElementErrorReverseSide represents an issue with the reverse side
+ // of a document. The error is considered resolved when the file with reverse
+ // side of the document changes.
+ PassportElementErrorReverseSide struct {
+ // Error source, must be reverse_side
+ Source string `json:"source"`
+
+ // The section of the user's Telegram Passport which has the issue, one
+ // of "driver_license", "identity_card"
+ Type string `json:"type"`
+
+ // Base64-encoded hash of the file with the reverse side of the document
+ FileHash string `json:"file_hash"`
+
+ // Error message
+ Message string `json:"message"`
+ }
+
+ // PassportElementErrorSelfie represents an issue with the selfie with a
+ // document. The error is considered resolved when the file with the selfie
+ // changes.
+ PassportElementErrorSelfie struct {
+ // Error source, must be selfie
+ Source string `json:"source"`
+
+ // The section of the user's Telegram Passport which has the issue, one
+ // of "passport", "driver_license", "identity_card", "internal_passport"
+ Type string `json:"type"`
+
+ // Base64-encoded hash of the file with the selfie
+ FileHash string `json:"file_hash"`
+
+ // Error message
+ Message string `json:"message"`
+ }
+
+ // PassportElementErrorFile represents an issue with a document scan. The
+ // error is considered resolved when the file with the document scan changes.
+ PassportElementErrorFile struct {
+ // Error source, must be a file
+ Source string `json:"source"`
+
+ // The section of the user's Telegram Passport which has the issue, one
+ // of "utility_bill", "bank_statement", "rental_agreement",
+ // "passport_registration", "temporary_registration"
+ Type string `json:"type"`
+
+ // Base64-encoded file hash
+ FileHash string `json:"file_hash"`
+
+ // Error message
+ Message string `json:"message"`
+ }
+
+ // PassportElementErrorFiles represents an issue with a list of scans. The
+ // error is considered resolved when the list of files containing the scans
+ // changes.
+ PassportElementErrorFiles struct {
+ // Error source, must be files
+ Source string `json:"source"`
+
+ // The section of the user's Telegram Passport which has the issue, one
+ // of "utility_bill", "bank_statement", "rental_agreement",
+ // "passport_registration", "temporary_registration"
+ Type string `json:"type"`
+
+ // List of base64-encoded file hashes
+ FileHashes []string `json:"file_hashes"`
+
+ // Error message
+ Message string `json:"message"`
+ }
+
+ // Credentials contains encrypted data.
+ Credentials struct {
+ Data SecureData `json:"secure_data"`
+ // Nonce the same nonce given in the request
+ Nonce string `json:"nonce"`
+ }
+
+ // SecureData is a map of the fields and their encrypted values.
+ SecureData map[string]*SecureValue
+ // PersonalDetails *SecureValue `json:"personal_details"`
+ // Passport *SecureValue `json:"passport"`
+ // InternalPassport *SecureValue `json:"internal_passport"`
+ // DriverLicense *SecureValue `json:"driver_license"`
+ // IdentityCard *SecureValue `json:"identity_card"`
+ // Address *SecureValue `json:"address"`
+ // UtilityBill *SecureValue `json:"utility_bill"`
+ // BankStatement *SecureValue `json:"bank_statement"`
+ // RentalAgreement *SecureValue `json:"rental_agreement"`
+ // PassportRegistration *SecureValue `json:"passport_registration"`
+ // TemporaryRegistration *SecureValue `json:"temporary_registration"`
+
+ // SecureValue contains encrypted values for a SecureData item.
+ SecureValue struct {
+ Data *DataCredentials `json:"data"`
+ FrontSide *FileCredentials `json:"front_side"`
+ ReverseSide *FileCredentials `json:"reverse_side"`
+ Selfie *FileCredentials `json:"selfie"`
+ Translation []*FileCredentials `json:"translation"`
+ Files []*FileCredentials `json:"files"`
+ }
+
+ // DataCredentials contains information required to decrypt data.
+ DataCredentials struct {
+ // DataHash checksum of encrypted data
+ DataHash string `json:"data_hash"`
+ // Secret of encrypted data
+ Secret string `json:"secret"`
+ }
+
+ // FileCredentials contains information required to decrypt files.
+ FileCredentials struct {
+ // FileHash checksum of encrypted data
+ FileHash string `json:"file_hash"`
+ // Secret of encrypted data
+ Secret string `json:"secret"`
+ }
+
+ // PersonalDetails https://core.telegram.org/passport#personaldetails
+ PersonalDetails struct {
+ FirstName string `json:"first_name"`
+ LastName string `json:"last_name"`
+ MiddleName string `json:"middle_name"`
+ BirthDate string `json:"birth_date"`
+ Gender string `json:"gender"`
+ CountryCode string `json:"country_code"`
+ ResidenceCountryCode string `json:"residence_country_code"`
+ FirstNameNative string `json:"first_name_native"`
+ LastNameNative string `json:"last_name_native"`
+ MiddleNameNative string `json:"middle_name_native"`
+ }
+
+ // IDDocumentData https://core.telegram.org/passport#iddocumentdata
+ IDDocumentData struct {
+ DocumentNumber string `json:"document_no"`
+ ExpiryDate string `json:"expiry_date"`
+ }
+)
diff --git a/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/types.go b/vendor/github.com/go-telegram-bot-api/telegram-bot-api/v5/types.go
@@ -0,0 +1,3225 @@
+package tgbotapi
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+)
+
+// APIResponse is a response from the Telegram API with the result
+// stored raw.
+type APIResponse struct {
+ Ok bool `json:"ok"`
+ Result json.RawMessage `json:"result,omitempty"`
+ ErrorCode int `json:"error_code,omitempty"`
+ Description string `json:"description,omitempty"`
+ Parameters *ResponseParameters `json:"parameters,omitempty"`
+}
+
+// Error is an error containing extra information returned by the Telegram API.
+type Error struct {
+ Code int
+ Message string
+ ResponseParameters
+}
+
+// Error message string.
+func (e Error) Error() string {
+ return e.Message
+}
+
+// Update is an update response, from GetUpdates.
+type Update struct {
+ // UpdateID is the update's unique identifier.
+ // Update identifiers start from a certain positive number and increase
+ // sequentially.
+ // This ID becomes especially handy if you're using Webhooks,
+ // since it allows you to ignore repeated updates or to restore
+ // the correct update sequence, should they get out of order.
+ // If there are no new updates for at least a week, then identifier
+ // of the next update will be chosen randomly instead of sequentially.
+ UpdateID int `json:"update_id"`
+ // Message new incoming message of any kind — text, photo, sticker, etc.
+ //
+ // optional
+ Message *Message `json:"message,omitempty"`
+ // EditedMessage new version of a message that is known to the bot and was
+ // edited
+ //
+ // optional
+ EditedMessage *Message `json:"edited_message,omitempty"`
+ // ChannelPost new version of a message that is known to the bot and was
+ // edited
+ //
+ // optional
+ ChannelPost *Message `json:"channel_post,omitempty"`
+ // EditedChannelPost new incoming channel post of any kind — text, photo,
+ // sticker, etc.
+ //
+ // optional
+ EditedChannelPost *Message `json:"edited_channel_post,omitempty"`
+ // InlineQuery new incoming inline query
+ //
+ // optional
+ InlineQuery *InlineQuery `json:"inline_query,omitempty"`
+ // ChosenInlineResult is the result of an inline query
+ // that was chosen by a user and sent to their chat partner.
+ // Please see our documentation on the feedback collecting
+ // for details on how to enable these updates for your bot.
+ //
+ // optional
+ ChosenInlineResult *ChosenInlineResult `json:"chosen_inline_result,omitempty"`
+ // CallbackQuery new incoming callback query
+ //
+ // optional
+ CallbackQuery *CallbackQuery `json:"callback_query,omitempty"`
+ // ShippingQuery new incoming shipping query. Only for invoices with
+ // flexible price
+ //
+ // optional
+ ShippingQuery *ShippingQuery `json:"shipping_query,omitempty"`
+ // PreCheckoutQuery new incoming pre-checkout query. Contains full
+ // information about checkout
+ //
+ // optional
+ PreCheckoutQuery *PreCheckoutQuery `json:"pre_checkout_query,omitempty"`
+ // Pool new poll state. Bots receive only updates about stopped polls and
+ // polls, which are sent by the bot
+ //
+ // optional
+ Poll *Poll `json:"poll,omitempty"`
+ // PollAnswer user changed their answer in a non-anonymous poll. Bots
+ // receive new votes only in polls that were sent by the bot itself.
+ //
+ // optional
+ PollAnswer *PollAnswer `json:"poll_answer,omitempty"`
+ // MyChatMember is the bot's chat member status was updated in a chat. For
+ // private chats, this update is received only when the bot is blocked or
+ // unblocked by the user.
+ //
+ // optional
+ MyChatMember *ChatMemberUpdated `json:"my_chat_member"`
+ // ChatMember is a chat member's status was updated in a chat. The bot must
+ // be an administrator in the chat and must explicitly specify "chat_member"
+ // in the list of allowed_updates to receive these updates.
+ //
+ // optional
+ ChatMember *ChatMemberUpdated `json:"chat_member"`
+ // ChatJoinRequest is a request to join the chat has been sent. The bot must
+ // have the can_invite_users administrator right in the chat to receive
+ // these updates.
+ //
+ // optional
+ ChatJoinRequest *ChatJoinRequest `json:"chat_join_request"`
+}
+
+// SentFrom returns the user who sent an update. Can be nil, if Telegram did not provide information
+// about the user in the update object.
+func (u *Update) SentFrom() *User {
+ switch {
+ case u.Message != nil:
+ return u.Message.From
+ case u.EditedMessage != nil:
+ return u.EditedMessage.From
+ case u.InlineQuery != nil:
+ return u.InlineQuery.From
+ case u.ChosenInlineResult != nil:
+ return u.ChosenInlineResult.From
+ case u.CallbackQuery != nil:
+ return u.CallbackQuery.From
+ case u.ShippingQuery != nil:
+ return u.ShippingQuery.From
+ case u.PreCheckoutQuery != nil:
+ return u.PreCheckoutQuery.From
+ default:
+ return nil
+ }
+}
+
+// CallbackData returns the callback query data, if it exists.
+func (u *Update) CallbackData() string {
+ if u.CallbackQuery != nil {
+ return u.CallbackQuery.Data
+ }
+ return ""
+}
+
+// FromChat returns the chat where an update occurred.
+func (u *Update) FromChat() *Chat {
+ switch {
+ case u.Message != nil:
+ return u.Message.Chat
+ case u.EditedMessage != nil:
+ return u.EditedMessage.Chat
+ case u.ChannelPost != nil:
+ return u.ChannelPost.Chat
+ case u.EditedChannelPost != nil:
+ return u.EditedChannelPost.Chat
+ case u.CallbackQuery != nil:
+ return u.CallbackQuery.Message.Chat
+ default:
+ return nil
+ }
+}
+
+// UpdatesChannel is the channel for getting updates.
+type UpdatesChannel <-chan Update
+
+// Clear discards all unprocessed incoming updates.
+func (ch UpdatesChannel) Clear() {
+ for len(ch) != 0 {
+ <-ch
+ }
+}
+
+// User represents a Telegram user or bot.
+type User struct {
+ // ID is a unique identifier for this user or bot
+ ID int64 `json:"id"`
+ // IsBot true, if this user is a bot
+ //
+ // optional
+ IsBot bool `json:"is_bot,omitempty"`
+ // FirstName user's or bot's first name
+ FirstName string `json:"first_name"`
+ // LastName user's or bot's last name
+ //
+ // optional
+ LastName string `json:"last_name,omitempty"`
+ // UserName user's or bot's username
+ //
+ // optional
+ UserName string `json:"username,omitempty"`
+ // LanguageCode IETF language tag of the user's language
+ // more info: https://en.wikipedia.org/wiki/IETF_language_tag
+ //
+ // optional
+ LanguageCode string `json:"language_code,omitempty"`
+ // CanJoinGroups is true, if the bot can be invited to groups.
+ // Returned only in getMe.
+ //
+ // optional
+ CanJoinGroups bool `json:"can_join_groups,omitempty"`
+ // CanReadAllGroupMessages is true, if privacy mode is disabled for the bot.
+ // Returned only in getMe.
+ //
+ // optional
+ CanReadAllGroupMessages bool `json:"can_read_all_group_messages,omitempty"`
+ // SupportsInlineQueries is true, if the bot supports inline queries.
+ // Returned only in getMe.
+ //
+ // optional
+ SupportsInlineQueries bool `json:"supports_inline_queries,omitempty"`
+}
+
+// String displays a simple text version of a user.
+//
+// It is normally a user's username, but falls back to a first/last
+// name as available.
+func (u *User) String() string {
+ if u == nil {
+ return ""
+ }
+ if u.UserName != "" {
+ return u.UserName
+ }
+
+ name := u.FirstName
+ if u.LastName != "" {
+ name += " " + u.LastName
+ }
+
+ return name
+}
+
+// Chat represents a chat.
+type Chat struct {
+ // ID is a unique identifier for this chat
+ ID int64 `json:"id"`
+ // Type of chat, can be either “private”, “group”, “supergroup” or “channel”
+ Type string `json:"type"`
+ // Title for supergroups, channels and group chats
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // UserName for private chats, supergroups and channels if available
+ //
+ // optional
+ UserName string `json:"username,omitempty"`
+ // FirstName of the other party in a private chat
+ //
+ // optional
+ FirstName string `json:"first_name,omitempty"`
+ // LastName of the other party in a private chat
+ //
+ // optional
+ LastName string `json:"last_name,omitempty"`
+ // Photo is a chat photo
+ Photo *ChatPhoto `json:"photo"`
+ // Bio is the bio of the other party in a private chat. Returned only in
+ // getChat
+ //
+ // optional
+ Bio string `json:"bio,omitempty"`
+ // HasPrivateForwards is true if privacy settings of the other party in the
+ // private chat allows to use tg://user?id=<user_id> links only in chats
+ // with the user. Returned only in getChat.
+ //
+ // optional
+ HasPrivateForwards bool `json:"has_private_forwards,omitempty"`
+ // Description for groups, supergroups and channel chats
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // InviteLink is a chat invite link, for groups, supergroups and channel chats.
+ // Each administrator in a chat generates their own invite links,
+ // so the bot must first generate the link using exportChatInviteLink
+ //
+ // optional
+ InviteLink string `json:"invite_link,omitempty"`
+ // PinnedMessage is the pinned message, for groups, supergroups and channels
+ //
+ // optional
+ PinnedMessage *Message `json:"pinned_message,omitempty"`
+ // Permissions are default chat member permissions, for groups and
+ // supergroups. Returned only in getChat.
+ //
+ // optional
+ Permissions *ChatPermissions `json:"permissions,omitempty"`
+ // SlowModeDelay is for supergroups, the minimum allowed delay between
+ // consecutive messages sent by each unpriviledged user. Returned only in
+ // getChat.
+ //
+ // optional
+ SlowModeDelay int `json:"slow_mode_delay,omitempty"`
+ // MessageAutoDeleteTime is the time after which all messages sent to the
+ // chat will be automatically deleted; in seconds. Returned only in getChat.
+ //
+ // optional
+ MessageAutoDeleteTime int `json:"message_auto_delete_time,omitempty"`
+ // HasProtectedContent is true if messages from the chat can't be forwarded
+ // to other chats. Returned only in getChat.
+ //
+ // optional
+ HasProtectedContent bool `json:"has_protected_content,omitempty"`
+ // StickerSetName is for supergroups, name of group sticker set.Returned
+ // only in getChat.
+ //
+ // optional
+ StickerSetName string `json:"sticker_set_name,omitempty"`
+ // CanSetStickerSet is true, if the bot can change the group sticker set.
+ // Returned only in getChat.
+ //
+ // optional
+ CanSetStickerSet bool `json:"can_set_sticker_set,omitempty"`
+ // LinkedChatID is a unique identifier for the linked chat, i.e. the
+ // discussion group identifier for a channel and vice versa; for supergroups
+ // and channel chats.
+ //
+ // optional
+ LinkedChatID int64 `json:"linked_chat_id,omitempty"`
+ // Location is for supergroups, the location to which the supergroup is
+ // connected. Returned only in getChat.
+ //
+ // optional
+ Location *ChatLocation `json:"location"`
+}
+
+// IsPrivate returns if the Chat is a private conversation.
+func (c Chat) IsPrivate() bool {
+ return c.Type == "private"
+}
+
+// IsGroup returns if the Chat is a group.
+func (c Chat) IsGroup() bool {
+ return c.Type == "group"
+}
+
+// IsSuperGroup returns if the Chat is a supergroup.
+func (c Chat) IsSuperGroup() bool {
+ return c.Type == "supergroup"
+}
+
+// IsChannel returns if the Chat is a channel.
+func (c Chat) IsChannel() bool {
+ return c.Type == "channel"
+}
+
+// ChatConfig returns a ChatConfig struct for chat related methods.
+func (c Chat) ChatConfig() ChatConfig {
+ return ChatConfig{ChatID: c.ID}
+}
+
+// Message represents a message.
+type Message struct {
+ // MessageID is a unique message identifier inside this chat
+ MessageID int `json:"message_id"`
+ // From is a sender, empty for messages sent to channels;
+ //
+ // optional
+ From *User `json:"from,omitempty"`
+ // SenderChat is the sender of the message, sent on behalf of a chat. The
+ // channel itself for channel messages. The supergroup itself for messages
+ // from anonymous group administrators. The linked channel for messages
+ // automatically forwarded to the discussion group
+ //
+ // optional
+ SenderChat *Chat `json:"sender_chat,omitempty"`
+ // Date of the message was sent in Unix time
+ Date int `json:"date"`
+ // Chat is the conversation the message belongs to
+ Chat *Chat `json:"chat"`
+ // ForwardFrom for forwarded messages, sender of the original message;
+ //
+ // optional
+ ForwardFrom *User `json:"forward_from,omitempty"`
+ // ForwardFromChat for messages forwarded from channels,
+ // information about the original channel;
+ //
+ // optional
+ ForwardFromChat *Chat `json:"forward_from_chat,omitempty"`
+ // ForwardFromMessageID for messages forwarded from channels,
+ // identifier of the original message in the channel;
+ //
+ // optional
+ ForwardFromMessageID int `json:"forward_from_message_id,omitempty"`
+ // ForwardSignature for messages forwarded from channels, signature of the
+ // post author if present
+ //
+ // optional
+ ForwardSignature string `json:"forward_signature,omitempty"`
+ // ForwardSenderName is the sender's name for messages forwarded from users
+ // who disallow adding a link to their account in forwarded messages
+ //
+ // optional
+ ForwardSenderName string `json:"forward_sender_name,omitempty"`
+ // ForwardDate for forwarded messages, date the original message was sent in Unix time;
+ //
+ // optional
+ ForwardDate int `json:"forward_date,omitempty"`
+ // IsAutomaticForward is true if the message is a channel post that was
+ // automatically forwarded to the connected discussion group.
+ //
+ // optional
+ IsAutomaticForward bool `json:"is_automatic_forward,omitempty"`
+ // ReplyToMessage for replies, the original message.
+ // Note that the Message object in this field will not contain further ReplyToMessage fields
+ // even if it itself is a reply;
+ //
+ // optional
+ ReplyToMessage *Message `json:"reply_to_message,omitempty"`
+ // ViaBot through which the message was sent;
+ //
+ // optional
+ ViaBot *User `json:"via_bot,omitempty"`
+ // EditDate of the message was last edited in Unix time;
+ //
+ // optional
+ EditDate int `json:"edit_date,omitempty"`
+ // HasProtectedContent is true if the message can't be forwarded.
+ //
+ // optional
+ HasProtectedContent bool `json:"has_protected_content,omitempty"`
+ // MediaGroupID is the unique identifier of a media message group this message belongs to;
+ //
+ // optional
+ MediaGroupID string `json:"media_group_id,omitempty"`
+ // AuthorSignature is the signature of the post author for messages in channels;
+ //
+ // optional
+ AuthorSignature string `json:"author_signature,omitempty"`
+ // Text is for text messages, the actual UTF-8 text of the message, 0-4096 characters;
+ //
+ // optional
+ Text string `json:"text,omitempty"`
+ // Entities are for text messages, special entities like usernames,
+ // URLs, bot commands, etc. that appear in the text;
+ //
+ // optional
+ Entities []MessageEntity `json:"entities,omitempty"`
+ // Animation message is an animation, information about the animation.
+ // For backward compatibility, when this field is set, the document field will also be set;
+ //
+ // optional
+ Animation *Animation `json:"animation,omitempty"`
+ // Audio message is an audio file, information about the file;
+ //
+ // optional
+ Audio *Audio `json:"audio,omitempty"`
+ // Document message is a general file, information about the file;
+ //
+ // optional
+ Document *Document `json:"document,omitempty"`
+ // Photo message is a photo, available sizes of the photo;
+ //
+ // optional
+ Photo []PhotoSize `json:"photo,omitempty"`
+ // Sticker message is a sticker, information about the sticker;
+ //
+ // optional
+ Sticker *Sticker `json:"sticker,omitempty"`
+ // Video message is a video, information about the video;
+ //
+ // optional
+ Video *Video `json:"video,omitempty"`
+ // VideoNote message is a video note, information about the video message;
+ //
+ // optional
+ VideoNote *VideoNote `json:"video_note,omitempty"`
+ // Voice message is a voice message, information about the file;
+ //
+ // optional
+ Voice *Voice `json:"voice,omitempty"`
+ // Caption for the animation, audio, document, photo, video or voice, 0-1024 characters;
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // CaptionEntities;
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // Contact message is a shared contact, information about the contact;
+ //
+ // optional
+ Contact *Contact `json:"contact,omitempty"`
+ // Dice is a dice with random value;
+ //
+ // optional
+ Dice *Dice `json:"dice,omitempty"`
+ // Game message is a game, information about the game;
+ //
+ // optional
+ Game *Game `json:"game,omitempty"`
+ // Poll is a native poll, information about the poll;
+ //
+ // optional
+ Poll *Poll `json:"poll,omitempty"`
+ // Venue message is a venue, information about the venue.
+ // For backward compatibility, when this field is set, the location field
+ // will also be set;
+ //
+ // optional
+ Venue *Venue `json:"venue,omitempty"`
+ // Location message is a shared location, information about the location;
+ //
+ // optional
+ Location *Location `json:"location,omitempty"`
+ // NewChatMembers that were added to the group or supergroup
+ // and information about them (the bot itself may be one of these members);
+ //
+ // optional
+ NewChatMembers []User `json:"new_chat_members,omitempty"`
+ // LeftChatMember is a member was removed from the group,
+ // information about them (this member may be the bot itself);
+ //
+ // optional
+ LeftChatMember *User `json:"left_chat_member,omitempty"`
+ // NewChatTitle is a chat title was changed to this value;
+ //
+ // optional
+ NewChatTitle string `json:"new_chat_title,omitempty"`
+ // NewChatPhoto is a chat photo was change to this value;
+ //
+ // optional
+ NewChatPhoto []PhotoSize `json:"new_chat_photo,omitempty"`
+ // DeleteChatPhoto is a service message: the chat photo was deleted;
+ //
+ // optional
+ DeleteChatPhoto bool `json:"delete_chat_photo,omitempty"`
+ // GroupChatCreated is a service message: the group has been created;
+ //
+ // optional
+ GroupChatCreated bool `json:"group_chat_created,omitempty"`
+ // SuperGroupChatCreated is a service message: the supergroup has been created.
+ // This field can't be received in a message coming through updates,
+ // because bot can't be a member of a supergroup when it is created.
+ // It can only be found in ReplyToMessage if someone replies to a very first message
+ // in a directly created supergroup;
+ //
+ // optional
+ SuperGroupChatCreated bool `json:"supergroup_chat_created,omitempty"`
+ // ChannelChatCreated is a service message: the channel has been created.
+ // This field can't be received in a message coming through updates,
+ // because bot can't be a member of a channel when it is created.
+ // It can only be found in ReplyToMessage
+ // if someone replies to a very first message in a channel;
+ //
+ // optional
+ ChannelChatCreated bool `json:"channel_chat_created,omitempty"`
+ // MessageAutoDeleteTimerChanged is a service message: auto-delete timer
+ // settings changed in the chat.
+ //
+ // optional
+ MessageAutoDeleteTimerChanged *MessageAutoDeleteTimerChanged `json:"message_auto_delete_timer_changed"`
+ // MigrateToChatID is the group has been migrated to a supergroup with the specified identifier.
+ // This number may be greater than 32 bits and some programming languages
+ // may have difficulty/silent defects in interpreting it.
+ // But it is smaller than 52 bits, so a signed 64-bit integer
+ // or double-precision float type are safe for storing this identifier;
+ //
+ // optional
+ MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
+ // MigrateFromChatID is the supergroup has been migrated from a group with the specified identifier.
+ // This number may be greater than 32 bits and some programming languages
+ // may have difficulty/silent defects in interpreting it.
+ // But it is smaller than 52 bits, so a signed 64-bit integer
+ // or double-precision float type are safe for storing this identifier;
+ //
+ // optional
+ MigrateFromChatID int64 `json:"migrate_from_chat_id,omitempty"`
+ // PinnedMessage is a specified message was pinned.
+ // Note that the Message object in this field will not contain further ReplyToMessage
+ // fields even if it is itself a reply;
+ //
+ // optional
+ PinnedMessage *Message `json:"pinned_message,omitempty"`
+ // Invoice message is an invoice for a payment;
+ //
+ // optional
+ Invoice *Invoice `json:"invoice,omitempty"`
+ // SuccessfulPayment message is a service message about a successful payment,
+ // information about the payment;
+ //
+ // optional
+ SuccessfulPayment *SuccessfulPayment `json:"successful_payment,omitempty"`
+ // ConnectedWebsite is the domain name of the website on which the user has
+ // logged in;
+ //
+ // optional
+ ConnectedWebsite string `json:"connected_website,omitempty"`
+ // PassportData is a Telegram Passport data;
+ //
+ // optional
+ PassportData *PassportData `json:"passport_data,omitempty"`
+ // ProximityAlertTriggered is a service message. A user in the chat
+ // triggered another user's proximity alert while sharing Live Location
+ //
+ // optional
+ ProximityAlertTriggered *ProximityAlertTriggered `json:"proximity_alert_triggered"`
+ // VoiceChatScheduled is a service message: voice chat scheduled.
+ //
+ // optional
+ VoiceChatScheduled *VoiceChatScheduled `json:"voice_chat_scheduled"`
+ // VoiceChatStarted is a service message: voice chat started.
+ //
+ // optional
+ VoiceChatStarted *VoiceChatStarted `json:"voice_chat_started"`
+ // VoiceChatEnded is a service message: voice chat ended.
+ //
+ // optional
+ VoiceChatEnded *VoiceChatEnded `json:"voice_chat_ended"`
+ // VoiceChatParticipantsInvited is a service message: new participants
+ // invited to a voice chat.
+ //
+ // optional
+ VoiceChatParticipantsInvited *VoiceChatParticipantsInvited `json:"voice_chat_participants_invited"`
+ // ReplyMarkup is the Inline keyboard attached to the message.
+ // login_url buttons are represented as ordinary url buttons.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+}
+
+// Time converts the message timestamp into a Time.
+func (m *Message) Time() time.Time {
+ return time.Unix(int64(m.Date), 0)
+}
+
+// IsCommand returns true if message starts with a "bot_command" entity.
+func (m *Message) IsCommand() bool {
+ if m.Entities == nil || len(m.Entities) == 0 {
+ return false
+ }
+
+ entity := m.Entities[0]
+ return entity.Offset == 0 && entity.IsCommand()
+}
+
+// Command checks if the message was a command and if it was, returns the
+// command. If the Message was not a command, it returns an empty string.
+//
+// If the command contains the at name syntax, it is removed. Use
+// CommandWithAt() if you do not want that.
+func (m *Message) Command() string {
+ command := m.CommandWithAt()
+
+ if i := strings.Index(command, "@"); i != -1 {
+ command = command[:i]
+ }
+
+ return command
+}
+
+// CommandWithAt checks if the message was a command and if it was, returns the
+// command. If the Message was not a command, it returns an empty string.
+//
+// If the command contains the at name syntax, it is not removed. Use Command()
+// if you want that.
+func (m *Message) CommandWithAt() string {
+ if !m.IsCommand() {
+ return ""
+ }
+
+ // IsCommand() checks that the message begins with a bot_command entity
+ entity := m.Entities[0]
+ return m.Text[1:entity.Length]
+}
+
+// CommandArguments checks if the message was a command and if it was,
+// returns all text after the command name. If the Message was not a
+// command, it returns an empty string.
+//
+// Note: The first character after the command name is omitted:
+// - "/foo bar baz" yields "bar baz", not " bar baz"
+// - "/foo-bar baz" yields "bar baz", too
+// Even though the latter is not a command conforming to the spec, the API
+// marks "/foo" as command entity.
+func (m *Message) CommandArguments() string {
+ if !m.IsCommand() {
+ return ""
+ }
+
+ // IsCommand() checks that the message begins with a bot_command entity
+ entity := m.Entities[0]
+
+ if len(m.Text) == entity.Length {
+ return "" // The command makes up the whole message
+ }
+
+ return m.Text[entity.Length+1:]
+}
+
+// MessageID represents a unique message identifier.
+type MessageID struct {
+ MessageID int `json:"message_id"`
+}
+
+// MessageEntity represents one special entity in a text message.
+type MessageEntity struct {
+ // Type of the entity.
+ // Can be:
+ // “mention” (@username),
+ // “hashtag” (#hashtag),
+ // “cashtag” ($USD),
+ // “bot_command” (/start@jobs_bot),
+ // “url” (https://telegram.org),
+ // “email” (do-not-reply@telegram.org),
+ // “phone_number” (+1-212-555-0123),
+ // “bold” (bold text),
+ // “italic” (italic text),
+ // “underline” (underlined text),
+ // “strikethrough” (strikethrough text),
+ // “code” (monowidth string),
+ // “pre” (monowidth block),
+ // “text_link” (for clickable text URLs),
+ // “text_mention” (for users without usernames)
+ Type string `json:"type"`
+ // Offset in UTF-16 code units to the start of the entity
+ Offset int `json:"offset"`
+ // Length
+ Length int `json:"length"`
+ // URL for “text_link” only, url that will be opened after user taps on the text
+ //
+ // optional
+ URL string `json:"url,omitempty"`
+ // User for “text_mention” only, the mentioned user
+ //
+ // optional
+ User *User `json:"user,omitempty"`
+ // Language for “pre” only, the programming language of the entity text
+ //
+ // optional
+ Language string `json:"language,omitempty"`
+}
+
+// ParseURL attempts to parse a URL contained within a MessageEntity.
+func (e MessageEntity) ParseURL() (*url.URL, error) {
+ if e.URL == "" {
+ return nil, errors.New(ErrBadURL)
+ }
+
+ return url.Parse(e.URL)
+}
+
+// IsMention returns true if the type of the message entity is "mention" (@username).
+func (e MessageEntity) IsMention() bool {
+ return e.Type == "mention"
+}
+
+// IsHashtag returns true if the type of the message entity is "hashtag".
+func (e MessageEntity) IsHashtag() bool {
+ return e.Type == "hashtag"
+}
+
+// IsCommand returns true if the type of the message entity is "bot_command".
+func (e MessageEntity) IsCommand() bool {
+ return e.Type == "bot_command"
+}
+
+// IsURL returns true if the type of the message entity is "url".
+func (e MessageEntity) IsURL() bool {
+ return e.Type == "url"
+}
+
+// IsEmail returns true if the type of the message entity is "email".
+func (e MessageEntity) IsEmail() bool {
+ return e.Type == "email"
+}
+
+// IsBold returns true if the type of the message entity is "bold" (bold text).
+func (e MessageEntity) IsBold() bool {
+ return e.Type == "bold"
+}
+
+// IsItalic returns true if the type of the message entity is "italic" (italic text).
+func (e MessageEntity) IsItalic() bool {
+ return e.Type == "italic"
+}
+
+// IsCode returns true if the type of the message entity is "code" (monowidth string).
+func (e MessageEntity) IsCode() bool {
+ return e.Type == "code"
+}
+
+// IsPre returns true if the type of the message entity is "pre" (monowidth block).
+func (e MessageEntity) IsPre() bool {
+ return e.Type == "pre"
+}
+
+// IsTextLink returns true if the type of the message entity is "text_link" (clickable text URL).
+func (e MessageEntity) IsTextLink() bool {
+ return e.Type == "text_link"
+}
+
+// PhotoSize represents one size of a photo or a file / sticker thumbnail.
+type PhotoSize struct {
+ // FileID identifier for this file, which can be used to download or reuse
+ // the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Width photo width
+ Width int `json:"width"`
+ // Height photo height
+ Height int `json:"height"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// Animation represents an animation file.
+type Animation struct {
+ // FileID is the identifier for this file, which can be used to download or reuse
+ // the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Width video width as defined by sender
+ Width int `json:"width"`
+ // Height video height as defined by sender
+ Height int `json:"height"`
+ // Duration of the video in seconds as defined by sender
+ Duration int `json:"duration"`
+ // Thumbnail animation thumbnail as defined by sender
+ //
+ // optional
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileName original animation filename as defined by sender
+ //
+ // optional
+ FileName string `json:"file_name,omitempty"`
+ // MimeType of the file as defined by sender
+ //
+ // optional
+ MimeType string `json:"mime_type,omitempty"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// Audio represents an audio file to be treated as music by the Telegram clients.
+type Audio struct {
+ // FileID is an identifier for this file, which can be used to download or
+ // reuse the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Duration of the audio in seconds as defined by sender
+ Duration int `json:"duration"`
+ // Performer of the audio as defined by sender or by audio tags
+ //
+ // optional
+ Performer string `json:"performer,omitempty"`
+ // Title of the audio as defined by sender or by audio tags
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // FileName is the original filename as defined by sender
+ //
+ // optional
+ FileName string `json:"file_name,omitempty"`
+ // MimeType of the file as defined by sender
+ //
+ // optional
+ MimeType string `json:"mime_type,omitempty"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+ // Thumbnail is the album cover to which the music file belongs
+ //
+ // optional
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+}
+
+// Document represents a general file.
+type Document struct {
+ // FileID is an identifier for this file, which can be used to download or
+ // reuse the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Thumbnail document thumbnail as defined by sender
+ //
+ // optional
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileName original filename as defined by sender
+ //
+ // optional
+ FileName string `json:"file_name,omitempty"`
+ // MimeType of the file as defined by sender
+ //
+ // optional
+ MimeType string `json:"mime_type,omitempty"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// Video represents a video file.
+type Video struct {
+ // FileID identifier for this file, which can be used to download or reuse
+ // the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Width video width as defined by sender
+ Width int `json:"width"`
+ // Height video height as defined by sender
+ Height int `json:"height"`
+ // Duration of the video in seconds as defined by sender
+ Duration int `json:"duration"`
+ // Thumbnail video thumbnail
+ //
+ // optional
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileName is the original filename as defined by sender
+ //
+ // optional
+ FileName string `json:"file_name,omitempty"`
+ // MimeType of a file as defined by sender
+ //
+ // optional
+ MimeType string `json:"mime_type,omitempty"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// VideoNote object represents a video message.
+type VideoNote struct {
+ // FileID identifier for this file, which can be used to download or reuse the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Length video width and height (diameter of the video message) as defined by sender
+ Length int `json:"length"`
+ // Duration of the video in seconds as defined by sender
+ Duration int `json:"duration"`
+ // Thumbnail video thumbnail
+ //
+ // optional
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// Voice represents a voice note.
+type Voice struct {
+ // FileID identifier for this file, which can be used to download or reuse the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Duration of the audio in seconds as defined by sender
+ Duration int `json:"duration"`
+ // MimeType of the file as defined by sender
+ //
+ // optional
+ MimeType string `json:"mime_type,omitempty"`
+ // FileSize file size
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// Contact represents a phone contact.
+//
+// Note that LastName and UserID may be empty.
+type Contact struct {
+ // PhoneNumber contact's phone number
+ PhoneNumber string `json:"phone_number"`
+ // FirstName contact's first name
+ FirstName string `json:"first_name"`
+ // LastName contact's last name
+ //
+ // optional
+ LastName string `json:"last_name,omitempty"`
+ // UserID contact's user identifier in Telegram
+ //
+ // optional
+ UserID int64 `json:"user_id,omitempty"`
+ // VCard is additional data about the contact in the form of a vCard.
+ //
+ // optional
+ VCard string `json:"vcard,omitempty"`
+}
+
+// Dice represents an animated emoji that displays a random value.
+type Dice struct {
+ // Emoji on which the dice throw animation is based
+ Emoji string `json:"emoji"`
+ // Value of the dice
+ Value int `json:"value"`
+}
+
+// PollOption contains information about one answer option in a poll.
+type PollOption struct {
+ // Text is the option text, 1-100 characters
+ Text string `json:"text"`
+ // VoterCount is the number of users that voted for this option
+ VoterCount int `json:"voter_count"`
+}
+
+// PollAnswer represents an answer of a user in a non-anonymous poll.
+type PollAnswer struct {
+ // PollID is the unique poll identifier
+ PollID string `json:"poll_id"`
+ // User who changed the answer to the poll
+ User User `json:"user"`
+ // OptionIDs is the 0-based identifiers of poll options chosen by the user.
+ // May be empty if user retracted vote.
+ OptionIDs []int `json:"option_ids"`
+}
+
+// Poll contains information about a poll.
+type Poll struct {
+ // ID is the unique poll identifier
+ ID string `json:"id"`
+ // Question is the poll question, 1-255 characters
+ Question string `json:"question"`
+ // Options is the list of poll options
+ Options []PollOption `json:"options"`
+ // TotalVoterCount is the total numbers of users who voted in the poll
+ TotalVoterCount int `json:"total_voter_count"`
+ // IsClosed is if the poll is closed
+ IsClosed bool `json:"is_closed"`
+ // IsAnonymous is if the poll is anonymous
+ IsAnonymous bool `json:"is_anonymous"`
+ // Type is the poll type, currently can be "regular" or "quiz"
+ Type string `json:"type"`
+ // AllowsMultipleAnswers is true, if the poll allows multiple answers
+ AllowsMultipleAnswers bool `json:"allows_multiple_answers"`
+ // CorrectOptionID is the 0-based identifier of the correct answer option.
+ // Available only for polls in quiz mode, which are closed, or was sent (not
+ // forwarded) by the bot or to the private chat with the bot.
+ //
+ // optional
+ CorrectOptionID int `json:"correct_option_id,omitempty"`
+ // Explanation is text that is shown when a user chooses an incorrect answer
+ // or taps on the lamp icon in a quiz-style poll, 0-200 characters
+ //
+ // optional
+ Explanation string `json:"explanation,omitempty"`
+ // ExplanationEntities are special entities like usernames, URLs, bot
+ // commands, etc. that appear in the explanation
+ //
+ // optional
+ ExplanationEntities []MessageEntity `json:"explanation_entities,omitempty"`
+ // OpenPeriod is the amount of time in seconds the poll will be active
+ // after creation
+ //
+ // optional
+ OpenPeriod int `json:"open_period,omitempty"`
+ // CloseDate is the point in time (unix timestamp) when the poll will be
+ // automatically closed
+ //
+ // optional
+ CloseDate int `json:"close_date,omitempty"`
+}
+
+// Location represents a point on the map.
+type Location struct {
+ // Longitude as defined by sender
+ Longitude float64 `json:"longitude"`
+ // Latitude as defined by sender
+ Latitude float64 `json:"latitude"`
+ // HorizontalAccuracy is the radius of uncertainty for the location,
+ // measured in meters; 0-1500
+ //
+ // optional
+ HorizontalAccuracy float64 `json:"horizontal_accuracy,omitempty"`
+ // LivePeriod is time relative to the message sending date, during which the
+ // location can be updated, in seconds. For active live locations only.
+ //
+ // optional
+ LivePeriod int `json:"live_period,omitempty"`
+ // Heading is the direction in which user is moving, in degrees; 1-360. For
+ // active live locations only.
+ //
+ // optional
+ Heading int `json:"heading,omitempty"`
+ // ProximityAlertRadius is the maximum distance for proximity alerts about
+ // approaching another chat member, in meters. For sent live locations only.
+ //
+ // optional
+ ProximityAlertRadius int `json:"proximity_alert_radius,omitempty"`
+}
+
+// Venue represents a venue.
+type Venue struct {
+ // Location is the venue location
+ Location Location `json:"location"`
+ // Title is the name of the venue
+ Title string `json:"title"`
+ // Address of the venue
+ Address string `json:"address"`
+ // FoursquareID is the foursquare identifier of the venue
+ //
+ // optional
+ FoursquareID string `json:"foursquare_id,omitempty"`
+ // FoursquareType is the foursquare type of the venue
+ //
+ // optional
+ FoursquareType string `json:"foursquare_type,omitempty"`
+ // GooglePlaceID is the Google Places identifier of the venue
+ //
+ // optional
+ GooglePlaceID string `json:"google_place_id,omitempty"`
+ // GooglePlaceType is the Google Places type of the venue
+ //
+ // optional
+ GooglePlaceType string `json:"google_place_type,omitempty"`
+}
+
+// ProximityAlertTriggered represents a service message sent when a user in the
+// chat triggers a proximity alert sent by another user.
+type ProximityAlertTriggered struct {
+ // Traveler is the user that triggered the alert
+ Traveler User `json:"traveler"`
+ // Watcher is the user that set the alert
+ Watcher User `json:"watcher"`
+ // Distance is the distance between the users
+ Distance int `json:"distance"`
+}
+
+// MessageAutoDeleteTimerChanged represents a service message about a change in
+// auto-delete timer settings.
+type MessageAutoDeleteTimerChanged struct {
+ // New auto-delete time for messages in the chat.
+ MessageAutoDeleteTime int `json:"message_auto_delete_time"`
+}
+
+// VoiceChatScheduled represents a service message about a voice chat scheduled
+// in the chat.
+type VoiceChatScheduled struct {
+ // Point in time (Unix timestamp) when the voice chat is supposed to be
+ // started by a chat administrator
+ StartDate int `json:"start_date"`
+}
+
+// Time converts the scheduled start date into a Time.
+func (m *VoiceChatScheduled) Time() time.Time {
+ return time.Unix(int64(m.StartDate), 0)
+}
+
+// VoiceChatStarted represents a service message about a voice chat started in
+// the chat.
+type VoiceChatStarted struct{}
+
+// VoiceChatEnded represents a service message about a voice chat ended in the
+// chat.
+type VoiceChatEnded struct {
+ // Voice chat duration; in seconds.
+ Duration int `json:"duration"`
+}
+
+// VoiceChatParticipantsInvited represents a service message about new members
+// invited to a voice chat.
+type VoiceChatParticipantsInvited struct {
+ // New members that were invited to the voice chat.
+ //
+ // optional
+ Users []User `json:"users"`
+}
+
+// UserProfilePhotos contains a set of user profile photos.
+type UserProfilePhotos struct {
+ // TotalCount total number of profile pictures the target user has
+ TotalCount int `json:"total_count"`
+ // Photos requested profile pictures (in up to 4 sizes each)
+ Photos [][]PhotoSize `json:"photos"`
+}
+
+// File contains information about a file to download from Telegram.
+type File struct {
+ // FileID identifier for this file, which can be used to download or reuse
+ // the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is the unique identifier for this file, which is supposed to
+ // be the same over time and for different bots. Can't be used to download
+ // or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // FileSize file size, if known
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+ // FilePath file path
+ //
+ // optional
+ FilePath string `json:"file_path,omitempty"`
+}
+
+// Link returns a full path to the download URL for a File.
+//
+// It requires the Bot token to create the link.
+func (f *File) Link(token string) string {
+ return fmt.Sprintf(FileEndpoint, token, f.FilePath)
+}
+
+// ReplyKeyboardMarkup represents a custom keyboard with reply options.
+type ReplyKeyboardMarkup struct {
+ // Keyboard is an array of button rows, each represented by an Array of KeyboardButton objects
+ Keyboard [][]KeyboardButton `json:"keyboard"`
+ // ResizeKeyboard 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.
+ //
+ // optional
+ ResizeKeyboard bool `json:"resize_keyboard,omitempty"`
+ // OneTimeKeyboard requests clients to hide the keyboard as soon as it's been used.
+ // The keyboard will still be available, but clients will automatically display
+ // the usual letter-keyboard in the chat – the user can press a special button
+ // in the input field to see the custom keyboard again.
+ // Defaults to false.
+ //
+ // optional
+ OneTimeKeyboard bool `json:"one_time_keyboard,omitempty"`
+ // InputFieldPlaceholder is the placeholder to be shown in the input field when
+ // the keyboard is active; 1-64 characters.
+ //
+ // optional
+ InputFieldPlaceholder string `json:"input_field_placeholder,omitempty"`
+ // Selective use this parameter if you want to show the keyboard to 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 Message.ReplyToMessage not nil), sender of the original message.
+ //
+ // Example: A user requests to change the bot's language,
+ // bot replies to the request with a keyboard to select the new language.
+ // Other users in the group don't see the keyboard.
+ //
+ // optional
+ Selective bool `json:"selective,omitempty"`
+}
+
+// KeyboardButton represents one button of the reply keyboard. For simple text
+// buttons String can be used instead of this object to specify text of the
+// button. Optional fields request_contact, request_location, and request_poll
+// are mutually exclusive.
+type KeyboardButton struct {
+ // Text of the button. If none of the optional fields are used,
+ // it will be sent as a message when the button is pressed.
+ Text string `json:"text"`
+ // RequestContact if True, the user's phone number will be sent
+ // as a contact when the button is pressed.
+ // Available in private chats only.
+ //
+ // optional
+ RequestContact bool `json:"request_contact,omitempty"`
+ // RequestLocation if True, the user's current location will be sent when
+ // the button is pressed.
+ // Available in private chats only.
+ //
+ // optional
+ RequestLocation bool `json:"request_location,omitempty"`
+ // RequestPoll if True, the user will be asked to create a poll and send it
+ // to the bot when the button is pressed. Available in private chats only
+ //
+ // optional
+ RequestPoll *KeyboardButtonPollType `json:"request_poll,omitempty"`
+}
+
+// KeyboardButtonPollType represents type of poll, which is allowed to
+// be created and sent when the corresponding button is pressed.
+type KeyboardButtonPollType struct {
+ // Type is if quiz is passed, the user will be allowed to create only polls
+ // in the quiz mode. If regular is passed, only regular polls will be
+ // allowed. Otherwise, the user will be allowed to create a poll of any type.
+ Type string `json:"type"`
+}
+
+// ReplyKeyboardRemove Upon receiving a message with this object, Telegram
+// clients will remove the current custom keyboard and display the default
+// letter-keyboard. By default, custom keyboards are displayed until a new
+// keyboard is sent by a bot. An exception is made for one-time keyboards
+// that are hidden immediately after the user presses a button.
+type ReplyKeyboardRemove struct {
+ // RemoveKeyboard requests clients to remove the custom keyboard
+ // (user will not be able to summon this keyboard;
+ // if you want to hide the keyboard from sight but keep it accessible,
+ // use one_time_keyboard in ReplyKeyboardMarkup).
+ RemoveKeyboard bool `json:"remove_keyboard"`
+ // Selective use this parameter if you want to remove the keyboard for 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 Message.ReplyToMessage not nil), sender of the original message.
+ //
+ // Example: A user votes in a poll, bot returns confirmation message
+ // in reply to the vote and removes the keyboard for that user,
+ // while still showing the keyboard with poll options to users who haven't voted yet.
+ //
+ // optional
+ Selective bool `json:"selective,omitempty"`
+}
+
+// InlineKeyboardMarkup represents an inline keyboard that appears right next to
+// the message it belongs to.
+type InlineKeyboardMarkup struct {
+ // InlineKeyboard array of button rows, each represented by an Array of
+ // InlineKeyboardButton objects
+ InlineKeyboard [][]InlineKeyboardButton `json:"inline_keyboard"`
+}
+
+// InlineKeyboardButton represents one button of an inline keyboard. You must
+// use exactly one of the optional fields.
+//
+// Note that some values are references as even an empty string
+// will change behavior.
+//
+// CallbackGame, if set, MUST be first button in first row.
+type InlineKeyboardButton struct {
+ // Text label text on the button
+ Text string `json:"text"`
+ // URL HTTP or tg:// url to be opened when button is pressed.
+ //
+ // optional
+ URL *string `json:"url,omitempty"`
+ // LoginURL is an HTTP URL used to automatically authorize the user. Can be
+ // used as a replacement for the Telegram Login Widget
+ //
+ // optional
+ LoginURL *LoginURL `json:"login_url,omitempty"`
+ // CallbackData data to be sent in a callback query to the bot when button is pressed, 1-64 bytes.
+ //
+ // optional
+ CallbackData *string `json:"callback_data,omitempty"`
+ // SwitchInlineQuery if set, pressing the button will prompt the user to select one of their chats,
+ // open that chat and insert the bot's username and the specified inline query in the input field.
+ // Can be empty, in which case just the bot's username will be inserted.
+ //
+ // This offers an easy way for users to start using your bot
+ // in inline mode when they are currently in a private chat with it.
+ // Especially useful when combined with switch_pm… actions – in this case
+ // the user will be automatically returned to the chat they switched from,
+ // skipping the chat selection screen.
+ //
+ // optional
+ SwitchInlineQuery *string `json:"switch_inline_query,omitempty"`
+ // SwitchInlineQueryCurrentChat if set, pressing the button will insert the bot's username
+ // and the specified inline query in the current chat's input field.
+ // Can be empty, in which case only the bot's username will be inserted.
+ //
+ // This offers a quick way for the user to open your bot in inline mode
+ // in the same chat – good for selecting something from multiple options.
+ //
+ // optional
+ SwitchInlineQueryCurrentChat *string `json:"switch_inline_query_current_chat,omitempty"`
+ // CallbackGame description of the game that will be launched when the user presses the button.
+ //
+ // optional
+ CallbackGame *CallbackGame `json:"callback_game,omitempty"`
+ // Pay specify True, to send a Pay button.
+ //
+ // NOTE: This type of button must always be the first button in the first row.
+ //
+ // optional
+ Pay bool `json:"pay,omitempty"`
+}
+
+// LoginURL 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. All the user
+// needs to do is tap/click a button and confirm that they want to log in.
+type LoginURL struct {
+ // URL is an HTTP URL to be opened with user authorization data added to the
+ // query string when the button is pressed. If the user refuses to provide
+ // authorization data, the original URL without information about the user
+ // will be opened. The data added is the same as described in Receiving
+ // authorization data.
+ //
+ // NOTE: You must always check the hash of the received data to verify the
+ // authentication and the integrity of the data as described in Checking
+ // authorization.
+ URL string `json:"url"`
+ // ForwardText is the new text of the button in forwarded messages
+ //
+ // optional
+ ForwardText string `json:"forward_text,omitempty"`
+ // BotUsername is the username of a bot, which will be used for user
+ // authorization. See Setting up a bot for more details. If not specified,
+ // the current bot's username will be assumed. The url's domain must be the
+ // same as the domain linked with the bot. See Linking your domain to the
+ // bot for more details.
+ //
+ // optional
+ BotUsername string `json:"bot_username,omitempty"`
+ // RequestWriteAccess if true requests permission for your bot to send
+ // messages to the user
+ //
+ // optional
+ RequestWriteAccess bool `json:"request_write_access,omitempty"`
+}
+
+// CallbackQuery represents an incoming callback query from a callback button in
+// an inline keyboard. If the button that originated the query was attached to a
+// message sent by the bot, the field message will be present. If the button was
+// attached to a message sent via the bot (in inline mode), the field
+// inline_message_id will be present. Exactly one of the fields data or
+// game_short_name will be present.
+type CallbackQuery struct {
+ // ID unique identifier for this query
+ ID string `json:"id"`
+ // From sender
+ From *User `json:"from"`
+ // Message with the callback button that originated the query.
+ // Note that message content and message date will not be available if the
+ // message is too old.
+ //
+ // optional
+ Message *Message `json:"message,omitempty"`
+ // InlineMessageID identifier of the message sent via the bot in inline
+ // mode, that originated the query.
+ //
+ // optional
+ InlineMessageID string `json:"inline_message_id,omitempty"`
+ // ChatInstance global identifier, uniquely corresponding to the chat to
+ // which the message with the callback button was sent. Useful for high
+ // scores in games.
+ ChatInstance string `json:"chat_instance"`
+ // Data associated with the callback button. Be aware that
+ // a bad client can send arbitrary data in this field.
+ //
+ // optional
+ Data string `json:"data,omitempty"`
+ // GameShortName short name of a Game to be returned, serves as the unique identifier for the game.
+ //
+ // optional
+ GameShortName string `json:"game_short_name,omitempty"`
+}
+
+// ForceReply when receiving a message with this object, Telegram clients will
+// display a reply interface to the user (act as if the user has selected the
+// bot's message and tapped 'Reply'). This can be extremely useful if you want
+// to create user-friendly step-by-step interfaces without having to sacrifice
+// privacy mode.
+type ForceReply struct {
+ // ForceReply shows reply interface to the user,
+ // as if they manually selected the bot's message and tapped 'Reply'.
+ ForceReply bool `json:"force_reply"`
+ // InputFieldPlaceholder is the placeholder to be shown in the input field when
+ // the reply is active; 1-64 characters.
+ //
+ // optional
+ InputFieldPlaceholder string `json:"input_field_placeholder,omitempty"`
+ // Selective use this parameter 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 Message.ReplyToMessage not nil), sender of the original message.
+ //
+ // optional
+ Selective bool `json:"selective,omitempty"`
+}
+
+// ChatPhoto represents a chat photo.
+type ChatPhoto struct {
+ // SmallFileID is a file identifier of small (160x160) chat photo.
+ // This file_id can be used only for photo download and
+ // only for as long as the photo is not changed.
+ SmallFileID string `json:"small_file_id"`
+ // SmallFileUniqueID is a unique file identifier of small (160x160) chat
+ // photo, which is supposed to be the same over time and for different bots.
+ // Can't be used to download or reuse the file.
+ SmallFileUniqueID string `json:"small_file_unique_id"`
+ // BigFileID is a file identifier of big (640x640) chat photo.
+ // This file_id can be used only for photo download and
+ // only for as long as the photo is not changed.
+ BigFileID string `json:"big_file_id"`
+ // BigFileUniqueID is a file identifier of big (640x640) chat photo, which
+ // is supposed to be the same over time and for different bots. Can't be
+ // used to download or reuse the file.
+ BigFileUniqueID string `json:"big_file_unique_id"`
+}
+
+// ChatInviteLink represents an invite link for a chat.
+type ChatInviteLink struct {
+ // InviteLink is the invite link. If the link was created by another chat
+ // administrator, then the second part of the link will be replaced with “…”.
+ InviteLink string `json:"invite_link"`
+ // Creator of the link.
+ Creator User `json:"creator"`
+ // CreatesJoinRequest is true if users joining the chat via the link need to
+ // be approved by chat administrators.
+ //
+ // optional
+ CreatesJoinRequest bool `json:"creates_join_request"`
+ // IsPrimary is true, if the link is primary.
+ IsPrimary bool `json:"is_primary"`
+ // IsRevoked is true, if the link is revoked.
+ IsRevoked bool `json:"is_revoked"`
+ // Name is the name of the invite link.
+ //
+ // optional
+ Name string `json:"name"`
+ // ExpireDate is the point in time (Unix timestamp) when the link will
+ // expire or has been expired.
+ //
+ // optional
+ ExpireDate int `json:"expire_date"`
+ // MemberLimit is the maximum number of users that can be members of the
+ // chat simultaneously after joining the chat via this invite link; 1-99999.
+ //
+ // optional
+ MemberLimit int `json:"member_limit"`
+ // PendingJoinRequestCount is the number of pending join requests created
+ // using this link.
+ //
+ // optional
+ PendingJoinRequestCount int `json:"pending_join_request_count"`
+}
+
+// ChatMember contains information about one member of a chat.
+type ChatMember struct {
+ // User information about the user
+ User *User `json:"user"`
+ // Status the member's status in the chat.
+ // Can be
+ // “creator”,
+ // “administrator”,
+ // “member”,
+ // “restricted”,
+ // “left” or
+ // “kicked”
+ Status string `json:"status"`
+ // CustomTitle owner and administrators only. Custom title for this user
+ //
+ // optional
+ CustomTitle string `json:"custom_title,omitempty"`
+ // IsAnonymous owner and administrators only. True, if the user's presence
+ // in the chat is hidden
+ //
+ // optional
+ IsAnonymous bool `json:"is_anonymous"`
+ // UntilDate restricted and kicked only.
+ // Date when restrictions will be lifted for this user;
+ // unix time.
+ //
+ // optional
+ UntilDate int64 `json:"until_date,omitempty"`
+ // CanBeEdited administrators only.
+ // True, if the bot is allowed to edit administrator privileges of that user.
+ //
+ // optional
+ CanBeEdited bool `json:"can_be_edited,omitempty"`
+ // CanManageChat administrators only.
+ // True, if the administrator can access the chat event log, chat
+ // statistics, message statistics in channels, see channel members, see
+ // anonymous administrators in supergroups and ignore slow mode. Implied by
+ // any other administrator privilege.
+ //
+ // optional
+ CanManageChat bool `json:"can_manage_chat"`
+ // CanPostMessages administrators only.
+ // True, if the administrator can post in the channel;
+ // channels only.
+ //
+ // optional
+ CanPostMessages bool `json:"can_post_messages,omitempty"`
+ // CanEditMessages administrators only.
+ // True, if the administrator can edit messages of other users and can pin messages;
+ // channels only.
+ //
+ // optional
+ CanEditMessages bool `json:"can_edit_messages,omitempty"`
+ // CanDeleteMessages administrators only.
+ // True, if the administrator can delete messages of other users.
+ //
+ // optional
+ CanDeleteMessages bool `json:"can_delete_messages,omitempty"`
+ // CanManageVoiceChats administrators only.
+ // True, if the administrator can manage voice chats.
+ //
+ // optional
+ CanManageVoiceChats bool `json:"can_manage_voice_chats"`
+ // CanRestrictMembers administrators only.
+ // True, if the administrator can restrict, ban or unban chat members.
+ //
+ // optional
+ CanRestrictMembers bool `json:"can_restrict_members,omitempty"`
+ // CanPromoteMembers administrators only.
+ // True, if the administrator can add new administrators
+ // with a subset of their own privileges or demote administrators that he has promoted,
+ // directly or indirectly (promoted by administrators that were appointed by the user).
+ //
+ // optional
+ CanPromoteMembers bool `json:"can_promote_members,omitempty"`
+ // CanChangeInfo administrators and restricted only.
+ // True, if the user is allowed to change the chat title, photo and other settings.
+ //
+ // optional
+ CanChangeInfo bool `json:"can_change_info,omitempty"`
+ // CanInviteUsers administrators and restricted only.
+ // True, if the user is allowed to invite new users to the chat.
+ //
+ // optional
+ CanInviteUsers bool `json:"can_invite_users,omitempty"`
+ // CanPinMessages administrators and restricted only.
+ // True, if the user is allowed to pin messages; groups and supergroups only
+ //
+ // optional
+ CanPinMessages bool `json:"can_pin_messages,omitempty"`
+ // IsMember is true, if the user is a member of the chat at the moment of
+ // the request
+ IsMember bool `json:"is_member"`
+ // CanSendMessages
+ //
+ // optional
+ CanSendMessages bool `json:"can_send_messages,omitempty"`
+ // CanSendMediaMessages restricted only.
+ // True, if the user is allowed to send text messages, contacts, locations and venues
+ //
+ // optional
+ CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"`
+ // CanSendPolls restricted only.
+ // True, if the user is allowed to send polls
+ //
+ // optional
+ CanSendPolls bool `json:"can_send_polls,omitempty"`
+ // CanSendOtherMessages restricted only.
+ // True, if the user is allowed to send audios, documents,
+ // photos, videos, video notes and voice notes.
+ //
+ // optional
+ CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"`
+ // CanAddWebPagePreviews restricted only.
+ // True, if the user is allowed to add web page previews to their messages.
+ //
+ // optional
+ CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"`
+}
+
+// IsCreator returns if the ChatMember was the creator of the chat.
+func (chat ChatMember) IsCreator() bool { return chat.Status == "creator" }
+
+// IsAdministrator returns if the ChatMember is a chat administrator.
+func (chat ChatMember) IsAdministrator() bool { return chat.Status == "administrator" }
+
+// HasLeft returns if the ChatMember left the chat.
+func (chat ChatMember) HasLeft() bool { return chat.Status == "left" }
+
+// WasKicked returns if the ChatMember was kicked from the chat.
+func (chat ChatMember) WasKicked() bool { return chat.Status == "kicked" }
+
+// ChatMemberUpdated represents changes in the status of a chat member.
+type ChatMemberUpdated struct {
+ // Chat the user belongs to.
+ Chat Chat `json:"chat"`
+ // From is the performer of the action, which resulted in the change.
+ From User `json:"from"`
+ // Date the change was done in Unix time.
+ Date int `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"`
+ // InviteLink is the link which was used by the user to join the chat;
+ // for joining by invite link events only.
+ //
+ // optional
+ InviteLink *ChatInviteLink `json:"invite_link"`
+}
+
+// ChatJoinRequest represents a join request sent to a chat.
+type ChatJoinRequest struct {
+ // Chat to which the request was sent.
+ Chat Chat `json:"chat"`
+ // User that sent the join request.
+ From User `json:"from"`
+ // Date the request was sent in Unix time.
+ Date int `json:"date"`
+ // Bio of the user.
+ //
+ // optional
+ Bio string `json:"bio"`
+ // InviteLink is the link that was used by the user to send the join request.
+ //
+ // optional
+ InviteLink *ChatInviteLink `json:"invite_link"`
+}
+
+// ChatPermissions describes actions that a non-administrator user is
+// allowed to take in a chat. All fields are optional.
+type ChatPermissions struct {
+ // CanSendMessages is true, if the user is allowed to send text messages,
+ // contacts, locations and venues
+ //
+ // optional
+ CanSendMessages bool `json:"can_send_messages,omitempty"`
+ // CanSendMediaMessages is true, if the user is allowed to send audios,
+ // documents, photos, videos, video notes and voice notes, implies
+ // can_send_messages
+ //
+ // optional
+ CanSendMediaMessages bool `json:"can_send_media_messages,omitempty"`
+ // CanSendPolls is true, if the user is allowed to send polls, implies
+ // can_send_messages
+ //
+ // optional
+ CanSendPolls bool `json:"can_send_polls,omitempty"`
+ // CanSendOtherMessages is true, if the user is allowed to send animations,
+ // games, stickers and use inline bots, implies can_send_media_messages
+ //
+ // optional
+ CanSendOtherMessages bool `json:"can_send_other_messages,omitempty"`
+ // CanAddWebPagePreviews is true, if the user is allowed to add web page
+ // previews to their messages, implies can_send_media_messages
+ //
+ // optional
+ CanAddWebPagePreviews bool `json:"can_add_web_page_previews,omitempty"`
+ // CanChangeInfo is true, if the user is allowed to change the chat title,
+ // photo and other settings. Ignored in public supergroups
+ //
+ // optional
+ CanChangeInfo bool `json:"can_change_info,omitempty"`
+ // CanInviteUsers is true, if the user is allowed to invite new users to the
+ // chat
+ //
+ // optional
+ CanInviteUsers bool `json:"can_invite_users,omitempty"`
+ // CanPinMessages is true, if the user is allowed to pin messages. Ignored
+ // in public supergroups
+ //
+ // optional
+ CanPinMessages bool `json:"can_pin_messages,omitempty"`
+}
+
+// ChatLocation represents a location to which a chat is connected.
+type ChatLocation struct {
+ // Location is the location to which the supergroup is connected. Can't be a
+ // live location.
+ Location Location `json:"location"`
+ // Address is the location address; 1-64 characters, as defined by the chat
+ // owner
+ Address string `json:"address"`
+}
+
+// BotCommand represents a bot command.
+type BotCommand struct {
+ // Command text of the command, 1-32 characters.
+ // Can contain only lowercase English letters, digits and underscores.
+ Command string `json:"command"`
+ // Description of the command, 3-256 characters.
+ Description string `json:"description"`
+}
+
+// BotCommandScope represents the scope to which bot commands are applied.
+//
+// It contains the fields for all types of scopes, different types only support
+// specific (or no) fields.
+type BotCommandScope struct {
+ Type string `json:"type"`
+ ChatID int64 `json:"chat_id,omitempty"`
+ UserID int64 `json:"user_id,omitempty"`
+}
+
+// ResponseParameters are various errors that can be returned in APIResponse.
+type ResponseParameters struct {
+ // The group has been migrated to a supergroup with the specified identifier.
+ //
+ // optional
+ MigrateToChatID int64 `json:"migrate_to_chat_id,omitempty"`
+ // In case of exceeding flood control, the number of seconds left to wait
+ // before the request can be repeated.
+ //
+ // optional
+ RetryAfter int `json:"retry_after,omitempty"`
+}
+
+// BaseInputMedia is a base type for the InputMedia types.
+type BaseInputMedia struct {
+ // Type of the result.
+ Type string `json:"type"`
+ // Media file to send. Pass a file_id to send a file
+ // that exists on the Telegram servers (recommended),
+ // pass an HTTP URL for Telegram to get a file from the Internet,
+ // or pass “attach://<file_attach_name>” to upload a new one
+ // using multipart/form-data under <file_attach_name> name.
+ Media RequestFileData `json:"media"`
+ // thumb intentionally missing as it is not currently compatible
+
+ // Caption of the video to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities"`
+}
+
+// InputMediaPhoto is a photo to send as part of a media group.
+type InputMediaPhoto struct {
+ BaseInputMedia
+}
+
+// InputMediaVideo is a video to send as part of a media group.
+type InputMediaVideo struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
+ //
+ // optional
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // Width video width
+ //
+ // optional
+ Width int `json:"width,omitempty"`
+ // Height video height
+ //
+ // optional
+ Height int `json:"height,omitempty"`
+ // Duration video duration
+ //
+ // optional
+ Duration int `json:"duration,omitempty"`
+ // SupportsStreaming pass True, if the uploaded video is suitable for streaming.
+ //
+ // optional
+ SupportsStreaming bool `json:"supports_streaming,omitempty"`
+}
+
+// InputMediaAnimation is an animation to send as part of a media group.
+type InputMediaAnimation struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
+ //
+ // optional
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // Width video width
+ //
+ // optional
+ Width int `json:"width,omitempty"`
+ // Height video height
+ //
+ // optional
+ Height int `json:"height,omitempty"`
+ // Duration video duration
+ //
+ // optional
+ Duration int `json:"duration,omitempty"`
+}
+
+// InputMediaAudio is an audio to send as part of a media group.
+type InputMediaAudio struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
+ //
+ // optional
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // Duration of the audio in seconds
+ //
+ // optional
+ Duration int `json:"duration,omitempty"`
+ // Performer of the audio
+ //
+ // optional
+ Performer string `json:"performer,omitempty"`
+ // Title of the audio
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+}
+
+// InputMediaDocument is a general file to send as part of a media group.
+type InputMediaDocument struct {
+ BaseInputMedia
+ // Thumbnail of the file sent; can be ignored if thumbnail generation for
+ // the file is supported server-side.
+ //
+ // optional
+ Thumb RequestFileData `json:"thumb,omitempty"`
+ // DisableContentTypeDetection disables automatic server-side content type
+ // detection for files uploaded using multipart/form-data. Always true, if
+ // the document is sent as part of an album
+ //
+ // optional
+ DisableContentTypeDetection bool `json:"disable_content_type_detection,omitempty"`
+}
+
+// Sticker represents a sticker.
+type Sticker struct {
+ // FileID is an identifier for this file, which can be used to download or
+ // reuse the file
+ FileID string `json:"file_id"`
+ // FileUniqueID is a unique identifier for this file,
+ // which is supposed to be the same over time and for different bots.
+ // Can't be used to download or reuse the file.
+ FileUniqueID string `json:"file_unique_id"`
+ // Width sticker width
+ Width int `json:"width"`
+ // Height sticker height
+ Height int `json:"height"`
+ // IsAnimated true, if the sticker is animated
+ //
+ // optional
+ IsAnimated bool `json:"is_animated,omitempty"`
+ // Thumbnail sticker thumbnail in the .WEBP or .JPG format
+ //
+ // optional
+ Thumbnail *PhotoSize `json:"thumb,omitempty"`
+ // Emoji associated with the sticker
+ //
+ // optional
+ Emoji string `json:"emoji,omitempty"`
+ // SetName of the sticker set to which the sticker belongs
+ //
+ // optional
+ SetName string `json:"set_name,omitempty"`
+ // MaskPosition is for mask stickers, the position where the mask should be
+ // placed
+ //
+ // optional
+ MaskPosition *MaskPosition `json:"mask_position,omitempty"`
+ // FileSize
+ //
+ // optional
+ FileSize int `json:"file_size,omitempty"`
+}
+
+// StickerSet represents a sticker set.
+type StickerSet struct {
+ // Name sticker set name
+ Name string `json:"name"`
+ // Title sticker set title
+ Title string `json:"title"`
+ // IsAnimated true, if the sticker set contains animated stickers
+ IsAnimated bool `json:"is_animated"`
+ // ContainsMasks true, if the sticker set contains masks
+ ContainsMasks bool `json:"contains_masks"`
+ // Stickers list of all set stickers
+ Stickers []Sticker `json:"stickers"`
+ // Thumb is the sticker set thumbnail in the .WEBP or .TGS format
+ Thumbnail *PhotoSize `json:"thumb"`
+}
+
+// MaskPosition describes the position on faces where a mask should be placed
+// by default.
+type MaskPosition struct {
+ // The part of the face relative to which the mask should be placed.
+ // One of “forehead”, “eyes”, “mouth”, or “chin”.
+ Point string `json:"point"`
+ // Shift by X-axis measured in widths of the mask scaled to the face size,
+ // from left to right. For example, choosing -1.0 will place mask just to
+ // the left of the default mask position.
+ XShift float64 `json:"x_shift"`
+ // Shift by Y-axis measured in heights of the mask scaled to the face size,
+ // from top to bottom. For example, 1.0 will place the mask just below the
+ // default mask position.
+ YShift float64 `json:"y_shift"`
+ // Mask scaling coefficient. For example, 2.0 means double size.
+ Scale float64 `json:"scale"`
+}
+
+// Game represents a game. Use BotFather to create and edit games, their short
+// names will act as unique identifiers.
+type Game struct {
+ // Title of the game
+ Title string `json:"title"`
+ // Description of the game
+ Description string `json:"description"`
+ // Photo that will be displayed in the game message in chats.
+ Photo []PhotoSize `json:"photo"`
+ // Text a brief description of the game or high scores included in the game message.
+ // Can be automatically edited to include current high scores for the game
+ // when the bot calls setGameScore, or manually edited using editMessageText. 0-4096 characters.
+ //
+ // optional
+ Text string `json:"text,omitempty"`
+ // TextEntities special entities that appear in text, such as usernames, URLs, bot commands, etc.
+ //
+ // optional
+ TextEntities []MessageEntity `json:"text_entities,omitempty"`
+ // Animation is an animation that will be displayed in the game message in chats.
+ // Upload via BotFather (https://t.me/botfather).
+ //
+ // optional
+ Animation Animation `json:"animation,omitempty"`
+}
+
+// GameHighScore is a user's score and position on the leaderboard.
+type GameHighScore struct {
+ // Position in high score table for the game
+ Position int `json:"position"`
+ // User user
+ User User `json:"user"`
+ // Score score
+ Score int `json:"score"`
+}
+
+// CallbackGame is for starting a game in an inline keyboard button.
+type CallbackGame struct{}
+
+// WebhookInfo is information about a currently set webhook.
+type WebhookInfo struct {
+ // URL webhook URL, may be empty if webhook is not set up.
+ URL string `json:"url"`
+ // HasCustomCertificate true, if a custom certificate was provided for webhook certificate checks.
+ HasCustomCertificate bool `json:"has_custom_certificate"`
+ // PendingUpdateCount number of updates awaiting delivery.
+ PendingUpdateCount int `json:"pending_update_count"`
+ // IPAddress is the currently used webhook IP address
+ //
+ // optional
+ IPAddress string `json:"ip_address,omitempty"`
+ // LastErrorDate unix time for the most recent error
+ // that happened when trying to deliver an update via webhook.
+ //
+ // optional
+ LastErrorDate int `json:"last_error_date,omitempty"`
+ // LastErrorMessage error message in human-readable format for the most recent error
+ // that happened when trying to deliver an update via webhook.
+ //
+ // optional
+ LastErrorMessage string `json:"last_error_message,omitempty"`
+ // MaxConnections maximum allowed number of simultaneous
+ // HTTPS connections to the webhook for update delivery.
+ //
+ // optional
+ MaxConnections int `json:"max_connections,omitempty"`
+ // AllowedUpdates is a list of update types the bot is subscribed to.
+ // Defaults to all update types
+ //
+ // optional
+ AllowedUpdates []string `json:"allowed_updates,omitempty"`
+}
+
+// IsSet returns true if a webhook is currently set.
+func (info WebhookInfo) IsSet() bool {
+ return info.URL != ""
+}
+
+// InlineQuery is a Query from Telegram for an inline request.
+type InlineQuery struct {
+ // ID unique identifier for this query
+ ID string `json:"id"`
+ // From sender
+ From *User `json:"from"`
+ // Query text of the query (up to 256 characters).
+ Query string `json:"query"`
+ // Offset of the results to be returned, can be controlled by the bot.
+ Offset string `json:"offset"`
+ // Type of the chat, from which the inline query was sent. Can be either
+ // “sender” for a private chat with the inline query sender, “private”,
+ // “group”, “supergroup”, or “channel”. The chat type should be always known
+ // for requests sent from official clients and most third-party clients,
+ // unless the request was sent from a secret chat
+ //
+ // optional
+ ChatType string `json:"chat_type"`
+ // Location sender location, only for bots that request user location.
+ //
+ // optional
+ Location *Location `json:"location,omitempty"`
+}
+
+// InlineQueryResultCachedAudio is an inline query response with cached audio.
+type InlineQueryResultCachedAudio struct {
+ // Type of the result, must be audio
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // AudioID a valid file identifier for the audio file
+ AudioID string `json:"audio_file_id"`
+ // Caption 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the audio
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedDocument is an inline query response with cached document.
+type InlineQueryResultCachedDocument struct {
+ // Type of the result, must be a document
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // DocumentID a valid file identifier for the file
+ DocumentID string `json:"document_file_id"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Caption of the document to be sent, 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // Description short description of the result
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // // See formatting options for more details
+ // // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the file
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedGIF is an inline query response with cached gif.
+type InlineQueryResultCachedGIF struct {
+ // Type of the result, must be gif.
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes.
+ ID string `json:"id"`
+ // GifID a valid file identifier for the GIF file.
+ GIFID string `json:"gif_file_id"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Caption of the GIF file to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the GIF animation.
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedMPEG4GIF is an inline query response with cached
+// H.264/MPEG-4 AVC video without sound gif.
+type InlineQueryResultCachedMPEG4GIF struct {
+ // Type of the result, must be mpeg4_gif
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // MPEG4FileID a valid file identifier for the MP4 file
+ MPEG4FileID string `json:"mpeg4_file_id"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the video animation.
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedPhoto is an inline query response with cached photo.
+type InlineQueryResultCachedPhoto struct {
+ // Type of the result, must be a photo.
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes.
+ ID string `json:"id"`
+ // PhotoID a valid file identifier of the photo.
+ PhotoID string `json:"photo_file_id"`
+ // Title for the result.
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Description short description of the result.
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // Caption of the photo to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the photo caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the photo.
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedSticker is an inline query response with cached sticker.
+type InlineQueryResultCachedSticker struct {
+ // Type of the result, must be a sticker
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // StickerID a valid file identifier of the sticker
+ StickerID string `json:"sticker_file_id"`
+ // Title is a title
+ Title string `json:"title"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the sticker
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedVideo is an inline query response with cached video.
+type InlineQueryResultCachedVideo struct {
+ // Type of the result, must be video
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // VideoID a valid file identifier for the video file
+ VideoID string `json:"video_file_id"`
+ // Title for the result
+ Title string `json:"title"`
+ // Description short description of the result
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // Caption of the video to be sent, 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the video
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultCachedVoice is an inline query response with cached voice.
+type InlineQueryResultCachedVoice struct {
+ // Type of the result, must be voice
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // VoiceID a valid file identifier for the voice message
+ VoiceID string `json:"voice_file_id"`
+ // Title voice message title
+ Title string `json:"title"`
+ // Caption 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the voice message
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultArticle represents a link to an article or web page.
+type InlineQueryResultArticle struct {
+ // Type of the result, must be article.
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 Bytes.
+ ID string `json:"id"`
+ // Title of the result
+ Title string `json:"title"`
+ // InputMessageContent content of the message to be sent.
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ // ReplyMarkup Inline keyboard attached to the message.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // URL of the result.
+ //
+ // optional
+ URL string `json:"url,omitempty"`
+ // HideURL pass True, if you don't want the URL to be shown in the message.
+ //
+ // optional
+ HideURL bool `json:"hide_url,omitempty"`
+ // Description short description of the result.
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // ThumbURL url of the thumbnail for the result
+ //
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // ThumbWidth thumbnail width
+ //
+ // optional
+ ThumbWidth int `json:"thumb_width,omitempty"`
+ // ThumbHeight thumbnail height
+ //
+ // optional
+ ThumbHeight int `json:"thumb_height,omitempty"`
+}
+
+// InlineQueryResultAudio is an inline query response audio.
+type InlineQueryResultAudio struct {
+ // Type of the result, must be audio
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // URL a valid url for the audio file
+ URL string `json:"audio_url"`
+ // Title is a title
+ Title string `json:"title"`
+ // Caption 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // Performer is a performer
+ //
+ // optional
+ Performer string `json:"performer,omitempty"`
+ // Duration audio duration in seconds
+ //
+ // optional
+ Duration int `json:"audio_duration,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the audio
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultContact is an inline query response contact.
+type InlineQueryResultContact struct {
+ Type string `json:"type"` // required
+ ID string `json:"id"` // required
+ PhoneNumber string `json:"phone_number"` // required
+ FirstName string `json:"first_name"` // required
+ LastName string `json:"last_name"`
+ VCard string `json:"vcard"`
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ ThumbURL string `json:"thumb_url"`
+ ThumbWidth int `json:"thumb_width"`
+ ThumbHeight int `json:"thumb_height"`
+}
+
+// InlineQueryResultGame is an inline query response game.
+type InlineQueryResultGame struct {
+ // Type of the result, must be game
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // GameShortName short name of the game
+ GameShortName string `json:"game_short_name"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+}
+
+// InlineQueryResultDocument is an inline query response document.
+type InlineQueryResultDocument struct {
+ // Type of the result, must be a document
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // Title for the result
+ Title string `json:"title"`
+ // Caption of the document to be sent, 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // URL a valid url for the file
+ URL string `json:"document_url"`
+ // MimeType of the content of the file, either “application/pdf” or “application/zip”
+ MimeType string `json:"mime_type"`
+ // Description short description of the result
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the file
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ // ThumbURL url of the thumbnail (jpeg only) for the file
+ //
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // ThumbWidth thumbnail width
+ //
+ // optional
+ ThumbWidth int `json:"thumb_width,omitempty"`
+ // ThumbHeight thumbnail height
+ //
+ // optional
+ ThumbHeight int `json:"thumb_height,omitempty"`
+}
+
+// InlineQueryResultGIF is an inline query response GIF.
+type InlineQueryResultGIF struct {
+ // Type of the result, must be gif.
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes.
+ ID string `json:"id"`
+ // URL a valid URL for the GIF file. File size must not exceed 1MB.
+ URL string `json:"gif_url"`
+ // ThumbURL url of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result.
+ ThumbURL string `json:"thumb_url"`
+ // Width of the GIF
+ //
+ // optional
+ Width int `json:"gif_width,omitempty"`
+ // Height of the GIF
+ //
+ // optional
+ Height int `json:"gif_height,omitempty"`
+ // Duration of the GIF
+ //
+ // optional
+ Duration int `json:"gif_duration,omitempty"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Caption of the GIF file to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the GIF animation.
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultLocation is an inline query response location.
+type InlineQueryResultLocation struct {
+ // Type of the result, must be location
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 Bytes
+ ID string `json:"id"`
+ // Latitude of the location in degrees
+ Latitude float64 `json:"latitude"`
+ // Longitude of the location in degrees
+ Longitude float64 `json:"longitude"`
+ // Title of the location
+ Title string `json:"title"`
+ // HorizontalAccuracy is the radius of uncertainty for the location,
+ // measured in meters; 0-1500
+ //
+ // optional
+ HorizontalAccuracy float64 `json:"horizontal_accuracy"`
+ // LivePeriod is the period in seconds for which the location can be
+ // updated, should be between 60 and 86400.
+ //
+ // optional
+ LivePeriod int `json:"live_period"`
+ // Heading is for live locations, a direction in which the user is moving,
+ // in degrees. Must be between 1 and 360 if specified.
+ //
+ // optional
+ Heading int `json:"heading"`
+ // ProximityAlertRadius is for live locations, a maximum distance for
+ // proximity alerts about approaching another chat member, in meters. Must
+ // be between 1 and 100000 if specified.
+ //
+ // optional
+ ProximityAlertRadius int `json:"proximity_alert_radius"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the location
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ // ThumbURL url of the thumbnail for the result
+ //
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // ThumbWidth thumbnail width
+ //
+ // optional
+ ThumbWidth int `json:"thumb_width,omitempty"`
+ // ThumbHeight thumbnail height
+ //
+ // optional
+ ThumbHeight int `json:"thumb_height,omitempty"`
+}
+
+// InlineQueryResultMPEG4GIF is an inline query response MPEG4 GIF.
+type InlineQueryResultMPEG4GIF struct {
+ // Type of the result, must be mpeg4_gif
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // URL a valid URL for the MP4 file. File size must not exceed 1MB
+ URL string `json:"mpeg4_url"`
+ // Width video width
+ //
+ // optional
+ Width int `json:"mpeg4_width"`
+ // Height vVideo height
+ //
+ // optional
+ Height int `json:"mpeg4_height"`
+ // Duration video duration
+ //
+ // optional
+ Duration int `json:"mpeg4_duration"`
+ // ThumbURL url of the static (JPEG or GIF) or animated (MPEG4) thumbnail for the result.
+ ThumbURL string `json:"thumb_url"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Caption of the MPEG-4 file to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the video animation
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultPhoto is an inline query response photo.
+type InlineQueryResultPhoto struct {
+ // Type of the result, must be article.
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 Bytes.
+ ID string `json:"id"`
+ // URL a valid URL of the photo. Photo must be in jpeg format.
+ // Photo size must not exceed 5MB.
+ URL string `json:"photo_url"`
+ // MimeType
+ MimeType string `json:"mime_type"`
+ // Width of the photo
+ //
+ // optional
+ Width int `json:"photo_width,omitempty"`
+ // Height of the photo
+ //
+ // optional
+ Height int `json:"photo_height,omitempty"`
+ // ThumbURL url of the thumbnail for the photo.
+ //
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // Title for the result
+ //
+ // optional
+ Title string `json:"title,omitempty"`
+ // Description short description of the result
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // Caption of the photo to be sent, 0-1024 characters after entities parsing.
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the photo caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message.
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the photo.
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultVenue is an inline query response venue.
+type InlineQueryResultVenue struct {
+ // Type of the result, must be venue
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 Bytes
+ ID string `json:"id"`
+ // Latitude of the venue location in degrees
+ Latitude float64 `json:"latitude"`
+ // Longitude of the venue location in degrees
+ Longitude float64 `json:"longitude"`
+ // Title of the venue
+ Title string `json:"title"`
+ // Address of the venue
+ Address string `json:"address"`
+ // FoursquareID foursquare identifier of the venue if known
+ //
+ // optional
+ FoursquareID string `json:"foursquare_id,omitempty"`
+ // FoursquareType foursquare type of the venue, if known.
+ // (For example, “arts_entertainment/default”, “arts_entertainment/aquarium” or “food/icecream”.)
+ //
+ // optional
+ FoursquareType string `json:"foursquare_type,omitempty"`
+ // GooglePlaceID is the Google Places identifier of the venue
+ //
+ // optional
+ GooglePlaceID string `json:"google_place_id,omitempty"`
+ // GooglePlaceType is the Google Places type of the venue
+ //
+ // optional
+ GooglePlaceType string `json:"google_place_type,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the venue
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+ // ThumbURL url of the thumbnail for the result
+ //
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // ThumbWidth thumbnail width
+ //
+ // optional
+ ThumbWidth int `json:"thumb_width,omitempty"`
+ // ThumbHeight thumbnail height
+ //
+ // optional
+ ThumbHeight int `json:"thumb_height,omitempty"`
+}
+
+// InlineQueryResultVideo is an inline query response video.
+type InlineQueryResultVideo struct {
+ // Type of the result, must be video
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // URL a valid url for the embedded video player or video file
+ URL string `json:"video_url"`
+ // MimeType of the content of video url, “text/html” or “video/mp4”
+ MimeType string `json:"mime_type"`
+ //
+ // ThumbURL url of the thumbnail (jpeg only) for the video
+ // optional
+ ThumbURL string `json:"thumb_url,omitempty"`
+ // Title for the result
+ Title string `json:"title"`
+ // Caption of the video to be sent, 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // Width video width
+ //
+ // optional
+ Width int `json:"video_width,omitempty"`
+ // Height video height
+ //
+ // optional
+ Height int `json:"video_height,omitempty"`
+ // Duration video duration in seconds
+ //
+ // optional
+ Duration int `json:"video_duration,omitempty"`
+ // Description short description of the result
+ //
+ // optional
+ Description string `json:"description,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the video.
+ // This field is required if InlineQueryResultVideo is used to send
+ // an HTML-page as a result (e.g., a YouTube video).
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// InlineQueryResultVoice is an inline query response voice.
+type InlineQueryResultVoice struct {
+ // Type of the result, must be voice
+ Type string `json:"type"`
+ // ID unique identifier for this result, 1-64 bytes
+ ID string `json:"id"`
+ // URL a valid URL for the voice recording
+ URL string `json:"voice_url"`
+ // Title recording title
+ Title string `json:"title"`
+ // Caption 0-1024 characters after entities parsing
+ //
+ // optional
+ Caption string `json:"caption,omitempty"`
+ // ParseMode mode for parsing entities in the video caption.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // CaptionEntities is a list of special entities that appear in the caption,
+ // which can be specified instead of parse_mode
+ //
+ // optional
+ CaptionEntities []MessageEntity `json:"caption_entities,omitempty"`
+ // Duration recording duration in seconds
+ //
+ // optional
+ Duration int `json:"voice_duration,omitempty"`
+ // ReplyMarkup inline keyboard attached to the message
+ //
+ // optional
+ ReplyMarkup *InlineKeyboardMarkup `json:"reply_markup,omitempty"`
+ // InputMessageContent content of the message to be sent instead of the voice recording
+ //
+ // optional
+ InputMessageContent interface{} `json:"input_message_content,omitempty"`
+}
+
+// ChosenInlineResult is an inline query result chosen by a User
+type ChosenInlineResult struct {
+ // ResultID the unique identifier for the result that was chosen
+ ResultID string `json:"result_id"`
+ // From the user that chose the result
+ From *User `json:"from"`
+ // Location sender location, only for bots that require user location
+ //
+ // optional
+ Location *Location `json:"location,omitempty"`
+ // InlineMessageID identifier of the sent inline message.
+ // Available only if there is an inline keyboard attached to the message.
+ // Will be also received in callback queries and can be used to edit the message.
+ //
+ // optional
+ InlineMessageID string `json:"inline_message_id,omitempty"`
+ // Query the query that was used to obtain the result
+ Query string `json:"query"`
+}
+
+// InputTextMessageContent contains text for displaying
+// as an inline query result.
+type InputTextMessageContent struct {
+ // Text of the message to be sent, 1-4096 characters
+ Text string `json:"message_text"`
+ // ParseMode mode for parsing entities in the message text.
+ // See formatting options for more details
+ // (https://core.telegram.org/bots/api#formatting-options).
+ //
+ // optional
+ ParseMode string `json:"parse_mode,omitempty"`
+ // Entities is a list of special entities that appear in message text, which
+ // can be specified instead of parse_mode
+ //
+ // optional
+ Entities []MessageEntity `json:"entities,omitempty"`
+ // DisableWebPagePreview disables link previews for links in the sent message
+ //
+ // optional
+ DisableWebPagePreview bool `json:"disable_web_page_preview,omitempty"`
+}
+
+// InputLocationMessageContent contains a location for displaying
+// as an inline query result.
+type InputLocationMessageContent struct {
+ // Latitude of the location in degrees
+ Latitude float64 `json:"latitude"`
+ // Longitude of the location in degrees
+ Longitude float64 `json:"longitude"`
+ // HorizontalAccuracy is the radius of uncertainty for the location,
+ // measured in meters; 0-1500
+ //
+ // optional
+ HorizontalAccuracy float64 `json:"horizontal_accuracy"`
+ // LivePeriod is the period in seconds for which the location can be
+ // updated, should be between 60 and 86400
+ //
+ // optional
+ LivePeriod int `json:"live_period,omitempty"`
+ // Heading is for live locations, a direction in which the user is moving,
+ // in degrees. Must be between 1 and 360 if specified.
+ //
+ // optional
+ Heading int `json:"heading"`
+ // ProximityAlertRadius is for live locations, a maximum distance for
+ // proximity alerts about approaching another chat member, in meters. Must
+ // be between 1 and 100000 if specified.
+ //
+ // optional
+ ProximityAlertRadius int `json:"proximity_alert_radius"`
+}
+
+// InputVenueMessageContent contains a venue for displaying
+// as an inline query result.
+type InputVenueMessageContent struct {
+ // Latitude of the venue in degrees
+ Latitude float64 `json:"latitude"`
+ // Longitude of the venue in degrees
+ Longitude float64 `json:"longitude"`
+ // Title name of the venue
+ Title string `json:"title"`
+ // Address of the venue
+ Address string `json:"address"`
+ // FoursquareID foursquare identifier of the venue, if known
+ //
+ // optional
+ FoursquareID string `json:"foursquare_id,omitempty"`
+ // FoursquareType Foursquare type of the venue, if known
+ //
+ // optional
+ FoursquareType string `json:"foursquare_type,omitempty"`
+ // GooglePlaceID is the Google Places identifier of the venue
+ //
+ // optional
+ GooglePlaceID string `json:"google_place_id"`
+ // GooglePlaceType is the Google Places type of the venue
+ //
+ // optional
+ GooglePlaceType string `json:"google_place_type"`
+}
+
+// InputContactMessageContent contains a contact for displaying
+// as an inline query result.
+type InputContactMessageContent struct {
+ // PhoneNumber contact's phone number
+ PhoneNumber string `json:"phone_number"`
+ // FirstName contact's first name
+ FirstName string `json:"first_name"`
+ // LastName contact's last name
+ //
+ // optional
+ LastName string `json:"last_name,omitempty"`
+ // Additional data about the contact in the form of a vCard
+ //
+ // optional
+ VCard string `json:"vcard,omitempty"`
+}
+
+// InputInvoiceMessageContent represents the content of an invoice message to be
+// sent as the result of an inline query.
+type InputInvoiceMessageContent struct {
+ // Product name, 1-32 characters
+ Title string `json:"title"`
+ // Product description, 1-255 characters
+ Description string `json:"description"`
+ // Bot-defined invoice payload, 1-128 bytes. This will not be displayed to
+ // the user, use for your internal processes.
+ Payload string `json:"payload"`
+ // Payment provider token, obtained via Botfather
+ ProviderToken string `json:"provider_token"`
+ // Three-letter ISO 4217 currency code
+ Currency string `json:"currency"`
+ // Price breakdown, a JSON-serialized list of components (e.g. product
+ // price, tax, discount, delivery cost, delivery tax, bonus, etc.)
+ Prices []LabeledPrice `json:"prices"`
+ // The maximum accepted amount for tips in the smallest units of the
+ // currency (integer, not float/double).
+ //
+ // optional
+ MaxTipAmount int `json:"max_tip_amount,omitempty"`
+ // An array of suggested amounts of tip in the smallest units of the
+ // currency (integer, not float/double). At most 4 suggested tip amounts can
+ // be specified. The suggested tip amounts must be positive, passed in a
+ // strictly increased order and must not exceed max_tip_amount.
+ //
+ // optional
+ SuggestedTipAmounts []int `json:"suggested_tip_amounts,omitempty"`
+ // A JSON-serialized object for data about the invoice, which will be shared
+ // with the payment provider. A detailed description of the required fields
+ // should be provided by the payment provider.
+ //
+ // optional
+ ProviderData string `json:"provider_data,omitempty"`
+ // URL of the product photo for the invoice. Can be a photo of the goods or
+ // a marketing image for a service. People like it better when they see what
+ // they are paying for.
+ //
+ // optional
+ PhotoURL string `json:"photo_url,omitempty"`
+ // Photo size
+ //
+ // optional
+ PhotoSize int `json:"photo_size,omitempty"`
+ // Photo width
+ //
+ // optional
+ PhotoWidth int `json:"photo_width,omitempty"`
+ // Photo height
+ //
+ // optional
+ PhotoHeight int `json:"photo_height,omitempty"`
+ // Pass True, if you require the user's full name to complete the order
+ //
+ // optional
+ NeedName bool `json:"need_name,omitempty"`
+ // Pass True, if you require the user's phone number to complete the order
+ //
+ // optional
+ NeedPhoneNumber bool `json:"need_phone_number,omitempty"`
+ // Pass True, if you require the user's email address to complete the order
+ //
+ // optional
+ NeedEmail bool `json:"need_email,omitempty"`
+ // Pass True, if you require the user's shipping address to complete the order
+ //
+ // optional
+ NeedShippingAddress bool `json:"need_shipping_address,omitempty"`
+ // Pass True, if user's phone number should be sent to provider
+ //
+ // optional
+ SendPhoneNumberToProvider bool `json:"send_phone_number_to_provider,omitempty"`
+ // Pass True, if user's email address should be sent to provider
+ //
+ // optional
+ SendEmailToProvider bool `json:"send_email_to_provider,omitempty"`
+ // Pass True, if the final price depends on the shipping method
+ //
+ // optional
+ IsFlexible bool `json:"is_flexible,omitempty"`
+}
+
+// LabeledPrice represents a portion of the price for goods or services.
+type LabeledPrice struct {
+ // Label portion label
+ Label string `json:"label"`
+ // Amount price of the product in the smallest units of the currency (integer, not float/double).
+ // For example, for a price of US$ 1.45 pass amount = 145.
+ // See the exp parameter in currencies.json
+ // (https://core.telegram.org/bots/payments/currencies.json),
+ // it shows the number of digits past the decimal point
+ // for each currency (2 for the majority of currencies).
+ Amount int `json:"amount"`
+}
+
+// Invoice contains basic information about an invoice.
+type Invoice struct {
+ // Title product name
+ Title string `json:"title"`
+ // Description product description
+ Description string `json:"description"`
+ // StartParameter unique bot deep-linking parameter that can be used to generate this invoice
+ StartParameter string `json:"start_parameter"`
+ // Currency three-letter ISO 4217 currency code
+ // (see https://core.telegram.org/bots/payments#supported-currencies)
+ Currency string `json:"currency"`
+ // TotalAmount total price in the smallest units of the currency (integer, not float/double).
+ // For example, for a price of US$ 1.45 pass amount = 145.
+ // See the exp parameter in currencies.json
+ // (https://core.telegram.org/bots/payments/currencies.json),
+ // it shows the number of digits past the decimal point
+ // for each currency (2 for the majority of currencies).
+ TotalAmount int `json:"total_amount"`
+}
+
+// ShippingAddress represents a shipping address.
+type ShippingAddress struct {
+ // CountryCode ISO 3166-1 alpha-2 country code
+ CountryCode string `json:"country_code"`
+ // State if applicable
+ State string `json:"state"`
+ // City city
+ City string `json:"city"`
+ // StreetLine1 first line for the address
+ StreetLine1 string `json:"street_line1"`
+ // StreetLine2 second line for the address
+ StreetLine2 string `json:"street_line2"`
+ // PostCode address post code
+ PostCode string `json:"post_code"`
+}
+
+// OrderInfo represents information about an order.
+type OrderInfo struct {
+ // Name user name
+ //
+ // optional
+ Name string `json:"name,omitempty"`
+ // PhoneNumber user's phone number
+ //
+ // optional
+ PhoneNumber string `json:"phone_number,omitempty"`
+ // Email user email
+ //
+ // optional
+ Email string `json:"email,omitempty"`
+ // ShippingAddress user shipping address
+ //
+ // optional
+ ShippingAddress *ShippingAddress `json:"shipping_address,omitempty"`
+}
+
+// ShippingOption represents one shipping option.
+type ShippingOption struct {
+ // ID shipping option identifier
+ ID string `json:"id"`
+ // Title option title
+ Title string `json:"title"`
+ // Prices list of price portions
+ Prices []LabeledPrice `json:"prices"`
+}
+
+// SuccessfulPayment contains basic information about a successful payment.
+type SuccessfulPayment struct {
+ // Currency three-letter ISO 4217 currency code
+ // (see https://core.telegram.org/bots/payments#supported-currencies)
+ Currency string `json:"currency"`
+ // TotalAmount total price in the smallest units of the currency (integer, not float/double).
+ // For example, for a price of US$ 1.45 pass amount = 145.
+ // See the exp parameter in currencies.json,
+ // (https://core.telegram.org/bots/payments/currencies.json)
+ // it shows the number of digits past the decimal point
+ // for each currency (2 for the majority of currencies).
+ TotalAmount int `json:"total_amount"`
+ // InvoicePayload bot specified invoice payload
+ InvoicePayload string `json:"invoice_payload"`
+ // ShippingOptionID identifier of the shipping option chosen by the user
+ //
+ // optional
+ ShippingOptionID string `json:"shipping_option_id,omitempty"`
+ // OrderInfo order info provided by the user
+ //
+ // optional
+ OrderInfo *OrderInfo `json:"order_info,omitempty"`
+ // TelegramPaymentChargeID telegram payment identifier
+ TelegramPaymentChargeID string `json:"telegram_payment_charge_id"`
+ // ProviderPaymentChargeID provider payment identifier
+ ProviderPaymentChargeID string `json:"provider_payment_charge_id"`
+}
+
+// ShippingQuery contains information about an incoming shipping query.
+type ShippingQuery struct {
+ // ID unique query identifier
+ ID string `json:"id"`
+ // From user who sent the query
+ From *User `json:"from"`
+ // InvoicePayload bot specified invoice payload
+ InvoicePayload string `json:"invoice_payload"`
+ // ShippingAddress user specified shipping address
+ ShippingAddress *ShippingAddress `json:"shipping_address"`
+}
+
+// PreCheckoutQuery contains information about an incoming pre-checkout query.
+type PreCheckoutQuery struct {
+ // ID unique query identifier
+ ID string `json:"id"`
+ // From user who sent the query
+ From *User `json:"from"`
+ // Currency three-letter ISO 4217 currency code
+ // // (see https://core.telegram.org/bots/payments#supported-currencies)
+ Currency string `json:"currency"`
+ // TotalAmount total price in the smallest units of the currency (integer, not float/double).
+ // // For example, for a price of US$ 1.45 pass amount = 145.
+ // // See the exp parameter in currencies.json,
+ // // (https://core.telegram.org/bots/payments/currencies.json)
+ // // it shows the number of digits past the decimal point
+ // // for each currency (2 for the majority of currencies).
+ TotalAmount int `json:"total_amount"`
+ // InvoicePayload bot specified invoice payload
+ InvoicePayload string `json:"invoice_payload"`
+ // ShippingOptionID identifier of the shipping option chosen by the user
+ //
+ // optional
+ ShippingOptionID string `json:"shipping_option_id,omitempty"`
+ // OrderInfo order info provided by the user
+ //
+ // optional
+ OrderInfo *OrderInfo `json:"order_info,omitempty"`
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
@@ -0,0 +1,8 @@
+# github.com/go-ini/ini v1.67.0
+## explicit
+github.com/go-ini/ini
+# github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1
+## explicit; go 1.16
+github.com/go-telegram-bot-api/telegram-bot-api/v5
+# github.com/stretchr/testify v1.8.1
+## explicit; go 1.13