telegem 0.2.0 → 0.2.5
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 +158 -151
- data/lib/core/bot.rb +7 -12
- data/lib/telegem.rb +1 -1
- data/telegem.gemspec +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dc3dc81c673fa1128b108e02c3401f4bac483d7173759b4c901c3db625fa8dd8
|
|
4
|
+
data.tar.gz: 4cb6de9fa5d9b8c5babbbd83ff86b5b7f6c0161628e74dbb7416ee5eeaa10e70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 32211bca88485aa5d19b41c8bc400b1af8b790de8ec4cf9761537a2f59de299e4ee6c5dca2de3e5561f58fe0280238c1452cff10429aa876d2fad2426556819d
|
|
7
|
+
data.tar.gz: fef73c4f8d976614ec815cee3c230409bf471564f38a46acb5fc7093c80d2154186a621f68068098cc1072cdb144184c92a683be83e42107f7ea409ea3175b5e
|
data/lib/api/client.rb
CHANGED
|
@@ -1,156 +1,163 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
end
|
|
31
|
-
|
|
32
|
-
def get_updates(offset: nil, timeout: 30, limit: 100)
|
|
33
|
-
params = { timeout: timeout, limit: limit }
|
|
34
|
-
params[:offset] = offset if offset
|
|
35
|
-
call('getUpdates', params)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
def close
|
|
39
|
-
@client&.close
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
private
|
|
43
|
-
|
|
44
|
-
def make_request(method, params)
|
|
45
|
-
with_client do |client|
|
|
46
|
-
headers = { 'content-type' => 'application/json' }
|
|
47
|
-
body = params.to_json
|
|
48
|
-
|
|
49
|
-
response = client.post("/bot#{@token}/#{method}", headers, body)
|
|
50
|
-
handle_response(response)
|
|
51
|
-
end
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def make_multipart_request(method, params)
|
|
55
|
-
with_client do |client|
|
|
56
|
-
form = build_multipart(params)
|
|
57
|
-
headers = form.headers
|
|
58
|
-
|
|
59
|
-
response = client.post("/bot#{@token}/#{method}", headers, form.body)
|
|
60
|
-
handle_response(response)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def with_client(&block)
|
|
65
|
-
@client ||= Async::HTTP::Client.new(@endpoint)
|
|
66
|
-
yield @client
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def clean_params(params)
|
|
70
|
-
params.reject { |_, v| v.nil? }
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def build_multipart(params)
|
|
74
|
-
# Build multipart form data for file uploads
|
|
75
|
-
boundary = SecureRandom.hex(16)
|
|
76
|
-
parts = []
|
|
77
|
-
|
|
78
|
-
params.each do |key, value|
|
|
79
|
-
if file?(value)
|
|
80
|
-
parts << part_from_file(key, value, boundary)
|
|
81
|
-
else
|
|
82
|
-
parts << part_from_field(key, value, boundary)
|
|
83
|
-
end
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
parts << "--#{boundary}--\r\n"
|
|
87
|
-
|
|
88
|
-
body = parts.join
|
|
89
|
-
headers = {
|
|
90
|
-
'content-type' => "multipart/form-data; boundary=#{boundary}",
|
|
91
|
-
'content-length' => body.bytesize.to_s
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
OpenStruct.new(headers: headers, body: body)
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def file?(value)
|
|
98
|
-
value.is_a?(File) ||
|
|
99
|
-
value.is_a?(StringIO) ||
|
|
100
|
-
(value.is_a?(String) && File.exist?(value))
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def part_from_file(key, file, boundary)
|
|
104
|
-
filename = File.basename(file.path) if file.respond_to?(:path)
|
|
105
|
-
filename ||= "file"
|
|
106
|
-
|
|
107
|
-
mime_type = MIME::Types.type_for(filename).first || 'application/octet-stream'
|
|
108
|
-
|
|
109
|
-
content = if file.is_a?(String)
|
|
110
|
-
File.read(file)
|
|
111
|
-
else
|
|
112
|
-
file.read
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
<<~PART
|
|
116
|
-
--#{boundary}\r
|
|
117
|
-
Content-Disposition: form-data; name="#{key}"; filename="#{filename}"\r
|
|
118
|
-
Content-Type: #{mime_type}\r
|
|
119
|
-
\r
|
|
120
|
-
#{content}\r
|
|
121
|
-
PART
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def part_from_field(key, value, boundary)
|
|
125
|
-
<<~PART
|
|
126
|
-
--#{boundary}\r
|
|
127
|
-
Content-Disposition: form-data; name="#{key}"\r
|
|
128
|
-
\r
|
|
129
|
-
#{value}\r
|
|
130
|
-
PART
|
|
131
|
-
end
|
|
132
|
-
|
|
133
|
-
def handle_response(response)
|
|
134
|
-
body = response.read
|
|
135
|
-
json = JSON.parse(body)
|
|
136
|
-
|
|
137
|
-
if json['ok']
|
|
138
|
-
json['result']
|
|
139
|
-
else
|
|
140
|
-
raise APIError.new(json['description'], json['error_code'])
|
|
141
|
-
end
|
|
142
|
-
rescue JSON::ParserError
|
|
143
|
-
raise APIError, "Invalid JSON response: #{body[0..100]}"
|
|
144
|
-
end
|
|
1
|
+
require 'async'
|
|
2
|
+
require 'async/http'
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'logger'
|
|
5
|
+
require 'mime/types'
|
|
6
|
+
require 'securerandom'
|
|
7
|
+
require 'ostruct'
|
|
8
|
+
|
|
9
|
+
module Telegem
|
|
10
|
+
module API
|
|
11
|
+
class Client
|
|
12
|
+
BASE_URL = 'https://api.telegram.org'
|
|
13
|
+
|
|
14
|
+
attr_reader :token, :logger
|
|
15
|
+
|
|
16
|
+
def initialize(token, endpoint: nil, logger: nil)
|
|
17
|
+
@token = token
|
|
18
|
+
@logger = logger || Logger.new($stdout)
|
|
19
|
+
@endpoint = endpoint || Async::HTTP::Endpoint.parse("#{BASE_URL}/bot#{token}")
|
|
20
|
+
@client = nil
|
|
21
|
+
@semaphore = Async::Semaphore.new(30)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def call(method, params = {})
|
|
25
|
+
Async do |task|
|
|
26
|
+
@semaphore.async do
|
|
27
|
+
make_request(method, clean_params(params))
|
|
145
28
|
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def upload(method, params)
|
|
33
|
+
Async do |task|
|
|
34
|
+
@semaphore.async do
|
|
35
|
+
make_multipart_request(method, params)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def get_updates(offset: nil, timeout: 30, limit: 100)
|
|
41
|
+
params = { timeout: timeout, limit: limit }
|
|
42
|
+
params[:offset] = offset if offset
|
|
43
|
+
call('getUpdates', params)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def close
|
|
47
|
+
@client&.close
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
146
51
|
|
|
147
|
-
|
|
148
|
-
|
|
52
|
+
def make_request(method, params)
|
|
53
|
+
with_client do |client|
|
|
54
|
+
headers = { 'content-type' => 'application/json' }
|
|
55
|
+
body = params.to_json
|
|
149
56
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
57
|
+
response = client.post("/bot#{@token}/#{method}", headers, body)
|
|
58
|
+
handle_response(response)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def make_multipart_request(method, params)
|
|
63
|
+
with_client do |client|
|
|
64
|
+
form = build_multipart(params)
|
|
65
|
+
headers = form.headers
|
|
66
|
+
|
|
67
|
+
response = client.post("/bot#{@token}/#{method}", headers, form.body)
|
|
68
|
+
handle_response(response)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def with_client(&block)
|
|
73
|
+
@client ||= Async::HTTP::Client.new(@endpoint)
|
|
74
|
+
yield @client
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def clean_params(params)
|
|
78
|
+
params.reject { |_, v| v.nil? }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def build_multipart(params)
|
|
82
|
+
boundary = SecureRandom.hex(16)
|
|
83
|
+
parts = []
|
|
84
|
+
|
|
85
|
+
params.each do |key, value|
|
|
86
|
+
if file?(value)
|
|
87
|
+
parts << part_from_file(key, value, boundary)
|
|
88
|
+
else
|
|
89
|
+
parts << part_from_field(key, value, boundary)
|
|
154
90
|
end
|
|
155
91
|
end
|
|
156
|
-
|
|
92
|
+
|
|
93
|
+
parts << "--#{boundary}--\r\n"
|
|
94
|
+
|
|
95
|
+
body = parts.join
|
|
96
|
+
headers = {
|
|
97
|
+
'content-type' => "multipart/form-data; boundary=#{boundary}",
|
|
98
|
+
'content-length' => body.bytesize.to_s
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
OpenStruct.new(headers: headers, body: body)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def file?(value)
|
|
105
|
+
value.is_a?(File) ||
|
|
106
|
+
value.is_a?(StringIO) ||
|
|
107
|
+
(value.is_a?(String) && File.exist?(value))
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def part_from_file(key, file, boundary)
|
|
111
|
+
filename = File.basename(file.path) if file.respond_to?(:path)
|
|
112
|
+
filename ||= "file"
|
|
113
|
+
|
|
114
|
+
mime_type = MIME::Types.type_for(filename).first || 'application/octet-stream'
|
|
115
|
+
|
|
116
|
+
content = if file.is_a?(String)
|
|
117
|
+
File.read(file)
|
|
118
|
+
else
|
|
119
|
+
file.read
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
<<~PART
|
|
123
|
+
--#{boundary}\r
|
|
124
|
+
Content-Disposition: form-data; name="#{key}"; filename="#{filename}"\r
|
|
125
|
+
Content-Type: #{mime_type}\r
|
|
126
|
+
\r
|
|
127
|
+
#{content}\r
|
|
128
|
+
PART
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def part_from_field(key, value, boundary)
|
|
132
|
+
<<~PART
|
|
133
|
+
--#{boundary}\r
|
|
134
|
+
Content-Disposition: form-data; name="#{key}"\r
|
|
135
|
+
\r
|
|
136
|
+
#{value}\r
|
|
137
|
+
PART
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def handle_response(response)
|
|
141
|
+
body = response.read
|
|
142
|
+
json = JSON.parse(body)
|
|
143
|
+
|
|
144
|
+
if json['ok']
|
|
145
|
+
json['result']
|
|
146
|
+
else
|
|
147
|
+
raise APIError.new(json['description'], json['error_code'])
|
|
148
|
+
end
|
|
149
|
+
rescue JSON::ParserError
|
|
150
|
+
raise APIError, "Invalid JSON response: #{body[0..100]}"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
class APIError < StandardError
|
|
155
|
+
attr_reader :code
|
|
156
|
+
|
|
157
|
+
def initialize(message, code = nil)
|
|
158
|
+
super(message)
|
|
159
|
+
@code = code
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
data/lib/core/bot.rb
CHANGED
|
@@ -71,7 +71,8 @@ module Telegem
|
|
|
71
71
|
begin
|
|
72
72
|
loop do
|
|
73
73
|
updates_task = fetch_updates(offset, **options)
|
|
74
|
-
|
|
74
|
+
updates_data = updates_task.wait
|
|
75
|
+
updates = updates_data.map { |data| Types::Update.new(data) }
|
|
75
76
|
|
|
76
77
|
updates.each do |update|
|
|
77
78
|
parent.async do |child|
|
|
@@ -152,17 +153,11 @@ module Telegem
|
|
|
152
153
|
private
|
|
153
154
|
|
|
154
155
|
def fetch_updates(offset, timeout: 30, limit: 100, allowed_updates: nil)
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
updates = await @api.get_updates(**params)
|
|
161
|
-
updates.map { |data| Types::Update.new(data) }
|
|
162
|
-
rescue API::APIError => e
|
|
163
|
-
@logger.error "Failed to fetch updates: #{e.message}"
|
|
164
|
-
[]
|
|
165
|
-
end
|
|
156
|
+
params = { timeout: timeout, limit: limit }
|
|
157
|
+
params[:offset] = offset if offset
|
|
158
|
+
params[:allowed_updates] = allowed_updates if allowed_updates
|
|
159
|
+
|
|
160
|
+
@api.get_updates(**params)
|
|
166
161
|
end
|
|
167
162
|
|
|
168
163
|
def process_update(update)
|
data/lib/telegem.rb
CHANGED
data/telegem.gemspec
CHANGED
|
@@ -4,7 +4,7 @@ Gem::Specification.new do |spec|
|
|
|
4
4
|
|
|
5
5
|
# Read version from lib/telegem.rb
|
|
6
6
|
version_file = File.read('lib/telegem.rb').match(/VERSION\s*=\s*['"]([^'"]+)['"]/)
|
|
7
|
-
spec.version = version_file ? version_file[1] : "0.
|
|
7
|
+
spec.version = version_file ? version_file[1] : "0.2.5"
|
|
8
8
|
|
|
9
9
|
spec.authors = ["Phantom"]
|
|
10
10
|
spec.email = ["ynghosted@icloud.com"]
|