copy from 537c005

This commit is contained in:
Кобелев Андрей Андреевич
2022-06-03 11:47:26 +03:00
commit f86a67eb8f
36 changed files with 10747 additions and 0 deletions

4
docs/internals/README.md Normal file
View File

@ -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.

View File

@ -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.

View File

@ -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.