telegem 3.0.6 → 3.1.3
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/CHANGELOG +92 -12
- data/CODE_OF_CONDUCT.md +13 -0
- data/Contributing.md +125 -517
- data/Readme.md +10 -61
- data/Starts_HallofFame.md +27 -21
- data/lib/api/client.rb +63 -56
- data/lib/core/bot.rb +32 -22
- data/lib/core/context.rb +47 -18
- data/lib/core/rate_limit.rb +100 -0
- data/lib/core/scene.rb +110 -43
- data/lib/plugins/file_extract.rb +97 -0
- data/lib/session/scene_middleware.rb +22 -0
- data/lib/telegem.rb +3 -1
- metadata +26 -84
- data/docs-src/Bot-registration_.PNG +0 -0
- data/docs-src/bot.md +0 -464
- data/docs-src/context|ctx|.md +0 -531
- data/docs-src/ctx.md +0 -399
- data/docs-src/getting-started.md +0 -328
- data/docs-src/keyboard_inline.md +0 -413
- data/docs-src/scene.md +0 -509
- data/docs-src/webhook.md +0 -341
- /data/{docs-src → lib/plugins}/.gitkeep +0 -0
data/docs-src/bot.md
DELETED
|
@@ -1,464 +0,0 @@
|
|
|
1
|
-
🧠 What is bot?
|
|
2
|
-
|
|
3
|
-
If ctx is your hands (doing things), bot is your brain (deciding what to do).
|
|
4
|
-
|
|
5
|
-
```ruby
|
|
6
|
-
bot = Telegem.new("YOUR_TOKEN") # Born!
|
|
7
|
-
|
|
8
|
-
bot.command('start') do |ctx| # Learns to respond to /start
|
|
9
|
-
ctx.reply("Hello!") # Says hello
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
bot.start_polling # Starts listening!
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
🎯 The 5 Ways Your Bot Listens
|
|
16
|
-
|
|
17
|
-
1. bot.command() - For Slash Commands
|
|
18
|
-
|
|
19
|
-
When users type /something:
|
|
20
|
-
|
|
21
|
-
```ruby
|
|
22
|
-
# Basic command
|
|
23
|
-
bot.command('start') do |ctx|
|
|
24
|
-
ctx.reply("Welcome! 👋")
|
|
25
|
-
end
|
|
26
|
-
|
|
27
|
-
# With arguments
|
|
28
|
-
bot.command('search') do |ctx|
|
|
29
|
-
query = ctx.command_args # What comes after /search
|
|
30
|
-
if query.empty?
|
|
31
|
-
ctx.reply("Usage: /search <something>")
|
|
32
|
-
else
|
|
33
|
-
ctx.reply("Searching for '#{query}'...")
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Multiple commands, same handler
|
|
38
|
-
['help', 'info', 'about'].each do |cmd|
|
|
39
|
-
bot.command(cmd) do |ctx|
|
|
40
|
-
ctx.reply("I'm a helpful bot! 🤖")
|
|
41
|
-
end
|
|
42
|
-
end
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
What users see:
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
User: /start
|
|
49
|
-
Bot: Welcome! 👋
|
|
50
|
-
|
|
51
|
-
User: /search cats
|
|
52
|
-
Bot: Searching for 'cats'...
|
|
53
|
-
|
|
54
|
-
User: /search
|
|
55
|
-
Bot: Usage: /search <something>
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
2. bot.hears() - For Text Patterns
|
|
59
|
-
|
|
60
|
-
When users say specific words or patterns:
|
|
61
|
-
|
|
62
|
-
```ruby
|
|
63
|
-
# Exact match
|
|
64
|
-
bot.hears('hello') do |ctx|
|
|
65
|
-
ctx.reply("Hello there! 😊")
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Case-insensitive
|
|
69
|
-
bot.hears(/hello|hi|hey/i) do |ctx|
|
|
70
|
-
ctx.reply("Greetings! 🖐️")
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
# Regex with capture groups
|
|
74
|
-
bot.hears(/my name is (\w+)/i) do |ctx|
|
|
75
|
-
name = ctx.match[1] # Captured part
|
|
76
|
-
ctx.reply("Nice to meet you, #{name}! 👋")
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# Pattern with wildcard
|
|
80
|
-
bot.hears(/I love (\w+)/) do |ctx|
|
|
81
|
-
thing = ctx.match[1]
|
|
82
|
-
ctx.reply("I love #{thing} too! ❤️")
|
|
83
|
-
end
|
|
84
|
-
```
|
|
85
|
-
|
|
86
|
-
What users see:
|
|
87
|
-
|
|
88
|
-
```
|
|
89
|
-
User: hello
|
|
90
|
-
Bot: Hello there! 😊
|
|
91
|
-
|
|
92
|
-
User: My name is John
|
|
93
|
-
Bot: Nice to meet you, John! 👋
|
|
94
|
-
|
|
95
|
-
User: I love pizza
|
|
96
|
-
Bot: I love pizza too! ❤️
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
3. bot.on() - For Everything Else
|
|
100
|
-
|
|
101
|
-
The Swiss Army knife handler:
|
|
102
|
-
|
|
103
|
-
```ruby
|
|
104
|
-
# Handle ALL messages
|
|
105
|
-
bot.on(:message) do |ctx|
|
|
106
|
-
ctx.reply("I got a message!")
|
|
107
|
-
end
|
|
108
|
-
|
|
109
|
-
# Handle only photos
|
|
110
|
-
bot.on(:message, photo: true) do |ctx|
|
|
111
|
-
ctx.reply("Nice photo! 📸")
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
# Handle only videos
|
|
115
|
-
bot.on(:message, video: true) do |ctx|
|
|
116
|
-
ctx.reply("Cool video! 🎥")
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
# Handle only documents
|
|
120
|
-
bot.on(:message, document: true) do |ctx|
|
|
121
|
-
ctx.reply("Thanks for the document! 📄")
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
# Handle only locations
|
|
125
|
-
bot.on(:message, location: true) do |ctx|
|
|
126
|
-
ctx.reply("Thanks for sharing location! 📍")
|
|
127
|
-
end
|
|
128
|
-
|
|
129
|
-
# Filter by chat type
|
|
130
|
-
bot.on(:message, chat_type: 'private') do |ctx|
|
|
131
|
-
ctx.reply("Private chat message!")
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
bot.on(:message, chat_type: 'group') do |ctx|
|
|
135
|
-
ctx.reply("Group chat message!")
|
|
136
|
-
end
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
4. bot.on(:callback_query) - For Button Clicks
|
|
140
|
-
|
|
141
|
-
When users click inline buttons:
|
|
142
|
-
|
|
143
|
-
```ruby
|
|
144
|
-
# Handle ALL button clicks
|
|
145
|
-
bot.on(:callback_query) do |ctx|
|
|
146
|
-
ctx.answer_callback_query(text: "Button clicked!")
|
|
147
|
-
ctx.reply("You clicked: #{ctx.data}")
|
|
148
|
-
end
|
|
149
|
-
|
|
150
|
-
# Filter by button data
|
|
151
|
-
bot.on(:callback_query, data: 'yes') do |ctx|
|
|
152
|
-
ctx.answer_callback_query(text: "You said YES! ✅")
|
|
153
|
-
ctx.edit_message_text("Confirmed! ✅")
|
|
154
|
-
end
|
|
155
|
-
|
|
156
|
-
bot.on(:callback_query, data: 'no') do |ctx|
|
|
157
|
-
ctx.answer_callback_query(text: "You said NO! ❌")
|
|
158
|
-
ctx.edit_message_text("Cancelled! ❌")
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
# Pattern matching button data
|
|
162
|
-
bot.on(:callback_query) do |ctx|
|
|
163
|
-
if ctx.data.start_with?('vote_')
|
|
164
|
-
option = ctx.data.split('_').last
|
|
165
|
-
ctx.answer_callback_query(text: "Voted for #{option}!")
|
|
166
|
-
ctx.edit_message_text("You voted: #{option} ✅")
|
|
167
|
-
end
|
|
168
|
-
end
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
5. bot.on(:inline_query) - For @bot Searches
|
|
172
|
-
|
|
173
|
-
When users type @yourbot something:
|
|
174
|
-
|
|
175
|
-
```ruby
|
|
176
|
-
bot.on(:inline_query) do |ctx|
|
|
177
|
-
query = ctx.query # What they typed after @yourbot
|
|
178
|
-
|
|
179
|
-
results = [
|
|
180
|
-
{
|
|
181
|
-
type: "article",
|
|
182
|
-
id: "1",
|
|
183
|
-
title: "Result for: #{query}",
|
|
184
|
-
input_message_content: {
|
|
185
|
-
message_text: "You searched: #{query}"
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
]
|
|
189
|
-
|
|
190
|
-
ctx.answer_inline_query(results)
|
|
191
|
-
end
|
|
192
|
-
```
|
|
193
|
-
|
|
194
|
-
🎭 Real Bot Examples
|
|
195
|
-
|
|
196
|
-
Example 1: Echo Bot (Beginners)
|
|
197
|
-
|
|
198
|
-
```ruby
|
|
199
|
-
bot = Telegem.new("TOKEN")
|
|
200
|
-
|
|
201
|
-
# Respond to /start
|
|
202
|
-
bot.command('start') do |ctx|
|
|
203
|
-
ctx.reply("I echo everything you say! 🔊")
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
# Echo all messages
|
|
207
|
-
bot.on(:message) do |ctx|
|
|
208
|
-
if ctx.message.text
|
|
209
|
-
ctx.reply("You said: #{ctx.message.text}")
|
|
210
|
-
end
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
bot.start_polling
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Example 2: Quiz Bot (Intermediate)
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
bot = Telegem.new("TOKEN")
|
|
220
|
-
|
|
221
|
-
QUESTIONS = [
|
|
222
|
-
{ q: "2 + 2?", a: "4", options: ["3", "4", "5"] },
|
|
223
|
-
{ q: "Capital of France?", a: "Paris", options: ["London", "Paris", "Berlin"] }
|
|
224
|
-
]
|
|
225
|
-
|
|
226
|
-
bot.command('quiz') do |ctx|
|
|
227
|
-
question = QUESTIONS.sample
|
|
228
|
-
|
|
229
|
-
inline = Telegem.inline do
|
|
230
|
-
question[:options].each do |option|
|
|
231
|
-
row button option, callback_data: "answer_#{option}"
|
|
232
|
-
end
|
|
233
|
-
end
|
|
234
|
-
|
|
235
|
-
ctx.reply(question[:q], reply_markup: inline)
|
|
236
|
-
ctx.session[:correct] = question[:a]
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
bot.on(:callback_query) do |ctx|
|
|
240
|
-
answer = ctx.data.split('_').last
|
|
241
|
-
correct = ctx.session[:correct]
|
|
242
|
-
|
|
243
|
-
if answer == correct
|
|
244
|
-
ctx.answer_callback_query(text: "✅ Correct!")
|
|
245
|
-
ctx.edit_message_text("🎉 Correct! The answer is #{correct}")
|
|
246
|
-
else
|
|
247
|
-
ctx.answer_callback_query(text: "❌ Wrong! It's #{correct}")
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
Example 3: Support Bot (Advanced)
|
|
253
|
-
|
|
254
|
-
```ruby
|
|
255
|
-
bot = Telegem.new("TOKEN")
|
|
256
|
-
|
|
257
|
-
# Main menu
|
|
258
|
-
bot.command('start') do |ctx|
|
|
259
|
-
keyboard = Telegem.keyboard do
|
|
260
|
-
row "Report Bug", "Request Feature"
|
|
261
|
-
row "Ask Question", "Contact Human"
|
|
262
|
-
end
|
|
263
|
-
|
|
264
|
-
ctx.reply("Support Menu:", reply_markup: keyboard)
|
|
265
|
-
end
|
|
266
|
-
|
|
267
|
-
# Handle menu choices
|
|
268
|
-
bot.hears("Report Bug") do |ctx|
|
|
269
|
-
ctx.reply("Describe the bug:")
|
|
270
|
-
ctx.state[:collecting_bug] = true
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
bot.hears("Ask Question") do |ctx|
|
|
274
|
-
ctx.reply("What's your question?")
|
|
275
|
-
ctx.state[:collecting_question] = true
|
|
276
|
-
end
|
|
277
|
-
|
|
278
|
-
# Collect user input
|
|
279
|
-
bot.on(:message) do |ctx|
|
|
280
|
-
if ctx.state[:collecting_bug]
|
|
281
|
-
bug = ctx.message.text
|
|
282
|
-
# Save to database...
|
|
283
|
-
ctx.reply("Bug reported! ID: ##{rand(1000)}")
|
|
284
|
-
ctx.state.delete(:collecting_bug)
|
|
285
|
-
|
|
286
|
-
elsif ctx.state[:collecting_question]
|
|
287
|
-
question = ctx.message.text
|
|
288
|
-
# Save to database...
|
|
289
|
-
ctx.reply("Question logged! We'll reply soon.")
|
|
290
|
-
ctx.state.delete(:collecting_question)
|
|
291
|
-
end
|
|
292
|
-
end
|
|
293
|
-
```
|
|
294
|
-
|
|
295
|
-
⚡ Pro Tips & Patterns
|
|
296
|
-
|
|
297
|
-
Tip 1: Organize Your Handlers
|
|
298
|
-
|
|
299
|
-
```ruby
|
|
300
|
-
# Instead of one giant file:
|
|
301
|
-
# handlers/commands.rb
|
|
302
|
-
def setup_commands(bot)
|
|
303
|
-
bot.command('start') { |ctx| ctx.reply("Start!") }
|
|
304
|
-
bot.command('help') { |ctx| ctx.reply("Help!") }
|
|
305
|
-
end
|
|
306
|
-
|
|
307
|
-
# handlers/messages.rb
|
|
308
|
-
def setup_messages(bot)
|
|
309
|
-
bot.on(:message, photo: true) { |ctx| ctx.reply("Photo!") }
|
|
310
|
-
bot.on(:message, video: true) { |ctx| ctx.reply("Video!") }
|
|
311
|
-
end
|
|
312
|
-
|
|
313
|
-
# main.rb
|
|
314
|
-
bot = Telegem.new("TOKEN")
|
|
315
|
-
setup_commands(bot)
|
|
316
|
-
setup_messages(bot)
|
|
317
|
-
```
|
|
318
|
-
|
|
319
|
-
Tip 2: Use Middleware for Common Tasks
|
|
320
|
-
|
|
321
|
-
```ruby
|
|
322
|
-
# Log all messages
|
|
323
|
-
class LoggerMiddleware
|
|
324
|
-
def call(ctx, next_middleware)
|
|
325
|
-
puts "[#{Time.now}] #{ctx.from.id}: #{ctx.message.text}"
|
|
326
|
-
next_middleware.call(ctx)
|
|
327
|
-
end
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
bot.use(LoggerMiddleware.new)
|
|
331
|
-
```
|
|
332
|
-
|
|
333
|
-
Tip 3: Rate Limiting
|
|
334
|
-
|
|
335
|
-
```ruby
|
|
336
|
-
bot.command('spam') do |ctx|
|
|
337
|
-
user_id = ctx.from.id
|
|
338
|
-
|
|
339
|
-
# Allow only 5 uses per minute
|
|
340
|
-
ctx.session[:spam_count] ||= 0
|
|
341
|
-
ctx.session[:spam_count] += 1
|
|
342
|
-
|
|
343
|
-
if ctx.session[:spam_count] > 5
|
|
344
|
-
ctx.reply("🚫 Too many requests! Wait a minute.")
|
|
345
|
-
else
|
|
346
|
-
ctx.reply("Spam count: #{ctx.session[:spam_count]}")
|
|
347
|
-
end
|
|
348
|
-
end
|
|
349
|
-
```
|
|
350
|
-
|
|
351
|
-
🚀 Bot Lifecycle Management
|
|
352
|
-
|
|
353
|
-
Starting Your Bot
|
|
354
|
-
|
|
355
|
-
```ruby
|
|
356
|
-
# Development (polling)
|
|
357
|
-
bot.start_polling(
|
|
358
|
-
timeout: 30, # Wait 30 seconds for updates
|
|
359
|
-
limit: 100 # Get up to 100 updates at once
|
|
360
|
-
)
|
|
361
|
-
|
|
362
|
-
# Production (webhook)
|
|
363
|
-
server = bot.webhook(port: 3000)
|
|
364
|
-
server.run
|
|
365
|
-
server.set_webhook
|
|
366
|
-
```
|
|
367
|
-
|
|
368
|
-
Stopping Gracefully
|
|
369
|
-
|
|
370
|
-
```ruby
|
|
371
|
-
# In your main file
|
|
372
|
-
Signal.trap("INT") do
|
|
373
|
-
puts "\nShutting down..."
|
|
374
|
-
bot.shutdown
|
|
375
|
-
exit
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
# Or handle in code
|
|
379
|
-
begin
|
|
380
|
-
bot.start_polling
|
|
381
|
-
rescue Interrupt
|
|
382
|
-
bot.shutdown
|
|
383
|
-
end
|
|
384
|
-
```
|
|
385
|
-
|
|
386
|
-
🎮 Interactive Challenge
|
|
387
|
-
|
|
388
|
-
Build a Fortune Cookie Bot in 15 minutes:
|
|
389
|
-
|
|
390
|
-
1. /fortune - Gives random fortune
|
|
391
|
-
2. Hears "tell me a joke" - Tells joke
|
|
392
|
-
3. Hears "I'm feeling lucky" - Random emoji response
|
|
393
|
-
4. Button "Get Another" - New fortune
|
|
394
|
-
|
|
395
|
-
```ruby
|
|
396
|
-
# Starter code
|
|
397
|
-
fortunes = [
|
|
398
|
-
"You will find happiness with a new friend.",
|
|
399
|
-
"A dream you have will come true.",
|
|
400
|
-
"Now is the time to try something new.",
|
|
401
|
-
"Your hard work will pay off soon."
|
|
402
|
-
]
|
|
403
|
-
|
|
404
|
-
bot.command('fortune') do |ctx|
|
|
405
|
-
fortune = fortunes.sample
|
|
406
|
-
ctx.reply("🔮 #{fortune}")
|
|
407
|
-
|
|
408
|
-
# Your turn: Add inline button "Get Another"
|
|
409
|
-
end
|
|
410
|
-
|
|
411
|
-
bot.hears(/tell me a joke/i) do |ctx|
|
|
412
|
-
# Your turn: Add a joke
|
|
413
|
-
end
|
|
414
|
-
|
|
415
|
-
# Your turn: Add button click handler
|
|
416
|
-
```
|
|
417
|
-
|
|
418
|
-
📋 Cheat Sheet
|
|
419
|
-
|
|
420
|
-
When user... Use... Example
|
|
421
|
-
- Types /command bot.command() bot.command('start')
|
|
422
|
-
- Says exact word bot.hears('word') bot.hears('hello')
|
|
423
|
-
- Says pattern bot.hears(/pattern/) bot.hears(/I love \w+/)
|
|
424
|
-
- Sends any message bot.on(:message) bot.on(:message)
|
|
425
|
-
- Sends photo bot.on(:message, photo: true) Handles only photos
|
|
426
|
-
- Clicks button bot.on(:callback_query) Handles inline buttons
|
|
427
|
-
- Searches @bot bot.on(:inline_query) Inline mode results
|
|
428
|
-
|
|
429
|
-
🔧 Debugging Common Issues
|
|
430
|
-
|
|
431
|
-
Handler Not Firing?
|
|
432
|
-
|
|
433
|
-
```ruby
|
|
434
|
-
# Add logging
|
|
435
|
-
bot.on(:message) do |ctx|
|
|
436
|
-
puts "DEBUG: Got message: #{ctx.message.text}"
|
|
437
|
-
# Your handler...
|
|
438
|
-
end
|
|
439
|
-
```
|
|
440
|
-
|
|
441
|
-
Buttons Not Working?
|
|
442
|
-
|
|
443
|
-
```ruby
|
|
444
|
-
bot.on(:callback_query) do |ctx|
|
|
445
|
-
puts "DEBUG: Button data: #{ctx.data}"
|
|
446
|
-
ctx.answer_callback_query # ← DON'T FORGET THIS!
|
|
447
|
-
# Your handler...
|
|
448
|
-
end
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
Multiple Handlers Conflict?
|
|
452
|
-
|
|
453
|
-
Handlers run in order. First matching handler wins!
|
|
454
|
-
|
|
455
|
-
```ruby
|
|
456
|
-
bot.command('test') { |ctx| ctx.reply("First!") }
|
|
457
|
-
bot.command('test') { |ctx| ctx.reply("Second!") } # Never runs!
|
|
458
|
-
```
|
|
459
|
-
|
|
460
|
-
---
|
|
461
|
-
|
|
462
|
-
Your bot is now ready to listen! Start with one handler, test it, add another. Like teaching a child new words, one at a time. 🧒→🤖
|
|
463
|
-
|
|
464
|
-
Remember: Every great bot started with just /start. Build that, then add one more feature. Then another. Consistency beats complexity!
|