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.
data/docs/scenes.md ADDED
@@ -0,0 +1,517 @@
1
+ # Scene System
2
+
3
+ Scenes enable multi-step conversations and complex interaction flows. They manage state across multiple messages, perfect for forms, wizards, and guided interactions.
4
+
5
+ ## What are Scenes?
6
+
7
+ Scenes are stateful conversation flows that:
8
+
9
+ - Maintain context across multiple messages
10
+ - Guide users through step-by-step processes
11
+ - Handle timeouts and cancellations
12
+ - Store temporary data during the conversation
13
+
14
+ ## Basic Scene Creation
15
+
16
+ ```ruby
17
+ bot.scene :registration do
18
+ step :ask_name do |ctx|
19
+ ctx.reply("What's your name?")
20
+ end
21
+
22
+ step :save_name do |ctx|
23
+ name = ctx.message.text
24
+ ctx.session[:user_name] = name
25
+ ctx.reply("Hi #{name}! What's your email?")
26
+ end
27
+
28
+ step :complete do |ctx|
29
+ email = ctx.message.text
30
+ ctx.session[:user_email] = email
31
+ ctx.reply("Registration complete!")
32
+ ctx.leave_scene
33
+ end
34
+ end
35
+ ```
36
+
37
+ ## Entering Scenes
38
+
39
+ ```ruby
40
+ bot.command('register') do |ctx|
41
+ ctx.enter_scene(:registration)
42
+ end
43
+
44
+ # With initial data
45
+ bot.command('edit_profile') do |ctx|
46
+ ctx.enter_scene(:edit_profile, current_name: ctx.session[:name])
47
+ end
48
+ ```
49
+
50
+ ## Scene Methods
51
+
52
+ ### Context Methods
53
+
54
+ ```ruby
55
+ ctx.enter_scene(:scene_name) # Enter a scene
56
+ ctx.leave_scene # Leave current scene
57
+ ctx.leave_scene(reason: :cancel) # Leave with reason
58
+ ctx.in_scene? # Check if in scene
59
+ ctx.current_scene # Get current scene name
60
+ ctx.scene_data # Get scene data hash
61
+ ctx.ask("Question?") # Ask question (helper)
62
+ ctx.next_step # Move to next step
63
+ ctx.next_step(:specific_step) # Jump to specific step
64
+ ```
65
+
66
+ ## Scene Definition
67
+
68
+ ### Basic Structure
69
+
70
+ ```ruby
71
+ bot.scene :my_scene do
72
+ # Enter callback (optional)
73
+ on_enter do |ctx|
74
+ ctx.reply("Welcome to the scene!")
75
+ end
76
+
77
+ # Leave callback (optional)
78
+ on_leave do |ctx, reason, data|
79
+ ctx.reply("Scene ended: #{reason}")
80
+ end
81
+
82
+ # Steps
83
+ step :step1 do |ctx|
84
+ # Step logic
85
+ end
86
+
87
+ step :step2 do |ctx|
88
+ # More logic
89
+ end
90
+ end
91
+ ```
92
+
93
+ ### Step Flow
94
+
95
+ Steps execute in order by default:
96
+
97
+ ```ruby
98
+ bot.scene :survey do
99
+ step :question1 do |ctx|
100
+ ctx.ask("What's your favorite color?")
101
+ end
102
+
103
+ step :question2 do |ctx|
104
+ color = ctx.message.text
105
+ ctx.session[:color] = color
106
+ ctx.ask("What's your age?")
107
+ end
108
+
109
+ step :finish do |ctx|
110
+ age = ctx.message.text.to_i
111
+ ctx.session[:age] = age
112
+ ctx.reply("Thanks for the survey!")
113
+ ctx.leave_scene
114
+ end
115
+ end
116
+ ```
117
+
118
+ ### Conditional Steps
119
+
120
+ ```ruby
121
+ step :check_age do |ctx|
122
+ age = ctx.message.text.to_i
123
+
124
+ if age < 18
125
+ ctx.reply("Must be 18+")
126
+ ctx.leave_scene(reason: :underage)
127
+ else
128
+ ctx.session[:age] = age
129
+ ctx.next_step(:collect_email)
130
+ end
131
+ end
132
+ ```
133
+
134
+ ## Advanced Scene Features
135
+
136
+ ### Timeouts
137
+
138
+ ```ruby
139
+ bot.scene :timed_scene do
140
+ timeout 300 # 5 minutes
141
+
142
+ step :start do |ctx|
143
+ ctx.reply("You have 5 minutes...")
144
+ end
145
+
146
+ on_leave do |ctx, reason, data|
147
+ if reason == :timeout
148
+ ctx.reply("Time's up!")
149
+ end
150
+ end
151
+ end
152
+ ```
153
+
154
+ ### Scene Data
155
+
156
+ ```ruby
157
+ bot.scene :form do
158
+ step :name do |ctx|
159
+ ctx.ask("Name?")
160
+ end
161
+
162
+ step :email do |ctx|
163
+ name = ctx.scene_data[:name] # Access previous step data
164
+ ctx.ask("Email?")
165
+ end
166
+ end
167
+ ```
168
+
169
+ ### Dynamic Scenes
170
+
171
+ ```ruby
172
+ # Create scenes programmatically
173
+ def create_quiz_scene(questions)
174
+ bot.scene :quiz do
175
+ questions.each_with_index do |question, index|
176
+ step "q#{index}".to_sym do |ctx|
177
+ if index < questions.size - 1
178
+ ctx.ask(question)
179
+ else
180
+ ctx.reply("Quiz complete!")
181
+ ctx.leave_scene
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
187
+
188
+ create_quiz_scene(["Q1?", "Q2?", "Q3?"])
189
+ ```
190
+
191
+ ## Scene Lifecycle
192
+
193
+ ### Entering a Scene
194
+
195
+ 1. `on_enter` callbacks execute
196
+ 2. Scene data initializes
197
+ 3. First step executes
198
+
199
+ ### During Scene
200
+
201
+ - Each message goes to current step handler
202
+ - `ask()` helper sets waiting state
203
+ - Steps can jump to other steps
204
+ - Data persists in `ctx.scene_data`
205
+
206
+ ### Leaving a Scene
207
+
208
+ 1. `on_leave` callbacks execute
209
+ 2. Scene data cleans up
210
+ 3. Normal message processing resumes
211
+
212
+ ## Scene State Management
213
+
214
+ ### Scene Data Storage
215
+
216
+ ```ruby
217
+ # Scene data is stored in session
218
+ ctx.session[:telegem_scene] = {
219
+ id: "registration",
220
+ step: "ask_name",
221
+ data: { name: "John" },
222
+ entered_at: 1234567890,
223
+ timeout: 300,
224
+ waiting_for_response: true,
225
+ last_question: "What's your name?"
226
+ }
227
+ ```
228
+
229
+ ### Accessing Scene Data
230
+
231
+ ```ruby
232
+ # In scene steps
233
+ step :process do |ctx|
234
+ data = ctx.scene_data
235
+ name = data[:name]
236
+ email = data[:email]
237
+ end
238
+
239
+ # Outside scenes
240
+ if ctx.in_scene?
241
+ scene_data = ctx.session[:telegem_scene][:data]
242
+ end
243
+ ```
244
+
245
+ ## Error Handling
246
+
247
+ ### Scene Errors
248
+
249
+ ```ruby
250
+ bot.scene :error_prone do
251
+ step :risky do |ctx|
252
+ begin
253
+ risky_operation()
254
+ ctx.next_step
255
+ rescue => e
256
+ ctx.reply("Error occurred")
257
+ ctx.leave_scene(reason: :error)
258
+ end
259
+ end
260
+ end
261
+ ```
262
+
263
+ ### Timeout Handling
264
+
265
+ ```ruby
266
+ bot.scene :with_timeout do
267
+ timeout 60 # 1 minute
268
+
269
+ on_leave do |ctx, reason, data|
270
+ case reason
271
+ when :timeout
272
+ ctx.reply("Scene timed out")
273
+ when :error
274
+ ctx.reply("Scene ended due to error")
275
+ when :manual
276
+ ctx.reply("Scene completed")
277
+ end
278
+ end
279
+ end
280
+ ```
281
+
282
+ ## Scene Best Practices
283
+
284
+ ### Keep Scenes Focused
285
+
286
+ ```ruby
287
+ # Bad: too many steps
288
+ bot.scene :everything do
289
+ step :login
290
+ step :select_option
291
+ step :process_payment
292
+ step :send_confirmation
293
+ # 10 more steps...
294
+ end
295
+
296
+ # Good: separate scenes
297
+ bot.scene :auth do
298
+ step :login
299
+ step :verify
300
+ end
301
+
302
+ bot.scene :payment do
303
+ step :select_amount
304
+ step :process
305
+ end
306
+ ```
307
+
308
+ ### Validate Input
309
+
310
+ ```ruby
311
+ step :collect_email do |ctx|
312
+ email = ctx.message.text
313
+
314
+ unless valid_email?(email)
315
+ ctx.reply("Invalid email. Try again.")
316
+ return # Stay on same step
317
+ end
318
+
319
+ ctx.scene_data[:email] = email
320
+ ctx.next_step
321
+ end
322
+ ```
323
+
324
+ ### Provide Escape Options
325
+
326
+ ```ruby
327
+ bot.hears(/^cancel$/i) do |ctx|
328
+ if ctx.in_scene?
329
+ ctx.leave_scene(reason: :cancel)
330
+ ctx.reply("Cancelled.")
331
+ end
332
+ end
333
+
334
+ bot.hears(/^back$/i) do |ctx|
335
+ if ctx.in_scene?
336
+ # Implement back logic
337
+ ctx.reply("Going back...")
338
+ end
339
+ end
340
+ ```
341
+
342
+ ### Use Helpers
343
+
344
+ ```ruby
345
+ def ask_with_validation(ctx, question, validator)
346
+ ctx.ask(question)
347
+
348
+ # In next step
349
+ response = ctx.message.text
350
+ if validator.call(response)
351
+ # Valid
352
+ else
353
+ ctx.reply("Invalid input")
354
+ # Retry
355
+ end
356
+ end
357
+ ```
358
+
359
+ ## Complex Scene Examples
360
+
361
+ ### Multi-choice Survey
362
+
363
+ ```ruby
364
+ bot.scene :survey do
365
+ step :start do |ctx|
366
+ keyboard = Telegem.keyboard do
367
+ row "Yes", "No", "Maybe"
368
+ end
369
+
370
+ ctx.reply("Do you like pizza?", reply_markup: keyboard)
371
+ end
372
+
373
+ step :follow_up do |ctx|
374
+ answer = ctx.message.text
375
+ ctx.scene_data[:pizza] = answer
376
+
377
+ if answer == "Yes"
378
+ ctx.ask("What's your favorite topping?")
379
+ else
380
+ ctx.reply("Survey complete!")
381
+ ctx.leave_scene
382
+ end
383
+ end
384
+
385
+ step :complete do |ctx|
386
+ topping = ctx.message.text
387
+ ctx.scene_data[:topping] = topping
388
+ ctx.reply("Thanks for your feedback!")
389
+ ctx.leave_scene
390
+ end
391
+ end
392
+ ```
393
+
394
+ ### File Upload Scene
395
+
396
+ ```ruby
397
+ bot.scene :upload do
398
+ step :request_file do |ctx|
399
+ ctx.reply("Please send me a document")
400
+ end
401
+
402
+ step :process_file do |ctx|
403
+ unless ctx.message.document
404
+ ctx.reply("Please send a document")
405
+ return
406
+ end
407
+
408
+ # Process file
409
+ file_id = ctx.message.document.file_id
410
+ ctx.download_file(file_id, "uploads/#{file_id}")
411
+
412
+ ctx.reply("File uploaded successfully!")
413
+ ctx.leave_scene
414
+ end
415
+ end
416
+ ```
417
+
418
+ ### Payment Flow
419
+
420
+ ```ruby
421
+ bot.scene :payment do
422
+ step :select_amount do |ctx|
423
+ keyboard = Telegem.keyboard do
424
+ row "$10", "$25", "$50"
425
+ end
426
+
427
+ ctx.reply("Select amount:", reply_markup: keyboard)
428
+ end
429
+
430
+ step :confirm do |ctx|
431
+ amount = ctx.message.text.delete('$').to_i
432
+ ctx.scene_data[:amount] = amount
433
+
434
+ keyboard = Telegem.inline do
435
+ callback "Confirm", "confirm_payment"
436
+ callback "Cancel", "cancel_payment"
437
+ end
438
+
439
+ ctx.reply("Pay $#{amount}?", reply_markup: keyboard)
440
+ end
441
+
442
+ step :process do |ctx|
443
+ # Process payment
444
+ ctx.reply("Payment successful!")
445
+ ctx.leave_scene
446
+ end
447
+ end
448
+
449
+ bot.callback_query(/^confirm_payment/) do |ctx|
450
+ ctx.answer_callback_query("Processing payment...")
451
+ ctx.next_step(:process)
452
+ end
453
+ ```
454
+
455
+ ## Scene Integration with Middleware
456
+
457
+ Scenes work with the scene middleware (included by default):
458
+
459
+ ```ruby
460
+ # Scene middleware intercepts updates when user is in scene
461
+ # Routes to appropriate scene step
462
+ # Handles timeouts and cleanup
463
+ ```
464
+
465
+ ## Testing Scenes
466
+
467
+ ```ruby
468
+ # Test scene flow
469
+ def test_scene_flow
470
+ # Enter scene
471
+ simulate_message(bot, '/start_scene')
472
+
473
+ # Simulate user responses
474
+ simulate_message(bot, 'John')
475
+ simulate_message(bot, 'john@example.com')
476
+
477
+ # Check final state
478
+ assert user_registered?('john@example.com')
479
+ end
480
+
481
+ # Test timeout
482
+ def test_scene_timeout
483
+ enter_scene(:timed_scene)
484
+
485
+ # Fast forward time
486
+ Timecop.travel(6.minutes)
487
+
488
+ simulate_message(bot, 'response')
489
+
490
+ # Should have timed out
491
+ assert !in_scene?
492
+ end
493
+ ```
494
+
495
+ ## Scene Limitations
496
+
497
+ - Scenes are user-specific (one scene per user)
498
+ - Scene data stored in session (memory/Redis limits apply)
499
+ - No concurrent scenes per user
500
+ - Scenes block normal message processing
501
+
502
+ ## Alternative Approaches
503
+
504
+ For simple interactions, consider:
505
+
506
+ - Inline keyboards with callback queries
507
+ - State machines in session
508
+ - Multiple command handlers
509
+
510
+ Use scenes when you need:
511
+ - Guided step-by-step flows
512
+ - Temporary data collection
513
+ - Complex validation logic
514
+ - Timeout handling
515
+
516
+ Scenes are powerful for creating interactive, stateful conversations in your Telegram bot.</content>
517
+ <parameter name="filePath">/home/slick/telegem/docs/scenes.md