copy from 537c005
This commit is contained in:
4
docs/internals/README.md
Normal file
4
docs/internals/README.md
Normal 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.
|
197
docs/internals/adding-endpoints.md
Normal file
197
docs/internals/adding-endpoints.md
Normal 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.
|
87
docs/internals/uploading-files.md
Normal file
87
docs/internals/uploading-files.md
Normal 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.
|
Reference in New Issue
Block a user