Merge branch 'master' into fix-closing-update-channel-add-serverless-method
commit
0cbcc040dd
|
@ -0,0 +1,33 @@
|
|||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
- develop
|
||||
pull_request:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Test
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go 1.x
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ^1.15
|
||||
id: go
|
||||
|
||||
- name: Check out code into the Go module directory
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Build
|
||||
run: go build -v .
|
||||
|
||||
- name: Test
|
||||
run: go test -coverprofile=coverage.out -covermode=atomic -v .
|
||||
|
||||
- name: Upload coverage report
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
file: ./coverage.out
|
|
@ -1,3 +1,4 @@
|
|||
.idea/
|
||||
coverage.out
|
||||
tmp/
|
||||
book/
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
language: go
|
||||
|
||||
go:
|
||||
- '1.13'
|
||||
- '1.14'
|
||||
- tip
|
10
README.md
10
README.md
|
@ -1,7 +1,7 @@
|
|||
# Golang bindings for the Telegram Bot API
|
||||
|
||||
[![GoDoc](https://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api?status.svg)](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api)
|
||||
[![Travis](https://travis-ci.org/go-telegram-bot-api/telegram-bot-api.svg)](https://travis-ci.org/go-telegram-bot-api/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](http://godoc.org/github.com/go-telegram-bot-api/telegram-bot-api) page should
|
||||
explain everything. If something isn't clear, open an issue or submit
|
||||
|
@ -18,7 +18,7 @@ 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`.
|
||||
`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.
|
||||
|
@ -29,7 +29,7 @@ package main
|
|||
import (
|
||||
"log"
|
||||
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -62,7 +62,7 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
There are more examples on the [wiki](https://github.com/go-telegram-bot-api/telegram-bot-api/wiki)
|
||||
There are more examples on the [site](https://go-telegram-bot-api.dev/)
|
||||
with detailed information on how to do many different kinds of things.
|
||||
It's a great place to get started on using keyboards, commands, or other
|
||||
kinds of reply markup.
|
||||
|
|
|
@ -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"
|
607
bot_test.go
607
bot_test.go
File diff suppressed because it is too large
Load Diff
2553
configs.go
2553
configs.go
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,17 @@
|
|||
# Summary
|
||||
|
||||
- [Getting Started](./getting-started/README.md)
|
||||
- [Library Structure](./getting-started/library-structure.md)
|
||||
- [Files](./getting-started/files.md)
|
||||
- [Important Notes](./getting-started/important-notes.md)
|
||||
- [Examples](./examples/README.md)
|
||||
- [Command Handling](./examples/command-handling.md)
|
||||
- [Keyboard](./examples/keyboard.md)
|
||||
- [Inline Keyboard](./examples/inline-keyboard.md)
|
||||
- [Change Log](./changelog.md)
|
||||
|
||||
# Contributing
|
||||
|
||||
- [Internals](./internals/README.md)
|
||||
- [Adding Endpoints](./internals/adding-endpoints.md)
|
||||
- [Uploading Files](./internals/uploading-files.md)
|
|
@ -0,0 +1,19 @@
|
|||
# Change Log
|
||||
|
||||
## v5
|
||||
|
||||
**Work In Progress**
|
||||
|
||||
- Remove all methods that return `(APIResponse, error)`.
|
||||
- Use the `Request` method instead.
|
||||
- For more information, see [Library Structure][library-structure].
|
||||
- Remove all `New*Upload` and `New*Share` methods, replace with `New*`.
|
||||
- Use different [file types][files] to specify if upload or share.
|
||||
- Rename `UploadFile` to `UploadFiles`, accept `[]RequestFile` instead of a
|
||||
single fieldname and file.
|
||||
- Fix methods returning `APIResponse` and errors to always use pointers.
|
||||
- Update user IDs to `int64` because of Bot API changes.
|
||||
- Add missing Bot API features.
|
||||
|
||||
[library-structure]: ./getting-started/library-structure.md#methods
|
||||
[files]: ./getting-started/files.md
|
|
@ -0,0 +1,4 @@
|
|||
# Examples
|
||||
|
||||
With a better understanding of how the library works, let's look at some more
|
||||
examples showing off some of Telegram's features.
|
|
@ -0,0 +1,60 @@
|
|||
# Command Handling
|
||||
|
||||
This is a simple example of changing behavior based on a provided command.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||
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 { // ignore any non-Message updates
|
||||
continue
|
||||
}
|
||||
|
||||
if !update.Message.IsCommand() { // ignore any non-command Messages
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a new MessageConfig. We don't have text yet,
|
||||
// so we leave it empty.
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, "")
|
||||
|
||||
// Extract the command from the Message.
|
||||
switch update.Message.Command() {
|
||||
case "help":
|
||||
msg.Text = "I understand /sayhi and /status."
|
||||
case "sayhi":
|
||||
msg.Text = "Hi :)"
|
||||
case "status":
|
||||
msg.Text = "I'm ok."
|
||||
default:
|
||||
msg.Text = "I don't know that command"
|
||||
}
|
||||
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,80 @@
|
|||
# Inline Keyboard
|
||||
|
||||
This bot waits for you to send it the message "open" before sending you an
|
||||
inline keyboard containing a URL and some numbers. When a number is clicked, it
|
||||
sends you a message with your selected number.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
var numericKeyboard = tgbotapi.NewInlineKeyboardMarkup(
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonURL("1.com", "http://1.com"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("2", "2"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("3", "3"),
|
||||
),
|
||||
tgbotapi.NewInlineKeyboardRow(
|
||||
tgbotapi.NewInlineKeyboardButtonData("4", "4"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("5", "5"),
|
||||
tgbotapi.NewInlineKeyboardButtonData("6", "6"),
|
||||
),
|
||||
)
|
||||
|
||||
func main() {
|
||||
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||
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)
|
||||
|
||||
// Loop through each update.
|
||||
for update := range updates {
|
||||
// Check if we've gotten a message update.
|
||||
if update.Message != nil {
|
||||
// Construct a new message from the given chat ID and containing
|
||||
// the text that we received.
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
|
||||
|
||||
// If the message was open, add a copy of our numeric keyboard.
|
||||
switch update.Message.Text {
|
||||
case "open":
|
||||
msg.ReplyMarkup = numericKeyboard
|
||||
|
||||
}
|
||||
|
||||
// Send the message.
|
||||
if _, err = bot.Send(msg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
} else if update.CallbackQuery != nil {
|
||||
// Respond to the callback query, telling Telegram to show the user
|
||||
// a message with the data received.
|
||||
callback := tgbotapi.NewCallback(update.CallbackQuery.ID, update.CallbackQuery.Data)
|
||||
if _, err := bot.Request(callback); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// And finally, send a message containing the data received.
|
||||
msg := tgbotapi.NewMessage(update.CallbackQuery.Message.Chat.ID, update.CallbackQuery.Data)
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,63 @@
|
|||
# Keyboard
|
||||
|
||||
This bot shows a numeric keyboard when you send a "open" message and hides it
|
||||
when you send "close" message.
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
var numericKeyboard = tgbotapi.NewReplyKeyboard(
|
||||
tgbotapi.NewKeyboardButtonRow(
|
||||
tgbotapi.NewKeyboardButton("1"),
|
||||
tgbotapi.NewKeyboardButton("2"),
|
||||
tgbotapi.NewKeyboardButton("3"),
|
||||
),
|
||||
tgbotapi.NewKeyboardButtonRow(
|
||||
tgbotapi.NewKeyboardButton("4"),
|
||||
tgbotapi.NewKeyboardButton("5"),
|
||||
tgbotapi.NewKeyboardButton("6"),
|
||||
),
|
||||
)
|
||||
|
||||
func main() {
|
||||
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||
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 { // ignore non-Message updates
|
||||
continue
|
||||
}
|
||||
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
|
||||
|
||||
switch update.Message.Text {
|
||||
case "open":
|
||||
msg.ReplyMarkup = numericKeyboard
|
||||
case "close":
|
||||
msg.ReplyMarkup = tgbotapi.NewRemoveKeyboard(true)
|
||||
}
|
||||
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
log.Panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,109 @@
|
|||
# Getting Started
|
||||
|
||||
This library is designed as a simple wrapper around the Telegram Bot API.
|
||||
It's encouraged to read [Telegram's docs][telegram-docs] first to get an
|
||||
understanding of what Bots are capable of doing. They also provide some good
|
||||
approaches to solve common problems.
|
||||
|
||||
[telegram-docs]: https://core.telegram.org/bots
|
||||
|
||||
## Installing
|
||||
|
||||
```bash
|
||||
go get -u github.com/go-telegram-bot-api/telegram-bot-api/v5
|
||||
```
|
||||
|
||||
## A Simple Bot
|
||||
|
||||
To walk through the basics, let's create a simple echo bot that replies to your
|
||||
messages repeating what you said. Make sure you get an API token from
|
||||
[@Botfather][botfather] before continuing.
|
||||
|
||||
Let's start by constructing a new [BotAPI][bot-api-docs].
|
||||
|
||||
[botfather]: https://t.me/Botfather
|
||||
[bot-api-docs]: https://pkg.go.dev/github.com/go-telegram-bot-api/telegram-bot-api/v5?tab=doc#BotAPI
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
|
||||
)
|
||||
|
||||
func main() {
|
||||
bot, err := tgbotapi.NewBotAPI(os.Getenv("TELEGRAM_APITOKEN"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
bot.Debug = true
|
||||
}
|
||||
```
|
||||
|
||||
Instead of typing the API token directly into the file, we're using
|
||||
environment variables. This makes it easy to configure our Bot to use the right
|
||||
account and prevents us from leaking our real token into the world. Anyone with
|
||||
your token can send and receive messages from your Bot!
|
||||
|
||||
We've also set `bot.Debug = true` in order to get more information about the
|
||||
requests being sent to Telegram. If you run the example above, you'll see
|
||||
information about a request to the [`getMe`][get-me] endpoint. The library
|
||||
automatically calls this to ensure your token is working as expected. It also
|
||||
fills in the `Self` field in your `BotAPI` struct with information about the
|
||||
Bot.
|
||||
|
||||
Now that we've connected to Telegram, let's start getting updates and doing
|
||||
things. We can add this code in right after the line enabling debug mode.
|
||||
|
||||
[get-me]: https://core.telegram.org/bots/api#getme
|
||||
|
||||
```go
|
||||
// Create a new UpdateConfig struct with an offset of 0. Offsets are used
|
||||
// to make sure Telegram knows we've handled previous values and we don't
|
||||
// need them repeated.
|
||||
updateConfig := tgbotapi.NewUpdate(0)
|
||||
|
||||
// Tell Telegram we should wait up to 30 seconds on each request for an
|
||||
// update. This way we can get information just as quickly as making many
|
||||
// frequent requests without having to send nearly as many.
|
||||
updateConfig.Timeout = 30
|
||||
|
||||
// Start polling Telegram for updates.
|
||||
updates := bot.GetUpdatesChan(updateConfig)
|
||||
|
||||
// Let's go through each update that we're getting from Telegram.
|
||||
for update := range updates {
|
||||
// Telegram can send many types of updates depending on what your Bot
|
||||
// is up to. We only want to look at messages for now, so we can
|
||||
// discard any other updates.
|
||||
if update.Message == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
// Now that we know we've gotten a new message, we can construct a
|
||||
// reply! We'll take the Chat ID and Text from the incoming message
|
||||
// and use it to create a new message.
|
||||
msg := tgbotapi.NewMessage(update.Message.Chat.ID, update.Message.Text)
|
||||
// We'll also say that this message is a reply to the previous message.
|
||||
// For any other specifications than Chat ID or Text, you'll need to
|
||||
// set fields on the `MessageConfig`.
|
||||
msg.ReplyToMessageID = update.Message.MessageID
|
||||
|
||||
// Okay, we're sending our message off! We don't care about the message
|
||||
// we just sent, so we'll discard it.
|
||||
if _, err := bot.Send(msg); err != nil {
|
||||
// Note that panics are a bad way to handle errors. Telegram can
|
||||
// have service outages or network errors, you should retry sending
|
||||
// messages or more gracefully handle failures.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Congradulations! You've made your very own bot!
|
||||
|
||||
Now that you've got some of the basics down, we can start talking about how the
|
||||
library is structured and more advanced features.
|
|
@ -0,0 +1,68 @@
|
|||
# Files
|
||||
|
||||
Telegram supports specifying files in many different formats. In order to
|
||||
accommodate them all, there are multiple structs and type aliases required.
|
||||
|
||||
All of these types implement the `RequestFileData` interface.
|
||||
|
||||
| Type | Description |
|
||||
| ------------ | ------------------------------------------------------------------------- |
|
||||
| `FilePath` | A local path to a file |
|
||||
| `FileID` | Existing file ID on Telegram's servers |
|
||||
| `FileURL` | URL to file, must be served with expected MIME type |
|
||||
| `FileReader` | Use an `io.Reader` to provide a file. Lazily read to save memory. |
|
||||
| `FileBytes` | `[]byte` containing file data. Prefer to use `FileReader` to save memory. |
|
||||
|
||||
## `FilePath`
|
||||
|
||||
A path to a local file.
|
||||
|
||||
```go
|
||||
file := tgbotapi.FilePath("tests/image.jpg")
|
||||
```
|
||||
|
||||
## `FileID`
|
||||
|
||||
An ID previously uploaded to Telegram. IDs may only be reused by the same bot
|
||||
that received them. Additionally, thumbnail IDs cannot be reused.
|
||||
|
||||
```go
|
||||
file := tgbotapi.FileID("AgACAgIAAxkDAALesF8dCjAAAa_…")
|
||||
```
|
||||
|
||||
## `FileURL`
|
||||
|
||||
A URL to an existing resource. It must be served with a correct MIME type to
|
||||
work as expected.
|
||||
|
||||
```go
|
||||
file := tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg")
|
||||
```
|
||||
|
||||
## `FileReader`
|
||||
|
||||
Use an `io.Reader` to provide file contents as needed. Requires a filename for
|
||||
the virtual file.
|
||||
|
||||
```go
|
||||
var reader io.Reader
|
||||
|
||||
file := tgbotapi.FileReader{
|
||||
Name: "image.jpg",
|
||||
Reader: reader,
|
||||
}
|
||||
```
|
||||
|
||||
## `FileBytes`
|
||||
|
||||
Use a `[]byte` to provide file contents. Generally try to avoid this as it
|
||||
results in high memory usage. Also requires a filename for the virtual file.
|
||||
|
||||
```go
|
||||
var data []byte
|
||||
|
||||
file := tgbotapi.FileBytes{
|
||||
Name: "image.jpg",
|
||||
Bytes: data,
|
||||
}
|
||||
```
|
|
@ -0,0 +1,56 @@
|
|||
# Important Notes
|
||||
|
||||
The Telegram Bot API has a few potentially unanticipated behaviors. Here are a
|
||||
few of them. If any behavior was surprising to you, please feel free to open a
|
||||
pull request!
|
||||
|
||||
## Callback Queries
|
||||
|
||||
- Every callback query must be answered, even if there is nothing to display to
|
||||
the user. Failure to do so will show a loading icon on the keyboard until the
|
||||
operation times out.
|
||||
|
||||
## ChatMemberUpdated
|
||||
|
||||
- In order to receive `ChatMember` updates, you must explicitly add
|
||||
`UpdateTypeChatMember` to your `AllowedUpdates` when getting updates or
|
||||
setting your webhook.
|
||||
|
||||
## Entities use UTF16
|
||||
|
||||
- When extracting text entities using offsets and lengths, characters can appear
|
||||
to be in incorrect positions. This is because Telegram uses UTF16 lengths
|
||||
while Golang uses UTF8. It's possible to convert between the two, see
|
||||
[issue #231][issue-231] for more details.
|
||||
|
||||
[issue-231]: https://github.com/go-telegram-bot-api/telegram-bot-api/issues/231
|
||||
|
||||
## GetUpdatesChan
|
||||
|
||||
- This method is very basic and likely unsuitable for production use. Consider
|
||||
creating your own implementation instead, as it's very simple to replicate.
|
||||
- This method only allows your bot to process one update at a time. You can
|
||||
spawn goroutines to handle updates concurrently or switch to webhooks instead.
|
||||
Webhooks are suggested for high traffic bots.
|
||||
|
||||
## Nil Updates
|
||||
|
||||
- At most one of the fields in an `Update` will be set to a non-nil value. When
|
||||
evaluating updates, you must make sure you check that the field is not nil
|
||||
before trying to access any of it's fields.
|
||||
|
||||
## Privacy Mode
|
||||
|
||||
- By default, bots only get updates directly addressed to them. If you need to
|
||||
get all messages, you must disable privacy mode with Botfather. Bots already
|
||||
added to groups will need to be removed and readded for the changes to take
|
||||
effect. You can read more on the [Telegram Bot API docs][api-docs].
|
||||
|
||||
[api-docs]: https://core.telegram.org/bots/faq#what-messages-will-my-bot-get
|
||||
|
||||
## User and Chat ID size
|
||||
|
||||
- These types require up to 52 significant bits to store correctly, making a
|
||||
64-bit integer type required in most languages. They are already `int64` types
|
||||
in this library, but make sure you use correct types when saving them to a
|
||||
database or passing them to another language.
|
|
@ -0,0 +1,37 @@
|
|||
# Library Structure
|
||||
|
||||
This library is generally broken into three components you need to understand.
|
||||
|
||||
## Configs
|
||||
|
||||
Configs are collections of fields related to a single request. For example, if
|
||||
one wanted to use the `sendMessage` endpoint, you could use the `MessageConfig`
|
||||
struct to configure the request. There is a one-to-one relationship between
|
||||
Telegram endpoints and configs. They generally have the naming pattern of
|
||||
removing the `send` prefix and they all end with the `Config` suffix. They
|
||||
generally implement the `Chattable` interface. If they can send files, they
|
||||
implement the `Fileable` interface.
|
||||
|
||||
## Helpers
|
||||
|
||||
Helpers are easier ways of constructing common Configs. Instead of having to
|
||||
create a `MessageConfig` struct and remember to set the `ChatID` and `Text`,
|
||||
you can use the `NewMessage` helper method. It takes the two required parameters
|
||||
for the request to succeed. You can then set fields on the resulting
|
||||
`MessageConfig` after it's creation. They are generally named the same as
|
||||
method names except with `send` replaced with `New`.
|
||||
|
||||
## Methods
|
||||
|
||||
Methods are used to send Configs after they are constructed. Generally,
|
||||
`Request` is the lowest level method you'll have to call. It accepts a
|
||||
`Chattable` parameter and knows how to upload files if needed. It returns an
|
||||
`APIResponse`, the most general return type from the Bot API. This method is
|
||||
called for any endpoint that doesn't have a more specific return type. For
|
||||
example, `setWebhook` only returns `true` or an error. Other methods may have
|
||||
more specific return types. The `getFile` endpoint returns a `File`. Almost
|
||||
every other method returns a `Message`, which you can use `Send` to obtain.
|
||||
|
||||
There's lower level methods such as `MakeRequest` which require an endpoint and
|
||||
parameters instead of accepting configs. These are primarily used internally.
|
||||
If you find yourself having to use them, please open an issue.
|
|
@ -0,0 +1,4 @@
|
|||
# Internals
|
||||
|
||||
If you want to contribute to the project, here's some more information about
|
||||
the internal structure of the library.
|
|
@ -0,0 +1,197 @@
|
|||
# Adding Endpoints
|
||||
|
||||
This is mostly useful if you've managed to catch a new Telegram Bot API update
|
||||
before the library can get updated. It's also a great source of information
|
||||
about how the types work internally.
|
||||
|
||||
## Creating the Config
|
||||
|
||||
The first step in adding a new endpoint is to create a new Config type for it.
|
||||
These belong in `configs.go`.
|
||||
|
||||
Let's try and add the `deleteMessage` endpoint. We can see it requires two
|
||||
fields; `chat_id` and `message_id`. We can create a struct for these.
|
||||
|
||||
```go
|
||||
type DeleteMessageConfig struct {
|
||||
ChatID ???
|
||||
MessageID int
|
||||
}
|
||||
```
|
||||
|
||||
What type should `ChatID` be? Telegram allows specifying numeric chat IDs or
|
||||
channel usernames. Golang doesn't have union types, and interfaces are entirely
|
||||
untyped. This library solves this by adding two fields, a `ChatID` and a
|
||||
`ChannelUsername`. We can now write the struct as follows.
|
||||
|
||||
```go
|
||||
type DeleteMessageConfig struct {
|
||||
ChannelUsername string
|
||||
ChatID int64
|
||||
MessageID int
|
||||
}
|
||||
```
|
||||
|
||||
Note that `ChatID` is an `int64`. Telegram chat IDs can be greater than 32 bits.
|
||||
|
||||
Okay, we now have our struct. But we can't send it yet. It doesn't implement
|
||||
`Chattable` so it won't work with `Request` or `Send`.
|
||||
|
||||
### Making it `Chattable`
|
||||
|
||||
We can see that `Chattable` only requires a few methods.
|
||||
|
||||
```go
|
||||
type Chattable interface {
|
||||
params() (Params, error)
|
||||
method() string
|
||||
}
|
||||
```
|
||||
|
||||
`params` is the fields associated with the request. `method` is the endpoint
|
||||
that this Config is associated with.
|
||||
|
||||
Implementing the `method` is easy, so let's start with that.
|
||||
|
||||
```go
|
||||
func (config DeleteMessageConfig) method() string {
|
||||
return "deleteMessage"
|
||||
}
|
||||
```
|
||||
|
||||
Now we have to add the `params`. The `Params` type is an alias for
|
||||
`map[string]string`. Telegram expects only a single field for `chat_id`, so we
|
||||
have to determine what data to send.
|
||||
|
||||
We could use an if statement to determine which field to get the value from.
|
||||
However, as this is a relatively common operation, there's helper methods for
|
||||
`Params`. We can use the `AddFirstValid` method to go through each possible
|
||||
value and stop when it discovers a valid one. Before writing your own Config,
|
||||
it's worth taking a look through `params.go` to see what other helpers exist.
|
||||
|
||||
Now we can take a look at what a completed `params` method looks like.
|
||||
|
||||
```go
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
### Uploading Files
|
||||
|
||||
Let's imagine that for some reason deleting a message requires a document to be
|
||||
uploaded and an optional thumbnail for that document. To add file upload
|
||||
support we need to implement `Fileable`. This only requires one additional
|
||||
method.
|
||||
|
||||
```go
|
||||
type Fileable interface {
|
||||
Chattable
|
||||
files() []RequestFile
|
||||
}
|
||||
```
|
||||
|
||||
First, let's add some fields to store our files in. Most of the standard Configs
|
||||
have similar fields for their files.
|
||||
|
||||
```diff
|
||||
type DeleteMessageConfig struct {
|
||||
ChannelUsername string
|
||||
ChatID int64
|
||||
MessageID int
|
||||
+ Delete RequestFileData
|
||||
+ Thumb RequestFileData
|
||||
}
|
||||
```
|
||||
|
||||
Adding another method is pretty simple. We'll always add a file named `delete`
|
||||
and add the `thumb` file if we have one.
|
||||
|
||||
```go
|
||||
func (config DeleteMessageConfig) files() []RequestFile {
|
||||
files := []RequestFile{{
|
||||
Name: "delete",
|
||||
Data: config.Delete,
|
||||
}}
|
||||
|
||||
if config.Thumb != nil {
|
||||
files = append(files, RequestFile{
|
||||
Name: "thumb",
|
||||
Data: config.Thumb,
|
||||
})
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
```
|
||||
|
||||
And now our files will upload! It will transparently handle uploads whether File
|
||||
is a `FilePath`, `FileURL`, `FileBytes`, `FileReader`, or `FileID`.
|
||||
|
||||
### Base Configs
|
||||
|
||||
Certain Configs have repeated elements. For example, many of the items sent to a
|
||||
chat have `ChatID` or `ChannelUsername` fields, along with `ReplyToMessageID`,
|
||||
`ReplyMarkup`, and `DisableNotification`. Instead of implementing all of this
|
||||
code for each item, there's a `BaseChat` that handles it for your Config.
|
||||
Simply embed it in your struct to get all of those fields.
|
||||
|
||||
There's only a few fields required for the `MessageConfig` struct after
|
||||
embedding the `BaseChat` struct.
|
||||
|
||||
```go
|
||||
type MessageConfig struct {
|
||||
BaseChat
|
||||
Text string
|
||||
ParseMode string
|
||||
DisableWebPagePreview bool
|
||||
}
|
||||
```
|
||||
|
||||
It also inherits the `params` method from `BaseChat`. This allows you to call
|
||||
it, then you only have to add your new fields.
|
||||
|
||||
```go
|
||||
func (config MessageConfig) params() (Params, error) {
|
||||
params, err := config.BaseChat.params()
|
||||
if err != nil {
|
||||
return params, err
|
||||
}
|
||||
|
||||
params.AddNonEmpty("text", config.Text)
|
||||
// Add your other fields
|
||||
|
||||
return params, nil
|
||||
}
|
||||
```
|
||||
|
||||
Similarly, there's a `BaseFile` struct for adding an associated file and
|
||||
`BaseEdit` struct for editing messages.
|
||||
|
||||
## Making it Friendly
|
||||
|
||||
After we've got a Config type, we'll want to make it more user-friendly. We can
|
||||
do this by adding a new helper to `helpers.go`. These are functions that take
|
||||
in the required data for the request to succeed and populate a Config.
|
||||
|
||||
Telegram only requires two fields to call `deleteMessage`, so this will be fast.
|
||||
|
||||
```go
|
||||
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
|
||||
return DeleteMessageConfig{
|
||||
ChatID: chatID,
|
||||
MessageID: messageID,
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Sometimes it makes sense to add more helpers if there's methods where you have
|
||||
to set exactly one field. You can also add helpers that accept a `username`
|
||||
string for channels if it's a common operation.
|
||||
|
||||
And that's it! You've added a new method.
|
|
@ -0,0 +1,87 @@
|
|||
# Uploading Files
|
||||
|
||||
To make files work as expected, there's a lot going on behind the scenes. Make
|
||||
sure to read through the [Files](../getting-started/files.md) section in
|
||||
Getting Started first as we'll be building on that information.
|
||||
|
||||
This section only talks about file uploading. For non-uploaded files such as
|
||||
URLs and file IDs, you just need to pass a string.
|
||||
|
||||
## Fields
|
||||
|
||||
Let's start by talking about how the library represents files as part of a
|
||||
Config.
|
||||
|
||||
### Static Fields
|
||||
|
||||
Most endpoints use static file fields. For example, `sendPhoto` expects a single
|
||||
file named `photo`. All we have to do is set that single field with the correct
|
||||
value (either a string or multipart file). Methods like `sendDocument` take two
|
||||
file uploads, a `document` and a `thumb`. These are pretty straightforward.
|
||||
|
||||
Remembering that the `Fileable` interface only requires one method, let's
|
||||
implement it for `DocumentConfig`.
|
||||
|
||||
```go
|
||||
func (config DocumentConfig) files() []RequestFile {
|
||||
// We can have multiple files, so we'll create an array. We also know that
|
||||
// there always is a document file, so initialize the array with that.
|
||||
files := []RequestFile{{
|
||||
Name: "document",
|
||||
Data: config.File,
|
||||
}}
|
||||
|
||||
// We'll only add a file if we have one.
|
||||
if config.Thumb != nil {
|
||||
files = append(files, RequestFile{
|
||||
Name: "thumb",
|
||||
Data: config.Thumb,
|
||||
})
|
||||
}
|
||||
|
||||
return files
|
||||
}
|
||||
```
|
||||
|
||||
Telegram also supports the `attach://` syntax (discussed more later) for
|
||||
thumbnails, but there's no reason to make things more complicated.
|
||||
|
||||
### Dynamic Fields
|
||||
|
||||
Of course, not everything can be so simple. Methods like `sendMediaGroup`
|
||||
can accept many files, and each file can have custom markup. Using a static
|
||||
field isn't possible because we need to specify which field is attached to each
|
||||
item. Telegram introduced the `attach://` syntax for this.
|
||||
|
||||
Let's follow through creating a new media group with string and file uploads.
|
||||
|
||||
First, we start by creating some `InputMediaPhoto`.
|
||||
|
||||
```go
|
||||
photo := tgbotapi.NewInputMediaPhoto(tgbotapi.FilePath("tests/image.jpg"))
|
||||
url := tgbotapi.NewInputMediaPhoto(tgbotapi.FileURL("https://i.imgur.com/unQLJIb.jpg"))
|
||||
```
|
||||
|
||||
This created a new `InputMediaPhoto` struct, with a type of `photo` and the
|
||||
media interface that we specified.
|
||||
|
||||
We'll now create our media group with the photo and URL.
|
||||
|
||||
```go
|
||||
mediaGroup := NewMediaGroup(ChatID, []interface{}{
|
||||
photo,
|
||||
url,
|
||||
})
|
||||
```
|
||||
|
||||
A `MediaGroupConfig` stores all of the media in an array of interfaces. We now
|
||||
have all of the data we need to upload, but how do we figure out field names for
|
||||
uploads? We didn't specify `attach://unique-file` anywhere.
|
||||
|
||||
When the library goes to upload the files, it looks at the `params` and `files`
|
||||
for the Config. The params are generated by transforming the file into a value
|
||||
more suitable for uploading, file IDs and URLs are untouched but uploaded types
|
||||
are all changed into `attach://file-%d`. When collecting a list of files to
|
||||
upload, it names them the same way. This creates a nearly transparent way of
|
||||
handling multiple files in the background without the user having to consider
|
||||
what's going on.
|
6
go.mod
6
go.mod
|
@ -1,5 +1,3 @@
|
|||
module github.com/go-telegram-bot-api/telegram-bot-api
|
||||
module github.com/go-telegram-bot-api/telegram-bot-api/v5
|
||||
|
||||
go 1.12
|
||||
|
||||
require github.com/technoweenie/multipartstreamer v1.0.1
|
||||
go 1.16
|
||||
|
|
2
go.sum
2
go.sum
|
@ -1,2 +0,0 @@
|
|||
github.com/technoweenie/multipartstreamer v1.0.1 h1:XRztA5MXiR1TIRHxH2uNxXxaIkKQDeX7m2XsSOlQEnM=
|
||||
github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6MDx5eUJoiEBsSvzDU9uzog=
|
552
helpers.go
552
helpers.go
|
@ -18,30 +18,6 @@ func NewMessage(chatID int64, text string) MessageConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// NewDice creates a new DiceConfig.
|
||||
//
|
||||
// chatID is where to send it
|
||||
func NewDice(chatID int64) DiceConfig {
|
||||
return DiceConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDiceWithEmoji creates a new DiceConfig.
|
||||
//
|
||||
// chatID is where to send it
|
||||
// emoji is type of the Dice
|
||||
func NewDiceWithEmoji(chatID int64, emoji string) DiceConfig {
|
||||
return DiceConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
Emoji: emoji,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDeleteMessage creates a request to delete a message.
|
||||
func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
|
||||
return DeleteMessageConfig{
|
||||
|
@ -76,241 +52,117 @@ func NewForward(chatID int64, fromChatID int64, messageID int) ForwardConfig {
|
|||
}
|
||||
}
|
||||
|
||||
// NewPhotoUpload creates a new photo uploader.
|
||||
// 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 NewPhotoUpload(chatID int64, file interface{}) PhotoConfig {
|
||||
func NewPhoto(chatID int64, file RequestFileData) PhotoConfig {
|
||||
return PhotoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewPhotoShare shares an existing photo.
|
||||
// You may use this to reshare an existing photo without reuploading it.
|
||||
// NewPhotoToChannel creates a new photo uploader to send a photo to a channel.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the file
|
||||
// already uploaded.
|
||||
func NewPhotoShare(chatID int64, fileID string) PhotoConfig {
|
||||
// Note that you must send animated GIFs as a document.
|
||||
func NewPhotoToChannel(username string, file RequestFileData) PhotoConfig {
|
||||
return PhotoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
BaseChat: BaseChat{
|
||||
ChannelUsername: username,
|
||||
},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAudioUpload creates a new audio uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewAudioUpload(chatID int64, file interface{}) AudioConfig {
|
||||
// NewAudio creates a new sendAudio request.
|
||||
func NewAudio(chatID int64, file RequestFileData) AudioConfig {
|
||||
return AudioConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAudioShare shares an existing audio file.
|
||||
// You may use this to reshare an existing audio file without
|
||||
// reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the audio
|
||||
// already uploaded.
|
||||
func NewAudioShare(chatID int64, fileID string) AudioConfig {
|
||||
return AudioConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDocumentUpload creates a new document uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewDocumentUpload(chatID int64, file interface{}) DocumentConfig {
|
||||
// NewDocument creates a new sendDocument request.
|
||||
func NewDocument(chatID int64, file RequestFileData) DocumentConfig {
|
||||
return DocumentConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDocumentShare shares an existing document.
|
||||
// You may use this to reshare an existing document without
|
||||
// reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the document
|
||||
// already uploaded.
|
||||
func NewDocumentShare(chatID int64, fileID string) DocumentConfig {
|
||||
return DocumentConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStickerUpload creates a new sticker uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewStickerUpload(chatID int64, file interface{}) StickerConfig {
|
||||
// NewSticker creates a new sendSticker request.
|
||||
func NewSticker(chatID int64, file RequestFileData) StickerConfig {
|
||||
return StickerConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewStickerShare shares an existing sticker.
|
||||
// You may use this to reshare an existing sticker without
|
||||
// reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the sticker
|
||||
// already uploaded.
|
||||
func NewStickerShare(chatID int64, fileID string) StickerConfig {
|
||||
return StickerConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVideoUpload creates a new video uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewVideoUpload(chatID int64, file interface{}) VideoConfig {
|
||||
// NewVideo creates a new sendVideo request.
|
||||
func NewVideo(chatID int64, file RequestFileData) VideoConfig {
|
||||
return VideoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVideoShare shares an existing video.
|
||||
// You may use this to reshare an existing video without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the video
|
||||
// already uploaded.
|
||||
func NewVideoShare(chatID int64, fileID string) VideoConfig {
|
||||
return VideoConfig{
|
||||
// NewAnimation creates a new sendAnimation request.
|
||||
func NewAnimation(chatID int64, file RequestFileData) AnimationConfig {
|
||||
return AnimationConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAnimationUpload creates a new animation uploader.
|
||||
// NewVideoNote creates a new sendVideoNote request.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewAnimationUpload(chatID int64, file interface{}) AnimationConfig {
|
||||
return AnimationConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewAnimationShare shares an existing animation.
|
||||
// You may use this to reshare an existing animation without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the animation
|
||||
// already uploaded.
|
||||
func NewAnimationShare(chatID int64, fileID string) AnimationConfig {
|
||||
return AnimationConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVideoNoteUpload creates a new video note uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewVideoNoteUpload(chatID int64, length int, file interface{}) VideoNoteConfig {
|
||||
func NewVideoNote(chatID int64, length int, file RequestFileData) VideoNoteConfig {
|
||||
return VideoNoteConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
Length: length,
|
||||
}
|
||||
}
|
||||
|
||||
// NewVideoNoteShare shares an existing video.
|
||||
// You may use this to reshare an existing video without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the video
|
||||
// already uploaded.
|
||||
func NewVideoNoteShare(chatID int64, length int, fileID string) VideoNoteConfig {
|
||||
return VideoNoteConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
},
|
||||
Length: length,
|
||||
}
|
||||
}
|
||||
|
||||
// NewVoiceUpload creates a new voice uploader.
|
||||
//
|
||||
// chatID is where to send it, file is a string path to the file,
|
||||
// FileReader, or FileBytes.
|
||||
func NewVoiceUpload(chatID int64, file interface{}) VoiceConfig {
|
||||
// NewVoice creates a new sendVoice request.
|
||||
func NewVoice(chatID int64, file RequestFileData) VoiceConfig {
|
||||
return VoiceConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
UseExisting: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVoiceShare shares an existing voice.
|
||||
// You may use this to reshare an existing voice without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the video
|
||||
// already uploaded.
|
||||
func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
|
||||
return VoiceConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
File: file,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -319,26 +171,58 @@ func NewVoiceShare(chatID int64, fileID string) VoiceConfig {
|
|||
// two to ten InputMediaPhoto or InputMediaVideo.
|
||||
func NewMediaGroup(chatID int64, files []interface{}) MediaGroupConfig {
|
||||
return MediaGroupConfig{
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
InputMedia: files,
|
||||
ChatID: chatID,
|
||||
Media: files,
|
||||
}
|
||||
}
|
||||
|
||||
// NewInputMediaPhoto creates a new InputMediaPhoto.
|
||||
func NewInputMediaPhoto(media string) InputMediaPhoto {
|
||||
func NewInputMediaPhoto(media RequestFileData) InputMediaPhoto {
|
||||
return InputMediaPhoto{
|
||||
Type: "photo",
|
||||
Media: media,
|
||||
BaseInputMedia{
|
||||
Type: "photo",
|
||||
Media: media,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewInputMediaVideo creates a new InputMediaVideo.
|
||||
func NewInputMediaVideo(media string) InputMediaVideo {
|
||||
func NewInputMediaVideo(media RequestFileData) InputMediaVideo {
|
||||
return InputMediaVideo{
|
||||
Type: "video",
|
||||
Media: media,
|
||||
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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -393,7 +277,7 @@ func NewChatAction(chatID int64, action string) ChatActionConfig {
|
|||
// NewUserProfilePhotos gets user profile photos.
|
||||
//
|
||||
// userID is the ID of the user you wish to get profile photos from.
|
||||
func NewUserProfilePhotos(userID int) UserProfilePhotosConfig {
|
||||
func NewUserProfilePhotos(userID int64) UserProfilePhotosConfig {
|
||||
return UserProfilePhotosConfig{
|
||||
UserID: userID,
|
||||
Offset: 0,
|
||||
|
@ -416,25 +300,33 @@ func NewUpdate(offset int) UpdateConfig {
|
|||
// NewWebhook creates a new webhook.
|
||||
//
|
||||
// link is the url parsable link you wish to get the updates.
|
||||
func NewWebhook(link string) WebhookConfig {
|
||||
u, _ := url.Parse(link)
|
||||
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 interface{}) WebhookConfig {
|
||||
u, _ := url.Parse(link)
|
||||
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.
|
||||
|
@ -502,7 +394,7 @@ func NewInlineQueryResultCachedGIF(id, gifID string) InlineQueryResultCachedGIF
|
|||
return InlineQueryResultCachedGIF{
|
||||
Type: "gif",
|
||||
ID: id,
|
||||
GifID: gifID,
|
||||
GIFID: gifID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -516,11 +408,11 @@ func NewInlineQueryResultMPEG4GIF(id, url string) InlineQueryResultMPEG4GIF {
|
|||
}
|
||||
|
||||
// NewInlineQueryResultCachedMPEG4GIF create a new inline query with cached MPEG4 GIF.
|
||||
func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GifID string) InlineQueryResultCachedMpeg4Gif {
|
||||
return InlineQueryResultCachedMpeg4Gif{
|
||||
Type: "mpeg4_gif",
|
||||
ID: id,
|
||||
MGifID: MPEG4GifID,
|
||||
func NewInlineQueryResultCachedMPEG4GIF(id, MPEG4GIFID string) InlineQueryResultCachedMPEG4GIF {
|
||||
return InlineQueryResultCachedMPEG4GIF{
|
||||
Type: "mpeg4_gif",
|
||||
ID: id,
|
||||
MPEG4FileID: MPEG4GIFID,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -710,17 +602,6 @@ func NewEditMessageReplyMarkup(chatID int64, messageID int, replyMarkup InlineKe
|
|||
}
|
||||
}
|
||||
|
||||
// NewHideKeyboard hides the keyboard, with the option for being selective
|
||||
// or hiding for everyone.
|
||||
func NewHideKeyboard(selective bool) ReplyKeyboardHide {
|
||||
log.Println("NewHideKeyboard is deprecated, please use NewRemoveKeyboard")
|
||||
|
||||
return ReplyKeyboardHide{
|
||||
HideKeyboard: true,
|
||||
Selective: selective,
|
||||
}
|
||||
}
|
||||
|
||||
// NewRemoveKeyboard hides the keyboard, with the option for being selective
|
||||
// or hiding for everyone.
|
||||
func NewRemoveKeyboard(selective bool) ReplyKeyboardRemove {
|
||||
|
@ -792,6 +673,15 @@ func NewInlineKeyboardButtonData(text, data string) InlineKeyboardButton {
|
|||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
|
@ -850,7 +740,7 @@ func NewCallbackWithAlert(id, text string) CallbackConfig {
|
|||
}
|
||||
|
||||
// NewInvoice creates a new Invoice request to the user.
|
||||
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices *[]LabeledPrice) InvoiceConfig {
|
||||
func NewInvoice(chatID int64, title, description, payload, providerToken, startParameter, currency string, prices []LabeledPrice) InvoiceConfig {
|
||||
return InvoiceConfig{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
Title: title,
|
||||
|
@ -862,33 +752,183 @@ func NewInvoice(chatID int64, title, description, payload, providerToken, startP
|
|||
Prices: prices}
|
||||
}
|
||||
|
||||
// NewSetChatPhotoUpload creates a new chat photo uploader.
|
||||
//
|
||||
// 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 NewSetChatPhotoUpload(chatID int64, file interface{}) SetChatPhotoConfig {
|
||||
// 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: file,
|
||||
UseExisting: false,
|
||||
BaseChat: BaseChat{
|
||||
ChatID: chatID,
|
||||
},
|
||||
File: photo,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSetChatPhotoShare shares an existing photo.
|
||||
// You may use this to reshare an existing photo without reuploading it.
|
||||
//
|
||||
// chatID is where to send it, fileID is the ID of the file
|
||||
// already uploaded.
|
||||
func NewSetChatPhotoShare(chatID int64, fileID string) SetChatPhotoConfig {
|
||||
return SetChatPhotoConfig{
|
||||
BaseFile: BaseFile{
|
||||
BaseChat: BaseChat{ChatID: chatID},
|
||||
FileID: fileID,
|
||||
UseExisting: true,
|
||||
// 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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewSendDice allows you to send a random dice roll.
|
||||
//
|
||||
// Deprecated: Use NewDice instead.
|
||||
func NewSendDice(chatID int64) DiceConfig {
|
||||
return NewDice(chatID)
|
||||
}
|
||||
|
||||
// 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}
|
||||
}
|
||||
|
||||
// NewSetMyCommands allows you to set the registered commands for a given scope.
|
||||
func NewSetMyCommandsWithScope(scope BotCommandScope, commands ...BotCommand) SetMyCommandsConfig {
|
||||
return SetMyCommandsConfig{Commands: commands, Scope: &scope}
|
||||
}
|
||||
|
||||
// NewSetMyCommands 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{}
|
||||
}
|
||||
|
||||
// NewDeleteMyCommands allows you to delete the registered commands for a given
|
||||
// scope.
|
||||
func NewDeleteMyCommandsWithScope(scope BotCommandScope) DeleteMyCommandsConfig {
|
||||
return DeleteMyCommandsConfig{Scope: &scope}
|
||||
}
|
||||
|
||||
// NewDeleteMyCommands 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}
|
||||
}
|
||||
|
|
|
@ -1,48 +1,71 @@
|
|||
package tgbotapi_test
|
||||
package tgbotapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
func TestNewWebhook(t *testing.T) {
|
||||
result, err := NewWebhook("https://example.com/token")
|
||||
|
||||
if err != nil ||
|
||||
result.URL.String() != "https://example.com/token" ||
|
||||
result.Certificate != interface{}(nil) ||
|
||||
result.MaxConnections != 0 ||
|
||||
len(result.AllowedUpdates) != 0 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWebhookWithCert(t *testing.T) {
|
||||
exampleFile := FileID("123")
|
||||
result, err := NewWebhookWithCert("https://example.com/token", exampleFile)
|
||||
|
||||
if err != nil ||
|
||||
result.URL.String() != "https://example.com/token" ||
|
||||
result.Certificate != exampleFile ||
|
||||
result.MaxConnections != 0 ||
|
||||
len(result.AllowedUpdates) != 0 {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInlineQueryResultArticle(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultArticle("id", "title", "message")
|
||||
result := NewInlineQueryResultArticle("id", "title", "message")
|
||||
|
||||
if result.Type != "article" ||
|
||||
result.ID != "id" ||
|
||||
result.Title != "title" ||
|
||||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "message" {
|
||||
result.InputMessageContent.(InputTextMessageContent).Text != "message" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInlineQueryResultArticleMarkdown(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
|
||||
result := NewInlineQueryResultArticleMarkdown("id", "title", "*message*")
|
||||
|
||||
if result.Type != "article" ||
|
||||
result.ID != "id" ||
|
||||
result.Title != "title" ||
|
||||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "*message*" ||
|
||||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "Markdown" {
|
||||
result.InputMessageContent.(InputTextMessageContent).Text != "*message*" ||
|
||||
result.InputMessageContent.(InputTextMessageContent).ParseMode != "Markdown" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInlineQueryResultArticleHTML(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
|
||||
result := NewInlineQueryResultArticleHTML("id", "title", "<b>message</b>")
|
||||
|
||||
if result.Type != "article" ||
|
||||
result.ID != "id" ||
|
||||
result.Title != "title" ||
|
||||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).Text != "<b>message</b>" ||
|
||||
result.InputMessageContent.(tgbotapi.InputTextMessageContent).ParseMode != "HTML" {
|
||||
result.InputMessageContent.(InputTextMessageContent).Text != "<b>message</b>" ||
|
||||
result.InputMessageContent.(InputTextMessageContent).ParseMode != "HTML" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewInlineQueryResultGIF(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultGIF("id", "google.com")
|
||||
result := NewInlineQueryResultGIF("id", "google.com")
|
||||
|
||||
if result.Type != "gif" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -52,7 +75,7 @@ func TestNewInlineQueryResultGIF(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultMPEG4GIF("id", "google.com")
|
||||
result := NewInlineQueryResultMPEG4GIF("id", "google.com")
|
||||
|
||||
if result.Type != "mpeg4_gif" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -62,7 +85,7 @@ func TestNewInlineQueryResultMPEG4GIF(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultPhoto(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultPhoto("id", "google.com")
|
||||
result := NewInlineQueryResultPhoto("id", "google.com")
|
||||
|
||||
if result.Type != "photo" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -72,7 +95,7 @@ func TestNewInlineQueryResultPhoto(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com")
|
||||
result := NewInlineQueryResultPhotoWithThumb("id", "google.com", "thumb.com")
|
||||
|
||||
if result.Type != "photo" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -83,7 +106,7 @@ func TestNewInlineQueryResultPhotoWithThumb(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultVideo(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultVideo("id", "google.com")
|
||||
result := NewInlineQueryResultVideo("id", "google.com")
|
||||
|
||||
if result.Type != "video" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -93,7 +116,7 @@ func TestNewInlineQueryResultVideo(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultAudio(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultAudio("id", "google.com", "title")
|
||||
result := NewInlineQueryResultAudio("id", "google.com", "title")
|
||||
|
||||
if result.Type != "audio" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -104,7 +127,7 @@ func TestNewInlineQueryResultAudio(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultVoice(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultVoice("id", "google.com", "title")
|
||||
result := NewInlineQueryResultVoice("id", "google.com", "title")
|
||||
|
||||
if result.Type != "voice" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -115,7 +138,7 @@ func TestNewInlineQueryResultVoice(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultDocument(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultDocument("id", "google.com", "title", "mime/type")
|
||||
result := NewInlineQueryResultDocument("id", "google.com", "title", "mime/type")
|
||||
|
||||
if result.Type != "document" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -127,7 +150,7 @@ func TestNewInlineQueryResultDocument(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewInlineQueryResultLocation(t *testing.T) {
|
||||
result := tgbotapi.NewInlineQueryResultLocation("id", "name", 40, 50)
|
||||
result := NewInlineQueryResultLocation("id", "name", 40, 50)
|
||||
|
||||
if result.Type != "location" ||
|
||||
result.ID != "id" ||
|
||||
|
@ -138,8 +161,25 @@ func TestNewInlineQueryResultLocation(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestNewInlineKeyboardButtonLoginURL(t *testing.T) {
|
||||
result := NewInlineKeyboardButtonLoginURL("text", LoginURL{
|
||||
URL: "url",
|
||||
ForwardText: "ForwardText",
|
||||
BotUsername: "username",
|
||||
RequestWriteAccess: false,
|
||||
})
|
||||
|
||||
if result.Text != "text" ||
|
||||
result.LoginURL.URL != "url" ||
|
||||
result.LoginURL.ForwardText != "ForwardText" ||
|
||||
result.LoginURL.BotUsername != "username" ||
|
||||
result.LoginURL.RequestWriteAccess != false {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewEditMessageText(t *testing.T) {
|
||||
edit := tgbotapi.NewEditMessageText(ChatID, ReplyToMessageID, "new text")
|
||||
edit := NewEditMessageText(ChatID, ReplyToMessageID, "new text")
|
||||
|
||||
if edit.Text != "new text" ||
|
||||
edit.BaseEdit.ChatID != ChatID ||
|
||||
|
@ -149,7 +189,7 @@ func TestNewEditMessageText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewEditMessageCaption(t *testing.T) {
|
||||
edit := tgbotapi.NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption")
|
||||
edit := NewEditMessageCaption(ChatID, ReplyToMessageID, "new caption")
|
||||
|
||||
if edit.Caption != "new caption" ||
|
||||
edit.BaseEdit.ChatID != ChatID ||
|
||||
|
@ -159,15 +199,15 @@ func TestNewEditMessageCaption(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewEditMessageReplyMarkup(t *testing.T) {
|
||||
markup := tgbotapi.InlineKeyboardMarkup{
|
||||
InlineKeyboard: [][]tgbotapi.InlineKeyboardButton{
|
||||
[]tgbotapi.InlineKeyboardButton{
|
||||
tgbotapi.InlineKeyboardButton{Text: "test"},
|
||||
markup := InlineKeyboardMarkup{
|
||||
InlineKeyboard: [][]InlineKeyboardButton{
|
||||
{
|
||||
{Text: "test"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
edit := tgbotapi.NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
|
||||
edit := NewEditMessageReplyMarkup(ChatID, ReplyToMessageID, markup)
|
||||
|
||||
if edit.ReplyMarkup.InlineKeyboard[0][0].Text != "test" ||
|
||||
edit.BaseEdit.ChatID != ChatID ||
|
||||
|
@ -178,7 +218,7 @@ func TestNewEditMessageReplyMarkup(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewDice(t *testing.T) {
|
||||
dice := tgbotapi.NewDice(42)
|
||||
dice := NewDice(42)
|
||||
|
||||
if dice.ChatID != 42 ||
|
||||
dice.Emoji != "" {
|
||||
|
@ -187,7 +227,7 @@ func TestNewDice(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestNewDiceWithEmoji(t *testing.T) {
|
||||
dice := tgbotapi.NewDiceWithEmoji(42, "🏀")
|
||||
dice := NewDiceWithEmoji(42, "🏀")
|
||||
|
||||
if dice.ChatID != 42 ||
|
||||
dice.Emoji != "🏀" {
|
||||
|
|
19
params.go
19
params.go
|
@ -2,7 +2,6 @@ package tgbotapi
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
@ -10,24 +9,6 @@ import (
|
|||
// Params represents a set of parameters that gets passed to a request.
|
||||
type Params map[string]string
|
||||
|
||||
func newParams(values url.Values) Params {
|
||||
params := Params{}
|
||||
for k, v := range values {
|
||||
if len(v) > 0 {
|
||||
params[k] = v[0]
|
||||
}
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
||||
func (p Params) toValues() url.Values {
|
||||
values := url.Values{}
|
||||
for k, v := range p {
|
||||
values[k] = []string{v}
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
// AddNonEmpty adds a value if it not an empty string.
|
||||
func (p Params) AddNonEmpty(key, value string) {
|
||||
if value != "" {
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
package tgbotapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func assertLen(t *testing.T, params Params, l int) {
|
||||
actual := len(params)
|
||||
if actual != l {
|
||||
t.Fatalf("Incorrect number of params, expected %d but found %d\n", l, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func assertEq(t *testing.T, a interface{}, b interface{}) {
|
||||
if a != b {
|
||||
t.Fatalf("Values did not match, a: %v, b: %v\n", a, b)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddNonEmpty(t *testing.T) {
|
||||
params := make(Params)
|
||||
params.AddNonEmpty("value", "value")
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], "value")
|
||||
params.AddNonEmpty("test", "")
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["test"], "")
|
||||
}
|
||||
|
||||
func TestAddNonZero(t *testing.T) {
|
||||
params := make(Params)
|
||||
params.AddNonZero("value", 1)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], "1")
|
||||
params.AddNonZero("test", 0)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["test"], "")
|
||||
}
|
||||
|
||||
func TestAddNonZero64(t *testing.T) {
|
||||
params := make(Params)
|
||||
params.AddNonZero64("value", 1)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], "1")
|
||||
params.AddNonZero64("test", 0)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["test"], "")
|
||||
}
|
||||
|
||||
func TestAddBool(t *testing.T) {
|
||||
params := make(Params)
|
||||
params.AddBool("value", true)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], "true")
|
||||
params.AddBool("test", false)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["test"], "")
|
||||
}
|
||||
|
||||
func TestAddNonZeroFloat(t *testing.T) {
|
||||
params := make(Params)
|
||||
params.AddNonZeroFloat("value", 1)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], "1.000000")
|
||||
params.AddNonZeroFloat("test", 0)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["test"], "")
|
||||
}
|
||||
|
||||
func TestAddInterface(t *testing.T) {
|
||||
params := make(Params)
|
||||
data := struct {
|
||||
Name string `json:"name"`
|
||||
}{
|
||||
Name: "test",
|
||||
}
|
||||
params.AddInterface("value", data)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], `{"name":"test"}`)
|
||||
params.AddInterface("test", nil)
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["test"], "")
|
||||
}
|
||||
|
||||
func TestAddFirstValid(t *testing.T) {
|
||||
params := make(Params)
|
||||
params.AddFirstValid("value", 0, "", "test")
|
||||
assertLen(t, params, 1)
|
||||
assertEq(t, params["value"], "test")
|
||||
params.AddFirstValid("value2", 3, "test")
|
||||
assertLen(t, params, 2)
|
||||
assertEq(t, params["value2"], "3")
|
||||
}
|
|
@ -61,6 +61,8 @@ type (
|
|||
// Unique identifier for this file
|
||||
FileID string `json:"file_id"`
|
||||
|
||||
FileUniqueID string `json:"file_unique_id"`
|
||||
|
||||
// File size
|
||||
FileSize int `json:"file_size"`
|
||||
|
||||
|
|
189
types_test.go
189
types_test.go
|
@ -1,14 +1,12 @@
|
|||
package tgbotapi_test
|
||||
package tgbotapi
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-telegram-bot-api/telegram-bot-api"
|
||||
)
|
||||
|
||||
func TestUserStringWith(t *testing.T) {
|
||||
user := tgbotapi.User{
|
||||
user := User{
|
||||
ID: 0,
|
||||
FirstName: "Test",
|
||||
LastName: "Test",
|
||||
|
@ -23,7 +21,7 @@ func TestUserStringWith(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestUserStringWithUserName(t *testing.T) {
|
||||
user := tgbotapi.User{
|
||||
user := User{
|
||||
ID: 0,
|
||||
FirstName: "Test",
|
||||
LastName: "Test",
|
||||
|
@ -37,7 +35,7 @@ func TestUserStringWithUserName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageTime(t *testing.T) {
|
||||
message := tgbotapi.Message{Date: 0}
|
||||
message := Message{Date: 0}
|
||||
|
||||
date := time.Unix(0, 0)
|
||||
if message.Time() != date {
|
||||
|
@ -46,33 +44,33 @@ func TestMessageTime(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageIsCommandWithCommand(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command"}
|
||||
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
message := Message{Text: "/command"}
|
||||
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
|
||||
if message.IsCommand() != true {
|
||||
if !message.IsCommand() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCommandWithText(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "some text"}
|
||||
message := Message{Text: "some text"}
|
||||
|
||||
if message.IsCommand() != false {
|
||||
if message.IsCommand() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsCommandWithEmptyText(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: ""}
|
||||
message := Message{Text: ""}
|
||||
|
||||
if message.IsCommand() != false {
|
||||
if message.IsCommand() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestCommandWithCommand(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command"}
|
||||
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
message := Message{Text: "/command"}
|
||||
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
|
||||
if message.Command() != "command" {
|
||||
t.Fail()
|
||||
|
@ -80,7 +78,7 @@ func TestCommandWithCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCommandWithEmptyText(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: ""}
|
||||
message := Message{Text: ""}
|
||||
|
||||
if message.Command() != "" {
|
||||
t.Fail()
|
||||
|
@ -88,7 +86,7 @@ func TestCommandWithEmptyText(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCommandWithNonCommand(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "test text"}
|
||||
message := Message{Text: "test text"}
|
||||
|
||||
if message.Command() != "" {
|
||||
t.Fail()
|
||||
|
@ -96,8 +94,8 @@ func TestCommandWithNonCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCommandWithBotName(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command@testbot"}
|
||||
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
|
||||
message := Message{Text: "/command@testbot"}
|
||||
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
|
||||
|
||||
if message.Command() != "command" {
|
||||
t.Fail()
|
||||
|
@ -105,8 +103,8 @@ func TestCommandWithBotName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCommandWithAtWithBotName(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command@testbot"}
|
||||
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
|
||||
message := Message{Text: "/command@testbot"}
|
||||
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 16}}
|
||||
|
||||
if message.CommandWithAt() != "command@testbot" {
|
||||
t.Fail()
|
||||
|
@ -114,37 +112,37 @@ func TestCommandWithAtWithBotName(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageCommandArgumentsWithArguments(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command with arguments"}
|
||||
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
message := Message{Text: "/command with arguments"}
|
||||
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
if message.CommandArguments() != "with arguments" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageCommandArgumentsWithMalformedArguments(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command-without argument space"}
|
||||
message.Entities = &[]tgbotapi.MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
message := Message{Text: "/command-without argument space"}
|
||||
message.Entities = []MessageEntity{{Type: "bot_command", Offset: 0, Length: 8}}
|
||||
if message.CommandArguments() != "without argument space" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageCommandArgumentsWithoutArguments(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "/command"}
|
||||
message := Message{Text: "/command"}
|
||||
if message.CommandArguments() != "" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageCommandArgumentsForNonCommand(t *testing.T) {
|
||||
message := tgbotapi.Message{Text: "test text"}
|
||||
message := Message{Text: "test text"}
|
||||
if message.CommandArguments() != "" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageEntityParseURLGood(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{URL: "https://www.google.com"}
|
||||
entity := MessageEntity{URL: "https://www.google.com"}
|
||||
|
||||
if _, err := entity.ParseURL(); err != nil {
|
||||
t.Fail()
|
||||
|
@ -152,7 +150,7 @@ func TestMessageEntityParseURLGood(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityParseURLBad(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{URL: ""}
|
||||
entity := MessageEntity{URL: ""}
|
||||
|
||||
if _, err := entity.ParseURL(); err == nil {
|
||||
t.Fail()
|
||||
|
@ -160,31 +158,31 @@ func TestMessageEntityParseURLBad(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestChatIsPrivate(t *testing.T) {
|
||||
chat := tgbotapi.Chat{ID: 10, Type: "private"}
|
||||
chat := Chat{ID: 10, Type: "private"}
|
||||
|
||||
if chat.IsPrivate() != true {
|
||||
if !chat.IsPrivate() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatIsGroup(t *testing.T) {
|
||||
chat := tgbotapi.Chat{ID: 10, Type: "group"}
|
||||
chat := Chat{ID: 10, Type: "group"}
|
||||
|
||||
if chat.IsGroup() != true {
|
||||
if !chat.IsGroup() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatIsChannel(t *testing.T) {
|
||||
chat := tgbotapi.Chat{ID: 10, Type: "channel"}
|
||||
chat := Chat{ID: 10, Type: "channel"}
|
||||
|
||||
if chat.IsChannel() != true {
|
||||
if !chat.IsChannel() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatIsSuperGroup(t *testing.T) {
|
||||
chat := tgbotapi.Chat{ID: 10, Type: "supergroup"}
|
||||
chat := Chat{ID: 10, Type: "supergroup"}
|
||||
|
||||
if !chat.IsSuperGroup() {
|
||||
t.Fail()
|
||||
|
@ -192,7 +190,7 @@ func TestChatIsSuperGroup(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsMention(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "mention"}
|
||||
entity := MessageEntity{Type: "mention"}
|
||||
|
||||
if !entity.IsMention() {
|
||||
t.Fail()
|
||||
|
@ -200,7 +198,7 @@ func TestMessageEntityIsMention(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsHashtag(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "hashtag"}
|
||||
entity := MessageEntity{Type: "hashtag"}
|
||||
|
||||
if !entity.IsHashtag() {
|
||||
t.Fail()
|
||||
|
@ -208,7 +206,7 @@ func TestMessageEntityIsHashtag(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsBotCommand(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "bot_command"}
|
||||
entity := MessageEntity{Type: "bot_command"}
|
||||
|
||||
if !entity.IsCommand() {
|
||||
t.Fail()
|
||||
|
@ -216,15 +214,15 @@ func TestMessageEntityIsBotCommand(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsUrl(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "url"}
|
||||
entity := MessageEntity{Type: "url"}
|
||||
|
||||
if !entity.IsUrl() {
|
||||
if !entity.IsURL() {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestMessageEntityIsEmail(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "email"}
|
||||
entity := MessageEntity{Type: "email"}
|
||||
|
||||
if !entity.IsEmail() {
|
||||
t.Fail()
|
||||
|
@ -232,7 +230,7 @@ func TestMessageEntityIsEmail(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsBold(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "bold"}
|
||||
entity := MessageEntity{Type: "bold"}
|
||||
|
||||
if !entity.IsBold() {
|
||||
t.Fail()
|
||||
|
@ -240,7 +238,7 @@ func TestMessageEntityIsBold(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsItalic(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "italic"}
|
||||
entity := MessageEntity{Type: "italic"}
|
||||
|
||||
if !entity.IsItalic() {
|
||||
t.Fail()
|
||||
|
@ -248,7 +246,7 @@ func TestMessageEntityIsItalic(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsCode(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "code"}
|
||||
entity := MessageEntity{Type: "code"}
|
||||
|
||||
if !entity.IsCode() {
|
||||
t.Fail()
|
||||
|
@ -256,7 +254,7 @@ func TestMessageEntityIsCode(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsPre(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "pre"}
|
||||
entity := MessageEntity{Type: "pre"}
|
||||
|
||||
if !entity.IsPre() {
|
||||
t.Fail()
|
||||
|
@ -264,7 +262,7 @@ func TestMessageEntityIsPre(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMessageEntityIsTextLink(t *testing.T) {
|
||||
entity := tgbotapi.MessageEntity{Type: "text_link"}
|
||||
entity := MessageEntity{Type: "text_link"}
|
||||
|
||||
if !entity.IsTextLink() {
|
||||
t.Fail()
|
||||
|
@ -272,9 +270,104 @@ func TestMessageEntityIsTextLink(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFileLink(t *testing.T) {
|
||||
file := tgbotapi.File{FilePath: "test/test.txt"}
|
||||
file := File{FilePath: "test/test.txt"}
|
||||
|
||||
if file.Link("token") != "https://api.telegram.org/file/bottoken/test/test.txt" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure all configs are sendable
|
||||
var (
|
||||
_ Chattable = AnimationConfig{}
|
||||
_ Chattable = AudioConfig{}
|
||||
_ Chattable = CallbackConfig{}
|
||||
_ Chattable = ChatActionConfig{}
|
||||
_ Chattable = ChatAdministratorsConfig{}
|
||||
_ Chattable = ChatInfoConfig{}
|
||||
_ Chattable = ChatInviteLinkConfig{}
|
||||
_ Chattable = CloseConfig{}
|
||||
_ Chattable = ContactConfig{}
|
||||
_ Chattable = CopyMessageConfig{}
|
||||
_ Chattable = CreateChatInviteLinkConfig{}
|
||||
_ Chattable = DeleteChatPhotoConfig{}
|
||||
_ Chattable = DeleteChatStickerSetConfig{}
|
||||
_ Chattable = DeleteMessageConfig{}
|
||||
_ Chattable = DeleteMyCommandsConfig{}
|
||||
_ Chattable = DeleteWebhookConfig{}
|
||||
_ Chattable = DocumentConfig{}
|
||||
_ Chattable = EditChatInviteLinkConfig{}
|
||||
_ Chattable = EditMessageCaptionConfig{}
|
||||
_ Chattable = EditMessageLiveLocationConfig{}
|
||||
_ Chattable = EditMessageMediaConfig{}
|
||||
_ Chattable = EditMessageReplyMarkupConfig{}
|
||||
_ Chattable = EditMessageTextConfig{}
|
||||
_ Chattable = FileConfig{}
|
||||
_ Chattable = ForwardConfig{}
|
||||
_ Chattable = GameConfig{}
|
||||
_ Chattable = GetChatMemberConfig{}
|
||||
_ Chattable = GetGameHighScoresConfig{}
|
||||
_ Chattable = InlineConfig{}
|
||||
_ Chattable = InvoiceConfig{}
|
||||
_ Chattable = KickChatMemberConfig{}
|
||||
_ Chattable = LeaveChatConfig{}
|
||||
_ Chattable = LocationConfig{}
|
||||
_ Chattable = LogOutConfig{}
|
||||
_ Chattable = MediaGroupConfig{}
|
||||
_ Chattable = MessageConfig{}
|
||||
_ Chattable = PhotoConfig{}
|
||||
_ Chattable = PinChatMessageConfig{}
|
||||
_ Chattable = PreCheckoutConfig{}
|
||||
_ Chattable = PromoteChatMemberConfig{}
|
||||
_ Chattable = RestrictChatMemberConfig{}
|
||||
_ Chattable = RevokeChatInviteLinkConfig{}
|
||||
_ Chattable = SendPollConfig{}
|
||||
_ Chattable = SetChatDescriptionConfig{}
|
||||
_ Chattable = SetChatPhotoConfig{}
|
||||
_ Chattable = SetChatTitleConfig{}
|
||||
_ Chattable = SetGameScoreConfig{}
|
||||
_ Chattable = ShippingConfig{}
|
||||
_ Chattable = StickerConfig{}
|
||||
_ Chattable = StopMessageLiveLocationConfig{}
|
||||
_ Chattable = StopPollConfig{}
|
||||
_ Chattable = UnbanChatMemberConfig{}
|
||||
_ Chattable = UnpinChatMessageConfig{}
|
||||
_ Chattable = UpdateConfig{}
|
||||
_ Chattable = UserProfilePhotosConfig{}
|
||||
_ Chattable = VenueConfig{}
|
||||
_ Chattable = VideoConfig{}
|
||||
_ Chattable = VideoNoteConfig{}
|
||||
_ Chattable = VoiceConfig{}
|
||||
_ Chattable = WebhookConfig{}
|
||||
)
|
||||
|
||||
// Ensure all Fileable types are correct.
|
||||
var (
|
||||
_ Fileable = (*PhotoConfig)(nil)
|
||||
_ Fileable = (*AudioConfig)(nil)
|
||||
_ Fileable = (*DocumentConfig)(nil)
|
||||
_ Fileable = (*StickerConfig)(nil)
|
||||
_ Fileable = (*VideoConfig)(nil)
|
||||
_ Fileable = (*AnimationConfig)(nil)
|
||||
_ Fileable = (*VideoNoteConfig)(nil)
|
||||
_ Fileable = (*VoiceConfig)(nil)
|
||||
_ Fileable = (*SetChatPhotoConfig)(nil)
|
||||
_ Fileable = (*EditMessageMediaConfig)(nil)
|
||||
_ Fileable = (*SetChatPhotoConfig)(nil)
|
||||
_ Fileable = (*UploadStickerConfig)(nil)
|
||||
_ Fileable = (*NewStickerSetConfig)(nil)
|
||||
_ Fileable = (*AddStickerConfig)(nil)
|
||||
_ Fileable = (*MediaGroupConfig)(nil)
|
||||
_ Fileable = (*WebhookConfig)(nil)
|
||||
_ Fileable = (*SetStickerSetThumbConfig)(nil)
|
||||
)
|
||||
|
||||
// Ensure all RequestFileData types are correct.
|
||||
var (
|
||||
_ RequestFileData = (*FilePath)(nil)
|
||||
_ RequestFileData = (*FileBytes)(nil)
|
||||
_ RequestFileData = (*FileReader)(nil)
|
||||
_ RequestFileData = (*FileURL)(nil)
|
||||
_ RequestFileData = (*FileID)(nil)
|
||||
_ RequestFileData = (*fileAttach)(nil)
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue