telegem 2.0.8 → 2.1.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/Test-Projects/Movie-tracker-bot/Gemfile +2 -0
  3. data/Test-Projects/Movie-tracker-bot/bot.rb +62 -0
  4. data/Test-Projects/Movie-tracker-bot/handlers/.gitkeep +0 -0
  5. data/Test-Projects/Movie-tracker-bot/handlers/add_1_.rb +160 -0
  6. data/Test-Projects/Movie-tracker-bot/handlers/add_2_.rb +139 -0
  7. data/Test-Projects/Movie-tracker-bot/handlers/premium.rb +13 -0
  8. data/Test-Projects/Movie-tracker-bot/handlers/report.rb +31 -0
  9. data/Test-Projects/Movie-tracker-bot/handlers/search.rb +150 -0
  10. data/Test-Projects/Movie-tracker-bot/handlers/sponsor.rb +14 -0
  11. data/Test-Projects/Movie-tracker-bot/handlers/start.rb +48 -0
  12. data/Test-Projects/Movie-tracker-bot/handlers/watch.rb +210 -0
  13. data/Test-Projects/Test-submitted-by-marvel/.gitkeep +0 -0
  14. data/Test-Projects/Test-submitted-by-marvel/Marvel-bot.md +3 -0
  15. data/docs-src/.gitkeep +0 -0
  16. data/docs-src/Bot-registration_.PNG +0 -0
  17. data/docs-src/bot.md +295 -0
  18. data/docs-src/context|ctx|.md +531 -0
  19. data/docs-src/getting-started.md +328 -0
  20. data/docs-src/keyboard_inline.md +413 -0
  21. data/docs-src/scene.md +509 -0
  22. data/docs-src/understanding-ctx.md +581 -0
  23. data/lib/api/client.rb +3 -0
  24. data/lib/core/bot.rb +31 -27
  25. data/lib/telegem.rb +1 -1
  26. data/lib/webhook/server.rb +1 -1
  27. metadata +26 -15
  28. data/docs/Api.md +0 -211
  29. data/docs/Cookbook(copy_paste).md +0 -644
  30. data/docs/Getting_started.md +0 -348
  31. data/docs/How_to_use.md +0 -571
  32. data/docs/QuickStart.md +0 -258
  33. data/docs/Understanding_Scene.md +0 -434
  34. data/docs/Usage.md +0 -717
  35. data/docs/webhook_setup.md +0 -199
  36. /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.synchronize 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
- params = {
175
- timeout: @polling_options[:timeout],
176
- limit: @polling_options[:limit]
177
- }
178
- params[:offset] = @offset if @offset
179
- params[:allowed_updates] = @polling_options[:allowed_updates] if @polling_options[:allowed_updates]
180
-
181
- @logger.debug("Fetching updates with offset: #{@offset}")
182
-
183
- # Simple direct call - no .wait
184
- updates = @api.call!('getUpdates', params)
185
-
186
- if updates && updates.is_a?(Array)
187
- @logger.debug("Got #{updates.length} updates")
188
- return { 'ok' => true, 'result' => updates }
189
- end
190
-
191
- nil
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
- ctx = Context.new(update, self)
254
-
255
- begin
256
- run_middleware_chain(ctx) do |context|
257
- dispatch_to_handlers(context)
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
- rescue => e
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
@@ -3,7 +3,7 @@ require 'logger'
3
3
  require 'json'
4
4
 
5
5
  module Telegem
6
- VERSION = "2.0.8".freeze
6
+ VERSION = "2.1.0".freeze
7
7
  end
8
8
 
9
9
  # Load core components
@@ -173,7 +173,7 @@ module Telegem
173
173
  end
174
174
  end
175
175
 
176
- sleep 1 until @server.running?
176
+ sleep 1 until @server.running
177
177
  end
178
178
 
179
179
  def handle_rack_request(req)