telegem 2.0.5 → 2.0.7

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.
Files changed (5) hide show
  1. checksums.yaml +4 -4
  2. data/lib/api/client.rb +27 -30
  3. data/lib/core/bot.rb +126 -93
  4. data/lib/telegem.rb +1 -1
  5. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c8a100bc051a797130ecebdf67164f6d2566fda1241a3240226396412b1cd6d7
4
- data.tar.gz: 6f41d93f14122381a14138be904987032abc577c8d0fde33e53d0632505d6363
3
+ metadata.gz: 272c8898e9301da49ef787a232fe0eca2b1374ed611a04ae43d6ffff0a51053b
4
+ data.tar.gz: 44c00d019fbcd8da206f7231d9a24cec1c10f00b8f075c8c233a9706ceb6675a
5
5
  SHA512:
6
- metadata.gz: 11ee3ef944c5ddafeb77c91e9e069bca3631061f073525abf3b7a60f009b8b2c30cc8f12e73a828001b2bec062039b73de55af23896d621b77f7c2dac6e544ca
7
- data.tar.gz: 9a378f859712a891550e5fd1647053a7a2dae80065d08f9c1b73f9fbdaf6ff7b006115165a403848866d6ba8da439a351166661200128ed7bfb23af83141f4a6
6
+ metadata.gz: 55eba2b0e9a9792e5bd4280656a2986a3150a5c9ebec65c6c5fe60144cbc66dd6c460578966b4077fb53d70b65fdcdc5ee40a1561b663cc0892237ade86a72b9
7
+ data.tar.gz: 1f151636509dd3ba2e63a61db759a45bee1d54a63e9e708694f6a137b517a88d77691728c7be7a371a0aad9c6b41e02d71396aa70f6ec3e418f78161431a37be
data/lib/api/client.rb CHANGED
@@ -1,4 +1,3 @@
1
- # lib/api/client.rb - MINIMAL WORKING VERSION
2
1
  require 'httpx'
3
2
  require 'json'
4
3
 
@@ -12,35 +11,54 @@ module Telegem
12
11
  def initialize(token, **options)
13
12
  @token = token
14
13
  @logger = options[:logger] || Logger.new($stdout)
15
- @timeout = options[:timeout] || 30
14
+ timeout = options[:timeout] || 30
16
15
 
17
16
  @http = HTTPX.with(
18
- timeout: { request_timeout: @timeout},
17
+ timeout: {
18
+ request_timeout: timeout,
19
+ connect_timeout: 10,
20
+ write_timeout: 10,
21
+ read_timeout: timeout
22
+ },
19
23
  headers: {
20
- 'Content-Type' => 'application/json'
24
+ 'Content-Type' => 'application/json',
25
+ 'User-Agent' => "Telegem/#{Telegem::VERSION}"
21
26
  }
22
27
  )
23
28
  end
24
29
 
25
30
  def call(method, params = {})
26
31
  url = "#{BASE_URL}/bot#{@token}/#{method}"
32
+ @logger.debug("API Call: #{method}") if @logger
27
33
  @http.post(url, json: params.compact)
28
34
  end
29
35
 
30
36
  def call!(method, params = {})
31
37
  request = call(method, params)
32
- request.wait
38
+ response = request.wait
33
39
 
34
- if request.error
35
- handle_error(request.error)
40
+ if response.status != 200
41
+ @logger.error("HTTP Error #{response.status}") if @logger
36
42
  return nil
37
43
  end
38
44
 
39
- handle_response(request.response)
45
+ json = response.json
46
+
47
+ if json && json['ok']
48
+ json['result']
49
+ else
50
+ error_msg = json ? json['description'] : "Invalid response"
51
+ @logger.error("API Error: #{error_msg}") if @logger
52
+ nil
53
+ end
54
+ rescue => e
55
+ @logger.error("Error in call!: #{e.class}: #{e.message}") if @logger
56
+ nil
40
57
  end
41
58
 
42
59
  def upload(method, params)
43
60
  url = "#{BASE_URL}/bot#{@token}/#{method}"
61
+
44
62
  form = params.map do |key, value|
45
63
  if file_object?(value)
46
64
  [key.to_s, HTTPX::FormData::File.new(value)]
@@ -48,6 +66,7 @@ module Telegem
48
66
  [key.to_s, value.to_s]
49
67
  end
50
68
  end
69
+
51
70
  @http.post(url, form: form)
52
71
  end
53
72
 
@@ -64,28 +83,6 @@ module Telegem
64
83
 
65
84
  private
66
85
 
67
- def handle_response(response)
68
- return nil unless response
69
-
70
- if response.status != 200
71
- raise APIError, "HTTP #{response.status}"
72
- end
73
-
74
- json = response.json
75
- return nil unless json
76
-
77
- if json['ok']
78
- json['result']
79
- else
80
- raise APIError.new(json['description'], json['error_code'])
81
- end
82
- end
83
-
84
- def handle_error(error)
85
- @logger.error("API Error: #{error.message}") if @logger
86
- raise APIError, error.message
87
- end
88
-
89
86
  def file_object?(obj)
90
87
  obj.is_a?(File) || obj.is_a?(StringIO) || obj.is_a?(Tempfile) ||
91
88
  (obj.is_a?(String) && File.exist?(obj))
data/lib/core/bot.rb CHANGED
@@ -4,11 +4,13 @@ require 'logger'
4
4
  module Telegem
5
5
  module Core
6
6
  class Bot
7
- attr_reader :token, :api, :handlers, :middleware, :logger, :scenes, :running, :polling_thread
7
+ attr_reader :token, :api, :handlers, :middleware, :logger, :scenes,
8
+ :running, :polling_thread, :session_store
8
9
 
9
10
  def initialize(token, **options)
10
11
  @token = token
11
12
  @api = API::Client.new(token, **options.slice(:logger, :timeout))
13
+
12
14
  @handlers = {
13
15
  message: [],
14
16
  callback_query: [],
@@ -18,6 +20,7 @@ module Telegem
18
20
  pre_checkout_query: [],
19
21
  shipping_query: []
20
22
  }
23
+
21
24
  @middleware = []
22
25
  @scenes = {}
23
26
  @logger = options[:logger] || Logger.new($stdout)
@@ -35,6 +38,49 @@ module Telegem
35
38
  start_workers(options[:worker_count] || 5)
36
39
  end
37
40
 
41
+ def start_polling(**options)
42
+ return if @running
43
+
44
+ @running = true
45
+ @polling_options = {
46
+ timeout: 30,
47
+ limit: 100,
48
+ allowed_updates: nil
49
+ }.merge(options)
50
+
51
+ @offset = nil
52
+
53
+ @logger.info "🤖 Starting Telegem bot (polling mode)..."
54
+
55
+ @polling_thread = Thread.new do
56
+ Thread.current.abort_on_exception = false
57
+ poll_loop
58
+ end
59
+
60
+ self
61
+ end
62
+
63
+ def shutdown
64
+ return unless @running
65
+
66
+ @logger.info "🛑 Shutting down bot..."
67
+ @running = false
68
+
69
+ if @polling_thread&.alive?
70
+ @polling_thread.join(3)
71
+ end
72
+
73
+ stop_workers
74
+
75
+ @api.close if @api.respond_to?(:close)
76
+
77
+ @logger.info "✅ Bot shutdown complete"
78
+ end
79
+
80
+ def running?
81
+ @running
82
+ end
83
+
38
84
  def command(name, **options, &block)
39
85
  pattern = /^\/#{Regexp.escape(name)}(?:@\w+)?(?:\s+(.+))?$/i
40
86
 
@@ -69,26 +115,6 @@ module Telegem
69
115
  @scenes[id] = Scene.new(id, &block)
70
116
  end
71
117
 
72
- def start_polling(**options)
73
- return if @running
74
-
75
- @running = true
76
- @polling_options = {
77
- timeout: 30,
78
- limit: 100,
79
- allowed_updates: nil
80
- }.merge(options)
81
-
82
- @offset = nil
83
-
84
- @polling_thread = Thread.new do
85
- @logger.info "🤖 Starting Telegem bot (HTTPX async)..."
86
- poll_loop
87
- end
88
-
89
- self
90
- end
91
-
92
118
  def webhook(app = nil, port: nil, host: '0.0.0.0', logger: nil, &block)
93
119
  require_relative '../webhook/server'
94
120
 
@@ -118,113 +144,119 @@ module Telegem
118
144
  process_update(update)
119
145
  end
120
146
 
121
- def shutdown
122
- @logger.info "🛑 Shutting down..."
123
-
124
- @running = false
147
+ private
148
+
149
+ def poll_loop
150
+ @logger.debug "Entering polling loop"
125
151
 
126
- @polling_thread&.join(5) if @polling_thread&.alive?
152
+ while @running
153
+ begin
154
+ result = fetch_updates
155
+
156
+ if result && result.is_a?(Hash) && result['ok']
157
+ handle_updates_response(result)
158
+ elsif result
159
+ @logger.warn "Unexpected API response format: #{result.class}"
160
+ end
161
+
162
+ sleep 0.1 unless @offset.nil?
163
+
164
+ rescue => e
165
+ handle_error(e)
166
+ sleep 5
167
+ end
168
+ end
127
169
 
128
- stop_workers
170
+ @logger.debug "Exiting polling loop"
171
+ end
172
+
173
+ def fetch_updates
174
+ params = {
175
+ timeout: @polling_options[:timeout],
176
+ limit: @polling_options[:limit]
177
+ }
178
+ params[:offset] = @offset if @offset
179
+ params[:allowed_updates] = @polling_options[:allowed_updates] if @polling_options[:allowed_updates]
129
180
 
130
- @api.close
181
+ begin
182
+ request = @api.get_updates(params)
183
+
184
+ if request.respond_to?(:wait)
185
+ response = request.wait
186
+
187
+ if response.respond_to?(:status) && response.status == 200
188
+ json = response.json
189
+ return json if json && json.is_a?(Hash)
190
+ else
191
+ @logger.error("HTTP Error: #{response.status}") if response.respond_to?(:status)
192
+ end
193
+ else
194
+ @logger.error("API didn't return a waitable request: #{request.class}")
195
+ end
196
+ rescue => e
197
+ @logger.error("Error fetching updates: #{e.class}: #{e.message}")
198
+ end
131
199
 
132
- @logger.info "✅ Bot stopped"
200
+ nil
133
201
  end
134
202
 
135
- def running?
136
- @running
203
+ def handle_updates_response(api_response)
204
+ updates = api_response['result'] || []
205
+
206
+ if updates.any?
207
+ @logger.debug "Processing #{updates.length} update(s)"
208
+
209
+ updates.each do |update_data|
210
+ @update_queue << [update_data, nil]
211
+ end
212
+
213
+ @offset = updates.last['update_id'] + 1
214
+ @logger.debug "Updated offset to #{@offset}"
215
+ end
137
216
  end
138
217
 
139
- private
140
-
141
218
  def start_workers(count)
142
219
  count.times do |i|
143
220
  @worker_threads << Thread.new do
221
+ Thread.current.abort_on_exception = false
144
222
  worker_loop(i)
145
223
  end
146
224
  end
225
+ @logger.debug "Started #{count} worker threads"
147
226
  end
148
227
 
149
228
  def stop_workers
229
+ @logger.debug "Stopping worker threads"
230
+
150
231
  @worker_threads.size.times { @update_queue << :shutdown }
232
+
151
233
  @worker_threads.each do |thread|
152
234
  thread.join(2) if thread.alive?
153
235
  end
236
+
154
237
  @worker_threads.clear
155
238
  end
156
239
 
157
240
  def worker_loop(id)
158
- @logger.debug "Worker #{id} started" if @logger
241
+ @logger.debug "Worker #{id} started"
242
+
159
243
  while @running
160
244
  begin
161
245
  task = @update_queue.pop
246
+
162
247
  break if task == :shutdown
163
248
 
164
249
  update_data, callback = task
165
- update = Types::Update.new(update_data)
166
- process_update(update)
167
- callback&.call(update) if callback.respond_to?(:call)
168
- rescue => e
169
- @logger.error "Worker #{id} error: #{e.message}"
170
- end
171
- end
172
- @logger.debug "Worker #{id} stopped" if @logger
173
- end
174
-
175
- def poll_loop
176
- while @running
177
- begin
178
- # Get updates - returns HTTPX request object
179
- updates_request = fetch_updates
180
-
181
- # Wait for the request to complete
182
- updates_request.wait(@polling_options[:timeout] + 5)
250
+ process_update(Types::Update.new(update_data))
183
251
 
184
- # Get the response AFTER waiting
185
- response = updates_request.response
186
-
187
- if response && response.status == 200
188
- if json = response.json
189
- handle_updates_response(json)
190
- end
191
- end
192
-
193
- # Small delay to prevent tight loop
194
- sleep 0.1 unless @offset.nil?
252
+ callback&.call if callback.respond_to?(:call)
195
253
 
196
254
  rescue => e
197
- handle_error(e)
198
- sleep 5
255
+ @logger.error "Worker #{id} error: #{e.message}"
199
256
  end
200
257
  end
201
- end
202
-
203
- def fetch_updates
204
- params = {
205
- timeout: @polling_options[:timeout],
206
- limit: @polling_options[:limit]
207
- }
208
- params[:offset] = @offset if @offset
209
- params[:allowed_updates] = @polling_options[:allowed_updates] if @polling_options[:allowed_updates]
210
-
211
- @api.call('getUpdates', params)
212
- end
213
-
214
- def handle_updates_response(api_response)
215
- return unless api_response['ok']
216
-
217
- updates_data = api_response['result'] || []
218
-
219
- # Queue updates for worker processing
220
- updates_data.each do |update_data|
221
- @update_queue << [update_data, nil]
222
- end
223
258
 
224
- # Update offset for next poll
225
- if updates_data.any?
226
- @offset = updates_data.last['update_id'] + 1
227
- end
259
+ @logger.debug "Worker #{id} stopped"
228
260
  end
229
261
 
230
262
  def process_update(update)
@@ -328,8 +360,9 @@ module Telegem
328
360
  @error_handler.call(error, ctx)
329
361
  else
330
362
  @logger.error("❌ Unhandled error: #{error.class}: #{error.message}")
331
- @logger.error("Context: #{ctx.raw_update}") if ctx
332
- @logger.error(error.backtrace&.join("\n")) if error.backtrace
363
+ if ctx
364
+ @logger.error("Context - User: #{ctx.from&.id}, Chat: #{ctx.chat&.id}")
365
+ end
333
366
  end
334
367
  end
335
368
  end
data/lib/telegem.rb CHANGED
@@ -3,7 +3,7 @@ require 'logger'
3
3
  require 'json'
4
4
 
5
5
  module Telegem
6
- VERSION = "2.0.5".freeze
6
+ VERSION = "2.0.7".freeze
7
7
  end
8
8
 
9
9
  # Load core components
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: telegem
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.5
4
+ version: 2.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - sick_phantom
@@ -198,7 +198,7 @@ metadata:
198
198
  bug_tracker_uri: https://gitlab.com/ruby-telegem/telegem/-/issues
199
199
  documentation_uri: https://gitlab.com/ruby-telegem/telegem/-/blob/main/README.md
200
200
  rubygems_mfa_required: 'false'
201
- post_install_message: "Thanks for installing Telegem 2.0.5!\n\nQuick start:\n bot
201
+ post_install_message: "Thanks for installing Telegem 2.0.7!\n\nQuick start:\n bot
202
202
  = Telegem.new(\"YOUR_TOKEN\")\n bot.on(:message) { |ctx| ctx.reply(\"Hello!\")
203
203
  }\n bot.start_polling\n\nDocumentation: https://gitlab.com/ruby-telegem/telegem\nHappy
204
204
  bot building! \U0001F916\n"