docs/internals/adding-endpoints.md (view raw)
1# Adding Endpoints
2
3This is mostly useful if you've managed to catch a new Telegram Bot API update
4before the library can get updated. It's also a great source of information
5about how the types work internally.
6
7## Creating the Config
8
9The first step in adding a new endpoint is to create a new Config type for it.
10These belong in `configs.go`.
11
12Let's try and add the `deleteMessage` endpoint. We can see it requires two
13fields; `chat_id` and `message_id`. We can create a struct for these.
14
15```go
16type DeleteMessageConfig struct {
17 ChatID ???
18 MessageID int
19}
20```
21
22What type should `ChatID` be? Telegram allows specifying numeric chat IDs or channel usernames. Golang doesn't have union types, and interfaces are entirely
23untyped. This library solves this by adding two fields, a `ChatID` and a
24`ChannelUsername`. We can now write the struct as follows.
25
26```go
27type DeleteMessageConfig struct {
28 ChannelUsername string
29 ChatID int64
30 MessageID int
31}
32```
33
34Note that `ChatID` is an `int64`. Telegram chat IDs can be greater than 32 bits.
35
36Okay, we now have our struct. But we can't send it yet. It doesn't implement
37`Chattable` so it won't work with `Request` or `Send`.
38
39### Making it `Chattable`
40
41We can see that `Chattable` only requires a few methods.
42
43```go
44type Chattable interface {
45 params() (Params, error)
46 method() string
47}
48```
49
50`params` is the fields associated with the request. `method` is the endpoint
51that this Config is associated with.
52
53Implementing the `method` is easy, so let's start with that.
54
55```go
56func (config DeleteMessageConfig) method() string {
57 return "deleteMessage"
58}
59```
60
61Now we have to add the `params`. The `Params` type is an alias for
62`map[string]string`. Telegram expects only a single field for `chat_id`, so we
63have to determine what data to send.
64
65We could use an if statement to determine which field to get the value from.
66However, as this is a relatively common operation, there's helper methods for
67`Params`. We can use the `AddFirstValid` method to go through each possible
68value and stop when it discovers a valid one. Before writing your own Config,
69it's worth taking a look through `params.go` to see what other helpers exist.
70
71Now we can take a look at what a completed `params` method looks like.
72
73```go
74func (config DeleteMessageConfig) params() (Params, error) {
75 params := make(Params)
76
77 params.AddFirstValid("chat_id", config.ChatID, config.ChannelUsername)
78 params.AddNonZero("message_id", config.MessageID)
79
80 return params, nil
81}
82```
83
84### Uploading Files
85
86Let's imagine that for some reason deleting a message requires a document to be
87uploaded and an optional thumbnail for that document. To add file upload
88support we need to implement `Fileable`. This only requires one additional
89method.
90
91```go
92type Fileable interface {
93 Chattable
94 files() []RequestFile
95}
96```
97
98First, let's add some fields to store our files in. Most of the standard Configs
99have similar fields for their files.
100
101```diff
102 type DeleteMessageConfig struct {
103 ChannelUsername string
104 ChatID int64
105 MessageID int
106+ Delete interface{}
107+ Thumb interface{}
108 }
109```
110
111Adding another method is pretty simple. We'll always add a file named `delete`
112and add the `thumb` file if we have one.
113
114```go
115func (config DeleteMessageConfig) files() []RequestFile {
116 files := []RequestFile{{
117 Name: "delete",
118 File: config.Delete,
119 }}
120
121 if config.Thumb != nil {
122 files = append(files, RequestFile{
123 Name: "thumb",
124 File: config.Thumb,
125 })
126 }
127
128 return files
129}
130```
131
132And now our files will upload! It will transparently handle uploads whether File is a string with a path to a file, `FileURL`, `FileBytes`, `FileReader`, or `FileID`.
133
134### Base Configs
135
136Certain Configs have repeated elements. For example, many of the items sent to a
137chat have `ChatID` or `ChannelUsername` fields, along with `ReplyToMessageID`,
138`ReplyMarkup`, and `DisableNotification`. Instead of implementing all of this
139code for each item, there's a `BaseChat` that handles it for your Config.
140Simply embed it in your struct to get all of those fields.
141
142There's only a few fields required for the `MessageConfig` struct after
143embedding the `BaseChat` struct.
144
145```go
146type MessageConfig struct {
147 BaseChat
148 Text string
149 ParseMode string
150 DisableWebPagePreview bool
151}
152```
153
154It also inherits the `params` method from `BaseChat`. This allows you to call
155it, then you only have to add your new fields.
156
157```go
158func (config MessageConfig) params() (Params, error) {
159 params, err := config.BaseChat.params()
160 if err != nil {
161 return params, err
162 }
163
164 params.AddNonEmpty("text", config.Text)
165 // Add your other fields
166
167 return params, nil
168}
169```
170
171Similarly, there's a `BaseFile` struct for adding an associated file and
172`BaseEdit` struct for editing messages.
173
174## Making it Friendly
175
176After we've got a Config type, we'll want to make it more user-friendly. We can
177do this by adding a new helper to `helpers.go`. These are functions that take
178in the required data for the request to succeed and populate a Config.
179
180Telegram only requires two fields to call `deleteMessage`, so this will be fast.
181
182```go
183func NewDeleteMessage(chatID int64, messageID int) DeleteMessageConfig {
184 return DeleteMessageConfig{
185 ChatID: chatID,
186 MessageID: messageID,
187 }
188}
189```
190
191Sometimes it makes sense to add more helpers if there's methods where you have
192to set exactly one field. You can also add helpers that accept a `username`
193string for channels if it's a common operation.
194
195And that's it! You've added a new method.