telegem 3.0.0 → 3.0.2

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,110 +1,128 @@
1
+ # lib/telegem/webhook/server.rb
1
2
  require 'async/http/server'
2
3
  require 'async/http/endpoint'
4
+ require 'openssl'
5
+ require 'yaml'
3
6
  require 'json'
4
- require 'logger'
7
+ require 'securerandom'
8
+ require 'uri'
5
9
 
6
10
  module Telegem
7
11
  module Webhook
8
12
  class Server
9
- attr_reader :bot, :port, :host, :logger, :secret_token, :running, :server
13
+ attr_reader :bot, :port, :host, :logger, :secret_token, :running, :server, :ssl_mode
10
14
 
11
- def initialize(bot, port: nil, host: '0.0.0.0', secret_token: nil, logger: nil)
15
+ def initialize(bot, port: nil, host: '0.0.0.0', secret_token: nil, logger: nil, ssl: nil)
12
16
  @bot = bot
13
- @port = port || 3000
17
+ @port = port || ENV['PORT'] || 3000
14
18
  @host = host
15
- @secret_token = secret_token
19
+ @secret_token = secret_token || ENV['WEBHOOK_SECRET_TOKEN'] || SecureRandom.hex(16)
16
20
  @logger = logger || Logger.new($stdout)
17
21
  @running = false
18
22
  @server = nil
19
- end
20
23
 
21
- def run
22
- return if @running
24
+ @ssl_mode, @ssl_context = determine_ssl_mode(ssl)
25
+ log_configuration
26
+ validate_ssl_setup
27
+ end
23
28
 
24
- @running = true
25
- @logger.info "Starting webhook server on #{@host}:#{@port}"
29
+ def determine_ssl_mode(ssl_options)
30
+ return [:none, nil] if ssl_options == false
26
31
 
27
- endpoint = Async::HTTP::Endpoint.parse("http://#{@host}:#{@port}")
32
+ if File.exist?('.telegem-ssl')
33
+ config = YAML.load_file('.telegem-ssl')
34
+ cert_path = config['cert_path']
35
+ key_path = config['key_path']
28
36
 
29
- @server = Async::HTTP::Server.for(endpoint) do |request|
30
- case request.path
31
- when '/webhook'
32
- handle_webhook_request(request)
33
- when '/health'
34
- health_endpoint(request)
35
- else
36
- [404, {}, ["Not Found"]]
37
+ if cert_path && key_path && File.exist?(cert_path) && File.exist?(key_path)
38
+ return [:cli, load_certificate_files(cert_path, key_path)]
37
39
  end
38
40
  end
39
41
 
40
- Async do |task|
41
- @server.run
42
- task.sleep while @running
42
+ if ssl_options && ssl_options[:cert_path] && ssl_options[:key_path]
43
+ return [:manual, load_certificate_files(ssl_options[:cert_path], ssl_options[:key_path])]
43
44
  end
44
45
 
45
- @logger.info "Webhook server running on http://#{@host}:#{@port}"
46
- end
46
+ if ENV['TELEGEM_WEBHOOK_URL'] && URI(ENV['TELEGEM_WEBHOOK_URL']).scheme == 'https'
47
+ return [:cloud, nil]
48
+ end
47
49
 
48
- def stop
49
- return unless @running
50
+ [:none, nil]
51
+ end
50
52
 
51
- @running = false
52
- @server&.close
53
- @logger.info "Webhook server stopped"
54
- @server = nil
53
+ def load_certificate_files(cert_path, key_path)
54
+ ctx = OpenSSL::SSL::SSLContext.new
55
+ ctx.cert = OpenSSL::X509::Certificate.new(File.read(cert_path))
56
+ ctx.key = OpenSSL::PKey::RSA.new(File.read(key_path))
57
+ ctx
58
+ rescue
59
+ nil
55
60
  end
56
61
 
57
- def webhook_url
58
- if @secret_token
59
- "https://#{@host}:#{@port}/webhook?secret_token=#{@secret_token}"
60
- else
61
- "https://#{@host}:#{@port}/webhook"
62
+ def validate_ssl_setup
63
+ case @ssl_mode
64
+ when :cli, :manual
65
+ raise "SSL certificate files not found or invalid" if @ssl_context.nil?
66
+ when :cloud
67
+ url = URI(ENV['TELEGEM_WEBHOOK_URL'])
68
+ raise "TELEGEM_WEBHOOK_URL must be HTTPS" unless url.scheme == 'https'
62
69
  end
63
70
  end
64
71
 
65
- def set_webhook(**options)
66
- url = webhook_url
67
- params = { url: url }
68
- params[:secret_token] = @secret_token if @secret_token
69
- params.merge!(options)
70
-
71
- @bot.set_webhook(**params)
72
- @logger.info "Webhook set to #{url}"
72
+ def log_configuration
73
+ @logger.info("Webhook Server Configuration:")
74
+ @logger.info(" Mode: #{@ssl_mode.to_s.upcase}")
75
+ @logger.info(" Port: #{@port}")
76
+ @logger.info(" Host: #{@host}")
77
+ @logger.info(" Secret: #{@secret_token[0..8]}...")
73
78
  end
74
79
 
75
- def delete_webhook
76
- @bot.delete_webhook
77
- @logger.info "Webhook deleted"
78
- end
80
+ def run
81
+ return if @running
82
+ @running = true
79
83
 
80
- def get_webhook_info
81
- info = @bot.get_webhook_info
82
- @logger.info "Webhook info retrieved"
83
- info
84
+ case @ssl_mode
85
+ when :cli, :manual
86
+ endpoint = Async::HTTP::Endpoint.parse("https://#{@host}:#{@port}", ssl_context: @ssl_context)
87
+ @logger.info("Starting HTTPS server with local certificates")
88
+ when :cloud
89
+ endpoint = Async::HTTP::Endpoint.parse("http://#{@host}:#{@port}")
90
+ @logger.info("Starting HTTP server (cloud platform handles SSL)")
91
+ else
92
+ endpoint = Async::HTTP::Endpoint.parse("http://#{@host}:#{@port}")
93
+ @logger.warn("Starting HTTP server (Telegram requires HTTPS)")
94
+ end
95
+
96
+ @server = Async::HTTP::Server.for(endpoint) do |request|
97
+ handle_request(request)
98
+ end
99
+
100
+ Async do |task|
101
+ @server.run
102
+ task.sleep while @running
103
+ end
84
104
  end
85
105
 
86
- private
106
+ def handle_request(request)
107
+ case request.path
108
+ when @secret_token, "/#{@secret_token}"
109
+ handle_webhook_request(request)
110
+ when '/health', '/healthz'
111
+ health_endpoint(request)
112
+ else
113
+ [404, {}, ["Not Found"]]
114
+ end
115
+ end
87
116
 
88
117
  def handle_webhook_request(request)
89
118
  return [405, {}, ["Method Not Allowed"]] unless request.post?
90
119
 
91
- unless validate_secret_token(request)
92
- return [403, {}, ["Forbidden"]]
93
- end
94
-
95
120
  begin
96
121
  body = request.body.read
97
122
  update_data = JSON.parse(body)
98
-
99
- Async do |task|
100
- process_webhook_update(update_data)
101
- end
102
-
123
+ Async { process_webhook_update(update_data) }
103
124
  [200, {}, ["OK"]]
104
- rescue JSON::ParserError
105
- [400, {}, ["Bad Request"]]
106
- rescue => e
107
- @logger.error "Webhook error: #{e.message}"
125
+ rescue
108
126
  [500, {}, ["Internal Server Error"]]
109
127
  end
110
128
  end
@@ -112,18 +130,56 @@ module Telegem
112
130
  def process_webhook_update(update_data)
113
131
  @bot.process(update_data)
114
132
  rescue => e
115
- @logger.error "Error processing update: #{e.message}"
133
+ @logger.error("Process error: #{e}")
134
+ end
135
+
136
+ def health_endpoint(request)
137
+ [200, { 'Content-Type' => 'application/json' }, [{
138
+ status: 'ok',
139
+ mode: @ssl_mode.to_s,
140
+ ssl: @ssl_mode != :none
141
+ }.to_json]]
142
+ end
143
+
144
+ def stop
145
+ return unless @running
146
+ @running = false
147
+ @server&.close
148
+ @logger.info("Server stopped")
149
+ @server = nil
150
+ end
151
+
152
+ def webhook_url
153
+ case @ssl_mode
154
+ when :cli, :manual
155
+ "https://#{@host}:#{@port}#{@secret_token}"
156
+ when :cloud
157
+ cloud_url = ENV['TELEGEM_WEBHOOK_URL'].chomp('/')
158
+ "#{cloud_url}#{@secret_token}"
159
+ else
160
+ "http://#{@host}:#{@port}#{@secret_token}"
161
+ end
162
+ end
163
+
164
+ def set_webhook(**options)
165
+ url = webhook_url
166
+ params = { url: url }.merge(options)
167
+ @bot.set_webhook(**params)
168
+ @logger.info("Webhook set to: #{url}")
169
+ url
116
170
  end
117
171
 
118
- def validate_secret_token(request)
119
- return true unless @secret_token
172
+ def delete_webhook
173
+ @bot.delete_webhook
174
+ @logger.info("Webhook deleted")
175
+ end
120
176
 
121
- token = request.query['secret_token'] || request.headers['x-telegram-bot-api-secret-token']
122
- token == @secret_token
177
+ def get_webhook_info
178
+ @bot.get_webhook_info
123
179
  end
124
180
 
125
- def health_endpoint(request)
126
- [200, { 'Content-Type' => 'application/json' }, [{ status: 'ok' }.to_json]]
181
+ def running?
182
+ @running
127
183
  end
128
184
  end
129
185
  end
data/public/.gitkeep ADDED
File without changes