telegem 1.0.6 → 2.0.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.
@@ -1,132 +1,196 @@
1
- # lib/webhook/server.rb - HTTPX VERSION
1
+ # lib/webhook/server.rb - V2.0.0 (CLOUD-READY)
2
2
  require 'json'
3
- require 'webrick'
3
+ require 'rack'
4
4
 
5
5
  module Telegem
6
6
  module Webhook
7
7
  class Server
8
- attr_reader :bot, :port, :host, :logger, :server, :running
9
-
10
- def initialize(bot, port: nil, host: '0.0.0.0', logger: nil)
8
+ attr_reader :bot, :port, :host, :logger, :running, :secret_token
9
+
10
+ def initialize(bot, port: nil, host: '0.0.0.0', logger: nil, max_threads: 10, secret_token: nil)
11
11
  @bot = bot
12
- @port = port || ENV['PORT'] || 1000
12
+ @port = port || ENV['PORT'] || 3000
13
13
  @host = host
14
14
  @logger = logger || Logger.new($stdout)
15
+ @max_threads = max_threads
16
+ @thread_pool = Concurrent::FixedThreadPool.new(max_threads)
17
+ @secret_token = secret_token || generate_secret_token
15
18
  @server = nil
16
19
  @running = false
20
+ @webhook_path = "/webhook"
17
21
  end
18
-
22
+
23
+ # Production server with Puma
19
24
  def run
20
25
  return if @running
21
26
 
22
27
  @logger.info "🚀 Starting Telegem webhook server on #{@host}:#{@port}"
23
- @logger.info "📝 Set your Telegram webhook to: #{webhook_url}"
28
+ @logger.info "📝 Set Telegram webhook to: #{webhook_url}"
29
+ @logger.info "🔐 Secret token: #{@secret_token}"
30
+ @logger.info "✅ Using Puma (production-ready)"
31
+ @logger.info "☁️ Cloud-ready: #{cloud_platform}"
24
32
 
25
33
  @running = true
26
34
 
27
- @server_thread = Thread.new do
28
- begin
29
- # Create WEBrick server
30
- @server = WEBrick::HTTPServer.new(
31
- Port: @port,
32
- BindAddress: @host,
33
- Logger: @logger,
34
- AccessLog: []
35
- )
36
-
37
- # Mount the webhook endpoint
38
- @server.mount_proc("/webhook/#{@bot.token}") do |req, res|
39
- handle_webhook_request(req, res)
40
- end
41
-
42
- # Mount health check
43
- @server.mount_proc('/health') do |req, res|
44
- res.status = 200
45
- res.content_type = 'text/plain'
46
- res.body = 'OK'
47
- end
48
-
49
- # Mount root
50
- @server.mount_proc('/') do |req, res|
51
- res.status = 200
52
- res.content_type = 'text/html'
53
- res.body = <<~HTML
54
- <html>
55
- <head><title>Telegem Webhook Server</title></head>
56
- <body>
57
- <h1>Telegem Webhook Server</h1>
58
- <p>Webhook URL: <code>#{webhook_url}</code></p>
59
- <p>Status: <span style="color: green;">Running</span></p>
60
- <p><a href="/health">Health Check</a></p>
61
- </body>
62
- </html>
63
- HTML
64
- end
65
-
66
- # Handle shutdown signals
67
- ['INT', 'TERM'].each do |signal|
68
- Signal.trap(signal) { shutdown }
69
- end
70
-
71
- # Start the server
72
- @server.start
73
-
74
- rescue => e
75
- @logger.error "❌ Webhook server error: #{e.class}: #{e.message}"
76
- @logger.error e.backtrace.join("\n") if e.backtrace
77
- raise
78
- ensure
79
- @running = false
80
- end
81
- end
35
+ app = build_rack_app
36
+ start_puma_server(app)
82
37
 
83
38
  self
84
39
  end
85
-
40
+
86
41
  def stop
87
42
  return unless @running
88
43
 
89
44
  @logger.info "🛑 Stopping webhook server..."
90
45
  @running = false
91
46
 
92
- # Stop WEBrick server
93
- @server&.shutdown
94
-
95
- # Wait for server thread
96
- @server_thread&.join(5)
47
+ @server&.stop
48
+ @thread_pool.shutdown
49
+ @thread_pool.wait_for_termination(5)
97
50
 
98
51
  @logger.info "✅ Webhook server stopped"
99
52
  end
100
-
53
+
101
54
  alias_method :shutdown, :stop
102
-
55
+
103
56
  def running?
104
57
  @running
105
58
  end
106
-
59
+
107
60
  def webhook_url
108
- base_url = ENV['WEBHOOK_URL'] || "http://#{@host}:#{@port}"
109
- "#{base_url}/webhook/#{@bot.token}"
61
+ # For cloud platforms, use their URL
62
+ if cloud_url = detect_cloud_url
63
+ "#{cloud_url}#{@webhook_path}"
64
+ elsif ENV['WEBHOOK_URL']
65
+ ENV['WEBHOOK_URL']
66
+ else
67
+ # Local development
68
+ "https://#{@host}:#{@port}#{@webhook_path}"
69
+ end
110
70
  end
111
-
71
+
72
+ def set_webhook(**options)
73
+ params = {
74
+ url: webhook_url,
75
+ secret_token: @secret_token,
76
+ drop_pending_updates: true
77
+ }.merge(options)
78
+
79
+ @bot.api.call!('setWebhook', params)
80
+ end
81
+
82
+ def delete_webhook
83
+ @bot.api.call!('deleteWebhook', {})
84
+ end
85
+
86
+ # Quick setup helper
87
+ def self.setup(bot, **options)
88
+ server = new(bot, **options)
89
+ server.run
90
+ server.set_webhook
91
+ server
92
+ end
93
+
112
94
  private
113
-
114
- def handle_webhook_request(req, res)
115
- # Only accept POST requests
116
- unless req.request_method == 'POST'
117
- res.status = 405
118
- res.content_type = 'text/plain'
119
- res.body = 'Method Not Allowed'
120
- return
95
+
96
+ def build_rack_app
97
+ Rack::Builder.new do
98
+ use Rack::CommonLogger
99
+ use Rack::ShowExceptions if ENV['RACK_ENV'] == 'development'
100
+
101
+ # Health endpoint (required by Render/Railway/Heroku)
102
+ map "/health" do
103
+ run ->(env) {
104
+ [200, {
105
+ 'Content-Type' => 'application/json',
106
+ 'Cache-Control' => 'no-cache'
107
+ }, [{
108
+ status: 'healthy',
109
+ timestamp: Time.now.iso8601,
110
+ service: 'telegem-webhook',
111
+ version: Telegem::VERSION
112
+ }.to_json]]
113
+ }
114
+ end
115
+
116
+ # Webhook endpoint
117
+ map "/webhook" do
118
+ run ->(env) do
119
+ server = env['telegem.server']
120
+ req = Rack::Request.new(env)
121
+
122
+ if req.post?
123
+ server.handle_rack_request(req)
124
+ else
125
+ [405, { 'Content-Type' => 'text/plain' }, ['Method Not Allowed']]
126
+ end
127
+ end
128
+ end
129
+
130
+ # Root landing page
131
+ map "/" do
132
+ run ->(env) do
133
+ server = env['telegem.server']
134
+ [200, { 'Content-Type' => 'text/html' }, [server.landing_page]]
135
+ end
136
+ end
121
137
  end
122
-
138
+ end
139
+
140
+ def start_puma_server(app)
141
+ require 'puma'
142
+
143
+ # Cloud platforms set PORT environment variable
144
+ port = ENV['PORT'] || @port
145
+
146
+ config = {
147
+ Host: @host,
148
+ Port: port,
149
+ Threads: "0:#{@max_threads}",
150
+ workers: ENV['WEB_CONCURRENCY']&.to_i || 1,
151
+ daemonize: false,
152
+ silent: false,
153
+ environment: ENV['RACK_ENV'] || 'production',
154
+ # Cloud platforms handle SSL termination
155
+ ssl_bind: nil # Let platform handle SSL
156
+ }
157
+
158
+ @server = Puma::Server.new(app)
159
+ @server.add_tcp_listener(@host, port)
160
+
161
+ @server.app = ->(env) do
162
+ env['telegem.server'] = self
163
+ app.call(env)
164
+ end
165
+
166
+ Thread.new do
167
+ begin
168
+ @server.run
169
+ rescue => e
170
+ @logger.error "❌ Puma server error: #{e.message}"
171
+ @running = false
172
+ raise
173
+ end
174
+ end
175
+
176
+ sleep 1 until @server.running?
177
+ end
178
+
179
+ def handle_rack_request(req)
180
+ # Validate secret token from Telegram
181
+ telegram_token = req.get_header('X-Telegram-Bot-Api-Secret-Token')
182
+
183
+ if @secret_token && telegram_token != @secret_token
184
+ @logger.warn "⚠️ Invalid secret token from #{req.ip}"
185
+ return [403, { 'Content-Type' => 'text/plain' }, ['Forbidden']]
186
+ end
187
+
123
188
  begin
124
- # Parse the update
125
189
  body = req.body.read
126
190
  update_data = JSON.parse(body)
127
191
 
128
- # Process the update in a separate thread to keep response fast
129
- Thread.new do
192
+ # Process in thread pool (not spawn new threads!)
193
+ @thread_pool.post do
130
194
  begin
131
195
  @bot.process(update_data)
132
196
  rescue => e
@@ -134,57 +198,127 @@ module Telegem
134
198
  end
135
199
  end
136
200
 
137
- # Immediate response to Telegram
138
- res.status = 200
139
- res.content_type = 'text/plain'
140
- res.body = 'OK'
141
-
142
- rescue JSON::ParserError => e
143
- @logger.error "Invalid JSON in webhook request: #{e.message}"
144
- res.status = 400
145
- res.content_type = 'text/plain'
146
- res.body = 'Bad Request'
201
+ [200, { 'Content-Type' => 'text/plain' }, ['OK']]
202
+ rescue JSON::ParserError
203
+ [400, { 'Content-Type' => 'text/plain' }, ['Bad Request']]
147
204
  rescue => e
148
- @logger.error "Error handling webhook: #{e.class}: #{e.message}"
149
- res.status = 500
150
- res.content_type = 'text/plain'
151
- res.body = 'Internal Server Error'
205
+ @logger.error "Webhook error: #{e.message}"
206
+ [500, { 'Content-Type' => 'text/plain' }, ['Internal Server Error']]
207
+ end
208
+ end
209
+
210
+ def landing_page
211
+ <<~HTML
212
+ <!DOCTYPE html>
213
+ <html>
214
+ <head>
215
+ <title>Telegem Webhook Server</title>
216
+ <style>
217
+ body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
218
+ max-width: 800px; margin: 0 auto; padding: 20px; }
219
+ .status { background: #4CAF50; color: white; padding: 5px 10px; border-radius: 3px; }
220
+ code { background: #f5f5f5; padding: 2px 5px; border-radius: 3px; }
221
+ a { color: #2196F3; text-decoration: none; }
222
+ </style>
223
+ </head>
224
+ <body>
225
+ <h1>🤖 Telegem Webhook Server</h1>
226
+ <p><strong>Status:</strong> <span class="status">Running</span></p>
227
+ <p><strong>Webhook URL:</strong> <code>#{webhook_url}</code></p>
228
+ <p><strong>Health Check:</strong> <a href="/health">/health</a></p>
229
+ <p><strong>Platform:</strong> #{cloud_platform}</p>
230
+ <hr>
231
+ <p>To set up your Telegram bot:</p>
232
+ <pre><code>bot = Telegem.new('YOUR_TOKEN')
233
+ server = bot.webhook
234
+ server.run
235
+ server.set_webhook</code></pre>
236
+ </body>
237
+ </html>
238
+ HTML
239
+ end
240
+
241
+ def generate_secret_token
242
+ SecureRandom.hex(32)
243
+ end
244
+
245
+ def detect_cloud_url
246
+ # Render
247
+ if ENV['RENDER_EXTERNAL_URL']
248
+ ENV['RENDER_EXTERNAL_URL']
249
+ # Railway
250
+ elsif ENV['RAILWAY_STATIC_URL']
251
+ ENV['RAILWAY_STATIC_URL']
252
+ # Heroku
253
+ elsif ENV['HEROKU_APP_NAME']
254
+ "https://#{ENV['HEROKU_APP_NAME']}.herokuapp.com"
255
+ # Fly.io
256
+ elsif ENV['FLY_APP_NAME']
257
+ "https://#{ENV['FLY_APP_NAME']}.fly.dev"
258
+ # Vercel (if using serverless)
259
+ elsif ENV['VERCEL_URL']
260
+ "https://#{ENV['VERCEL_URL']}"
261
+ else
262
+ nil
263
+ end
264
+ end
265
+
266
+ def cloud_platform
267
+ if ENV['RENDER'] then 'Render'
268
+ elsif ENV['RAILWAY'] then 'Railway'
269
+ elsif ENV['HEROKU_APP_NAME'] then 'Heroku'
270
+ elsif ENV['FLY_APP_NAME'] then 'Fly.io'
271
+ elsif ENV['VERCEL'] then 'Vercel'
272
+ elsif ENV['DYNO'] then 'Heroku'
273
+ else 'Local/Unknown'
152
274
  end
153
275
  end
154
276
  end
155
-
156
- # Middleware for Rack apps (Rails, Sinatra, etc.)
277
+
278
+ # Rack Middleware for existing apps
157
279
  class Middleware
158
- def initialize(app, bot)
280
+ def initialize(app, bot, secret_token: nil)
159
281
  @app = app
160
282
  @bot = bot
283
+ @secret_token = secret_token || ENV['TELEGRAM_SECRET_TOKEN']
284
+ @thread_pool = Concurrent::FixedThreadPool.new(10)
161
285
  end
162
-
286
+
163
287
  def call(env)
164
288
  req = Rack::Request.new(env)
165
289
 
166
- # Check if this is a webhook request
167
- if req.post? && req.path == "/webhook/#{@bot.token}"
290
+ if req.post? && req.path == "/webhook"
168
291
  handle_webhook(req)
169
292
  else
170
293
  @app.call(env)
171
294
  end
172
295
  end
173
-
296
+
174
297
  private
175
-
298
+
176
299
  def handle_webhook(req)
300
+ telegram_token = req.get_header('X-Telegram-Bot-Api-Secret-Token')
301
+
302
+ if @secret_token && telegram_token != @secret_token
303
+ return [403, { 'Content-Type' => 'text/plain' }, ['Forbidden']]
304
+ end
305
+
177
306
  begin
178
307
  update_data = JSON.parse(req.body.read)
179
308
 
180
- # Process async in background
181
- Thread.new { @bot.process(update_data) }
309
+ @thread_pool.post do
310
+ begin
311
+ @bot.process(update_data)
312
+ rescue => e
313
+ @bot.logger&.error("Webhook error: #{e.message}")
314
+ end
315
+ end
182
316
 
183
317
  [200, { 'Content-Type' => 'text/plain' }, ['OK']]
184
318
  rescue JSON::ParserError
185
319
  [400, { 'Content-Type' => 'text/plain' }, ['Bad Request']]
186
320
  rescue => e
187
- @bot.logger.error("Webhook error: #{e.message}") if @bot.logger
321
+ @bot.logger&.error("Webhook error: #{e.message}")
188
322
  [500, { 'Content-Type' => 'text/plain' }, ['Internal Server Error']]
189
323
  end
190
324
  end
metadata CHANGED
@@ -1,10 +1,10 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegem
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
- - Your Name
7
+ - sick_phantom
8
8
  bindir: exe
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
@@ -15,14 +15,68 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: 0.24.0
18
+ version: '0.20'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: 0.24.0
25
+ version: '0.20'
26
+ - !ruby/object:Gem::Dependency
27
+ name: concurrent-ruby
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '1.2'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '1.2'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rack
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ - - "<"
48
+ - !ruby/object:Gem::Version
49
+ version: '4.0'
50
+ type: :runtime
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: '2.0'
57
+ - - "<"
58
+ - !ruby/object:Gem::Version
59
+ version: '4.0'
60
+ - !ruby/object:Gem::Dependency
61
+ name: puma
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '5.0'
67
+ - - "<"
68
+ - !ruby/object:Gem::Version
69
+ version: '7.0'
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '5.0'
77
+ - - "<"
78
+ - !ruby/object:Gem::Version
79
+ version: '7.0'
26
80
  - !ruby/object:Gem::Dependency
27
81
  name: rake
28
82
  requirement: !ruby/object:Gem::Requirement
@@ -43,32 +97,62 @@ dependencies:
43
97
  requirements:
44
98
  - - "~>"
45
99
  - !ruby/object:Gem::Version
46
- version: '3.0'
100
+ version: '3.12'
101
+ type: :development
102
+ prerelease: false
103
+ version_requirements: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - "~>"
106
+ - !ruby/object:Gem::Version
107
+ version: '3.12'
108
+ - !ruby/object:Gem::Dependency
109
+ name: simplecov
110
+ requirement: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: '0.22'
115
+ type: :development
116
+ prerelease: false
117
+ version_requirements: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '0.22'
122
+ - !ruby/object:Gem::Dependency
123
+ name: webmock
124
+ requirement: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: '3.18'
47
129
  type: :development
48
130
  prerelease: false
49
131
  version_requirements: !ruby/object:Gem::Requirement
50
132
  requirements:
51
133
  - - "~>"
52
134
  - !ruby/object:Gem::Version
53
- version: '3.0'
135
+ version: '3.18'
54
136
  - !ruby/object:Gem::Dependency
55
- name: pry
137
+ name: rubocop
56
138
  requirement: !ruby/object:Gem::Requirement
57
139
  requirements:
58
140
  - - "~>"
59
141
  - !ruby/object:Gem::Version
60
- version: 0.14.0
142
+ version: '1.50'
61
143
  type: :development
62
144
  prerelease: false
63
145
  version_requirements: !ruby/object:Gem::Requirement
64
146
  requirements:
65
147
  - - "~>"
66
148
  - !ruby/object:Gem::Version
67
- version: 0.14.0
68
- description: Blazing-fast Telegram Bot framework with true async/await patterns, inspired
69
- by Telegraf.js
149
+ version: '1.50'
150
+ description: |
151
+ Telegem is a modern Telegram Bot Framework for Ruby inspired by Telegraf.js.
152
+ Built with async-first design using HTTPX, featuring scenes, middleware,
153
+ and a clean DSL. Perfect for building scalable Telegram bots.
70
154
  email:
71
- - your-email@example.com
155
+ - ynwghosted@icloud.com
72
156
  executables: []
73
157
  extensions: []
74
158
  extra_rdoc_files: []
@@ -84,19 +168,20 @@ files:
84
168
  - Test-Projects/pizza_test_bot_guide.md
85
169
  - docs/.gitkeep
86
170
  - docs/Api.md
87
- - docs/Cookbook.md
171
+ - docs/Cookbook(copy_paste).md
172
+ - docs/Getting_started.md
88
173
  - docs/How_to_use.md
89
174
  - docs/QuickStart.md
90
- - docs/SETTING_WEBHOOK.md
91
- - docs/UNDERSTANDING-WEBHOOK-n-POLLING.md
92
175
  - docs/Understanding_Scene.md
93
176
  - docs/Usage.md
177
+ - docs/webhook_setup.md
94
178
  - lib/api/client.rb
95
179
  - lib/api/types.rb
96
180
  - lib/core/bot.rb
97
181
  - lib/core/composer.rb
98
182
  - lib/core/context.rb
99
183
  - lib/core/scene.rb
184
+ - lib/markup/.gitkeep
100
185
  - lib/markup/keyboard.rb
101
186
  - lib/session/memory_store.rb
102
187
  - lib/session/middleware.rb
@@ -110,7 +195,13 @@ metadata:
110
195
  homepage_uri: https://gitlab.com/ruby-telegem/telegem
111
196
  source_code_uri: https://gitlab.com/ruby-telegem/telegem
112
197
  changelog_uri: https://gitlab.com/ruby-telegem/telegem/-/blob/main/CHANGELOG.md
113
- documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/tree/main/docs
198
+ bug_tracker_uri: https://gitlab.com/ruby-telegem/telegem/-/issues
199
+ documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/blob/main/README.md
200
+ rubygems_mfa_required: 'false'
201
+ post_install_message: "Thanks for installing Telegem 2.0.0!\n\nQuick start:\n bot
202
+ = Telegem.new(\"YOUR_TOKEN\")\n bot.on(:message) { |ctx| ctx.reply(\"Hello!\")
203
+ }\n bot.start_polling\n\nDocumentation: https://gitlab.com/ruby-telegem/telegem\nHappy
204
+ bot building! \U0001F916\n"
114
205
  rdoc_options: []
115
206
  require_paths:
116
207
  - lib
@@ -127,5 +218,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
127
218
  requirements: []
128
219
  rubygems_version: 3.6.9
129
220
  specification_version: 4
130
- summary: Modern, async Telegram Bot API for Ruby
221
+ summary: Modern, fast Telegram Bot Framework for Ruby
131
222
  test_files: []