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/examples.md ADDED
@@ -0,0 +1,752 @@
1
+ # Examples
2
+
3
+ Practical examples showing how to build various types of bots with Telegem.
4
+
5
+ ## Basic Echo Bot
6
+
7
+ ```ruby
8
+ require 'telegem'
9
+
10
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
11
+
12
+ bot.hears(/.*/) do |ctx|
13
+ ctx.reply(ctx.text)
14
+ end
15
+
16
+ bot.start_polling
17
+ ```
18
+
19
+ ## Command Bot
20
+
21
+ ```ruby
22
+ require 'telegem'
23
+
24
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
25
+
26
+ bot.command('start') do |ctx|
27
+ ctx.reply("Hello! I'm a command bot.")
28
+ end
29
+
30
+ bot.command('help') do |ctx|
31
+ help_text = <<~HELP
32
+ Available commands:
33
+ /start - Start the bot
34
+ /help - Show this help
35
+ /echo <text> - Echo back your text
36
+ /time - Show current time
37
+ HELP
38
+
39
+ ctx.reply(help_text)
40
+ end
41
+
42
+ bot.command('echo') do |ctx|
43
+ text = ctx.command_args
44
+ if text.empty?
45
+ ctx.reply("Usage: /echo <text>")
46
+ else
47
+ ctx.reply(text)
48
+ end
49
+ end
50
+
51
+ bot.command('time') do |ctx|
52
+ ctx.reply("Current time: #{Time.now.strftime('%Y-%m-%d %H:%M:%S')}")
53
+ end
54
+
55
+ bot.start_polling
56
+ ```
57
+
58
+ ## Inline Keyboard Bot
59
+
60
+ ```ruby
61
+ require 'telegem'
62
+
63
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
64
+
65
+ bot.command('menu') do |ctx|
66
+ keyboard = Telegem.inline_keyboard do |kb|
67
+ kb.row do
68
+ kb.button('Option 1', callback_data: 'opt1')
69
+ kb.button('Option 2', callback_data: 'opt2')
70
+ end
71
+ kb.row do
72
+ kb.button('Help', callback_data: 'help')
73
+ end
74
+ end
75
+
76
+ ctx.reply('Choose an option:', keyboard: keyboard)
77
+ end
78
+
79
+ bot.callback_query do |ctx|
80
+ case ctx.callback_data
81
+ when 'opt1'
82
+ ctx.answer_callback_query('You chose Option 1')
83
+ ctx.edit_message_text('You selected Option 1')
84
+ when 'opt2'
85
+ ctx.answer_callback_query('You chose Option 2')
86
+ ctx.edit_message_text('You selected Option 2')
87
+ when 'help'
88
+ ctx.answer_callback_query('Help coming soon')
89
+ ctx.edit_message_text('Help: This is a demo bot')
90
+ end
91
+ end
92
+
93
+ bot.start_polling
94
+ ```
95
+
96
+ ## Reply Keyboard Bot
97
+
98
+ ```ruby
99
+ require 'telegem'
100
+
101
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
102
+
103
+ bot.command('keyboard') do |ctx|
104
+ keyboard = Telegem.reply_keyboard do |kb|
105
+ kb.row('Button 1', 'Button 2')
106
+ kb.row('Button 3', 'Button 4')
107
+ kb.row do
108
+ kb.button('Remove Keyboard', request_contact: false, request_location: false)
109
+ end
110
+ end
111
+
112
+ ctx.reply('Choose a button:', keyboard: keyboard)
113
+ end
114
+
115
+ bot.hears(/^Button/) do |ctx|
116
+ ctx.reply("You pressed: #{ctx.text}")
117
+ end
118
+
119
+ bot.hears('Remove Keyboard') do |ctx|
120
+ ctx.reply('Keyboard removed', keyboard: Telegem.remove_keyboard)
121
+ end
122
+
123
+ bot.start_polling
124
+ ```
125
+
126
+ ## File Handling Bot
127
+
128
+ ```ruby
129
+ require 'telegem'
130
+
131
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
132
+
133
+ bot.document do |ctx|
134
+ doc = ctx.message.document
135
+ file_name = doc.file_name
136
+ file_size = doc.file_size
137
+
138
+ ctx.reply("Received file: #{file_name} (#{file_size} bytes)")
139
+
140
+ # Download file
141
+ begin
142
+ file_content = ctx.download_file(doc.file_id)
143
+ ctx.reply("Downloaded #{file_content.length} bytes")
144
+ rescue => e
145
+ ctx.reply("Failed to download file: #{e.message}")
146
+ end
147
+ end
148
+
149
+ bot.photo do |ctx|
150
+ photo = ctx.message.photo.last # Get highest resolution
151
+ file_id = photo.file_id
152
+
153
+ ctx.reply("Received photo: #{photo.width}x#{photo.height}")
154
+
155
+ # Download and process photo
156
+ begin
157
+ photo_data = ctx.download_file(file_id)
158
+ # Process photo_data...
159
+ ctx.reply("Photo processed successfully")
160
+ rescue => e
161
+ ctx.reply("Failed to process photo: #{e.message}")
162
+ end
163
+ end
164
+
165
+ bot.start_polling
166
+ ```
167
+
168
+ ## Scene-Based Bot (Multi-step Conversation)
169
+
170
+ ```ruby
171
+ require 'telegem'
172
+
173
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
174
+
175
+ # Registration scene
176
+ bot.scene('register') do |scene|
177
+ scene.step('name') do |ctx|
178
+ ctx.session[:name] = ctx.text
179
+ ctx.reply('Enter your email:')
180
+ scene.next_step
181
+ end
182
+
183
+ scene.step('email') do |ctx|
184
+ ctx.session[:email] = ctx.text
185
+ ctx.reply('Enter your age:')
186
+ scene.next_step
187
+ end
188
+
189
+ scene.step('age') do |ctx|
190
+ ctx.session[:age] = ctx.text.to_i
191
+ ctx.reply("Registration complete!\nName: #{ctx.session[:name]}\nEmail: #{ctx.session[:email]}\nAge: #{ctx.session[:age]}")
192
+ scene.complete
193
+ end
194
+
195
+ scene.on_timeout do |ctx|
196
+ ctx.reply('Registration timed out. Start again with /register')
197
+ end
198
+ end
199
+
200
+ bot.command('register') do |ctx|
201
+ ctx.reply('Enter your name:')
202
+ ctx.enter_scene('register')
203
+ end
204
+
205
+ bot.command('cancel') do |ctx|
206
+ if ctx.current_scene
207
+ ctx.exit_scene
208
+ ctx.reply('Registration cancelled')
209
+ else
210
+ ctx.reply('No active registration')
211
+ end
212
+ end
213
+
214
+ bot.start_polling
215
+ ```
216
+
217
+ ## Weather Bot
218
+
219
+ ```ruby
220
+ require 'telegem'
221
+ require 'httparty'
222
+
223
+ class WeatherService
224
+ BASE_URL = 'https://api.openweathermap.org/data/2.5'
225
+
226
+ def initialize(api_key)
227
+ @api_key = api_key
228
+ end
229
+
230
+ def get_weather(city)
231
+ response = HTTParty.get("#{BASE_URL}/weather", query: {
232
+ q: city,
233
+ appid: @api_key,
234
+ units: 'metric'
235
+ })
236
+
237
+ if response.success?
238
+ data = response.parsed_response
239
+ {
240
+ city: data['name'],
241
+ temp: data['main']['temp'],
242
+ description: data['weather'].first['description'],
243
+ humidity: data['main']['humidity']
244
+ }
245
+ else
246
+ nil
247
+ end
248
+ end
249
+ end
250
+
251
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
252
+ weather_service = WeatherService.new('YOUR_OPENWEATHER_API_KEY')
253
+
254
+ bot.command('weather') do |ctx|
255
+ city = ctx.command_args
256
+
257
+ if city.empty?
258
+ ctx.reply('Usage: /weather <city>')
259
+ return
260
+ end
261
+
262
+ ctx.reply('Getting weather data...')
263
+
264
+ Async do
265
+ weather = weather_service.get_weather(city)
266
+
267
+ if weather
268
+ message = <<~WEATHER
269
+ Weather in #{weather[:city]}:
270
+ Temperature: #{weather[:temp]}°C
271
+ Conditions: #{weather[:description]}
272
+ Humidity: #{weather[:humidity]}%
273
+ WEATHER
274
+
275
+ ctx.reply(message)
276
+ else
277
+ ctx.reply('Could not get weather data. Check city name.')
278
+ end
279
+ end
280
+ end
281
+
282
+ bot.start_polling
283
+ ```
284
+
285
+ ## Translation Bot
286
+
287
+ ```ruby
288
+ require 'telegem'
289
+ require 'telegem/plugins/translate'
290
+
291
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
292
+
293
+ bot.command('translate') do |ctx|
294
+ args = ctx.command_args.split(' to ')
295
+
296
+ if args.length != 2
297
+ ctx.reply('Usage: /translate <text> to <language>')
298
+ ctx.reply('Example: /translate hello world to es')
299
+ return
300
+ end
301
+
302
+ text, target_lang = args
303
+
304
+ ctx.reply('Translating...')
305
+
306
+ Async do
307
+ translator = Telegem::Plugins::Translate.new(text, 'auto', target_lang)
308
+ result = translator.start_translating
309
+
310
+ if result['error'] == 'false'
311
+ ctx.reply("Translation: #{result['translation']}")
312
+ else
313
+ ctx.reply('Translation failed. Please try again.')
314
+ end
315
+ end
316
+ end
317
+
318
+ bot.hears(/^translate (.+) to (\w+)/) do |ctx|
319
+ text = ctx.match[1]
320
+ target_lang = ctx.match[2]
321
+
322
+ translator = Telegem::Plugins::Translate.new(text, 'auto', target_lang)
323
+ result = translator.start_translating
324
+
325
+ if result['error'] == 'false'
326
+ ctx.reply("Translation: #{result['translation']}")
327
+ else
328
+ ctx.reply('Translation failed.')
329
+ end
330
+ end
331
+
332
+ bot.start_polling
333
+ ```
334
+
335
+ ## File Analysis Bot
336
+
337
+ ```ruby
338
+ require 'telegem'
339
+ require 'telegem/plugins/file_extract'
340
+
341
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
342
+
343
+ bot.document do |ctx|
344
+ doc = ctx.message.document
345
+ file_name = doc.file_name
346
+
347
+ ctx.reply("Analyzing file: #{file_name}")
348
+
349
+ Async do
350
+ begin
351
+ extractor = Telegem::Plugins::FileExtract.new(ctx.bot, doc.file_id)
352
+ result = extractor.extract
353
+
354
+ if result[:success]
355
+ content_preview = result[:content].first(500)
356
+ metadata_info = result[:metadata].map { |k,v| "#{k}: #{v}" }.join("\n")
357
+
358
+ response = <<~ANALYSIS
359
+ File Type: #{result[:type].to_s.upcase}
360
+ Metadata:
361
+ #{metadata_info}
362
+
363
+ Content Preview:
364
+ #{content_preview}#{'...' if result[:content].length > 500}
365
+ ANALYSIS
366
+
367
+ ctx.reply(response)
368
+ else
369
+ ctx.reply("Analysis failed: #{result[:error]}")
370
+ end
371
+ rescue => e
372
+ ctx.reply("Error analyzing file: #{e.message}")
373
+ end
374
+ end
375
+ end
376
+
377
+ bot.start_polling
378
+ ```
379
+
380
+ ## Admin Bot with Middleware
381
+
382
+ ```ruby
383
+ require 'telegem'
384
+
385
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
386
+
387
+ # Admin check middleware
388
+ bot.use do |ctx, next_middleware|
389
+ admin_ids = [123456789, 987654321] # Replace with actual admin IDs
390
+
391
+ if ctx.from && admin_ids.include?(ctx.from.id)
392
+ ctx.is_admin = true
393
+ else
394
+ ctx.is_admin = false
395
+ end
396
+
397
+ next_middleware.call(ctx)
398
+ end
399
+
400
+ # Logging middleware
401
+ bot.use do |ctx, next_middleware|
402
+ start_time = Time.now
403
+ next_middleware.call(ctx)
404
+ duration = Time.now - start_time
405
+
406
+ puts "[#{Time.now}] #{ctx.from&.username}: #{ctx.text} (#{duration.round(3)}s)"
407
+ end
408
+
409
+ # Admin-only commands
410
+ bot.command('stats') do |ctx|
411
+ unless ctx.is_admin
412
+ ctx.reply('Access denied')
413
+ return
414
+ end
415
+
416
+ # Get bot statistics
417
+ stats = {
418
+ uptime: '24h 30m',
419
+ messages_processed: 15420,
420
+ active_users: 234
421
+ }
422
+
423
+ message = <<~STATS
424
+ Bot Statistics:
425
+ Uptime: #{stats[:uptime]}
426
+ Messages: #{stats[:messages_processed]}
427
+ Active Users: #{stats[:active_users]}
428
+ STATS
429
+
430
+ ctx.reply(message)
431
+ end
432
+
433
+ bot.command('broadcast') do |ctx|
434
+ unless ctx.is_admin
435
+ ctx.reply('Access denied')
436
+ return
437
+ end
438
+
439
+ message = ctx.command_args
440
+ if message.empty?
441
+ ctx.reply('Usage: /broadcast <message>')
442
+ return
443
+ end
444
+
445
+ # In a real bot, you'd get all user IDs from database
446
+ user_ids = [111111, 222222, 333333] # Example user IDs
447
+
448
+ user_ids.each do |user_id|
449
+ begin
450
+ bot.api.call('sendMessage', chat_id: user_id, text: message)
451
+ rescue => e
452
+ puts "Failed to send to #{user_id}: #{e.message}"
453
+ end
454
+ end
455
+
456
+ ctx.reply("Broadcast sent to #{user_ids.length} users")
457
+ end
458
+
459
+ # Public commands
460
+ bot.command('help') do |ctx|
461
+ help_text = <<~HELP
462
+ Commands:
463
+ /help - Show this help
464
+ /info - Bot information
465
+ HELP
466
+
467
+ if ctx.is_admin
468
+ help_text += "\nAdmin Commands:\n/stats - Bot statistics\n/broadcast <msg> - Send broadcast"
469
+ end
470
+
471
+ ctx.reply(help_text)
472
+ end
473
+
474
+ bot.command('info') do |ctx|
475
+ ctx.reply('This is a demo admin bot with Telegem')
476
+ end
477
+
478
+ bot.start_polling
479
+ ```
480
+
481
+ ## E-commerce Bot
482
+
483
+ ```ruby
484
+ require 'telegem'
485
+
486
+ class ProductCatalog
487
+ def initialize
488
+ @products = {
489
+ '1' => { name: 'Laptop', price: 999.99, description: 'High-performance laptop' },
490
+ '2' => { name: 'Mouse', price: 29.99, description: 'Wireless mouse' },
491
+ '3' => { name: 'Keyboard', price: 79.99, description: 'Mechanical keyboard' }
492
+ }
493
+ end
494
+
495
+ def get_product(id)
496
+ @products[id]
497
+ end
498
+
499
+ def all_products
500
+ @products
501
+ end
502
+ end
503
+
504
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
505
+ catalog = ProductCatalog.new()
506
+
507
+ # Session-based shopping cart
508
+ bot.use Telegem::Session::Middleware.new
509
+
510
+ bot.command('shop') do |ctx|
511
+ keyboard = Telegem.inline_keyboard do |kb|
512
+ catalog.all_products.each do |id, product|
513
+ kb.row do
514
+ kb.button("#{product[:name]} - $#{product[:price]}", callback_data: "view_#{id}")
515
+ end
516
+ end
517
+ end
518
+
519
+ ctx.reply('Welcome to our shop! Choose a product:', keyboard: keyboard)
520
+ end
521
+
522
+ bot.callback_query(/^view_/) do |ctx|
523
+ product_id = ctx.callback_data.sub('view_', '')
524
+ product = catalog.get_product(product_id)
525
+
526
+ if product
527
+ keyboard = Telegem.inline_keyboard do |kb|
528
+ kb.row do
529
+ kb.button('Add to Cart', callback_data: "add_#{product_id}")
530
+ kb.button('Back to Shop', callback_data: 'shop')
531
+ end
532
+ end
533
+
534
+ message = <<~PRODUCT
535
+ #{product[:name]}
536
+ Price: $#{product[:price]}
537
+ Description: #{product[:description]}
538
+ PRODUCT
539
+
540
+ ctx.edit_message_text(message, keyboard: keyboard)
541
+ else
542
+ ctx.answer_callback_query('Product not found')
543
+ end
544
+ end
545
+
546
+ bot.callback_query(/^add_/) do |ctx|
547
+ product_id = ctx.callback_data.sub('add_', '')
548
+ product = catalog.get_product(product_id)
549
+
550
+ if product
551
+ ctx.session[:cart] ||= []
552
+ ctx.session[:cart] << product_id
553
+
554
+ cart_count = ctx.session[:cart].length
555
+ ctx.answer_callback_query("Added to cart! Items in cart: #{cart_count}")
556
+
557
+ # Update message with cart count
558
+ ctx.edit_message_reply_markup(
559
+ keyboard: Telegem.inline_keyboard do |kb|
560
+ kb.row do
561
+ kb.button("View Cart (#{cart_count})", callback_data: 'cart')
562
+ kb.button('Continue Shopping', callback_data: 'shop')
563
+ end
564
+ end
565
+ )
566
+ end
567
+ end
568
+
569
+ bot.callback_query('cart') do |ctx|
570
+ cart = ctx.session[:cart] || []
571
+
572
+ if cart.empty?
573
+ ctx.edit_message_text('Your cart is empty')
574
+ return
575
+ end
576
+
577
+ total = 0
578
+ message = "Your Cart:\n\n"
579
+
580
+ cart.each do |product_id|
581
+ product = catalog.get_product(product_id)
582
+ if product
583
+ message += "#{product[:name]} - $#{product[:price]}\n"
584
+ total += product[:price]
585
+ end
586
+ end
587
+
588
+ message += "\nTotal: $#{total.round(2)}"
589
+
590
+ keyboard = Telegem.inline_keyboard do |kb|
591
+ kb.row do
592
+ kb.button('Checkout', callback_data: 'checkout')
593
+ kb.button('Clear Cart', callback_data: 'clear_cart')
594
+ kb.button('Continue Shopping', callback_data: 'shop')
595
+ end
596
+ end
597
+
598
+ ctx.edit_message_text(message, keyboard: keyboard)
599
+ end
600
+
601
+ bot.callback_query('clear_cart') do |ctx|
602
+ ctx.session[:cart] = []
603
+ ctx.edit_message_text('Cart cleared!')
604
+ end
605
+
606
+ bot.callback_query('checkout') do |ctx|
607
+ cart = ctx.session[:cart] || []
608
+
609
+ if cart.empty?
610
+ ctx.answer_callback_query('Your cart is empty')
611
+ return
612
+ end
613
+
614
+ # In a real bot, you'd process payment here
615
+ ctx.session[:cart] = []
616
+ ctx.edit_message_text('Thank you for your purchase! Your order has been processed.')
617
+ end
618
+
619
+ bot.callback_query('shop') do |ctx|
620
+ # Re-show shop
621
+ keyboard = Telegem.inline_keyboard do |kb|
622
+ catalog.all_products.each do |id, product|
623
+ kb.row do
624
+ kb.button("#{product[:name]} - $#{product[:price]}", callback_data: "view_#{id}")
625
+ end
626
+ end
627
+ end
628
+
629
+ ctx.edit_message_text('Welcome to our shop! Choose a product:', keyboard: keyboard)
630
+ end
631
+
632
+ bot.start_polling
633
+ ```
634
+
635
+ ## Reminder Bot
636
+
637
+ ```ruby
638
+ require 'telegem'
639
+
640
+ bot = Telegem.new(token: 'YOUR_BOT_TOKEN')
641
+
642
+ # Use session to store reminders
643
+ bot.use Telegem::Session::Middleware.new
644
+
645
+ bot.command('remind') do |ctx|
646
+ args = ctx.command_args.split(' in ')
647
+
648
+ if args.length != 2
649
+ ctx.reply('Usage: /remind <message> in <time>')
650
+ ctx.reply('Example: /remind Buy milk in 5 minutes')
651
+ return
652
+ end
653
+
654
+ message, time_spec = args
655
+
656
+ # Parse time (simple implementation)
657
+ minutes = parse_time(time_spec)
658
+
659
+ if minutes.nil?
660
+ ctx.reply('Invalid time format. Use: 5 minutes, 1 hour, 30 seconds, etc.')
661
+ return
662
+ end
663
+
664
+ # Store reminder
665
+ ctx.session[:reminders] ||= []
666
+ reminder = {
667
+ message: message,
668
+ time: Time.now + (minutes * 60),
669
+ id: rand(1000000)
670
+ }
671
+
672
+ ctx.session[:reminders] << reminder
673
+
674
+ ctx.reply("Reminder set: '#{message}' in #{minutes} minutes")
675
+
676
+ # Schedule reminder (in real bot, use a job queue)
677
+ Async do
678
+ sleep(minutes * 60)
679
+ ctx.reply("⏰ Reminder: #{message}")
680
+ end
681
+ end
682
+
683
+ bot.command('list') do |ctx|
684
+ reminders = ctx.session[:reminders] || []
685
+
686
+ if reminders.empty?
687
+ ctx.reply('No active reminders')
688
+ return
689
+ end
690
+
691
+ message = "Your reminders:\n\n"
692
+ reminders.each_with_index do |reminder, index|
693
+ time_left = ((reminder[:time] - Time.now) / 60).round
694
+ message += "#{index + 1}. #{reminder[:message]} (in #{time_left} minutes)\n"
695
+ end
696
+
697
+ ctx.reply(message)
698
+ end
699
+
700
+ bot.command('cancel') do |ctx|
701
+ reminders = ctx.session[:reminders] || []
702
+
703
+ if reminders.empty?
704
+ ctx.reply('No active reminders to cancel')
705
+ return
706
+ end
707
+
708
+ keyboard = Telegem.inline_keyboard do |kb|
709
+ reminders.each_with_index do |reminder, index|
710
+ kb.row do
711
+ kb.button("Cancel: #{reminder[:message][0..20]}...", callback_data: "cancel_#{reminder[:id]}")
712
+ end
713
+ end
714
+ end
715
+
716
+ ctx.reply('Select reminder to cancel:', keyboard: keyboard)
717
+ end
718
+
719
+ bot.callback_query(/^cancel_/) do |ctx|
720
+ reminder_id = ctx.callback_data.sub('cancel_', '').to_i
721
+
722
+ reminders = ctx.session[:reminders] || []
723
+ reminder = reminders.find { |r| r[:id] == reminder_id }
724
+
725
+ if reminder
726
+ ctx.session[:reminders].delete(reminder)
727
+ ctx.edit_message_text("Reminder cancelled: #{reminder[:message]}")
728
+ else
729
+ ctx.answer_callback_query('Reminder not found')
730
+ end
731
+ end
732
+
733
+ def parse_time(time_spec)
734
+ match = time_spec.match(/(\d+)\s*(second|minute|hour|day)s?/)
735
+ return nil unless match
736
+
737
+ value = match[1].to_i
738
+ unit = match[2]
739
+
740
+ case unit
741
+ when 'second' then value / 60.0 # Convert to minutes
742
+ when 'minute' then value
743
+ when 'hour' then value * 60
744
+ when 'day' then value * 24 * 60
745
+ end
746
+ end
747
+
748
+ bot.start_polling
749
+ ```
750
+
751
+ These examples demonstrate various bot patterns and use cases. Each example can be extended and customized based on specific requirements.</content>
752
+ <parameter name="filePath">/home/slick/telegem/docs/examples.md