telegem 3.3.1 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +19 -1
- data/README.md +147 -0
- data/bin/telegem-ssl +71 -25
- data/contributing.md +375 -0
- data/docs/api.md +663 -0
- data/docs/bot.md +332 -0
- data/docs/changelog.md +182 -0
- data/docs/context.md +554 -0
- data/docs/core_concepts.md +218 -0
- data/docs/deployment.md +702 -0
- data/docs/error_handling.md +435 -0
- data/docs/examples.md +752 -0
- data/docs/getting_started.md +151 -0
- data/docs/handlers.md +580 -0
- data/docs/keyboards.md +446 -0
- data/docs/middleware.md +536 -0
- data/docs/plugins.md +384 -0
- data/docs/scenes.md +517 -0
- data/docs/sessions.md +544 -0
- data/docs/testing.md +612 -0
- data/docs/troubleshooting.md +574 -0
- data/docs/types.md +538 -0
- data/docs/webhooks.md +456 -0
- data/lib/api/client.rb +38 -10
- data/lib/api/types.rb +129 -106
- data/lib/core/composer.rb +3 -3
- data/lib/core/context.rb +17 -11
- data/lib/plugins/cc +3 -0
- data/lib/plugins/translate.rb +43 -0
- data/lib/session/redis.rb +91 -0
- data/lib/telegem.rb +3 -3
- data/lib/webhook/server.rb +5 -3
- metadata +41 -29
- data/Contributing.md +0 -161
- data/Readme.md +0 -298
- data/bin/telegem-init +0 -32
- data/examples/.gitkeep +0 -0
- data/public/.gitkeep +0 -0
|
@@ -0,0 +1,435 @@
|
|
|
1
|
+
# Error Handling
|
|
2
|
+
|
|
3
|
+
Comprehensive error handling is crucial for robust Telegram bots. Telegem provides multiple layers of error handling with recovery strategies.
|
|
4
|
+
|
|
5
|
+
## Error Types
|
|
6
|
+
|
|
7
|
+
### API Errors
|
|
8
|
+
|
|
9
|
+
Telegram API errors from invalid requests or server issues.
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
begin
|
|
13
|
+
bot.api.call('sendMessage', invalid_params)
|
|
14
|
+
rescue Telegem::API::APIError => e
|
|
15
|
+
puts "API Error: #{e.message}"
|
|
16
|
+
puts "Error code: #{e.code}" if e.code
|
|
17
|
+
end
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
**Common API Error Codes:**
|
|
21
|
+
- `400` - Bad Request (invalid parameters)
|
|
22
|
+
- `401` - Unauthorized (invalid token)
|
|
23
|
+
- `403` - Forbidden (bot blocked/kicked)
|
|
24
|
+
- `404` - Not Found (chat/user not found)
|
|
25
|
+
- `429` - Too Many Requests (rate limited)
|
|
26
|
+
|
|
27
|
+
### Network Errors
|
|
28
|
+
|
|
29
|
+
Connection issues, timeouts, DNS failures.
|
|
30
|
+
|
|
31
|
+
```ruby
|
|
32
|
+
begin
|
|
33
|
+
result = bot.api.call('sendMessage', params)
|
|
34
|
+
rescue Telegem::API::NetworkError => e
|
|
35
|
+
puts "Network error: #{e.message}"
|
|
36
|
+
# Retry logic
|
|
37
|
+
end
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
### Handler Errors
|
|
41
|
+
|
|
42
|
+
Exceptions in message handlers.
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
bot.error do |error, ctx|
|
|
46
|
+
ctx.logger.error("Handler error: #{error.message}")
|
|
47
|
+
ctx.logger.error("User: #{ctx.from&.id}, Chat: #{ctx.chat&.id}")
|
|
48
|
+
|
|
49
|
+
# Send user-friendly message
|
|
50
|
+
ctx.reply("Sorry, something went wrong. Please try again.") if ctx.chat
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Global Error Handler
|
|
55
|
+
|
|
56
|
+
Catch all unhandled errors in handlers.
|
|
57
|
+
|
|
58
|
+
```ruby
|
|
59
|
+
bot.error do |error, ctx|
|
|
60
|
+
case error
|
|
61
|
+
when Telegem::API::APIError
|
|
62
|
+
handle_api_error(error, ctx)
|
|
63
|
+
when StandardError
|
|
64
|
+
handle_generic_error(error, ctx)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def handle_api_error(error, ctx)
|
|
69
|
+
case error.code
|
|
70
|
+
when 403
|
|
71
|
+
ctx.reply("I don't have permission to do that.")
|
|
72
|
+
when 429
|
|
73
|
+
ctx.reply("Please wait a moment before trying again.")
|
|
74
|
+
else
|
|
75
|
+
ctx.reply("API error occurred.")
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def handle_generic_error(error, ctx)
|
|
80
|
+
ctx.logger.error("Unexpected error: #{error.class}: #{error.message}")
|
|
81
|
+
ctx.logger.error(error.backtrace.join("\n"))
|
|
82
|
+
ctx.reply("An unexpected error occurred.")
|
|
83
|
+
end
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Handler-Level Error Handling
|
|
87
|
+
|
|
88
|
+
Handle errors within specific handlers.
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
bot.command('process') do |ctx|
|
|
92
|
+
begin
|
|
93
|
+
data = process_user_input(ctx.text)
|
|
94
|
+
result = perform_calculation(data)
|
|
95
|
+
ctx.reply("Result: #{result}")
|
|
96
|
+
rescue ArgumentError => e
|
|
97
|
+
ctx.reply("Invalid input: #{e.message}")
|
|
98
|
+
rescue ZeroDivisionError
|
|
99
|
+
ctx.reply("Cannot divide by zero")
|
|
100
|
+
rescue => e
|
|
101
|
+
ctx.logger.error("Processing error: #{e.message}")
|
|
102
|
+
ctx.reply("Processing failed. Please try again.")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Middleware Error Handling
|
|
108
|
+
|
|
109
|
+
Handle errors in middleware chain.
|
|
110
|
+
|
|
111
|
+
```ruby
|
|
112
|
+
class SafeMiddleware
|
|
113
|
+
def call(ctx, next_middleware)
|
|
114
|
+
begin
|
|
115
|
+
next_middleware.call(ctx)
|
|
116
|
+
rescue => e
|
|
117
|
+
ctx.logger.error("Middleware error: #{e.message}")
|
|
118
|
+
# Continue processing or re-raise
|
|
119
|
+
raise
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# Use safe wrapper
|
|
125
|
+
bot.use SafeMiddleware.new
|
|
126
|
+
bot.use ProblematicMiddleware.new
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Async Error Handling
|
|
130
|
+
|
|
131
|
+
Handle errors in async operations.
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
bot.command('async_task') do |ctx|
|
|
135
|
+
ctx.reply("Processing...")
|
|
136
|
+
|
|
137
|
+
Async do
|
|
138
|
+
begin
|
|
139
|
+
result = perform_async_operation()
|
|
140
|
+
ctx.reply("Success: #{result}")
|
|
141
|
+
rescue => e
|
|
142
|
+
ctx.logger.error("Async error: #{e.message}")
|
|
143
|
+
ctx.reply("Operation failed")
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Network Resilience
|
|
150
|
+
|
|
151
|
+
Handle network issues with retries.
|
|
152
|
+
|
|
153
|
+
```ruby
|
|
154
|
+
class RetryMiddleware
|
|
155
|
+
def initialize(max_retries: 3, backoff: 1)
|
|
156
|
+
@max_retries = max_retries
|
|
157
|
+
@backoff = backoff
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def call(ctx, next_middleware)
|
|
161
|
+
retries = 0
|
|
162
|
+
|
|
163
|
+
begin
|
|
164
|
+
next_middleware.call(ctx)
|
|
165
|
+
rescue Telegem::API::NetworkError => e
|
|
166
|
+
retries += 1
|
|
167
|
+
if retries <= @max_retries
|
|
168
|
+
sleep(@backoff * retries)
|
|
169
|
+
retry
|
|
170
|
+
else
|
|
171
|
+
raise
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
bot.use RetryMiddleware.new(max_retries: 3)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Rate Limiting Errors
|
|
181
|
+
|
|
182
|
+
Handle Telegram's rate limits.
|
|
183
|
+
|
|
184
|
+
```ruby
|
|
185
|
+
class RateLimitHandler
|
|
186
|
+
def call(ctx, next_middleware)
|
|
187
|
+
begin
|
|
188
|
+
next_middleware.call(ctx)
|
|
189
|
+
rescue Telegem::API::APIError => e
|
|
190
|
+
if e.code == 429
|
|
191
|
+
# Rate limited
|
|
192
|
+
retry_after = parse_retry_after(e.message)
|
|
193
|
+
ctx.logger.warn("Rate limited, retry after #{retry_after}s")
|
|
194
|
+
|
|
195
|
+
if retry_after && retry_after < 60
|
|
196
|
+
sleep(retry_after)
|
|
197
|
+
retry
|
|
198
|
+
else
|
|
199
|
+
ctx.reply("Service temporarily unavailable")
|
|
200
|
+
end
|
|
201
|
+
else
|
|
202
|
+
raise
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
def parse_retry_after(message)
|
|
210
|
+
# Parse retry-after from error message
|
|
211
|
+
match = message.match(/retry after (\d+)/)
|
|
212
|
+
match ? match[1].to_i : nil
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## File Operation Errors
|
|
218
|
+
|
|
219
|
+
Handle file upload/download errors.
|
|
220
|
+
|
|
221
|
+
```ruby
|
|
222
|
+
bot.document do |ctx|
|
|
223
|
+
begin
|
|
224
|
+
file_id = ctx.message.document.file_id
|
|
225
|
+
content = ctx.download_file(file_id)
|
|
226
|
+
|
|
227
|
+
# Process content
|
|
228
|
+
result = process_file(content)
|
|
229
|
+
ctx.reply("File processed successfully")
|
|
230
|
+
|
|
231
|
+
rescue => e
|
|
232
|
+
ctx.logger.error("File processing error: #{e.message}")
|
|
233
|
+
ctx.reply("Failed to process file. Please try again.")
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
## Session Errors
|
|
239
|
+
|
|
240
|
+
Handle session storage failures.
|
|
241
|
+
|
|
242
|
+
```ruby
|
|
243
|
+
class SessionErrorHandler
|
|
244
|
+
def call(ctx, next_middleware)
|
|
245
|
+
begin
|
|
246
|
+
next_middleware.call(ctx)
|
|
247
|
+
rescue => e
|
|
248
|
+
if e.message.include?('session')
|
|
249
|
+
ctx.logger.error("Session error: #{e.message}")
|
|
250
|
+
# Continue without session
|
|
251
|
+
ctx.instance_variable_set(:@session, {})
|
|
252
|
+
else
|
|
253
|
+
raise
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
next_middleware.call(ctx)
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Validation Errors
|
|
263
|
+
|
|
264
|
+
Validate user input before processing.
|
|
265
|
+
|
|
266
|
+
```ruby
|
|
267
|
+
class ValidationError < StandardError; end
|
|
268
|
+
|
|
269
|
+
def validate_input(text)
|
|
270
|
+
raise ValidationError, "Text is required" if text.nil? || text.empty?
|
|
271
|
+
raise ValidationError, "Text too long" if text.length > 1000
|
|
272
|
+
raise ValidationError, "Invalid characters" unless text.match?(/\A[\w\s]+\z/)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
bot.command('validate') do |ctx|
|
|
276
|
+
begin
|
|
277
|
+
validate_input(ctx.text)
|
|
278
|
+
ctx.reply("Input is valid")
|
|
279
|
+
rescue ValidationError => e
|
|
280
|
+
ctx.reply("Validation error: #{e.message}")
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
## Graceful Degradation
|
|
286
|
+
|
|
287
|
+
Continue operating when some features fail.
|
|
288
|
+
|
|
289
|
+
```ruby
|
|
290
|
+
bot.command('complex') do |ctx|
|
|
291
|
+
result = {}
|
|
292
|
+
|
|
293
|
+
# Try optional features
|
|
294
|
+
begin
|
|
295
|
+
result[:feature1] = optional_feature1()
|
|
296
|
+
rescue => e
|
|
297
|
+
ctx.logger.warn("Feature 1 failed: #{e.message}")
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
begin
|
|
301
|
+
result[:feature2] = optional_feature2()
|
|
302
|
+
rescue => e
|
|
303
|
+
ctx.logger.warn("Feature 2 failed: #{e.message}")
|
|
304
|
+
end
|
|
305
|
+
|
|
306
|
+
# Core functionality
|
|
307
|
+
result[:core] = core_functionality()
|
|
308
|
+
|
|
309
|
+
ctx.reply("Result: #{result}")
|
|
310
|
+
end
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Error Monitoring
|
|
314
|
+
|
|
315
|
+
Log and monitor errors for debugging.
|
|
316
|
+
|
|
317
|
+
```ruby
|
|
318
|
+
class ErrorMonitoringMiddleware
|
|
319
|
+
def initialize(error_tracker)
|
|
320
|
+
@error_tracker = error_tracker
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def call(ctx, next_middleware)
|
|
324
|
+
begin
|
|
325
|
+
next_middleware.call(ctx)
|
|
326
|
+
rescue => e
|
|
327
|
+
# Log error with context
|
|
328
|
+
error_data = {
|
|
329
|
+
error: e.message,
|
|
330
|
+
class: e.class.name,
|
|
331
|
+
backtrace: e.backtrace.first(10),
|
|
332
|
+
user_id: ctx.from&.id,
|
|
333
|
+
chat_id: ctx.chat&.id,
|
|
334
|
+
update_type: ctx.update_type,
|
|
335
|
+
timestamp: Time.now.to_i
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
@error_tracker.report(error_data)
|
|
339
|
+
|
|
340
|
+
# Re-raise to let other handlers deal with it
|
|
341
|
+
raise
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
end
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
## User Communication
|
|
348
|
+
|
|
349
|
+
Provide helpful error messages to users.
|
|
350
|
+
|
|
351
|
+
```ruby
|
|
352
|
+
ERROR_MESSAGES = {
|
|
353
|
+
'network' => "Connection problem. Please try again.",
|
|
354
|
+
'timeout' => "Request timed out. Please try again.",
|
|
355
|
+
'invalid_input' => "Please check your input and try again.",
|
|
356
|
+
'permission_denied' => "I don't have permission to do that.",
|
|
357
|
+
'not_found' => "The requested item was not found.",
|
|
358
|
+
'rate_limited' => "Too many requests. Please wait a moment.",
|
|
359
|
+
'server_error' => "Server error. Please try again later."
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
def user_friendly_error(error_type, ctx)
|
|
363
|
+
message = ERROR_MESSAGES[error_type] || "An error occurred."
|
|
364
|
+
ctx.reply(message)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
bot.error do |error, ctx|
|
|
368
|
+
error_type = classify_error(error)
|
|
369
|
+
user_friendly_error(error_type, ctx)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def classify_error(error)
|
|
373
|
+
case error
|
|
374
|
+
when Telegem::API::NetworkError
|
|
375
|
+
'network'
|
|
376
|
+
when Timeout::Error
|
|
377
|
+
'timeout'
|
|
378
|
+
when Telegem::API::APIError
|
|
379
|
+
case error.code
|
|
380
|
+
when 403 then 'permission_denied'
|
|
381
|
+
when 404 then 'not_found'
|
|
382
|
+
when 429 then 'rate_limited'
|
|
383
|
+
else 'server_error'
|
|
384
|
+
end
|
|
385
|
+
else
|
|
386
|
+
'server_error'
|
|
387
|
+
end
|
|
388
|
+
end
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
## Testing Error Scenarios
|
|
392
|
+
|
|
393
|
+
```ruby
|
|
394
|
+
# Test error handlers
|
|
395
|
+
def test_error_handling
|
|
396
|
+
# Simulate API error
|
|
397
|
+
allow(bot.api).to receive(:call).and_raise(Telegem::API::APIError.new("Test error"))
|
|
398
|
+
|
|
399
|
+
# Trigger handler
|
|
400
|
+
simulate_message(bot, '/test')
|
|
401
|
+
|
|
402
|
+
# Assert error handling
|
|
403
|
+
expect(last_response).to include("error occurred")
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
# Test network resilience
|
|
407
|
+
def test_network_retry
|
|
408
|
+
call_count = 0
|
|
409
|
+
allow(bot.api).to receive(:call) do
|
|
410
|
+
call_count += 1
|
|
411
|
+
raise Telegem::API::NetworkError.new("Connection failed") if call_count < 3
|
|
412
|
+
{ ok: true }
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
simulate_message(bot, '/retry_test')
|
|
416
|
+
|
|
417
|
+
expect(call_count).to eq(3)
|
|
418
|
+
end
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
## Best Practices
|
|
422
|
+
|
|
423
|
+
1. **Always implement global error handlers**
|
|
424
|
+
2. **Use specific error types for different scenarios**
|
|
425
|
+
3. **Provide user-friendly error messages**
|
|
426
|
+
4. **Log errors with sufficient context**
|
|
427
|
+
5. **Implement retry logic for transient errors**
|
|
428
|
+
6. **Use circuit breakers for external services**
|
|
429
|
+
7. **Monitor error rates and patterns**
|
|
430
|
+
8. **Test error scenarios thoroughly**
|
|
431
|
+
9. **Gracefully degrade when features fail**
|
|
432
|
+
10. **Validate input before processing**
|
|
433
|
+
|
|
434
|
+
Proper error handling ensures your bot remains stable and provides a good user experience even when things go wrong.</content>
|
|
435
|
+
<parameter name="filePath">/home/slick/telegem/docs/error_handling.md
|