telegem 2.0.6 → 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.
- checksums.yaml +4 -4
- data/lib/api/client.rb +27 -30
- data/lib/core/bot.rb +25 -59
- 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: 272c8898e9301da49ef787a232fe0eca2b1374ed611a04ae43d6ffff0a51053b
|
|
4
|
+
data.tar.gz: 44c00d019fbcd8da206f7231d9a24cec1c10f00b8f075c8c233a9706ceb6675a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
37
|
request = call(method, params)
|
|
32
|
-
request.wait
|
|
38
|
+
response = request.wait
|
|
33
39
|
|
|
34
|
-
if
|
|
35
|
-
|
|
40
|
+
if response.status != 200
|
|
41
|
+
@logger.error("HTTP Error #{response.status}") if @logger
|
|
36
42
|
return nil
|
|
37
43
|
end
|
|
38
44
|
|
|
39
|
-
|
|
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
|
@@ -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
|
|
|
@@ -211,8 +178,26 @@ module Telegem
|
|
|
211
178
|
params[:offset] = @offset if @offset
|
|
212
179
|
params[:allowed_updates] = @polling_options[:allowed_updates] if @polling_options[:allowed_updates]
|
|
213
180
|
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
199
|
+
|
|
200
|
+
nil
|
|
216
201
|
end
|
|
217
202
|
|
|
218
203
|
def handle_updates_response(api_response)
|
|
@@ -222,18 +207,14 @@ module Telegem
|
|
|
222
207
|
@logger.debug "Processing #{updates.length} update(s)"
|
|
223
208
|
|
|
224
209
|
updates.each do |update_data|
|
|
225
|
-
# Queue for processing by worker threads
|
|
226
210
|
@update_queue << [update_data, nil]
|
|
227
211
|
end
|
|
228
212
|
|
|
229
|
-
# Update offset for next request
|
|
230
213
|
@offset = updates.last['update_id'] + 1
|
|
231
214
|
@logger.debug "Updated offset to #{@offset}"
|
|
232
215
|
end
|
|
233
216
|
end
|
|
234
217
|
|
|
235
|
-
# ----- WORKER THREAD MANAGEMENT -----
|
|
236
|
-
|
|
237
218
|
def start_workers(count)
|
|
238
219
|
count.times do |i|
|
|
239
220
|
@worker_threads << Thread.new do
|
|
@@ -247,10 +228,8 @@ module Telegem
|
|
|
247
228
|
def stop_workers
|
|
248
229
|
@logger.debug "Stopping worker threads"
|
|
249
230
|
|
|
250
|
-
# Send shutdown signals to all workers
|
|
251
231
|
@worker_threads.size.times { @update_queue << :shutdown }
|
|
252
232
|
|
|
253
|
-
# Wait for threads to finish
|
|
254
233
|
@worker_threads.each do |thread|
|
|
255
234
|
thread.join(2) if thread.alive?
|
|
256
235
|
end
|
|
@@ -263,16 +242,13 @@ module Telegem
|
|
|
263
242
|
|
|
264
243
|
while @running
|
|
265
244
|
begin
|
|
266
|
-
# Wait for a task from the queue
|
|
267
245
|
task = @update_queue.pop
|
|
268
246
|
|
|
269
|
-
# Check for shutdown signal
|
|
270
247
|
break if task == :shutdown
|
|
271
248
|
|
|
272
249
|
update_data, callback = task
|
|
273
250
|
process_update(Types::Update.new(update_data))
|
|
274
251
|
|
|
275
|
-
# Execute callback if provided
|
|
276
252
|
callback&.call if callback.respond_to?(:call)
|
|
277
253
|
|
|
278
254
|
rescue => e
|
|
@@ -283,13 +259,10 @@ module Telegem
|
|
|
283
259
|
@logger.debug "Worker #{id} stopped"
|
|
284
260
|
end
|
|
285
261
|
|
|
286
|
-
# ----- UPDATE PROCESSING PIPELINE -----
|
|
287
|
-
|
|
288
262
|
def process_update(update)
|
|
289
263
|
ctx = Context.new(update, self)
|
|
290
264
|
|
|
291
265
|
begin
|
|
292
|
-
# Run through middleware chain, then dispatch to handlers
|
|
293
266
|
run_middleware_chain(ctx) do |context|
|
|
294
267
|
dispatch_to_handlers(context)
|
|
295
268
|
end
|
|
@@ -306,7 +279,6 @@ module Telegem
|
|
|
306
279
|
def build_middleware_chain
|
|
307
280
|
chain = Composer.new
|
|
308
281
|
|
|
309
|
-
# Add user-defined middleware
|
|
310
282
|
@middleware.each do |middleware_class, args, block|
|
|
311
283
|
if middleware_class.respond_to?(:new)
|
|
312
284
|
middleware = middleware_class.new(*args, &block)
|
|
@@ -316,7 +288,6 @@ module Telegem
|
|
|
316
288
|
end
|
|
317
289
|
end
|
|
318
290
|
|
|
319
|
-
# Add session middleware if not already present
|
|
320
291
|
unless @middleware.any? { |m, _, _| m.is_a?(Session::Middleware) }
|
|
321
292
|
chain.use(Session::Middleware.new(@session_store))
|
|
322
293
|
end
|
|
@@ -328,17 +299,14 @@ module Telegem
|
|
|
328
299
|
update_type = detect_update_type(ctx.update)
|
|
329
300
|
handlers = @handlers[update_type] || []
|
|
330
301
|
|
|
331
|
-
# Find and execute the first matching handler
|
|
332
302
|
handlers.each do |handler|
|
|
333
303
|
if matches_filters?(ctx, handler[:filters])
|
|
334
304
|
handler[:handler].call(ctx)
|
|
335
|
-
break
|
|
305
|
+
break
|
|
336
306
|
end
|
|
337
307
|
end
|
|
338
308
|
end
|
|
339
309
|
|
|
340
|
-
# ----- FILTER MATCHING LOGIC -----
|
|
341
|
-
|
|
342
310
|
def detect_update_type(update)
|
|
343
311
|
return :message if update.message
|
|
344
312
|
return :callback_query if update.callback_query
|
|
@@ -387,8 +355,6 @@ module Telegem
|
|
|
387
355
|
ctx.message.command_name == command_name.to_s
|
|
388
356
|
end
|
|
389
357
|
|
|
390
|
-
# ----- ERROR HANDLING -----
|
|
391
|
-
|
|
392
358
|
def handle_error(error, ctx = nil)
|
|
393
359
|
if @error_handler
|
|
394
360
|
@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.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.
|
|
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"
|