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/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
- begin
27
- result = make_request(method, params)
28
- callback.call(result, nil)
29
- rescue => error
30
- callback.call(nil, error)
31
- end
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
- raise APIError.new(json ? json['description'] : "Api Error")
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