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.
- checksums.yaml +4 -4
- data/.rubocop.yml +57 -0
- data/CHANGELOG.md +121 -0
- data/Gemfile +1 -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 +433 -172
- data/lib/core/composer.rb +3 -3
- data/lib/core/context.rb +17 -11
- data/lib/plugins/cc +3 -0
- data/lib/plugins/file_extract.rb +2 -2
- data/lib/plugins/translate.rb +43 -0
- data/lib/session/memory_store.rb +90 -103
- data/lib/session/redis.rb +91 -0
- data/lib/telegem.rb +4 -4
- data/lib/webhook/server.rb +5 -3
- metadata +51 -35
- data/CHANGELOG +0 -95
- data/Contributing.md +0 -161
- data/Readme.md +0 -302
- data/examples/.gitkeep +0 -0
- data/public/.gitkeep +0 -0
data/docs/webhooks.md
ADDED
|
@@ -0,0 +1,456 @@
|
|
|
1
|
+
# Webhook Deployment
|
|
2
|
+
|
|
3
|
+
Webhooks provide production-ready deployment for Telegram bots. Unlike polling, webhooks push updates instantly and scale better.
|
|
4
|
+
|
|
5
|
+
## How Webhooks Work
|
|
6
|
+
|
|
7
|
+
1. Telegram sends updates to your server URL
|
|
8
|
+
2. Server processes updates and responds
|
|
9
|
+
3. No need to constantly poll for updates
|
|
10
|
+
|
|
11
|
+
## Basic Webhook Setup
|
|
12
|
+
|
|
13
|
+
### Using Built-in Server
|
|
14
|
+
|
|
15
|
+
```ruby
|
|
16
|
+
require 'telegem'
|
|
17
|
+
|
|
18
|
+
bot = Telegem.new('YOUR_BOT_TOKEN')
|
|
19
|
+
|
|
20
|
+
# Define handlers
|
|
21
|
+
bot.command('start') do |ctx|
|
|
22
|
+
ctx.reply("Hello!")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Start webhook server
|
|
26
|
+
server = bot.webhook(port: 3000, host: '0.0.0.0')
|
|
27
|
+
server.run
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Manual Webhook Setup
|
|
31
|
+
|
|
32
|
+
```ruby
|
|
33
|
+
# Set webhook URL
|
|
34
|
+
bot.set_webhook(url: 'https://yourdomain.com/webhook')
|
|
35
|
+
|
|
36
|
+
# In your web framework (Sinatra example)
|
|
37
|
+
post '/webhook' do
|
|
38
|
+
update_data = JSON.parse(request.body.read)
|
|
39
|
+
bot.process(update_data)
|
|
40
|
+
status 200
|
|
41
|
+
body 'OK'
|
|
42
|
+
end
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Production Deployment
|
|
46
|
+
|
|
47
|
+
### Heroku Deployment
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# Gemfile
|
|
51
|
+
source 'https://rubygems.org'
|
|
52
|
+
gem 'telegem'
|
|
53
|
+
|
|
54
|
+
# app.rb
|
|
55
|
+
require 'telegem'
|
|
56
|
+
|
|
57
|
+
bot = Telegem.new(ENV['BOT_TOKEN'])
|
|
58
|
+
|
|
59
|
+
# Bot handlers...
|
|
60
|
+
|
|
61
|
+
if ENV['RACK_ENV'] == 'production'
|
|
62
|
+
# Production: use webhook
|
|
63
|
+
server = bot.webhook
|
|
64
|
+
server.run
|
|
65
|
+
else
|
|
66
|
+
# Development: use polling
|
|
67
|
+
bot.start_polling
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Procfile
|
|
71
|
+
web: bundle exec ruby app.rb
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Docker Deployment
|
|
75
|
+
|
|
76
|
+
```dockerfile
|
|
77
|
+
FROM ruby:3.2-alpine
|
|
78
|
+
|
|
79
|
+
WORKDIR /app
|
|
80
|
+
COPY Gemfile Gemfile.lock ./
|
|
81
|
+
RUN bundle install
|
|
82
|
+
COPY . .
|
|
83
|
+
|
|
84
|
+
EXPOSE 3000
|
|
85
|
+
CMD ["ruby", "bot.rb"]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
# bot.rb
|
|
90
|
+
bot = Telegem.new(ENV['BOT_TOKEN'])
|
|
91
|
+
|
|
92
|
+
# Set webhook in production
|
|
93
|
+
if ENV['WEBHOOK_URL']
|
|
94
|
+
bot.set_webhook(url: ENV['WEBHOOK_URL'])
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
server = bot.webhook(port: ENV['PORT'] || 3000)
|
|
98
|
+
server.run
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Railway/Render Deployment
|
|
102
|
+
|
|
103
|
+
```ruby
|
|
104
|
+
# Similar to Heroku
|
|
105
|
+
webhook_url = ENV['WEBHOOK_URL'] || "https://#{ENV['DOMAIN']}/webhook"
|
|
106
|
+
|
|
107
|
+
bot.set_webhook(url: webhook_url)
|
|
108
|
+
server = bot.webhook
|
|
109
|
+
server.run
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## SSL/TLS Configuration
|
|
113
|
+
|
|
114
|
+
Telegram requires HTTPS for webhooks. Telegem supports multiple SSL setups.
|
|
115
|
+
|
|
116
|
+
### Cloud Platform SSL (Recommended)
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
# For Heroku, Railway, Render, etc.
|
|
120
|
+
server = bot.webhook
|
|
121
|
+
server.run # Platform handles SSL
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Local SSL Certificates
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
# Create .telegem-ssl file
|
|
128
|
+
# cert_path: /path/to/cert.pem
|
|
129
|
+
# key_path: /path/to/key.pem
|
|
130
|
+
|
|
131
|
+
server = bot.webhook
|
|
132
|
+
server.run # Uses local certificates
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Manual SSL Configuration
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
require 'openssl'
|
|
139
|
+
|
|
140
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
|
141
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read('cert.pem'))
|
|
142
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(File.read('key.pem'))
|
|
143
|
+
|
|
144
|
+
server = bot.webhook(ssl_context: ssl_context)
|
|
145
|
+
server.run
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Webhook Security
|
|
149
|
+
|
|
150
|
+
### Secret Token
|
|
151
|
+
|
|
152
|
+
```ruby
|
|
153
|
+
# Generate secure token
|
|
154
|
+
require 'securerandom'
|
|
155
|
+
secret_token = SecureRandom.hex(16)
|
|
156
|
+
|
|
157
|
+
server = bot.webhook(secret_token: secret_token)
|
|
158
|
+
|
|
159
|
+
# Webhook URL: https://yourdomain.com/webhook/SECRET_TOKEN
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### IP Whitelisting
|
|
163
|
+
|
|
164
|
+
```ruby
|
|
165
|
+
# Only accept from Telegram IPs
|
|
166
|
+
ALLOWED_IPS = [
|
|
167
|
+
'149.154.160.0/20',
|
|
168
|
+
'91.108.4.0/22'
|
|
169
|
+
# Add other Telegram IP ranges
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
before do
|
|
173
|
+
client_ip = request.ip
|
|
174
|
+
unless ALLOWED_IPS.any? { |range| IPAddr.new(range).include?(client_ip) }
|
|
175
|
+
halt 403, 'Forbidden'
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Request Validation
|
|
181
|
+
|
|
182
|
+
```ruby
|
|
183
|
+
post '/webhook/:token' do
|
|
184
|
+
provided_token = params[:token]
|
|
185
|
+
expected_token = ENV['WEBHOOK_SECRET']
|
|
186
|
+
|
|
187
|
+
if provided_token != expected_token
|
|
188
|
+
halt 401, 'Unauthorized'
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Process update...
|
|
192
|
+
end
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Webhook Management
|
|
196
|
+
|
|
197
|
+
### Setting Webhooks
|
|
198
|
+
|
|
199
|
+
```ruby
|
|
200
|
+
# Basic setup
|
|
201
|
+
bot.set_webhook(url: 'https://example.com/webhook')
|
|
202
|
+
|
|
203
|
+
# With options
|
|
204
|
+
bot.set_webhook(
|
|
205
|
+
url: 'https://example.com/webhook',
|
|
206
|
+
max_connections: 40,
|
|
207
|
+
allowed_updates: ['message', 'callback_query'],
|
|
208
|
+
secret_token: 'your_secret'
|
|
209
|
+
)
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Checking Webhook Status
|
|
213
|
+
|
|
214
|
+
```ruby
|
|
215
|
+
info = bot.get_webhook_info
|
|
216
|
+
puts "Webhook URL: #{info.url}"
|
|
217
|
+
puts "Pending updates: #{info.pending_update_count}"
|
|
218
|
+
puts "Last error: #{info.last_error_message}"
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Removing Webhooks
|
|
222
|
+
|
|
223
|
+
```ruby
|
|
224
|
+
bot.delete_webhook
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Error Handling
|
|
228
|
+
|
|
229
|
+
### Webhook Errors
|
|
230
|
+
|
|
231
|
+
```ruby
|
|
232
|
+
# Handle processing errors
|
|
233
|
+
server = bot.webhook do |error, update|
|
|
234
|
+
logger.error("Webhook error: #{error.message}")
|
|
235
|
+
logger.error("Failed update: #{update}")
|
|
236
|
+
end
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Timeout Handling
|
|
240
|
+
|
|
241
|
+
```ruby
|
|
242
|
+
# Set processing timeout
|
|
243
|
+
bot = Telegem.new('TOKEN', timeout: 30)
|
|
244
|
+
|
|
245
|
+
# Handle slow requests
|
|
246
|
+
server = bot.webhook(timeout: 25) # Process within 25 seconds
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Health Checks
|
|
250
|
+
|
|
251
|
+
```ruby
|
|
252
|
+
# Add health endpoint
|
|
253
|
+
server = bot.webhook do
|
|
254
|
+
get '/health' do
|
|
255
|
+
content_type :json
|
|
256
|
+
{
|
|
257
|
+
status: 'ok',
|
|
258
|
+
timestamp: Time.now.to_i,
|
|
259
|
+
version: Telegem::VERSION
|
|
260
|
+
}.to_json
|
|
261
|
+
end
|
|
262
|
+
end
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Scaling Considerations
|
|
266
|
+
|
|
267
|
+
### Multiple Workers
|
|
268
|
+
|
|
269
|
+
```ruby
|
|
270
|
+
# Use web server with multiple processes
|
|
271
|
+
# Puma, Unicorn, or similar
|
|
272
|
+
|
|
273
|
+
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
|
|
274
|
+
threads_count = Integer(ENV['MAX_THREADS'] || 5)
|
|
275
|
+
threads threads_count, threads_count
|
|
276
|
+
|
|
277
|
+
preload_app!
|
|
278
|
+
|
|
279
|
+
rackup DefaultRackup
|
|
280
|
+
port ENV['PORT'] || 3000
|
|
281
|
+
environment ENV['RACK_ENV'] || 'development'
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
### Load Balancing
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
# Multiple bot instances behind load balancer
|
|
288
|
+
# Each instance processes updates independently
|
|
289
|
+
# Use Redis for shared session storage
|
|
290
|
+
|
|
291
|
+
bot = Telegem.new('TOKEN', session_store: redis_store)
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Rate Limiting
|
|
295
|
+
|
|
296
|
+
```ruby
|
|
297
|
+
# Implement rate limiting middleware
|
|
298
|
+
bot.use do |ctx, next_middleware|
|
|
299
|
+
# Rate limiting logic
|
|
300
|
+
next_middleware.call(ctx)
|
|
301
|
+
end
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
## Monitoring and Logging
|
|
305
|
+
|
|
306
|
+
### Request Logging
|
|
307
|
+
|
|
308
|
+
```ruby
|
|
309
|
+
server = bot.webhook do
|
|
310
|
+
use Rack::CommonLogger, logger
|
|
311
|
+
end
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
### Performance Monitoring
|
|
315
|
+
|
|
316
|
+
```ruby
|
|
317
|
+
# Log request duration
|
|
318
|
+
bot.use do |ctx, next_middleware|
|
|
319
|
+
start = Time.now
|
|
320
|
+
next_middleware.call(ctx)
|
|
321
|
+
duration = Time.now - start
|
|
322
|
+
|
|
323
|
+
if duration > 1.0
|
|
324
|
+
logger.warn("Slow request: #{duration}s for #{ctx.update_type}")
|
|
325
|
+
end
|
|
326
|
+
end
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
### Error Tracking
|
|
330
|
+
|
|
331
|
+
```ruby
|
|
332
|
+
# Send errors to monitoring service
|
|
333
|
+
bot.error do |error, ctx|
|
|
334
|
+
# Send to Sentry, Rollbar, etc.
|
|
335
|
+
error_tracker.capture(error, context: ctx)
|
|
336
|
+
end
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
## Development vs Production
|
|
340
|
+
|
|
341
|
+
### Development Setup
|
|
342
|
+
|
|
343
|
+
```ruby
|
|
344
|
+
# Use polling in development
|
|
345
|
+
if ENV['RACK_ENV'] == 'development'
|
|
346
|
+
bot.start_polling
|
|
347
|
+
else
|
|
348
|
+
# Production webhook
|
|
349
|
+
webhook_url = ENV['WEBHOOK_URL']
|
|
350
|
+
bot.set_webhook(url: webhook_url)
|
|
351
|
+
server = bot.webhook
|
|
352
|
+
server.run
|
|
353
|
+
end
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Local Development with ngrok
|
|
357
|
+
|
|
358
|
+
```bash
|
|
359
|
+
# Install ngrok
|
|
360
|
+
npm install -g ngrok
|
|
361
|
+
|
|
362
|
+
# Expose local server
|
|
363
|
+
ngrok http 3000
|
|
364
|
+
|
|
365
|
+
# Set webhook to ngrok URL
|
|
366
|
+
bot.set_webhook(url: 'https://abc123.ngrok.io/webhook')
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
## Common Issues
|
|
370
|
+
|
|
371
|
+
### Webhook Not Receiving Updates
|
|
372
|
+
|
|
373
|
+
```ruby
|
|
374
|
+
# Check webhook info
|
|
375
|
+
info = bot.get_webhook_info
|
|
376
|
+
puts info.inspect
|
|
377
|
+
|
|
378
|
+
# Common issues:
|
|
379
|
+
# - Wrong URL
|
|
380
|
+
# - SSL certificate issues
|
|
381
|
+
# - Server not responding
|
|
382
|
+
# - Firewall blocking requests
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### SSL Certificate Errors
|
|
386
|
+
|
|
387
|
+
```ruby
|
|
388
|
+
# Telegram requires valid SSL
|
|
389
|
+
# Use services like Let's Encrypt
|
|
390
|
+
# Or cloud platforms with built-in SSL
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
### Timeout Errors
|
|
394
|
+
|
|
395
|
+
```ruby
|
|
396
|
+
# Increase timeout
|
|
397
|
+
bot = Telegem.new('TOKEN', timeout: 60)
|
|
398
|
+
|
|
399
|
+
# Optimize handler performance
|
|
400
|
+
# Use async operations for I/O
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
### High Memory Usage
|
|
404
|
+
|
|
405
|
+
```ruby
|
|
406
|
+
# Monitor memory usage
|
|
407
|
+
# Use session TTL
|
|
408
|
+
# Implement cleanup routines
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Webhook Best Practices
|
|
412
|
+
|
|
413
|
+
1. **Use HTTPS**: Always use SSL/TLS
|
|
414
|
+
2. **Validate Requests**: Check secret tokens and IPs
|
|
415
|
+
3. **Handle Errors**: Implement proper error handling
|
|
416
|
+
4. **Monitor Performance**: Track response times and errors
|
|
417
|
+
5. **Scale Horizontally**: Use multiple instances behind load balancer
|
|
418
|
+
6. **Use Timeouts**: Prevent hanging requests
|
|
419
|
+
7. **Log Everything**: Comprehensive logging for debugging
|
|
420
|
+
|
|
421
|
+
## Alternative Deployment Options
|
|
422
|
+
|
|
423
|
+
### Serverless Functions
|
|
424
|
+
|
|
425
|
+
```javascript
|
|
426
|
+
// Vercel/Netlify function
|
|
427
|
+
export default async function handler(req, res) {
|
|
428
|
+
if (req.method === 'POST') {
|
|
429
|
+
const update = req.body;
|
|
430
|
+
// Process update with Telegem
|
|
431
|
+
res.status(200).json({ ok: true });
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
### Docker Compose
|
|
437
|
+
|
|
438
|
+
```yaml
|
|
439
|
+
version: '3'
|
|
440
|
+
services:
|
|
441
|
+
bot:
|
|
442
|
+
build: .
|
|
443
|
+
ports:
|
|
444
|
+
- "3000:3000"
|
|
445
|
+
environment:
|
|
446
|
+
- BOT_TOKEN=${BOT_TOKEN}
|
|
447
|
+
- REDIS_URL=redis://redis:6379
|
|
448
|
+
depends_on:
|
|
449
|
+
- redis
|
|
450
|
+
|
|
451
|
+
redis:
|
|
452
|
+
image: redis:alpine
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
Webhooks provide reliable, scalable deployment for production Telegram bots. Choose the right hosting platform and configure SSL properly for best results.</content>
|
|
456
|
+
<parameter name="filePath">/home/slick/telegem/docs/webhooks.md
|
data/lib/api/client.rb
CHANGED
|
@@ -12,25 +12,31 @@ module Telegem
|
|
|
12
12
|
@token = token
|
|
13
13
|
@logger = options[:logger] || Logger.new($stdout)
|
|
14
14
|
@timeout = options[:timeout] || 30
|
|
15
|
+
@retries = options[:retries] || 3
|
|
16
|
+
@retry_delay = options[:retry_delay] || 1 # seconds
|
|
15
17
|
|
|
16
|
-
@endpoint = Async::HTTP::Endpoint.parse(BASE_URL)
|
|
18
|
+
@endpoint = Async::HTTP::Endpoint.parse(BASE_URL, timeout: @timeout)
|
|
17
19
|
@client = Async::HTTP::Client.new(@endpoint)
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
def call(method, params = {})
|
|
23
|
+
with_retry do
|
|
21
24
|
make_request(method, params)
|
|
25
|
+
end
|
|
22
26
|
end
|
|
23
27
|
|
|
24
28
|
def call!(method, params = {}, &callback)
|
|
25
29
|
return unless callback
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
begin
|
|
31
|
+
result = call(method, params)
|
|
32
|
+
callback.call(result, nil)
|
|
33
|
+
rescue => error
|
|
34
|
+
callback.call(nil, error)
|
|
35
|
+
end
|
|
32
36
|
end
|
|
37
|
+
|
|
33
38
|
def upload(method, params)
|
|
39
|
+
with_retry do
|
|
34
40
|
url = "/bot#{@token}/#{method}"
|
|
35
41
|
|
|
36
42
|
body = Async::HTTP::Body::Multipart.new
|
|
@@ -45,10 +51,11 @@ module Telegem
|
|
|
45
51
|
|
|
46
52
|
response = @client.post(url, {}, body)
|
|
47
53
|
handle_response(response)
|
|
48
|
-
|
|
54
|
+
end
|
|
49
55
|
end
|
|
50
56
|
|
|
51
57
|
def download(file_id, destination_path = nil)
|
|
58
|
+
with_retry do
|
|
52
59
|
file_info = call('getFile', file_id: file_id)
|
|
53
60
|
return nil unless file_info && file_info['file_path']
|
|
54
61
|
|
|
@@ -68,6 +75,7 @@ module Telegem
|
|
|
68
75
|
else
|
|
69
76
|
raise NetworkError.new("Download failed: HTTP #{response.status}")
|
|
70
77
|
end
|
|
78
|
+
end
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def get_updates(offset: nil, timeout: 30, limit: 100, allowed_updates: nil)
|
|
@@ -83,6 +91,25 @@ module Telegem
|
|
|
83
91
|
|
|
84
92
|
private
|
|
85
93
|
|
|
94
|
+
def with_retry(&block)
|
|
95
|
+
retries = 0
|
|
96
|
+
begin
|
|
97
|
+
block.call
|
|
98
|
+
rescue NetworkError, Async::TimeoutError => e
|
|
99
|
+
retries += 1
|
|
100
|
+
if retries <= @retries
|
|
101
|
+
@logger.warn("API request failed: #{e.message}. Retry #{retries}/#{@retries}") if @logger
|
|
102
|
+
sleep @retry_delay * retries # exponential backoff
|
|
103
|
+
retry
|
|
104
|
+
else
|
|
105
|
+
raise
|
|
106
|
+
end
|
|
107
|
+
rescue APIError => e
|
|
108
|
+
# Don't retry API errors (bad request, unauthorized, etc.)
|
|
109
|
+
raise
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
86
113
|
def make_request(method, params)
|
|
87
114
|
url = "/bot#{@token}/#{method}"
|
|
88
115
|
@logger.debug("Api call #{method}") if @logger
|
|
@@ -102,7 +129,8 @@ module Telegem
|
|
|
102
129
|
if json && json['ok']
|
|
103
130
|
json['result']
|
|
104
131
|
else
|
|
105
|
-
|
|
132
|
+
error_msg = json ? json['description'] : "HTTP #{response.status} - Empty response"
|
|
133
|
+
raise APIError.new(error_msg, response.status)
|
|
106
134
|
end
|
|
107
135
|
end
|
|
108
136
|
|
|
@@ -123,4 +151,4 @@ module Telegem
|
|
|
123
151
|
|
|
124
152
|
class NetworkError < APIError; end
|
|
125
153
|
end
|
|
126
|
-
end
|
|
154
|
+
end
|