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.
- checksums.yaml +4 -4
- data/lib/api/client.rb +27 -30
- data/lib/core/bot.rb +126 -93
- 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
|
@@ -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,
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
@
|
|
147
|
+
private
|
|
148
|
+
|
|
149
|
+
def poll_loop
|
|
150
|
+
@logger.debug "Entering polling loop"
|
|
125
151
|
|
|
126
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
200
|
+
nil
|
|
133
201
|
end
|
|
134
202
|
|
|
135
|
-
def
|
|
136
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
332
|
-
|
|
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
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"
|