telegem 2.0.8 → 2.0.9
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/Test-Projects/Movie-tracker-bot/Gemfile +2 -0
- data/Test-Projects/Movie-tracker-bot/bot.rb +62 -0
- data/Test-Projects/Movie-tracker-bot/handlers/.gitkeep +0 -0
- data/Test-Projects/Movie-tracker-bot/handlers/add_1_.rb +160 -0
- data/Test-Projects/Movie-tracker-bot/handlers/add_2_.rb +139 -0
- data/Test-Projects/Movie-tracker-bot/handlers/premium.rb +13 -0
- data/Test-Projects/Movie-tracker-bot/handlers/report.rb +31 -0
- data/Test-Projects/Movie-tracker-bot/handlers/search.rb +150 -0
- data/Test-Projects/Movie-tracker-bot/handlers/sponsor.rb +14 -0
- data/Test-Projects/Movie-tracker-bot/handlers/start.rb +48 -0
- data/Test-Projects/Movie-tracker-bot/handlers/watch.rb +210 -0
- data/Test-Projects/Test-submitted-by-marvel/.gitkeep +0 -0
- data/Test-Projects/Test-submitted-by-marvel/Marvel-bot.md +3 -0
- data/docs-src/.gitkeep +0 -0
- data/docs-src/Bot-registration_.PNG +0 -0
- data/docs-src/bot.md +295 -0
- data/docs-src/context|ctx|.md +531 -0
- data/docs-src/getting-started.md +328 -0
- data/docs-src/keyboard_inline.md +413 -0
- data/docs-src/scene.md +509 -0
- data/docs-src/understanding-ctx.md +581 -0
- data/lib/api/client.rb +3 -0
- data/lib/core/bot.rb +31 -27
- data/lib/telegem.rb +1 -1
- data/lib/webhook/server.rb +1 -1
- metadata +26 -15
- data/docs/Api.md +0 -211
- data/docs/Cookbook(copy_paste).md +0 -644
- data/docs/Getting_started.md +0 -348
- data/docs/How_to_use.md +0 -571
- data/docs/QuickStart.md +0 -258
- data/docs/Understanding_Scene.md +0 -434
- data/docs/Usage.md +0 -717
- data/docs/webhook_setup.md +0 -199
- /data/{docs → Test-Projects/Movie-tracker-bot}/.gitkeep +0 -0
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
ctx - Your Complete Context Guide
|
|
2
|
+
|
|
3
|
+
Welcome to ctx, your Swiss Army knife for Telegram bot development! This guide balances quick understanding with detailed information.
|
|
4
|
+
|
|
5
|
+
📦 The Core Methods
|
|
6
|
+
|
|
7
|
+
ctx.reply()
|
|
8
|
+
|
|
9
|
+
Quick: Send messages with optional formatting and buttons.
|
|
10
|
+
In-depth: Wraps sendMessage API method with smart defaults.
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
# Basic usage
|
|
14
|
+
ctx.reply("Hello!") # Simple text
|
|
15
|
+
|
|
16
|
+
# With formatting
|
|
17
|
+
ctx.reply("*Bold* and _italic_", parse_mode: "Markdown")
|
|
18
|
+
ctx.reply("<b>HTML bold</b>", parse_mode: "HTML")
|
|
19
|
+
|
|
20
|
+
# With reply to message
|
|
21
|
+
ctx.reply("Answering this", reply_to_message_id: ctx.message.message_id)
|
|
22
|
+
|
|
23
|
+
# With keyboard
|
|
24
|
+
ctx.reply("Choose:", reply_markup: keyboard)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
ctx.message
|
|
28
|
+
|
|
29
|
+
Quick: Access the current message.
|
|
30
|
+
In-depth: Returns a Message object or nil if not a message update.
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Common properties
|
|
34
|
+
text = ctx.message.text # Message content
|
|
35
|
+
msg_id = ctx.message.message_id # Unique message identifier
|
|
36
|
+
date = ctx.message.date # Unix timestamp
|
|
37
|
+
chat = ctx.message.chat # Chat object
|
|
38
|
+
|
|
39
|
+
# Media properties (only present if applicable)
|
|
40
|
+
photo = ctx.message.photo # Array of photo sizes
|
|
41
|
+
document = ctx.message.document # Document object
|
|
42
|
+
location = ctx.message.location # Location object
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
ctx.from
|
|
46
|
+
|
|
47
|
+
Quick: Get information about the message sender.
|
|
48
|
+
In-depth: Returns a User object with identity information.
|
|
49
|
+
|
|
50
|
+
```ruby
|
|
51
|
+
user_id = ctx.from.id # Unique user identifier
|
|
52
|
+
username = ctx.from.username # @username or nil
|
|
53
|
+
first_name = ctx.from.first_name # User's first name
|
|
54
|
+
last_name = ctx.from.last_name # User's last name
|
|
55
|
+
full_name = ctx.from.full_name # Combined first + last name
|
|
56
|
+
is_bot = ctx.from.is_bot # Boolean (true for bot users)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
ctx.chat
|
|
60
|
+
|
|
61
|
+
Quick: Information about where the conversation is happening.
|
|
62
|
+
In-depth: Returns a Chat object; always check for nil first.
|
|
63
|
+
|
|
64
|
+
```ruby
|
|
65
|
+
chat_id = ctx.chat.id # Unique chat identifier
|
|
66
|
+
chat_type = ctx.chat.type # "private", "group", "supergroup", or "channel"
|
|
67
|
+
chat_title = ctx.chat.title # Group/channel title (nil for private)
|
|
68
|
+
|
|
69
|
+
# Safety first!
|
|
70
|
+
if ctx.chat
|
|
71
|
+
ctx.reply("Chat ID: #{ctx.chat.id}")
|
|
72
|
+
else
|
|
73
|
+
ctx.logger.warn("No chat object in this update")
|
|
74
|
+
end
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
ctx.data
|
|
78
|
+
|
|
79
|
+
Quick: Get callback data from inline button clicks.
|
|
80
|
+
In-depth: Only available for callback_query updates.
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
bot.on(:callback_query) do |ctx|
|
|
84
|
+
# Data from the clicked button
|
|
85
|
+
button_data = ctx.data # String set in callback_data
|
|
86
|
+
|
|
87
|
+
# Always answer callback queries!
|
|
88
|
+
ctx.answer_callback_query(text: "Clicked!")
|
|
89
|
+
|
|
90
|
+
# Process based on data
|
|
91
|
+
case ctx.data
|
|
92
|
+
when "pizza"
|
|
93
|
+
ctx.edit_message_text("🍕 Pizza ordered!")
|
|
94
|
+
when "burger"
|
|
95
|
+
ctx.edit_message_text("🍔 Burger coming up!")
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# 💡 PRO TIP: callback_data has 64-byte limit. Keep it short!
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
💾 Data Storage Methods
|
|
103
|
+
|
|
104
|
+
ctx.session - Persistent User Storage
|
|
105
|
+
|
|
106
|
+
Think of it as: User-specific memory that lasts between conversations.
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
# Store data (survives bot restarts with proper store)
|
|
110
|
+
ctx.session[:language] = "en"
|
|
111
|
+
ctx.session[:preferences] = { theme: "dark", notifications: true }
|
|
112
|
+
|
|
113
|
+
# Retrieve data
|
|
114
|
+
lang = ctx.session[:language] || "en" # Default if not set
|
|
115
|
+
|
|
116
|
+
# Complex data structures
|
|
117
|
+
ctx.session[:cart] ||= []
|
|
118
|
+
ctx.session[:cart] << { id: 1, name: "Pizza", quantity: 2 }
|
|
119
|
+
|
|
120
|
+
# Delete data
|
|
121
|
+
ctx.session.delete(:cart)
|
|
122
|
+
|
|
123
|
+
# 🚨 SECURITY WARNING: Never store sensitive data!
|
|
124
|
+
# ❌ BAD: ctx.session[:password] = "secret"
|
|
125
|
+
# ✅ GOOD: Store only user preferences, game state, etc.
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
ctx.state - Temporary Request Storage
|
|
129
|
+
|
|
130
|
+
Think of it as: Short-term memory for the current conversation flow.
|
|
131
|
+
|
|
132
|
+
```ruby
|
|
133
|
+
# Multi-step forms
|
|
134
|
+
ctx.state[:awaiting_email] = true
|
|
135
|
+
ctx.reply("What's your email address?")
|
|
136
|
+
|
|
137
|
+
# Later in another handler
|
|
138
|
+
if ctx.state[:awaiting_email]
|
|
139
|
+
email = ctx.message.text
|
|
140
|
+
# Validate email...
|
|
141
|
+
ctx.state[:awaiting_email] = false
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# 💡 Key difference from session:
|
|
145
|
+
# - state: Cleared after request (for step-by-step flows)
|
|
146
|
+
# - session: Persists (for user preferences)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
ctx.match - Pattern Matching Results
|
|
150
|
+
|
|
151
|
+
Think of it as: Regex capture groups made easy.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
# Capture parts of messages
|
|
155
|
+
bot.hears(/order (\d+) (.+)/) do |ctx|
|
|
156
|
+
quantity = ctx.match[1].to_i # First capture: "3" → 3
|
|
157
|
+
item = ctx.match[2] # Second capture: "pizzas"
|
|
158
|
+
|
|
159
|
+
ctx.reply("Ordering #{quantity} #{item}!")
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Multiple captures
|
|
163
|
+
bot.hears(/from (.+) to (.+)/) do |ctx|
|
|
164
|
+
origin = ctx.match[1] # "New York"
|
|
165
|
+
destination = ctx.match[2] # "Los Angeles"
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# 💡 Works with: hears(), command() with regex, on() with text patterns
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
ctx.scene - Conversation Flow Management
|
|
172
|
+
|
|
173
|
+
Think of it as: Organized multi-step conversations.
|
|
174
|
+
|
|
175
|
+
```ruby
|
|
176
|
+
# Define a scene
|
|
177
|
+
bot.scene(:registration) do |scene|
|
|
178
|
+
scene.enter do |ctx|
|
|
179
|
+
ctx.reply("Welcome! What's your name?")
|
|
180
|
+
ctx.session[:step] = :name
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
scene.on(:message) do |ctx|
|
|
184
|
+
case ctx.session[:step]
|
|
185
|
+
when :name
|
|
186
|
+
ctx.session[:name] = ctx.message.text
|
|
187
|
+
ctx.session[:step] = :email
|
|
188
|
+
ctx.reply("Great! Now your email:")
|
|
189
|
+
when :email
|
|
190
|
+
ctx.session[:email] = ctx.message.text
|
|
191
|
+
ctx.reply("Registration complete!")
|
|
192
|
+
ctx.leave_scene
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
scene.leave do |ctx|
|
|
197
|
+
ctx.reply("Thank you for registering!")
|
|
198
|
+
ctx.session.delete(:step)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Enter the scene
|
|
203
|
+
bot.command("register") do |ctx|
|
|
204
|
+
ctx.enter_scene(:registration)
|
|
205
|
+
end
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
ctx.query - Inline Query Text
|
|
209
|
+
|
|
210
|
+
Think of it as: What users type after @yourbot.
|
|
211
|
+
|
|
212
|
+
```ruby
|
|
213
|
+
# Only works in inline mode
|
|
214
|
+
bot.on(:inline_query) do |ctx|
|
|
215
|
+
search_term = ctx.query # User's search text
|
|
216
|
+
|
|
217
|
+
results = [
|
|
218
|
+
{
|
|
219
|
+
type: "article",
|
|
220
|
+
id: "1",
|
|
221
|
+
title: "Result for: #{search_term}",
|
|
222
|
+
input_message_content: {
|
|
223
|
+
message_text: "You searched: #{search_term}"
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
]
|
|
227
|
+
|
|
228
|
+
ctx.answer_inline_query(results)
|
|
229
|
+
end
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
📸 Media Methods
|
|
233
|
+
|
|
234
|
+
File Sending Methods
|
|
235
|
+
|
|
236
|
+
```ruby
|
|
237
|
+
# Send photo
|
|
238
|
+
ctx.photo("image.jpg", caption: "Look at this!")
|
|
239
|
+
ctx.photo(photo_file_id) # Reuse Telegram file ID
|
|
240
|
+
|
|
241
|
+
# Send document
|
|
242
|
+
ctx.document("report.pdf", caption: "Monthly report")
|
|
243
|
+
|
|
244
|
+
# Send audio
|
|
245
|
+
ctx.audio("song.mp3", performer: "Artist", title: "Song Title")
|
|
246
|
+
|
|
247
|
+
# Send video
|
|
248
|
+
ctx.video("clip.mp4", caption: "Check this out!")
|
|
249
|
+
|
|
250
|
+
# Send voice message
|
|
251
|
+
ctx.voice("message.ogg", caption: "Voice note")
|
|
252
|
+
|
|
253
|
+
# Send sticker
|
|
254
|
+
ctx.sticker("CAACAgIAAxk...") # Sticker file_id
|
|
255
|
+
|
|
256
|
+
# Send location
|
|
257
|
+
ctx.location(51.5074, -0.1278) # London coordinates
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
File Input Options
|
|
261
|
+
|
|
262
|
+
```ruby
|
|
263
|
+
# All accept multiple input types:
|
|
264
|
+
ctx.photo("path/to/file.jpg") # File path
|
|
265
|
+
ctx.photo(File.open("image.jpg")) # File object
|
|
266
|
+
ctx.photo(file_id_from_telegram) # Telegram file ID
|
|
267
|
+
ctx.photo(StringIO.new(image_data)) # In-memory data
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
⌨️ Keyboard Methods
|
|
271
|
+
|
|
272
|
+
Reply Keyboards (Appear at bottom)
|
|
273
|
+
|
|
274
|
+
```ruby
|
|
275
|
+
# Create a keyboard
|
|
276
|
+
keyboard = ctx.keyboard do |k|
|
|
277
|
+
k.button("Yes")
|
|
278
|
+
k.button("No")
|
|
279
|
+
k.row # New row
|
|
280
|
+
k.button("Maybe")
|
|
281
|
+
k.button("Cancel")
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# Send with keyboard
|
|
285
|
+
ctx.reply_with_keyboard("Choose option:", keyboard)
|
|
286
|
+
|
|
287
|
+
# Remove keyboard
|
|
288
|
+
ctx.remove_keyboard("Keyboard removed!")
|
|
289
|
+
|
|
290
|
+
# Keyboard options
|
|
291
|
+
keyboard = ctx.keyboard(resize: true, one_time: true) do |k|
|
|
292
|
+
k.button("Option")
|
|
293
|
+
end
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
Inline Keyboards (Buttons in message)
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
# Create inline keyboard
|
|
300
|
+
inline_kb = ctx.inline_keyboard do |k|
|
|
301
|
+
k.button("Order Pizza", callback_data: "order_pizza")
|
|
302
|
+
k.button("View Menu", url: "https://example.com/menu")
|
|
303
|
+
k.button("Share", switch_inline_query: "Check this pizza place!")
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Send with inline keyboard
|
|
307
|
+
ctx.reply_with_inline_keyboard("What would you like?", inline_kb)
|
|
308
|
+
|
|
309
|
+
# Edit existing keyboard
|
|
310
|
+
ctx.edit_message_reply_markup(new_inline_kb)
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
⏳ Action Methods
|
|
314
|
+
|
|
315
|
+
Chat Actions (Typing indicators)
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
# Show typing
|
|
319
|
+
ctx.typing # Shows "typing..."
|
|
320
|
+
|
|
321
|
+
# Show upload status
|
|
322
|
+
ctx.uploading_photo # "uploading photo..."
|
|
323
|
+
ctx.uploading_video # "uploading video..."
|
|
324
|
+
ctx.uploading_document # "uploading document..."
|
|
325
|
+
ctx.uploading_audio # "uploading audio..."
|
|
326
|
+
|
|
327
|
+
# Generic action
|
|
328
|
+
ctx.send_chat_action("choose_sticker") # Any valid action
|
|
329
|
+
|
|
330
|
+
# Wrap long operations
|
|
331
|
+
ctx.with_typing do
|
|
332
|
+
# Complex processing...
|
|
333
|
+
sleep(2)
|
|
334
|
+
ctx.reply("Done!")
|
|
335
|
+
end
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
✏️ Message Management
|
|
339
|
+
|
|
340
|
+
How to Get Message IDs for Editing
|
|
341
|
+
|
|
342
|
+
Important: To edit a message, you need its message_id. Here's the easiest way:
|
|
343
|
+
|
|
344
|
+
```ruby
|
|
345
|
+
# Method 1: Capture the ID right after sending
|
|
346
|
+
bot.command("editme") do |ctx|
|
|
347
|
+
# Send a message first and capture the response
|
|
348
|
+
response = ctx.reply("This will be edited in 2 seconds...")
|
|
349
|
+
|
|
350
|
+
# Extract the message_id from the API response
|
|
351
|
+
if response && response["ok"]
|
|
352
|
+
message_id = response["result"]["message_id"]
|
|
353
|
+
|
|
354
|
+
# Now you can edit it later
|
|
355
|
+
sleep(2)
|
|
356
|
+
ctx.edit_message_text("This is the edited text!", message_id: message_id)
|
|
357
|
+
else
|
|
358
|
+
ctx.reply("Failed to send message!")
|
|
359
|
+
end
|
|
360
|
+
end
|
|
361
|
+
|
|
362
|
+
# 💡 PRO TIP: The response structure is:
|
|
363
|
+
# {
|
|
364
|
+
# "ok": true,
|
|
365
|
+
# "result": {
|
|
366
|
+
# "message_id": 123, # ← THIS IS WHAT YOU NEED!
|
|
367
|
+
# "chat": {...},
|
|
368
|
+
# "text": "..."
|
|
369
|
+
# }
|
|
370
|
+
# }
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Edit & Delete
|
|
374
|
+
|
|
375
|
+
```ruby
|
|
376
|
+
# Edit message text (requires message_id)
|
|
377
|
+
ctx.edit_message_text("Updated text!", message_id: message_id)
|
|
378
|
+
ctx.edit_message_text("New text", message_id: message_id, parse_mode: "HTML")
|
|
379
|
+
|
|
380
|
+
# Delete messages
|
|
381
|
+
ctx.delete_message # Delete the current message (if triggered by message)
|
|
382
|
+
ctx.delete_message(specific_message_id) # Delete any message by ID
|
|
383
|
+
|
|
384
|
+
# Pin/unpin messages
|
|
385
|
+
ctx.pin_message(message_id) # Pin to chat
|
|
386
|
+
ctx.unpin_message # Unpin from chat
|
|
387
|
+
ctx.unpin_message(message_id) # Unpin specific message
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Practical Editing Example
|
|
391
|
+
|
|
392
|
+
```ruby
|
|
393
|
+
# Progressive editing example
|
|
394
|
+
bot.command("countdown") do |ctx|
|
|
395
|
+
# Send initial message
|
|
396
|
+
response = ctx.reply("Starting countdown...")
|
|
397
|
+
return unless response["ok"]
|
|
398
|
+
|
|
399
|
+
message_id = response["result"]["message_id"]
|
|
400
|
+
|
|
401
|
+
# Edit it multiple times
|
|
402
|
+
3.downto(1) do |num|
|
|
403
|
+
sleep(1)
|
|
404
|
+
ctx.edit_message_text("#{num}...", message_id: message_id)
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
sleep(1)
|
|
408
|
+
ctx.edit_message_text("🎉 Blast off!", message_id: message_id)
|
|
409
|
+
end
|
|
410
|
+
|
|
411
|
+
# Store and edit later
|
|
412
|
+
bot.command("setreminder") do |ctx|
|
|
413
|
+
# Send reminder message
|
|
414
|
+
response = ctx.reply("Reminder set for 5 seconds from now...")
|
|
415
|
+
return unless response["ok"]
|
|
416
|
+
|
|
417
|
+
# Store the message ID in session
|
|
418
|
+
ctx.session[:reminder_message_id] = response["result"]["message_id"]
|
|
419
|
+
|
|
420
|
+
# Schedule edit (in real app, use background job)
|
|
421
|
+
Thread.new do
|
|
422
|
+
sleep(5)
|
|
423
|
+
# You'd need to retrieve context somehow here
|
|
424
|
+
# Real implementation would use a job queue
|
|
425
|
+
end
|
|
426
|
+
end
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
Forward & Copy
|
|
430
|
+
|
|
431
|
+
```ruby
|
|
432
|
+
# Forward message
|
|
433
|
+
ctx.forward_message(source_chat_id, message_id)
|
|
434
|
+
|
|
435
|
+
# Copy message (keeps formatting)
|
|
436
|
+
ctx.copy_message(source_chat_id, message_id)
|
|
437
|
+
|
|
438
|
+
# Both support options
|
|
439
|
+
ctx.forward_message(
|
|
440
|
+
source_chat_id,
|
|
441
|
+
message_id,
|
|
442
|
+
disable_notification: true
|
|
443
|
+
)
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
👥 Group Management
|
|
447
|
+
|
|
448
|
+
Member Management
|
|
449
|
+
|
|
450
|
+
```ruby
|
|
451
|
+
# Requires bot admin permissions!
|
|
452
|
+
ctx.kick_chat_member(user_id) # Remove from group
|
|
453
|
+
ctx.ban_chat_member(user_id) # Ban permanently
|
|
454
|
+
ctx.unban_chat_member(user_id) # Remove ban
|
|
455
|
+
|
|
456
|
+
# With options
|
|
457
|
+
ctx.ban_chat_member(
|
|
458
|
+
user_id,
|
|
459
|
+
until_date: Time.now + 86400, # 24-hour ban
|
|
460
|
+
revoke_messages: true # Delete user's messages
|
|
461
|
+
)
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
Chat Information
|
|
465
|
+
|
|
466
|
+
```ruby
|
|
467
|
+
# Get chat details
|
|
468
|
+
chat_info = ctx.get_chat
|
|
469
|
+
# Returns: {id, type, title, username, etc.}
|
|
470
|
+
|
|
471
|
+
# Get administrators
|
|
472
|
+
admins = ctx.get_chat_administrators
|
|
473
|
+
# Array of ChatMember objects
|
|
474
|
+
|
|
475
|
+
# Member count
|
|
476
|
+
count = ctx.get_chat_members_count
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
🔧 Utility Methods
|
|
480
|
+
|
|
481
|
+
Command Helpers
|
|
482
|
+
|
|
483
|
+
```ruby
|
|
484
|
+
# Check if message is a command
|
|
485
|
+
if ctx.command?
|
|
486
|
+
ctx.reply("I see a command!")
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
# Get command arguments
|
|
490
|
+
# User sends: /search funny cats
|
|
491
|
+
args = ctx.command_args # "funny cats"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
API & Logging
|
|
495
|
+
|
|
496
|
+
```ruby
|
|
497
|
+
# Direct API access (advanced)
|
|
498
|
+
ctx.api.call('sendMessage', chat_id: 123, text: "Direct call")
|
|
499
|
+
|
|
500
|
+
# Logging
|
|
501
|
+
ctx.logger.info("User #{ctx.from.id} said hello")
|
|
502
|
+
ctx.logger.error("Something went wrong!", error: e)
|
|
503
|
+
|
|
504
|
+
# Raw update (debugging)
|
|
505
|
+
puts ctx.raw_update # Original Telegram JSON
|
|
506
|
+
|
|
507
|
+
# User ID shortcut
|
|
508
|
+
user_id = ctx.user_id # Same as ctx.from.id
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
🎯 Best Practices
|
|
512
|
+
|
|
513
|
+
1. Always Check for Nil
|
|
514
|
+
|
|
515
|
+
```ruby
|
|
516
|
+
# Safe access pattern
|
|
517
|
+
if ctx.chat && ctx.chat.type == "private"
|
|
518
|
+
# Handle private chat
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
# Ruby 2.3+ safe navigation
|
|
522
|
+
title = ctx.chat&.title || "Unknown"
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
2. Handle Different Update Types
|
|
526
|
+
|
|
527
|
+
```ruby
|
|
528
|
+
bot.on(:message) do |ctx|
|
|
529
|
+
# Handle messages
|
|
530
|
+
end
|
|
531
|
+
|
|
532
|
+
bot.on(:callback_query) do |ctx|
|
|
533
|
+
# Handle button clicks (ALWAYS answer!)
|
|
534
|
+
ctx.answer_callback_query
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
bot.on(:inline_query) do |ctx|
|
|
538
|
+
# Handle @bot queries
|
|
539
|
+
ctx.answer_inline_query(results)
|
|
540
|
+
end
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
3. Error Handling
|
|
544
|
+
|
|
545
|
+
```ruby
|
|
546
|
+
begin
|
|
547
|
+
ctx.reply(some_message)
|
|
548
|
+
rescue => e
|
|
549
|
+
ctx.logger.error("Failed to send: #{e.message}")
|
|
550
|
+
# Optionally notify user
|
|
551
|
+
ctx.reply("Sorry, something went wrong!")
|
|
552
|
+
end
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
4. Performance Tips
|
|
556
|
+
|
|
557
|
+
```ruby
|
|
558
|
+
# Use sessions wisely
|
|
559
|
+
ctx.session[:data] = large_data # ❌ Avoid huge data
|
|
560
|
+
ctx.session[:count] = 123 # ✅ Store simple data
|
|
561
|
+
|
|
562
|
+
# Clean up state
|
|
563
|
+
ctx.state.clear # When done with multi-step process
|
|
564
|
+
|
|
565
|
+
# Use file IDs for repeated media
|
|
566
|
+
# First send gets file_id, reuse it!
|
|
567
|
+
photo_file_id = ctx.message.photo.last.file_id
|
|
568
|
+
ctx.session[:last_photo] = photo_file_id
|
|
569
|
+
```
|
|
570
|
+
|
|
571
|
+
📚 Quick Reference
|
|
572
|
+
|
|
573
|
+
Method Best For When to Use
|
|
574
|
+
ctx.session User preferences, game state, shopping carts Data that should persist
|
|
575
|
+
ctx.state Multi-step forms, temporary flags Within a single conversation
|
|
576
|
+
ctx.match Parsing commands with arguments Regex pattern matching
|
|
577
|
+
ctx.scene Complex conversations Registration, surveys, workflows
|
|
578
|
+
ctx.reply_with_keyboard Multiple choice, menus When you need user selection
|
|
579
|
+
ctx.reply_with_inline_keyboard Interactive messages Actions without leaving chat
|
|
580
|
+
|
|
581
|
+
Remember: ctx is your interface to everything Telegram. Use it wisely, and your bot will shine! 🌟
|
data/lib/api/client.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Telegem
|
|
|
10
10
|
|
|
11
11
|
def initialize(token, **options)
|
|
12
12
|
@token = token
|
|
13
|
+
@mutex = Mutex.new
|
|
13
14
|
@logger = options[:logger] || Logger.new($stdout)
|
|
14
15
|
timeout = options[:timeout] || 30
|
|
15
16
|
|
|
@@ -34,6 +35,7 @@ module Telegem
|
|
|
34
35
|
end
|
|
35
36
|
|
|
36
37
|
def call!(method, params = {})
|
|
38
|
+
@mutex.synchroniz do
|
|
37
39
|
url = "#{BASE_URL}/bot#{@token}/#{method}"
|
|
38
40
|
|
|
39
41
|
@logger.debug("API Call (sync): #{method}") if @logger
|
|
@@ -52,6 +54,7 @@ module Telegem
|
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
nil
|
|
57
|
+
end
|
|
55
58
|
end
|
|
56
59
|
|
|
57
60
|
def upload(method, params)
|
data/lib/core/bot.rb
CHANGED
|
@@ -10,6 +10,7 @@ module Telegem
|
|
|
10
10
|
def initialize(token, **options)
|
|
11
11
|
@token = token
|
|
12
12
|
@api = API::Client.new(token, **options.slice(:logger, :timeout))
|
|
13
|
+
@api_mutex = Mutex.new # ← LINE 1 ADDED HERE
|
|
13
14
|
|
|
14
15
|
@handlers = {
|
|
15
16
|
message: [],
|
|
@@ -171,25 +172,26 @@ module Telegem
|
|
|
171
172
|
end
|
|
172
173
|
|
|
173
174
|
def fetch_updates
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
end
|
|
175
|
+
params = {
|
|
176
|
+
timeout: @polling_options[:timeout],
|
|
177
|
+
limit: @polling_options[:limit]
|
|
178
|
+
}
|
|
179
|
+
params[:offset] = @offset if @offset
|
|
180
|
+
params[:allowed_updates] = @polling_options[:allowed_updates] if @polling_options[:allowed_updates]
|
|
181
|
+
|
|
182
|
+
@logger.debug("Fetching updates with offset: #{@offset}")
|
|
183
|
+
|
|
184
|
+
# Simple direct call - no .wait
|
|
185
|
+
updates = @api.call!('getUpdates', params)
|
|
186
|
+
|
|
187
|
+
if updates && updates.is_a?(Array)
|
|
188
|
+
@logger.debug("Got #{updates.length} updates")
|
|
189
|
+
return { 'ok' => true, 'result' => updates }
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
nil
|
|
193
|
+
end
|
|
194
|
+
|
|
193
195
|
def handle_updates_response(api_response)
|
|
194
196
|
updates = api_response['result'] || []
|
|
195
197
|
|
|
@@ -250,15 +252,17 @@ end
|
|
|
250
252
|
end
|
|
251
253
|
|
|
252
254
|
def process_update(update)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
255
|
+
@api_mutex.synchronize do # ← LINE 2 ADDED HERE
|
|
256
|
+
ctx = Context.new(update, self)
|
|
257
|
+
|
|
258
|
+
begin
|
|
259
|
+
run_middleware_chain(ctx) do |context|
|
|
260
|
+
dispatch_to_handlers(context)
|
|
261
|
+
end
|
|
262
|
+
rescue => e
|
|
263
|
+
handle_error(e, ctx)
|
|
258
264
|
end
|
|
259
|
-
|
|
260
|
-
handle_error(e, ctx)
|
|
261
|
-
end
|
|
265
|
+
end # ← This 'end' matches the 'do' above
|
|
262
266
|
end
|
|
263
267
|
|
|
264
268
|
def run_middleware_chain(ctx, &final)
|
data/lib/telegem.rb
CHANGED