telegem 3.3.0 â 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +121 -0
- data/Gemfile +1 -1
- data/README.md +147 -0
- data/bin/telegem-ssl +71 -25
- data/contributing.md +375 -0
- data/docs/api.md +663 -0
- data/docs/bot.md +332 -0
- data/docs/changelog.md +182 -0
- data/docs/context.md +554 -0
- data/docs/core_concepts.md +218 -0
- data/docs/deployment.md +702 -0
- data/docs/error_handling.md +435 -0
- data/docs/examples.md +752 -0
- data/docs/getting_started.md +151 -0
- data/docs/handlers.md +580 -0
- data/docs/keyboards.md +446 -0
- data/docs/middleware.md +536 -0
- data/docs/plugins.md +384 -0
- data/docs/scenes.md +517 -0
- data/docs/sessions.md +544 -0
- data/docs/testing.md +612 -0
- data/docs/troubleshooting.md +574 -0
- data/docs/types.md +538 -0
- data/docs/webhooks.md +456 -0
- data/lib/api/client.rb +38 -10
- data/lib/api/types.rb +433 -172
- data/lib/core/composer.rb +3 -3
- data/lib/core/context.rb +17 -11
- data/lib/plugins/cc +3 -0
- data/lib/plugins/file_extract.rb +2 -2
- data/lib/plugins/translate.rb +43 -0
- data/lib/session/memory_store.rb +90 -103
- data/lib/session/redis.rb +91 -0
- data/lib/telegem.rb +4 -4
- data/lib/webhook/server.rb +5 -3
- metadata +51 -35
- data/CHANGELOG +0 -95
- data/Contributing.md +0 -161
- data/Readme.md +0 -302
- data/examples/.gitkeep +0 -0
- data/public/.gitkeep +0 -0
data/docs/keyboards.md
ADDED
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
# Keyboard Markup
|
|
2
|
+
|
|
3
|
+
Telegem provides a clean DSL for creating inline and reply keyboards. Keyboards enable interactive buttons for better user experience.
|
|
4
|
+
|
|
5
|
+
## Reply Keyboards
|
|
6
|
+
|
|
7
|
+
Reply keyboards appear at the bottom of the chat and replace the system keyboard.
|
|
8
|
+
|
|
9
|
+
### Basic Reply Keyboard
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
keyboard = Telegem.keyboard do
|
|
13
|
+
row "Button 1", "Button 2"
|
|
14
|
+
row "Button 3"
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
ctx.reply("Choose an option:", reply_markup: keyboard)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
### Keyboard Options
|
|
21
|
+
|
|
22
|
+
```ruby
|
|
23
|
+
keyboard = Telegem.keyboard do
|
|
24
|
+
row "Yes", "No"
|
|
25
|
+
end.resize.one_time.selective
|
|
26
|
+
|
|
27
|
+
ctx.reply("Confirm?", reply_markup: keyboard)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Available options:
|
|
31
|
+
- `resize` - Make keyboard smaller
|
|
32
|
+
- `one_time` - Hide keyboard after use
|
|
33
|
+
- `selective` - Show only to mentioned users
|
|
34
|
+
|
|
35
|
+
### Special Button Types
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
keyboard = Telegem.keyboard do
|
|
39
|
+
row "đ Location", "đ Contact"
|
|
40
|
+
request_location "Share Location"
|
|
41
|
+
request_contact "Share Contact"
|
|
42
|
+
request_poll "Create Poll", poll_type: 'regular'
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
ctx.reply("What would you like to share?", reply_markup: keyboard)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Inline Keyboards
|
|
49
|
+
|
|
50
|
+
Inline keyboards appear directly in messages and work with callback queries.
|
|
51
|
+
|
|
52
|
+
### Basic Inline Keyboard
|
|
53
|
+
|
|
54
|
+
```ruby
|
|
55
|
+
inline = Telegem.inline do
|
|
56
|
+
row callback("Yes", "yes"), callback("No", "no")
|
|
57
|
+
row url("Visit Site", "https://example.com")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ctx.reply("Do you agree?", reply_markup: inline)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Button Types
|
|
64
|
+
|
|
65
|
+
#### Callback Buttons
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
callback("Button Text", "callback_data")
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
- Text: Button label
|
|
72
|
+
- Data: String passed to callback handler (max 64 bytes)
|
|
73
|
+
|
|
74
|
+
#### URL Buttons
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
url("Visit Website", "https://example.com")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Opens URL when pressed.
|
|
81
|
+
|
|
82
|
+
#### Login Buttons
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
login("Login", "https://example.com/login", forward_text: "Login to site")
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
For web app authorization.
|
|
89
|
+
|
|
90
|
+
#### Web App Buttons
|
|
91
|
+
|
|
92
|
+
```ruby
|
|
93
|
+
web_app("Open App", "https://example.com/app")
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Opens web app in Telegram interface.
|
|
97
|
+
|
|
98
|
+
#### Pay Buttons
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
pay("Pay $10")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
For payment integrations.
|
|
105
|
+
|
|
106
|
+
#### Game Buttons
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
callback_game("Play Game", "game_short_name")
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
For HTML5 games.
|
|
113
|
+
|
|
114
|
+
### Advanced Inline Keyboard
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
keyboard = Telegem.inline do
|
|
118
|
+
# First row
|
|
119
|
+
row callback("đ Like", "like"), callback("đ Dislike", "dislike")
|
|
120
|
+
|
|
121
|
+
# Second row
|
|
122
|
+
row url("đ Read More", "https://example.com/article")
|
|
123
|
+
|
|
124
|
+
# Third row
|
|
125
|
+
row callback("đ Subscribe", "subscribe"), callback("đ Unsubscribe", "unsubscribe")
|
|
126
|
+
|
|
127
|
+
# Fourth row
|
|
128
|
+
row web_app("đŽ Play Game", "https://game.example.com")
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
ctx.reply("What do you think?", reply_markup: keyboard)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
## Handling Button Presses
|
|
135
|
+
|
|
136
|
+
### Callback Query Handlers
|
|
137
|
+
|
|
138
|
+
```ruby
|
|
139
|
+
bot.callback_query do |ctx|
|
|
140
|
+
data = ctx.data
|
|
141
|
+
ctx.answer_callback_query("You pressed: #{data}")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# Specific data
|
|
145
|
+
bot.callback_query('like') do |ctx|
|
|
146
|
+
ctx.answer_callback_query("Thanks for liking!")
|
|
147
|
+
# Update message or database
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Pattern matching
|
|
151
|
+
bot.callback_query(/^action_/) do |ctx|
|
|
152
|
+
action = ctx.data.split('_').last
|
|
153
|
+
handle_action(ctx, action)
|
|
154
|
+
end
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Callback Query Methods
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
ctx.answer_callback_query(text: "Processing...")
|
|
161
|
+
ctx.answer_callback_query(text: "Done!", show_alert: true)
|
|
162
|
+
ctx.answer_callback_query(url: "https://example.com")
|
|
163
|
+
ctx.answer_callback_query(cache_time: 30)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Dynamic Keyboards
|
|
167
|
+
|
|
168
|
+
### Building from Data
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
def create_menu_keyboard(options)
|
|
172
|
+
Telegem.keyboard do
|
|
173
|
+
options.each_slice(2) do |row_buttons|
|
|
174
|
+
row(*row_buttons)
|
|
175
|
+
end
|
|
176
|
+
end.resize
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
options = ["Pizza", "Burger", "Salad", "Soup"]
|
|
180
|
+
keyboard = create_menu_keyboard(options)
|
|
181
|
+
ctx.reply("Choose food:", reply_markup: keyboard)
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Conditional Buttons
|
|
185
|
+
|
|
186
|
+
```ruby
|
|
187
|
+
def create_user_keyboard(ctx)
|
|
188
|
+
Telegem.inline do
|
|
189
|
+
row callback("Profile", "profile")
|
|
190
|
+
|
|
191
|
+
if ctx.session[:admin]
|
|
192
|
+
row callback("Admin Panel", "admin")
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
if ctx.session[:premium]
|
|
196
|
+
row callback("Premium Features", "premium")
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
row callback("Settings", "settings")
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
keyboard = create_user_keyboard(ctx)
|
|
204
|
+
ctx.reply("Menu:", reply_markup: keyboard)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Keyboard Management
|
|
208
|
+
|
|
209
|
+
### Removing Keyboards
|
|
210
|
+
|
|
211
|
+
```ruby
|
|
212
|
+
# Remove reply keyboard
|
|
213
|
+
ctx.remove_keyboard
|
|
214
|
+
ctx.remove_keyboard("Keyboard removed!")
|
|
215
|
+
|
|
216
|
+
# Remove inline keyboard
|
|
217
|
+
ctx.edit_message_reply_markup(reply_markup: nil)
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
### Updating Keyboards
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
# Edit inline keyboard
|
|
224
|
+
new_keyboard = Telegem.inline do
|
|
225
|
+
row callback("Updated Button", "updated")
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
ctx.edit_message_reply_markup(reply_markup: new_keyboard)
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Force Reply
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
# Force user to reply to message
|
|
235
|
+
ctx.reply("What's your name?", reply_markup: Telegem::Markup.force_reply)
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Advanced Patterns
|
|
239
|
+
|
|
240
|
+
### Pagination
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
def create_pagination_keyboard(current_page, total_pages)
|
|
244
|
+
Telegem.inline do
|
|
245
|
+
row = []
|
|
246
|
+
|
|
247
|
+
if current_page > 1
|
|
248
|
+
row << callback("âŦ
ī¸ Previous", "page:#{current_page - 1}")
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
row << callback("#{current_page}/#{total_pages}", "current")
|
|
252
|
+
|
|
253
|
+
if current_page < total_pages
|
|
254
|
+
row << callback("Next âĄī¸", "page:#{current_page + 1}")
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
row(*row)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
bot.callback_query(/^page:/) do |ctx|
|
|
262
|
+
page = ctx.data.split(':').last.to_i
|
|
263
|
+
keyboard = create_pagination_keyboard(page, 10)
|
|
264
|
+
ctx.edit_message_reply_markup(reply_markup: keyboard)
|
|
265
|
+
end
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Multi-select Interface
|
|
269
|
+
|
|
270
|
+
```ruby
|
|
271
|
+
def create_selection_keyboard(selected_items, all_items)
|
|
272
|
+
Telegem.inline do
|
|
273
|
+
all_items.each do |item|
|
|
274
|
+
status = selected_items.include?(item) ? "â
" : "âŦ"
|
|
275
|
+
row callback("#{status} #{item}", "toggle:#{item}")
|
|
276
|
+
end
|
|
277
|
+
|
|
278
|
+
row callback("Done", "done")
|
|
279
|
+
end
|
|
280
|
+
end
|
|
281
|
+
|
|
282
|
+
bot.callback_query(/^toggle:/) do |ctx|
|
|
283
|
+
item = ctx.data.split(':', 2).last
|
|
284
|
+
selected = ctx.session[:selected] ||= []
|
|
285
|
+
|
|
286
|
+
if selected.include?(item)
|
|
287
|
+
selected.delete(item)
|
|
288
|
+
else
|
|
289
|
+
selected << item
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
keyboard = create_selection_keyboard(selected, ALL_ITEMS)
|
|
293
|
+
ctx.edit_message_reply_markup(reply_markup: keyboard)
|
|
294
|
+
end
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
### Inline Search
|
|
298
|
+
|
|
299
|
+
```ruby
|
|
300
|
+
bot.inline_query do |ctx|
|
|
301
|
+
query = ctx.query
|
|
302
|
+
|
|
303
|
+
results = search_items(query).map do |item|
|
|
304
|
+
Telegem::Types::InlineQueryResultArticle.new(
|
|
305
|
+
id: item.id,
|
|
306
|
+
title: item.title,
|
|
307
|
+
description: item.description,
|
|
308
|
+
input_message_content: {
|
|
309
|
+
message_text: "Selected: #{item.title}"
|
|
310
|
+
},
|
|
311
|
+
reply_markup: Telegem.inline do
|
|
312
|
+
callback "More Info", "info:#{item.id}"
|
|
313
|
+
end
|
|
314
|
+
)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
ctx.answer_inline_query(results)
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
## Keyboard Best Practices
|
|
322
|
+
|
|
323
|
+
### Design Guidelines
|
|
324
|
+
|
|
325
|
+
1. **Keep it Simple**: 3-5 buttons per row, 2-3 rows max
|
|
326
|
+
2. **Clear Labels**: Use descriptive text and emojis
|
|
327
|
+
3. **Consistent Style**: Same button style for similar actions
|
|
328
|
+
4. **Progressive Disclosure**: Show more options as needed
|
|
329
|
+
|
|
330
|
+
### User Experience
|
|
331
|
+
|
|
332
|
+
```ruby
|
|
333
|
+
# Good: Clear, actionable buttons
|
|
334
|
+
keyboard = Telegem.inline do
|
|
335
|
+
row callback("đ
Book Now", "book"), callback("âšī¸ More Info", "info")
|
|
336
|
+
row callback("đ Call Us", "call")
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
# Bad: Confusing, too many options
|
|
340
|
+
keyboard = Telegem.inline do
|
|
341
|
+
row "Option A", "Option B", "Option C", "Option D", "Option E"
|
|
342
|
+
end
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Error Handling
|
|
346
|
+
|
|
347
|
+
```ruby
|
|
348
|
+
bot.callback_query do |ctx|
|
|
349
|
+
begin
|
|
350
|
+
handle_callback(ctx)
|
|
351
|
+
rescue => e
|
|
352
|
+
ctx.logger.error("Callback error: #{e.message}")
|
|
353
|
+
ctx.answer_callback_query("Something went wrong", show_alert: true)
|
|
354
|
+
end
|
|
355
|
+
end
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Performance Considerations
|
|
359
|
+
|
|
360
|
+
```ruby
|
|
361
|
+
# Cache keyboards for repeated use
|
|
362
|
+
KEYBOARDS = {
|
|
363
|
+
main_menu: Telegem.inline do
|
|
364
|
+
row callback("Home", "home"), callback("Settings", "settings")
|
|
365
|
+
end
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
ctx.reply("Menu:", reply_markup: KEYBOARDS[:main_menu])
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
## Keyboard Types Reference
|
|
372
|
+
|
|
373
|
+
### Reply Keyboard Buttons
|
|
374
|
+
|
|
375
|
+
| Method | Description | Parameters |
|
|
376
|
+
|--------|-------------|------------|
|
|
377
|
+
| `text` | Regular text button | text, style, icon_custom_emoji_id |
|
|
378
|
+
| `request_contact` | Request phone number | text, style, icon_custom_emoji_id |
|
|
379
|
+
| `request_location` | Request location | text, style, icon_custom_emoji_id |
|
|
380
|
+
| `request_poll` | Request poll creation | text, poll_type, style, icon_custom_emoji_id |
|
|
381
|
+
|
|
382
|
+
### Inline Keyboard Buttons
|
|
383
|
+
|
|
384
|
+
| Method | Description | Parameters |
|
|
385
|
+
|--------|-------------|------------|
|
|
386
|
+
| `callback` | Callback query button | text, data, style, icon_custom_emoji_id |
|
|
387
|
+
| `url` | URL button | text, url, style, icon_custom_emoji_id |
|
|
388
|
+
| `login` | Login button | text, url, style, icon_custom_emoji_id, **options |
|
|
389
|
+
| `web_app` | Web app button | text, url, style, icon_custom_emoji_id |
|
|
390
|
+
| `pay` | Payment button | text, style, icon_custom_emoji_id |
|
|
391
|
+
| `switch_inline` | Switch to inline query | text, query, style, icon_custom_emoji_id |
|
|
392
|
+
| `switch_inline_current_chat` | Switch inline in current chat | text, query, style, icon_custom_emoji_id |
|
|
393
|
+
| `callback_game` | Game button | text, game_short_name, style, icon_custom_emoji_id |
|
|
394
|
+
|
|
395
|
+
## Testing Keyboards
|
|
396
|
+
|
|
397
|
+
```ruby
|
|
398
|
+
# Test keyboard creation
|
|
399
|
+
def test_keyboard_creation
|
|
400
|
+
keyboard = Telegem.keyboard do
|
|
401
|
+
row "Yes", "No"
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
assert keyboard.to_h.key?(:keyboard)
|
|
405
|
+
assert_equal [["Yes", "No"]], keyboard.to_h[:keyboard]
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# Test callback handling
|
|
409
|
+
def test_callback_handling
|
|
410
|
+
# Simulate callback query
|
|
411
|
+
simulate_callback_query(bot, "test_data")
|
|
412
|
+
|
|
413
|
+
# Assert expected behavior
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
## Common Issues
|
|
418
|
+
|
|
419
|
+
### Callback Data Too Long
|
|
420
|
+
|
|
421
|
+
```ruby
|
|
422
|
+
# Bad: too much data
|
|
423
|
+
callback("Button", "very_long_data_that_exceeds_64_bytes_limit_and_will_cause_errors")
|
|
424
|
+
|
|
425
|
+
# Good: use IDs
|
|
426
|
+
callback("Button", "action:123") # Reference by ID
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### Keyboard Not Updating
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
# Force update
|
|
433
|
+
ctx.edit_message_reply_markup(reply_markup: new_keyboard, message_id: ctx.message.message_id)
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Buttons Not Working
|
|
437
|
+
|
|
438
|
+
```ruby
|
|
439
|
+
# Check for typos in callback data
|
|
440
|
+
bot.callback_query('correct_data') do |ctx|
|
|
441
|
+
# Handle callback
|
|
442
|
+
end
|
|
443
|
+
```
|
|
444
|
+
|
|
445
|
+
Keyboards are essential for creating interactive, user-friendly Telegram bots. Use them to guide users and collect input efficiently.</content>
|
|
446
|
+
<parameter name="filePath">/home/slick/telegem/docs/keyboards.md
|