telegem 2.1.0 → 3.0.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/Readme.md +2 -2
- data/Starts_HallofFame.md +65 -0
- data/Test-Projects/bot.md +145 -0
- data/Test-Projects/bot1.rb +91 -0
- data/docs-src/Bot-registration_.PNG +0 -0
- data/docs-src/bot.md +349 -180
- data/docs-src/ctx.md +399 -0
- data/lib/api/client.rb +24 -23
- data/lib/core/bot.rb +69 -148
- data/lib/telegem.rb +1 -1
- data/lib/webhook/server.rb +87 -284
- data/setup.sh +30 -0
- metadata +21 -20
- data/Test-Projects/.gitkeep +0 -0
- data/Test-Projects/Movie-tracker-bot/.gitkeep +0 -0
- data/Test-Projects/Movie-tracker-bot/Gemfile +0 -2
- data/Test-Projects/Movie-tracker-bot/bot.rb +0 -62
- data/Test-Projects/Movie-tracker-bot/handlers/.gitkeep +0 -0
- data/Test-Projects/Movie-tracker-bot/handlers/add_1_.rb +0 -160
- data/Test-Projects/Movie-tracker-bot/handlers/add_2_.rb +0 -139
- data/Test-Projects/Movie-tracker-bot/handlers/premium.rb +0 -13
- data/Test-Projects/Movie-tracker-bot/handlers/report.rb +0 -31
- data/Test-Projects/Movie-tracker-bot/handlers/search.rb +0 -150
- data/Test-Projects/Movie-tracker-bot/handlers/sponsor.rb +0 -14
- data/Test-Projects/Movie-tracker-bot/handlers/start.rb +0 -48
- data/Test-Projects/Movie-tracker-bot/handlers/watch.rb +0 -210
- data/Test-Projects/Test-submitted-by-marvel/.gitkeep +0 -0
- data/Test-Projects/Test-submitted-by-marvel/Marvel-bot.md +0 -3
- data/Test-Projects/bot_test1.rb +0 -75
- data/Test-Projects/pizza_test_bot_guide.md +0 -163
- data/docs-src/understanding-ctx.md +0 -581
data/docs-src/bot.md
CHANGED
|
@@ -1,295 +1,464 @@
|
|
|
1
|
+
🧠 What is bot?
|
|
1
2
|
|
|
2
|
-
|
|
3
|
+
If ctx is your hands (doing things), bot is your brain (deciding what to do).
|
|
3
4
|
|
|
4
|
-
|
|
5
|
+
```ruby
|
|
6
|
+
bot = Telegem.new("YOUR_TOKEN") # Born!
|
|
5
7
|
|
|
6
|
-
|
|
8
|
+
bot.command('start') do |ctx| # Learns to respond to /start
|
|
9
|
+
ctx.reply("Hello!") # Says hello
|
|
10
|
+
end
|
|
7
11
|
|
|
8
|
-
|
|
9
|
-
bot = Telegem.new(token, **options)
|
|
10
|
-
# or
|
|
11
|
-
bot = Telegem::Core::Bot.new(token, **options)
|
|
12
|
+
bot.start_polling # Starts listening!
|
|
12
13
|
```
|
|
13
14
|
|
|
14
|
-
Bot
|
|
15
|
+
🎯 The 5 Ways Your Bot Listens
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
1. bot.command() - For Slash Commands
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
When users type /something:
|
|
19
20
|
|
|
20
21
|
```ruby
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
|
26
43
|
```
|
|
27
44
|
|
|
28
|
-
|
|
45
|
+
What users see:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
User: /start
|
|
49
|
+
Bot: Welcome! 👋
|
|
29
50
|
|
|
30
|
-
|
|
51
|
+
User: /search cats
|
|
52
|
+
Bot: Searching for 'cats'...
|
|
31
53
|
|
|
32
|
-
|
|
33
|
-
|
|
54
|
+
User: /search
|
|
55
|
+
Bot: Usage: /search <something>
|
|
34
56
|
```
|
|
35
57
|
|
|
36
|
-
|
|
58
|
+
2. bot.hears() - For Text Patterns
|
|
37
59
|
|
|
38
|
-
|
|
60
|
+
When users say specific words or patterns:
|
|
39
61
|
|
|
40
62
|
```ruby
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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! ❤️")
|
|
45
83
|
end
|
|
46
84
|
```
|
|
47
85
|
|
|
48
|
-
|
|
86
|
+
What users see:
|
|
49
87
|
|
|
50
|
-
|
|
88
|
+
```
|
|
89
|
+
User: hello
|
|
90
|
+
Bot: Hello there! 😊
|
|
51
91
|
|
|
52
|
-
|
|
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:
|
|
53
102
|
|
|
54
103
|
```ruby
|
|
55
|
-
|
|
56
|
-
|
|
104
|
+
# Handle ALL messages
|
|
105
|
+
bot.on(:message) do |ctx|
|
|
106
|
+
ctx.reply("I got a message!")
|
|
57
107
|
end
|
|
58
108
|
|
|
59
|
-
#
|
|
60
|
-
bot.
|
|
61
|
-
|
|
62
|
-
ctx.reply "Hello, #{name}!"
|
|
109
|
+
# Handle only photos
|
|
110
|
+
bot.on(:message, photo: true) do |ctx|
|
|
111
|
+
ctx.reply("Nice photo! 📸")
|
|
63
112
|
end
|
|
64
|
-
```
|
|
65
113
|
|
|
66
|
-
|
|
114
|
+
# Handle only videos
|
|
115
|
+
bot.on(:message, video: true) do |ctx|
|
|
116
|
+
ctx.reply("Cool video! 🎥")
|
|
117
|
+
end
|
|
67
118
|
|
|
68
|
-
|
|
119
|
+
# Handle only documents
|
|
120
|
+
bot.on(:message, document: true) do |ctx|
|
|
121
|
+
ctx.reply("Thanks for the document! 📄")
|
|
122
|
+
end
|
|
69
123
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
ctx.reply "Hello there! 👋"
|
|
124
|
+
# Handle only locations
|
|
125
|
+
bot.on(:message, location: true) do |ctx|
|
|
126
|
+
ctx.reply("Thanks for sharing location! 📍")
|
|
74
127
|
end
|
|
75
128
|
|
|
76
|
-
#
|
|
77
|
-
bot.
|
|
78
|
-
ctx.reply
|
|
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!")
|
|
79
136
|
end
|
|
80
137
|
```
|
|
81
138
|
|
|
82
|
-
on(
|
|
139
|
+
4. bot.on(:callback_query) - For Button Clicks
|
|
83
140
|
|
|
84
|
-
|
|
141
|
+
When users click inline buttons:
|
|
85
142
|
|
|
86
143
|
```ruby
|
|
87
|
-
#
|
|
88
|
-
bot.on(:
|
|
89
|
-
ctx.
|
|
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}")
|
|
90
148
|
end
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
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! ✅")
|
|
94
154
|
end
|
|
95
155
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
ctx.
|
|
156
|
+
bot.on(:callback_query, data: 'no') do |ctx|
|
|
157
|
+
ctx.answer_callback_query(text: "You said NO! ❌")
|
|
158
|
+
ctx.edit_message_text("Cancelled! ❌")
|
|
99
159
|
end
|
|
100
160
|
|
|
101
|
-
#
|
|
102
|
-
bot.on(:
|
|
103
|
-
ctx.
|
|
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
|
|
104
168
|
end
|
|
105
169
|
```
|
|
106
170
|
|
|
107
|
-
|
|
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
|
+
```
|
|
108
193
|
|
|
109
|
-
|
|
194
|
+
🎭 Real Bot Examples
|
|
110
195
|
|
|
111
|
-
|
|
196
|
+
Example 1: Echo Bot (Beginners)
|
|
112
197
|
|
|
113
198
|
```ruby
|
|
114
|
-
|
|
115
|
-
bot.use(Telegem::Session::Middleware)
|
|
199
|
+
bot = Telegem.new("TOKEN")
|
|
116
200
|
|
|
117
|
-
#
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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}")
|
|
122
210
|
end
|
|
123
211
|
end
|
|
124
212
|
|
|
125
|
-
bot.
|
|
213
|
+
bot.start_polling
|
|
126
214
|
```
|
|
127
215
|
|
|
128
|
-
|
|
216
|
+
Example 2: Quiz Bot (Intermediate)
|
|
129
217
|
|
|
130
|
-
|
|
218
|
+
```ruby
|
|
219
|
+
bot = Telegem.new("TOKEN")
|
|
131
220
|
|
|
132
|
-
|
|
221
|
+
QUESTIONS = [
|
|
222
|
+
{ q: "2 + 2?", a: "4", options: ["3", "4", "5"] },
|
|
223
|
+
{ q: "Capital of France?", a: "Paris", options: ["London", "Paris", "Berlin"] }
|
|
224
|
+
]
|
|
133
225
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
scene.step(:name) do |ctx|
|
|
142
|
-
name = ctx.message.text
|
|
143
|
-
ctx.reply "Hello #{name}! How old are you?"
|
|
144
|
-
ctx.session[:name] = name
|
|
145
|
-
ctx.session[:step] = :age
|
|
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
|
|
146
233
|
end
|
|
234
|
+
|
|
235
|
+
ctx.reply(question[:q], reply_markup: inline)
|
|
236
|
+
ctx.session[:correct] = question[:a]
|
|
147
237
|
end
|
|
148
238
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
ctx.
|
|
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
|
|
152
249
|
end
|
|
153
250
|
```
|
|
154
251
|
|
|
155
|
-
|
|
252
|
+
Example 3: Support Bot (Advanced)
|
|
156
253
|
|
|
157
|
-
|
|
254
|
+
```ruby
|
|
255
|
+
bot = Telegem.new("TOKEN")
|
|
158
256
|
|
|
159
|
-
|
|
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
|
|
160
263
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
server = bot.webhook.run
|
|
164
|
-
|
|
165
|
-
# Manual configuration
|
|
166
|
-
server = bot.webhook(
|
|
167
|
-
port: 3000,
|
|
168
|
-
host: '0.0.0.0',
|
|
169
|
-
secret_token: ENV['SECRET_TOKEN'],
|
|
170
|
-
logger: Logger.new('webhook.log')
|
|
171
|
-
)
|
|
172
|
-
server.run
|
|
173
|
-
server.set_webhook
|
|
174
|
-
```
|
|
264
|
+
ctx.reply("Support Menu:", reply_markup: keyboard)
|
|
265
|
+
end
|
|
175
266
|
|
|
176
|
-
|
|
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
|
|
177
272
|
|
|
178
|
-
|
|
273
|
+
bot.hears("Ask Question") do |ctx|
|
|
274
|
+
ctx.reply("What's your question?")
|
|
275
|
+
ctx.state[:collecting_question] = true
|
|
276
|
+
end
|
|
179
277
|
|
|
180
|
-
|
|
181
|
-
bot.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
)
|
|
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
|
|
186
293
|
```
|
|
187
294
|
|
|
188
|
-
|
|
295
|
+
⚡ Pro Tips & Patterns
|
|
189
296
|
|
|
190
|
-
|
|
297
|
+
Tip 1: Organize Your Handlers
|
|
191
298
|
|
|
192
299
|
```ruby
|
|
193
|
-
|
|
194
|
-
|
|
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
|
|
195
306
|
|
|
196
|
-
|
|
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
|
+
```
|
|
197
318
|
|
|
198
|
-
|
|
319
|
+
Tip 2: Use Middleware for Common Tasks
|
|
199
320
|
|
|
200
321
|
```ruby
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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)
|
|
204
331
|
```
|
|
205
332
|
|
|
206
|
-
|
|
333
|
+
Tip 3: Rate Limiting
|
|
207
334
|
|
|
208
|
-
|
|
335
|
+
```ruby
|
|
336
|
+
bot.command('spam') do |ctx|
|
|
337
|
+
user_id = ctx.from.id
|
|
209
338
|
|
|
210
|
-
|
|
339
|
+
# Allow only 5 uses per minute
|
|
340
|
+
ctx.session[:spam_count] ||= 0
|
|
341
|
+
ctx.session[:spam_count] += 1
|
|
211
342
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
# Send error to user
|
|
218
|
-
ctx.reply "Something went wrong! 😅" if ctx&.chat
|
|
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
|
|
219
348
|
end
|
|
220
349
|
```
|
|
221
350
|
|
|
222
|
-
|
|
351
|
+
🚀 Bot Lifecycle Management
|
|
223
352
|
|
|
224
|
-
|
|
353
|
+
Starting Your Bot
|
|
225
354
|
|
|
226
|
-
|
|
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
|
|
227
369
|
|
|
228
370
|
```ruby
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
bot.
|
|
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
|
|
241
384
|
```
|
|
242
385
|
|
|
243
|
-
|
|
386
|
+
🎮 Interactive Challenge
|
|
387
|
+
|
|
388
|
+
Build a Fortune Cookie Bot in 15 minutes:
|
|
244
389
|
|
|
245
|
-
|
|
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
|
|
246
394
|
|
|
247
395
|
```ruby
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
|
255
416
|
```
|
|
256
417
|
|
|
257
|
-
|
|
418
|
+
📋 Cheat Sheet
|
|
258
419
|
|
|
259
|
-
|
|
260
|
-
|
|
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
|
|
261
428
|
|
|
262
|
-
|
|
429
|
+
🔧 Debugging Common Issues
|
|
263
430
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
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...
|
|
267
438
|
end
|
|
439
|
+
```
|
|
268
440
|
|
|
269
|
-
|
|
270
|
-
bot.command('start') { |ctx| ctx.reply "Welcome!" }
|
|
271
|
-
bot.command('help') { |ctx| ctx.reply "Available: /start, /help, /menu" }
|
|
441
|
+
Buttons Not Working?
|
|
272
442
|
|
|
273
|
-
|
|
274
|
-
bot.
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
end
|
|
279
|
-
ctx.reply "Choose:", reply_markup: keyboard
|
|
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...
|
|
280
448
|
end
|
|
449
|
+
```
|
|
281
450
|
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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!
|
|
288
458
|
```
|
|
289
459
|
|
|
290
460
|
---
|
|
291
461
|
|
|
292
|
-
|
|
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. 🧒→🤖
|
|
293
463
|
|
|
294
|
-
|
|
295
|
-
```
|
|
464
|
+
Remember: Every great bot started with just /start. Build that, then add one more feature. Then another. Consistency beats complexity!
|