telegem 2.0.6 → 2.0.8
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/lib/api/client.rb +27 -32
- data/lib/core/bot.rb +24 -68
- data/lib/telegem.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b5e8e2d8f412d455281a8ed45a4384161431715964447abf7d323c8e5a651eaf
|
|
4
|
+
data.tar.gz: e92cf5b7324c3e745a66dfcf935b9ed18bae231fa0051316070cf726dc259d4b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 95f13660c4b21333d0a4d68a6fb1ba25767a1f3d5aa7f06f07d0f76b82796730f8803c3036c7297d9b8aaea99cd73e22cfe01999d5a52c6456b6b0b9a42921b3
|
|
7
|
+
data.tar.gz: f5cc5f64bb886ea1f38b8c51993db49c8850a0017a0722a70f670f783fbb997fcbdebc816fb28fb92e74bf543473addda1da8300430565df0fce260c84ea8cb4
|
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,52 @@ module Telegem
|
|
|
12
11
|
def initialize(token, **options)
|
|
13
12
|
@token = token
|
|
14
13
|
@logger = options[:logger] || Logger.new($stdout)
|
|
15
|
-
|
|
14
|
+
timeout = options[:timeout] || 30
|
|
16
15
|
|
|
17
16
|
@http = HTTPX.with(
|
|
18
|
-
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
|
-
|
|
32
|
-
|
|
37
|
+
url = "#{BASE_URL}/bot#{@token}/#{method}"
|
|
38
|
+
|
|
39
|
+
@logger.debug("API Call (sync): #{method}") if @logger
|
|
33
40
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
41
|
+
begin
|
|
42
|
+
response = @http.post(url, json: params.compact)
|
|
43
|
+
|
|
44
|
+
if response.respond_to?(:status) && response.status == 200
|
|
45
|
+
json = response.json
|
|
46
|
+
return json['result'] if json && json['ok']
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
@logger.error("API Error: HTTP #{response.status}") if response.respond_to?(:status) && @logger
|
|
50
|
+
rescue => e
|
|
51
|
+
@logger.error("Exception: #{e.message}") if @logger
|
|
37
52
|
end
|
|
38
53
|
|
|
39
|
-
|
|
54
|
+
nil
|
|
40
55
|
end
|
|
41
56
|
|
|
42
57
|
def upload(method, params)
|
|
43
58
|
url = "#{BASE_URL}/bot#{@token}/#{method}"
|
|
59
|
+
|
|
44
60
|
form = params.map do |key, value|
|
|
45
61
|
if file_object?(value)
|
|
46
62
|
[key.to_s, HTTPX::FormData::File.new(value)]
|
|
@@ -48,6 +64,7 @@ module Telegem
|
|
|
48
64
|
[key.to_s, value.to_s]
|
|
49
65
|
end
|
|
50
66
|
end
|
|
67
|
+
|
|
51
68
|
@http.post(url, form: form)
|
|
52
69
|
end
|
|
53
70
|
|
|
@@ -64,28 +81,6 @@ module Telegem
|
|
|
64
81
|
|
|
65
82
|
private
|
|
66
83
|
|
|
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
84
|
def file_object?(obj)
|
|
90
85
|
obj.is_a?(File) || obj.is_a?(StringIO) || obj.is_a?(Tempfile) ||
|
|
91
86
|
(obj.is_a?(String) && File.exist?(obj))
|
data/lib/core/bot.rb
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
# lib/core/bot.rb - Telegem v2.0.0 (Stable)
|
|
2
1
|
require 'concurrent'
|
|
3
2
|
require 'logger'
|
|
4
3
|
|
|
5
4
|
module Telegem
|
|
6
5
|
module Core
|
|
7
6
|
class Bot
|
|
8
|
-
# Public accessors for bot state and components
|
|
9
7
|
attr_reader :token, :api, :handlers, :middleware, :logger, :scenes,
|
|
10
8
|
:running, :polling_thread, :session_store
|
|
11
9
|
|
|
@@ -13,7 +11,6 @@ module Telegem
|
|
|
13
11
|
@token = token
|
|
14
12
|
@api = API::Client.new(token, **options.slice(:logger, :timeout))
|
|
15
13
|
|
|
16
|
-
# Initialize handler registries for different update types
|
|
17
14
|
@handlers = {
|
|
18
15
|
message: [],
|
|
19
16
|
callback_query: [],
|
|
@@ -30,24 +27,17 @@ module Telegem
|
|
|
30
27
|
@error_handler = nil
|
|
31
28
|
@session_store = options[:session_store] || Session::MemoryStore.new
|
|
32
29
|
|
|
33
|
-
# Thread pool and worker management
|
|
34
30
|
@thread_pool = Concurrent::FixedThreadPool.new(options[:max_threads] || 10)
|
|
35
31
|
@update_queue = Queue.new
|
|
36
32
|
@worker_threads = []
|
|
37
33
|
|
|
38
|
-
# Polling state
|
|
39
34
|
@polling_thread = nil
|
|
40
35
|
@running = false
|
|
41
|
-
@offset = nil
|
|
36
|
+
@offset = nil
|
|
42
37
|
|
|
43
|
-
# Start worker threads for processing updates
|
|
44
38
|
start_workers(options[:worker_count] || 5)
|
|
45
39
|
end
|
|
46
40
|
|
|
47
|
-
# ========================
|
|
48
|
-
# POLLING LIFECYCLE METHODS
|
|
49
|
-
# ========================
|
|
50
|
-
|
|
51
41
|
def start_polling(**options)
|
|
52
42
|
return if @running
|
|
53
43
|
|
|
@@ -58,7 +48,7 @@ module Telegem
|
|
|
58
48
|
allowed_updates: nil
|
|
59
49
|
}.merge(options)
|
|
60
50
|
|
|
61
|
-
@offset = nil
|
|
51
|
+
@offset = nil
|
|
62
52
|
|
|
63
53
|
@logger.info "🤖 Starting Telegem bot (polling mode)..."
|
|
64
54
|
|
|
@@ -76,15 +66,12 @@ module Telegem
|
|
|
76
66
|
@logger.info "🛑 Shutting down bot..."
|
|
77
67
|
@running = false
|
|
78
68
|
|
|
79
|
-
# Gracefully stop polling thread
|
|
80
69
|
if @polling_thread&.alive?
|
|
81
70
|
@polling_thread.join(3)
|
|
82
71
|
end
|
|
83
72
|
|
|
84
|
-
# Stop worker threads
|
|
85
73
|
stop_workers
|
|
86
74
|
|
|
87
|
-
# Close API connections
|
|
88
75
|
@api.close if @api.respond_to?(:close)
|
|
89
76
|
|
|
90
77
|
@logger.info "✅ Bot shutdown complete"
|
|
@@ -94,10 +81,6 @@ module Telegem
|
|
|
94
81
|
@running
|
|
95
82
|
end
|
|
96
83
|
|
|
97
|
-
# ========================
|
|
98
|
-
# COMMAND & EVENT HANDLERS
|
|
99
|
-
# ========================
|
|
100
|
-
|
|
101
84
|
def command(name, **options, &block)
|
|
102
85
|
pattern = /^\/#{Regexp.escape(name)}(?:@\w+)?(?:\s+(.+))?$/i
|
|
103
86
|
|
|
@@ -132,10 +115,6 @@ module Telegem
|
|
|
132
115
|
@scenes[id] = Scene.new(id, &block)
|
|
133
116
|
end
|
|
134
117
|
|
|
135
|
-
# ========================
|
|
136
|
-
# WEBHOOK METHODS
|
|
137
|
-
# ========================
|
|
138
|
-
|
|
139
118
|
def webhook(app = nil, port: nil, host: '0.0.0.0', logger: nil, &block)
|
|
140
119
|
require_relative '../webhook/server'
|
|
141
120
|
|
|
@@ -160,43 +139,31 @@ module Telegem
|
|
|
160
139
|
@api.call!('getWebhookInfo', {})
|
|
161
140
|
end
|
|
162
141
|
|
|
163
|
-
# ========================
|
|
164
|
-
# UPDATE PROCESSING
|
|
165
|
-
# ========================
|
|
166
|
-
|
|
167
142
|
def process(update_data)
|
|
168
143
|
update = Types::Update.new(update_data)
|
|
169
144
|
process_update(update)
|
|
170
145
|
end
|
|
171
146
|
|
|
172
|
-
# ========================
|
|
173
|
-
# PRIVATE METHODS
|
|
174
|
-
# ========================
|
|
175
|
-
|
|
176
147
|
private
|
|
177
148
|
|
|
178
|
-
# ----- POLLING LOGIC -----
|
|
179
|
-
|
|
180
149
|
def poll_loop
|
|
181
150
|
@logger.debug "Entering polling loop"
|
|
182
151
|
|
|
183
152
|
while @running
|
|
184
153
|
begin
|
|
185
|
-
# Use synchronous call to avoid HTTPX version conflicts
|
|
186
154
|
result = fetch_updates
|
|
187
155
|
|
|
188
156
|
if result && result.is_a?(Hash) && result['ok']
|
|
189
157
|
handle_updates_response(result)
|
|
190
158
|
elsif result
|
|
191
|
-
@logger.warn "Unexpected API response format"
|
|
159
|
+
@logger.warn "Unexpected API response format: #{result.class}"
|
|
192
160
|
end
|
|
193
161
|
|
|
194
|
-
# Small delay between polls unless we just processed updates
|
|
195
162
|
sleep 0.1 unless @offset.nil?
|
|
196
163
|
|
|
197
164
|
rescue => e
|
|
198
165
|
handle_error(e)
|
|
199
|
-
sleep 5
|
|
166
|
+
sleep 5
|
|
200
167
|
end
|
|
201
168
|
end
|
|
202
169
|
|
|
@@ -204,17 +171,25 @@ module Telegem
|
|
|
204
171
|
end
|
|
205
172
|
|
|
206
173
|
def fetch_updates
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
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]
|
|
180
|
+
|
|
181
|
+
@logger.debug("Fetching updates with offset: #{@offset}")
|
|
182
|
+
|
|
183
|
+
# Simple direct call - no .wait
|
|
184
|
+
updates = @api.call!('getUpdates', params)
|
|
185
|
+
|
|
186
|
+
if updates && updates.is_a?(Array)
|
|
187
|
+
@logger.debug("Got #{updates.length} updates")
|
|
188
|
+
return { 'ok' => true, 'result' => updates }
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
nil
|
|
192
|
+
end
|
|
218
193
|
def handle_updates_response(api_response)
|
|
219
194
|
updates = api_response['result'] || []
|
|
220
195
|
|
|
@@ -222,18 +197,14 @@ module Telegem
|
|
|
222
197
|
@logger.debug "Processing #{updates.length} update(s)"
|
|
223
198
|
|
|
224
199
|
updates.each do |update_data|
|
|
225
|
-
# Queue for processing by worker threads
|
|
226
200
|
@update_queue << [update_data, nil]
|
|
227
201
|
end
|
|
228
202
|
|
|
229
|
-
# Update offset for next request
|
|
230
203
|
@offset = updates.last['update_id'] + 1
|
|
231
204
|
@logger.debug "Updated offset to #{@offset}"
|
|
232
205
|
end
|
|
233
206
|
end
|
|
234
207
|
|
|
235
|
-
# ----- WORKER THREAD MANAGEMENT -----
|
|
236
|
-
|
|
237
208
|
def start_workers(count)
|
|
238
209
|
count.times do |i|
|
|
239
210
|
@worker_threads << Thread.new do
|
|
@@ -247,10 +218,8 @@ module Telegem
|
|
|
247
218
|
def stop_workers
|
|
248
219
|
@logger.debug "Stopping worker threads"
|
|
249
220
|
|
|
250
|
-
# Send shutdown signals to all workers
|
|
251
221
|
@worker_threads.size.times { @update_queue << :shutdown }
|
|
252
222
|
|
|
253
|
-
# Wait for threads to finish
|
|
254
223
|
@worker_threads.each do |thread|
|
|
255
224
|
thread.join(2) if thread.alive?
|
|
256
225
|
end
|
|
@@ -263,16 +232,13 @@ module Telegem
|
|
|
263
232
|
|
|
264
233
|
while @running
|
|
265
234
|
begin
|
|
266
|
-
# Wait for a task from the queue
|
|
267
235
|
task = @update_queue.pop
|
|
268
236
|
|
|
269
|
-
# Check for shutdown signal
|
|
270
237
|
break if task == :shutdown
|
|
271
238
|
|
|
272
239
|
update_data, callback = task
|
|
273
240
|
process_update(Types::Update.new(update_data))
|
|
274
241
|
|
|
275
|
-
# Execute callback if provided
|
|
276
242
|
callback&.call if callback.respond_to?(:call)
|
|
277
243
|
|
|
278
244
|
rescue => e
|
|
@@ -283,13 +249,10 @@ module Telegem
|
|
|
283
249
|
@logger.debug "Worker #{id} stopped"
|
|
284
250
|
end
|
|
285
251
|
|
|
286
|
-
# ----- UPDATE PROCESSING PIPELINE -----
|
|
287
|
-
|
|
288
252
|
def process_update(update)
|
|
289
253
|
ctx = Context.new(update, self)
|
|
290
254
|
|
|
291
255
|
begin
|
|
292
|
-
# Run through middleware chain, then dispatch to handlers
|
|
293
256
|
run_middleware_chain(ctx) do |context|
|
|
294
257
|
dispatch_to_handlers(context)
|
|
295
258
|
end
|
|
@@ -306,7 +269,6 @@ module Telegem
|
|
|
306
269
|
def build_middleware_chain
|
|
307
270
|
chain = Composer.new
|
|
308
271
|
|
|
309
|
-
# Add user-defined middleware
|
|
310
272
|
@middleware.each do |middleware_class, args, block|
|
|
311
273
|
if middleware_class.respond_to?(:new)
|
|
312
274
|
middleware = middleware_class.new(*args, &block)
|
|
@@ -316,7 +278,6 @@ module Telegem
|
|
|
316
278
|
end
|
|
317
279
|
end
|
|
318
280
|
|
|
319
|
-
# Add session middleware if not already present
|
|
320
281
|
unless @middleware.any? { |m, _, _| m.is_a?(Session::Middleware) }
|
|
321
282
|
chain.use(Session::Middleware.new(@session_store))
|
|
322
283
|
end
|
|
@@ -328,17 +289,14 @@ module Telegem
|
|
|
328
289
|
update_type = detect_update_type(ctx.update)
|
|
329
290
|
handlers = @handlers[update_type] || []
|
|
330
291
|
|
|
331
|
-
# Find and execute the first matching handler
|
|
332
292
|
handlers.each do |handler|
|
|
333
293
|
if matches_filters?(ctx, handler[:filters])
|
|
334
294
|
handler[:handler].call(ctx)
|
|
335
|
-
break
|
|
295
|
+
break
|
|
336
296
|
end
|
|
337
297
|
end
|
|
338
298
|
end
|
|
339
299
|
|
|
340
|
-
# ----- FILTER MATCHING LOGIC -----
|
|
341
|
-
|
|
342
300
|
def detect_update_type(update)
|
|
343
301
|
return :message if update.message
|
|
344
302
|
return :callback_query if update.callback_query
|
|
@@ -387,8 +345,6 @@ module Telegem
|
|
|
387
345
|
ctx.message.command_name == command_name.to_s
|
|
388
346
|
end
|
|
389
347
|
|
|
390
|
-
# ----- ERROR HANDLING -----
|
|
391
|
-
|
|
392
348
|
def handle_error(error, ctx = nil)
|
|
393
349
|
if @error_handler
|
|
394
350
|
@error_handler.call(error, ctx)
|
data/lib/telegem.rb
CHANGED
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.
|
|
4
|
+
version: 2.0.8
|
|
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.
|
|
201
|
+
post_install_message: "Thanks for installing Telegem 2.0.8!\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"
|