mtproto 0.0.5 → 0.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/.env.example +5 -0
- data/Rakefile +13 -0
- data/lib/mtproto/auth_key_generator.rb +36 -13
- data/lib/mtproto/client/rpc.rb +141 -0
- data/lib/mtproto/client.rb +60 -185
- data/lib/mtproto/crypto/aes_ige.rb +1 -1
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/crypto/rsa_key.rb +9 -15
- data/lib/mtproto/errors.rb +33 -0
- data/lib/mtproto/message_id.rb +13 -0
- data/lib/mtproto/rpc/get_config.rb +37 -0
- data/lib/mtproto/rpc/get_contacts.rb +22 -0
- data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
- data/lib/mtproto/rpc/get_updates_state.rb +22 -0
- data/lib/mtproto/rpc/get_users.rb +22 -0
- data/lib/mtproto/rpc/ping.rb +26 -0
- data/lib/mtproto/rpc/send_code.rb +44 -0
- data/lib/mtproto/rpc/send_message.rb +31 -0
- data/lib/mtproto/rpc/sign_in.rb +52 -0
- data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
- data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
- data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
- data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
- data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
- data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
- data/lib/mtproto/tl/code_settings.rb +25 -0
- data/lib/mtproto/tl/config.rb +4 -2
- data/lib/mtproto/tl/gzip_packed.rb +1 -1
- data/lib/mtproto/tl/message.rb +8 -216
- data/lib/mtproto/tl/method_builder.rb +29 -0
- data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
- data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
- data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
- data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
- data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
- data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
- data/lib/mtproto/tl/rpc/help/config.rb +35 -0
- data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
- data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
- data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
- data/lib/mtproto/tl/rpc/ping.rb +18 -0
- data/lib/mtproto/tl/rpc/pong.rb +46 -0
- data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
- data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
- data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
- data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
- data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
- data/lib/mtproto/tl/rpc/users/users.rb +99 -0
- data/lib/mtproto/tl/sent_code.rb +128 -0
- data/lib/mtproto/transport/tcp_connection.rb +1 -1
- data/lib/mtproto/updates_poller.rb +111 -0
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +13 -0
- metadata +57 -6
- data/ext/aes_ige/Makefile +0 -273
- data/ext/aes_ige/aes_ige.bundle +0 -0
- data/ext/factorization/Makefile +0 -273
- data/ext/factorization/factorization.bundle +0 -0
data/lib/mtproto/tl/message.rb
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'serializer'
|
|
4
|
-
|
|
5
3
|
module MTProto
|
|
6
4
|
module TL
|
|
7
5
|
class Message
|
|
8
|
-
CONSTRUCTOR_REQ_PQ_MULTI = 0xbe7e8ef1
|
|
9
|
-
CONSTRUCTOR_RES_PQ = 0x05162463
|
|
10
|
-
CONSTRUCTOR_REQ_DH_PARAMS = 0xd712e4be
|
|
11
|
-
CONSTRUCTOR_SERVER_DH_PARAMS_OK = 0xd0e8075c
|
|
12
|
-
CONSTRUCTOR_SET_CLIENT_DH_PARAMS = 0xf5045f1f
|
|
13
|
-
CONSTRUCTOR_DH_GEN_OK = 0x3bcbf734
|
|
14
|
-
CONSTRUCTOR_DH_GEN_RETRY = 0x46dc1fb9
|
|
15
|
-
CONSTRUCTOR_DH_GEN_FAIL = 0xa69dae02
|
|
16
|
-
CONSTRUCTOR_PING = 0x7abe77ec
|
|
17
|
-
CONSTRUCTOR_PONG = 0x347773c5
|
|
18
|
-
CONSTRUCTOR_BAD_MSG_NOTIFICATION = 0xa7eff811
|
|
19
|
-
CONSTRUCTOR_MSG_CONTAINER = 0x73f1f8dc
|
|
20
|
-
|
|
21
6
|
attr_reader :auth_key_id, :msg_id, :body
|
|
22
7
|
|
|
23
|
-
def initialize(auth_key_id
|
|
8
|
+
def initialize(auth_key_id:, msg_id:, body:)
|
|
24
9
|
@auth_key_id = auth_key_id
|
|
25
|
-
@msg_id = msg_id
|
|
10
|
+
@msg_id = msg_id
|
|
26
11
|
@body = body
|
|
27
12
|
end
|
|
28
13
|
|
|
@@ -34,46 +19,13 @@ module MTProto
|
|
|
34
19
|
auth_key_id_bytes + msg_id_bytes + body_length + @body
|
|
35
20
|
end
|
|
36
21
|
|
|
37
|
-
def self.req_pq_multi(nonce)
|
|
38
|
-
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
39
|
-
|
|
40
|
-
constructor = [CONSTRUCTOR_REQ_PQ_MULTI].pack('L<')
|
|
41
|
-
body = constructor + nonce
|
|
42
|
-
|
|
43
|
-
new(auth_key_id: 0, body: body)
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def self.req_DH_params(nonce:, server_nonce:, p:, q:, public_key_fingerprint:, encrypted_data:)
|
|
47
|
-
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
48
|
-
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
49
|
-
|
|
50
|
-
p_bytes = Serializer.integer_to_bytes(p)
|
|
51
|
-
q_bytes = Serializer.integer_to_bytes(q)
|
|
52
|
-
|
|
53
|
-
body = Serializer.serialize_int(CONSTRUCTOR_REQ_DH_PARAMS)
|
|
54
|
-
body += nonce
|
|
55
|
-
body += server_nonce
|
|
56
|
-
body += Serializer.serialize_bytes(p_bytes)
|
|
57
|
-
body += Serializer.serialize_bytes(q_bytes)
|
|
58
|
-
body += Serializer.serialize_long(public_key_fingerprint)
|
|
59
|
-
body += Serializer.serialize_bytes(encrypted_data)
|
|
60
|
-
|
|
61
|
-
new(auth_key_id: 0, body: body)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def self.set_client_DH_params(nonce:, server_nonce:, encrypted_data:)
|
|
65
|
-
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
66
|
-
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
67
|
-
|
|
68
|
-
body = Serializer.serialize_int(CONSTRUCTOR_SET_CLIENT_DH_PARAMS)
|
|
69
|
-
body += nonce
|
|
70
|
-
body += server_nonce
|
|
71
|
-
body += Serializer.serialize_bytes(encrypted_data)
|
|
72
|
-
|
|
73
|
-
new(auth_key_id: 0, body: body)
|
|
74
|
-
end
|
|
75
|
-
|
|
76
22
|
def self.deserialize(data)
|
|
23
|
+
if data.bytesize < 20
|
|
24
|
+
raise(ArgumentError,
|
|
25
|
+
"Invalid MTProto message: expected at least 20 bytes, got #{data.bytesize} bytes (hex: #{data.unpack1('H*')})",
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
77
29
|
auth_key_id = data[0, 8].unpack1('Q<')
|
|
78
30
|
msg_id = data[8, 8].unpack1('Q<')
|
|
79
31
|
body_length = data[16, 4].unpack1('L<')
|
|
@@ -81,166 +33,6 @@ module MTProto
|
|
|
81
33
|
|
|
82
34
|
new(auth_key_id: auth_key_id, msg_id: msg_id, body: body)
|
|
83
35
|
end
|
|
84
|
-
|
|
85
|
-
def parse_res_pq
|
|
86
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
87
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_RES_PQ
|
|
88
|
-
|
|
89
|
-
offset = 4
|
|
90
|
-
|
|
91
|
-
nonce = @body[offset, 16]
|
|
92
|
-
offset += 16
|
|
93
|
-
|
|
94
|
-
server_nonce = @body[offset, 16]
|
|
95
|
-
offset += 16
|
|
96
|
-
|
|
97
|
-
pq_length_byte = @body[offset].unpack1('C')
|
|
98
|
-
offset += 1
|
|
99
|
-
|
|
100
|
-
pq_length = if pq_length_byte == 254
|
|
101
|
-
@body[offset, 3].unpack1('L<') & 0xffffff
|
|
102
|
-
offset += 3
|
|
103
|
-
else
|
|
104
|
-
pq_length_byte
|
|
105
|
-
end
|
|
106
|
-
|
|
107
|
-
pq = @body[offset, pq_length]
|
|
108
|
-
offset += pq_length
|
|
109
|
-
offset += padding_length(pq_length + 1)
|
|
110
|
-
|
|
111
|
-
vector_constructor = @body[offset, 4].unpack1('L<')
|
|
112
|
-
offset += 4
|
|
113
|
-
raise 'Expected vector constructor' unless vector_constructor == 0x1cb5c415
|
|
114
|
-
|
|
115
|
-
fingerprints_count = @body[offset, 4].unpack1('L<')
|
|
116
|
-
offset += 4
|
|
117
|
-
|
|
118
|
-
fingerprints = []
|
|
119
|
-
fingerprints_count.times do
|
|
120
|
-
fingerprints << @body[offset, 8].unpack1('Q<')
|
|
121
|
-
offset += 8
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
{
|
|
125
|
-
nonce: nonce,
|
|
126
|
-
server_nonce: server_nonce,
|
|
127
|
-
pq: pq,
|
|
128
|
-
fingerprints: fingerprints
|
|
129
|
-
}
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
def parse_server_DH_params_ok
|
|
133
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
134
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_SERVER_DH_PARAMS_OK
|
|
135
|
-
|
|
136
|
-
offset = 4
|
|
137
|
-
|
|
138
|
-
nonce = @body[offset, 16]
|
|
139
|
-
offset += 16
|
|
140
|
-
|
|
141
|
-
server_nonce = @body[offset, 16]
|
|
142
|
-
offset += 16
|
|
143
|
-
|
|
144
|
-
length_byte = @body[offset].ord
|
|
145
|
-
offset += 1
|
|
146
|
-
|
|
147
|
-
if length_byte == 254
|
|
148
|
-
length_bytes = @body[offset, 3].bytes
|
|
149
|
-
encrypted_answer_length = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16)
|
|
150
|
-
offset += 3
|
|
151
|
-
else
|
|
152
|
-
encrypted_answer_length = length_byte
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
encrypted_answer = @body[offset, encrypted_answer_length]
|
|
156
|
-
|
|
157
|
-
{
|
|
158
|
-
nonce: nonce,
|
|
159
|
-
server_nonce: server_nonce,
|
|
160
|
-
encrypted_answer: encrypted_answer
|
|
161
|
-
}
|
|
162
|
-
end
|
|
163
|
-
|
|
164
|
-
def parse_dh_gen_response
|
|
165
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
166
|
-
|
|
167
|
-
offset = 4
|
|
168
|
-
nonce = @body[offset, 16]
|
|
169
|
-
offset += 16
|
|
170
|
-
|
|
171
|
-
server_nonce = @body[offset, 16]
|
|
172
|
-
offset += 16
|
|
173
|
-
|
|
174
|
-
new_nonce_hash = @body[offset, 16]
|
|
175
|
-
|
|
176
|
-
case constructor
|
|
177
|
-
when CONSTRUCTOR_DH_GEN_OK
|
|
178
|
-
{ status: :ok, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
179
|
-
when CONSTRUCTOR_DH_GEN_RETRY
|
|
180
|
-
{ status: :retry, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
181
|
-
when CONSTRUCTOR_DH_GEN_FAIL
|
|
182
|
-
{ status: :fail, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
|
|
183
|
-
else
|
|
184
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}"
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
def self.ping(ping_id)
|
|
189
|
-
body = Serializer.serialize_int(CONSTRUCTOR_PING)
|
|
190
|
-
body += [ping_id].pack('Q<')
|
|
191
|
-
|
|
192
|
-
body
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def parse_pong
|
|
196
|
-
constructor = @body[0, 4].unpack1('L<')
|
|
197
|
-
|
|
198
|
-
if constructor == CONSTRUCTOR_BAD_MSG_NOTIFICATION
|
|
199
|
-
bad_msg = TL::BadMsgNotification.deserialize(@body)
|
|
200
|
-
raise "Bad message notification: #{bad_msg.error_message} (code: #{bad_msg.error_code}, msg_id: #{bad_msg.bad_msg_id}, seqno: #{bad_msg.bad_msg_seqno})"
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
if constructor == CONSTRUCTOR_MSG_CONTAINER
|
|
204
|
-
container = TL::MsgContainer.deserialize(@body)
|
|
205
|
-
pong_message = container.messages.find do |msg|
|
|
206
|
-
msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_PONG
|
|
207
|
-
end
|
|
208
|
-
|
|
209
|
-
raise 'No pong message found in container' unless pong_message
|
|
210
|
-
|
|
211
|
-
offset = 4
|
|
212
|
-
msg_id = pong_message[:body][offset, 8].unpack1('Q<')
|
|
213
|
-
offset += 8
|
|
214
|
-
ping_id = pong_message[:body][offset, 8].unpack1('Q<')
|
|
215
|
-
|
|
216
|
-
return { msg_id: msg_id, ping_id: ping_id }
|
|
217
|
-
end
|
|
218
|
-
|
|
219
|
-
raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR_PONG
|
|
220
|
-
|
|
221
|
-
offset = 4
|
|
222
|
-
msg_id = @body[offset, 8].unpack1('Q<')
|
|
223
|
-
offset += 8
|
|
224
|
-
ping_id = @body[offset, 8].unpack1('Q<')
|
|
225
|
-
|
|
226
|
-
{ msg_id: msg_id, ping_id: ping_id }
|
|
227
|
-
end
|
|
228
|
-
|
|
229
|
-
private
|
|
230
|
-
|
|
231
|
-
def generate_msg_id
|
|
232
|
-
time = Time.now.to_f
|
|
233
|
-
msg_id = (time * (2**32)).to_i
|
|
234
|
-
(msg_id / 4) * 4
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def padding_length(length)
|
|
238
|
-
(4 - (length % 4)) % 4
|
|
239
|
-
end
|
|
240
|
-
|
|
241
|
-
def self.padding_length(length)
|
|
242
|
-
(4 - (length % 4)) % 4
|
|
243
|
-
end
|
|
244
36
|
end
|
|
245
37
|
end
|
|
246
38
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
module MethodBuilder
|
|
8
|
+
def invoke_with_layer(layer, query)
|
|
9
|
+
body = Serializer.serialize_int(0xda9b0d0d)
|
|
10
|
+
body += Serializer.serialize_int(layer)
|
|
11
|
+
body + query
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def init_connection(api_id:, device_model:, system_version:, app_version:, system_lang_code:, lang_pack:, lang_code:, query:)
|
|
15
|
+
body = Serializer.serialize_int(0xc1cd5ea9)
|
|
16
|
+
flags = 0
|
|
17
|
+
body += Serializer.serialize_int(flags)
|
|
18
|
+
body += Serializer.serialize_int(api_id)
|
|
19
|
+
body += Serializer.serialize_string(device_model)
|
|
20
|
+
body += Serializer.serialize_string(system_version)
|
|
21
|
+
body += Serializer.serialize_string(app_version)
|
|
22
|
+
body += Serializer.serialize_string(system_lang_code)
|
|
23
|
+
body += Serializer.serialize_string(lang_pack)
|
|
24
|
+
body += Serializer.serialize_string(lang_code)
|
|
25
|
+
body + query
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../gzip_packed'
|
|
4
|
+
require_relative '../../rpc_error'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module TL
|
|
8
|
+
module RPC
|
|
9
|
+
module Auth
|
|
10
|
+
class Authorization
|
|
11
|
+
CONSTRUCTOR = 0x2ea2c0d4
|
|
12
|
+
CONSTRUCTOR_SIGN_UP_REQUIRED = 0x44747e9a
|
|
13
|
+
|
|
14
|
+
def self.parse(response)
|
|
15
|
+
constructor = response[0, 4].unpack1('L<')
|
|
16
|
+
|
|
17
|
+
if constructor == TL::GzipPacked::CONSTRUCTOR
|
|
18
|
+
response = TL::GzipPacked.unpack(response)
|
|
19
|
+
constructor = response[0, 4].unpack1('L<')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
23
|
+
error = TL::RpcError.deserialize(response)
|
|
24
|
+
raise MTProto::RpcError.new(error.error_code, error.error_message)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if constructor == CONSTRUCTOR_SIGN_UP_REQUIRED
|
|
28
|
+
return { authorization: nil, sign_up_required: true }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if constructor == CONSTRUCTOR
|
|
32
|
+
offset = 4
|
|
33
|
+
|
|
34
|
+
# Parse flags
|
|
35
|
+
flags = response[offset, 4].unpack1('L<')
|
|
36
|
+
offset += 4
|
|
37
|
+
|
|
38
|
+
# tmp_sessions:flags.0?int
|
|
39
|
+
if (flags & (1 << 0)) != 0
|
|
40
|
+
offset += 4
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# otherwise_relogin_days:flags.1?int
|
|
44
|
+
if (flags & (1 << 1)) != 0
|
|
45
|
+
offset += 4
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# future_auth_token:flags.2?bytes
|
|
49
|
+
if (flags & (1 << 2)) != 0
|
|
50
|
+
first_byte = response[offset].unpack1('C')
|
|
51
|
+
|
|
52
|
+
if first_byte < 254
|
|
53
|
+
# Short format: 1 byte length + data + padding
|
|
54
|
+
token_len = first_byte
|
|
55
|
+
offset += 1 + token_len
|
|
56
|
+
padding = (4 - ((1 + token_len) % 4)) % 4
|
|
57
|
+
offset += padding
|
|
58
|
+
else
|
|
59
|
+
# Long format: 0xfe + 3 bytes length + data + padding
|
|
60
|
+
offset += 1
|
|
61
|
+
token_len = response[offset, 3].unpack('CCC').inject(0) {|sum, b| (sum << 8) + b}
|
|
62
|
+
offset += 3 + token_len
|
|
63
|
+
padding = (4 - ((4 + token_len) % 4)) % 4
|
|
64
|
+
offset += padding
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Parse User object
|
|
69
|
+
# user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
|
|
70
|
+
user_constructor = response[offset, 4].unpack1('L<')
|
|
71
|
+
offset += 4
|
|
72
|
+
|
|
73
|
+
user_flags = response[offset, 4].unpack1('L<')
|
|
74
|
+
offset += 4
|
|
75
|
+
|
|
76
|
+
user_flags2 = response[offset, 4].unpack1('L<')
|
|
77
|
+
offset += 4
|
|
78
|
+
|
|
79
|
+
# id:long (always present)
|
|
80
|
+
user_id = response[offset, 8].unpack1('Q<')
|
|
81
|
+
offset += 8
|
|
82
|
+
|
|
83
|
+
# access_hash:flags.0?long (conditional on flags.0)
|
|
84
|
+
access_hash = nil
|
|
85
|
+
if (user_flags & (1 << 0)) != 0
|
|
86
|
+
access_hash = response[offset, 8].unpack1('Q<')
|
|
87
|
+
offset += 8
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# We have what we need, skip the rest of User fields
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
authorization: true,
|
|
94
|
+
flags: flags,
|
|
95
|
+
sign_up_required: false,
|
|
96
|
+
user_id: user_id,
|
|
97
|
+
access_hash: access_hash
|
|
98
|
+
}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
raise UnexpectedConstructorError.new(constructor)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../serializer'
|
|
4
|
+
require_relative '../../code_settings'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module TL
|
|
8
|
+
module RPC
|
|
9
|
+
module Auth
|
|
10
|
+
class SendCode
|
|
11
|
+
CONSTRUCTOR = 0xa677244f
|
|
12
|
+
|
|
13
|
+
def self.build(phone_number:, api_id:, api_hash:, code_settings: {})
|
|
14
|
+
raise ArgumentError, 'phone_number is required' if phone_number.nil? || phone_number.empty?
|
|
15
|
+
|
|
16
|
+
query = [CONSTRUCTOR].pack('L<')
|
|
17
|
+
query += Serializer.serialize_string(phone_number)
|
|
18
|
+
query += Serializer.serialize_int(api_id)
|
|
19
|
+
query += Serializer.serialize_string(api_hash)
|
|
20
|
+
query += CodeSettings.serialize(code_settings)
|
|
21
|
+
|
|
22
|
+
query
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../gzip_packed'
|
|
4
|
+
require_relative '../../rpc_error'
|
|
5
|
+
require_relative '../../sent_code'
|
|
6
|
+
|
|
7
|
+
module MTProto
|
|
8
|
+
module TL
|
|
9
|
+
module RPC
|
|
10
|
+
module Auth
|
|
11
|
+
class SentCode
|
|
12
|
+
def self.parse(response)
|
|
13
|
+
constructor = response[0, 4].unpack1('L<')
|
|
14
|
+
|
|
15
|
+
if constructor == TL::GzipPacked::CONSTRUCTOR
|
|
16
|
+
response = TL::GzipPacked.unpack(response)
|
|
17
|
+
constructor = response[0, 4].unpack1('L<')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
21
|
+
error = TL::RpcError.deserialize(response)
|
|
22
|
+
raise MTProto::RpcError.new(error.error_code, error.error_message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if constructor == TL::SentCode::CONSTRUCTOR
|
|
26
|
+
sent_code = TL::SentCode.deserialize(response)
|
|
27
|
+
sent_code.to_h
|
|
28
|
+
else
|
|
29
|
+
raise UnexpectedConstructorError.new(constructor)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
module RPC
|
|
8
|
+
module Auth
|
|
9
|
+
class SignIn
|
|
10
|
+
CONSTRUCTOR = 0x8d52a951
|
|
11
|
+
|
|
12
|
+
def self.build(phone_number:, phone_code_hash:, phone_code:)
|
|
13
|
+
raise ArgumentError, 'phone_number is required' if phone_number.nil? || phone_number.empty?
|
|
14
|
+
raise ArgumentError, 'phone_code_hash is required' if phone_code_hash.nil? || phone_code_hash.empty?
|
|
15
|
+
raise ArgumentError, 'phone_code is required' if phone_code.nil? || phone_code.empty?
|
|
16
|
+
|
|
17
|
+
flags = 0
|
|
18
|
+
flags |= (1 << 0) # phone_code present
|
|
19
|
+
|
|
20
|
+
query = [CONSTRUCTOR].pack('L<')
|
|
21
|
+
query += Serializer.serialize_int(flags)
|
|
22
|
+
query += Serializer.serialize_string(phone_number)
|
|
23
|
+
query += Serializer.serialize_string(phone_code_hash)
|
|
24
|
+
query += Serializer.serialize_string(phone_code)
|
|
25
|
+
|
|
26
|
+
query
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../gzip_packed'
|
|
4
|
+
require_relative '../../rpc_error'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module TL
|
|
8
|
+
module RPC
|
|
9
|
+
module Contacts
|
|
10
|
+
class Contacts
|
|
11
|
+
CONSTRUCTOR_CONTACTS = 0xeae87e42
|
|
12
|
+
CONSTRUCTOR_CONTACTS_NOT_MODIFIED = 0xb74ba9d2
|
|
13
|
+
VECTOR_CONSTRUCTOR = 0x1cb5c415
|
|
14
|
+
|
|
15
|
+
def self.parse(response)
|
|
16
|
+
offset = 0
|
|
17
|
+
|
|
18
|
+
constructor = response[offset, 4].unpack1('L<')
|
|
19
|
+
offset += 4
|
|
20
|
+
|
|
21
|
+
if constructor == TL::GzipPacked::CONSTRUCTOR
|
|
22
|
+
response = TL::GzipPacked.unpack(response)
|
|
23
|
+
constructor = response[0, 4].unpack1('L<')
|
|
24
|
+
offset = 4
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
28
|
+
error = TL::RpcError.deserialize(response)
|
|
29
|
+
raise MTProto::RpcError.new(error.error_code, error.error_message)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
case constructor
|
|
33
|
+
when CONSTRUCTOR_CONTACTS_NOT_MODIFIED
|
|
34
|
+
# contacts.contactsNotModified#b74ba9d2 = contacts.Contacts;
|
|
35
|
+
{
|
|
36
|
+
contacts: [],
|
|
37
|
+
users: []
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
when CONSTRUCTOR_CONTACTS
|
|
41
|
+
# contacts.contacts#eae87e42 contacts:Vector<Contact> saved_count:int users:Vector<User> = contacts.Contacts;
|
|
42
|
+
|
|
43
|
+
# Parse contacts vector
|
|
44
|
+
contacts, offset = parse_contacts_vector(response, offset)
|
|
45
|
+
|
|
46
|
+
# Parse saved_count
|
|
47
|
+
saved_count = response[offset, 4].unpack1('L<')
|
|
48
|
+
offset += 4
|
|
49
|
+
|
|
50
|
+
# Parse users vector
|
|
51
|
+
users, offset = parse_users_vector(response, offset)
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
contacts: contacts,
|
|
55
|
+
saved_count: saved_count,
|
|
56
|
+
users: users
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
else
|
|
60
|
+
raise "Unknown Contacts constructor: 0x#{constructor.to_s(16)}"
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def self.parse_contacts_vector(response, offset)
|
|
65
|
+
vector_constructor = response[offset, 4].unpack1('L<')
|
|
66
|
+
offset += 4
|
|
67
|
+
|
|
68
|
+
return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
|
|
69
|
+
|
|
70
|
+
count = response[offset, 4].unpack1('L<')
|
|
71
|
+
offset += 4
|
|
72
|
+
|
|
73
|
+
contacts = []
|
|
74
|
+
count.times do
|
|
75
|
+
contact, offset = parse_contact(response, offset)
|
|
76
|
+
contacts << contact if contact
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
[contacts, offset]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def self.parse_contact(response, offset)
|
|
83
|
+
# contact#145ade0b user_id:long mutual:Bool
|
|
84
|
+
constructor = response[offset, 4].unpack1('L<')
|
|
85
|
+
offset += 4
|
|
86
|
+
|
|
87
|
+
return [nil, offset] unless constructor == 0x145ade0b
|
|
88
|
+
|
|
89
|
+
user_id = response[offset, 8].unpack1('Q<')
|
|
90
|
+
offset += 8
|
|
91
|
+
|
|
92
|
+
mutual_constructor = response[offset, 4].unpack1('L<')
|
|
93
|
+
offset += 4
|
|
94
|
+
mutual = mutual_constructor == 0x997275b5 # boolTrue
|
|
95
|
+
|
|
96
|
+
[{
|
|
97
|
+
user_id: user_id,
|
|
98
|
+
mutual: mutual
|
|
99
|
+
}, offset]
|
|
100
|
+
rescue StandardError
|
|
101
|
+
[nil, offset]
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def self.parse_users_vector(response, offset)
|
|
105
|
+
vector_constructor = response[offset, 4].unpack1('L<')
|
|
106
|
+
offset += 4
|
|
107
|
+
|
|
108
|
+
return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
|
|
109
|
+
|
|
110
|
+
count = response[offset, 4].unpack1('L<')
|
|
111
|
+
offset += 4
|
|
112
|
+
|
|
113
|
+
users = []
|
|
114
|
+
count.times do
|
|
115
|
+
user, offset = parse_user(response, offset)
|
|
116
|
+
users << user if user
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
[users, offset]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def self.parse_user(response, offset)
|
|
123
|
+
user_constructor = response[offset, 4].unpack1('L<')
|
|
124
|
+
offset += 4
|
|
125
|
+
|
|
126
|
+
# user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
|
|
127
|
+
return [nil, offset] unless user_constructor == 0x20b1422
|
|
128
|
+
|
|
129
|
+
flags = response[offset, 4].unpack1('L<')
|
|
130
|
+
offset += 4
|
|
131
|
+
|
|
132
|
+
flags2 = response[offset, 4].unpack1('L<')
|
|
133
|
+
offset += 4
|
|
134
|
+
|
|
135
|
+
user_id = response[offset, 8].unpack1('Q<')
|
|
136
|
+
offset += 8
|
|
137
|
+
|
|
138
|
+
access_hash = nil
|
|
139
|
+
if (flags & (1 << 0)) != 0
|
|
140
|
+
access_hash = response[offset, 8].unpack1('Q<')
|
|
141
|
+
offset += 8
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
[{
|
|
145
|
+
id: user_id,
|
|
146
|
+
access_hash: access_hash
|
|
147
|
+
}, offset]
|
|
148
|
+
rescue StandardError
|
|
149
|
+
[nil, offset]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
module RPC
|
|
6
|
+
module Contacts
|
|
7
|
+
class GetContacts
|
|
8
|
+
def self.build(hash: 0)
|
|
9
|
+
# contacts.getContacts#22c6aa08 hash:int = contacts.Contacts;
|
|
10
|
+
data = [0x22c6aa08].pack('L<')
|
|
11
|
+
data += [hash].pack('l<')
|
|
12
|
+
data
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../gzip_packed'
|
|
4
|
+
require_relative '../../rpc_error'
|
|
5
|
+
require_relative '../../config'
|
|
6
|
+
|
|
7
|
+
module MTProto
|
|
8
|
+
module TL
|
|
9
|
+
module RPC
|
|
10
|
+
module Help
|
|
11
|
+
class Config
|
|
12
|
+
def self.parse(response)
|
|
13
|
+
constructor = response[0, 4].unpack1('L<')
|
|
14
|
+
|
|
15
|
+
if constructor == TL::GzipPacked::CONSTRUCTOR
|
|
16
|
+
response = TL::GzipPacked.unpack(response)
|
|
17
|
+
constructor = response[0, 4].unpack1('L<')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
21
|
+
error = TL::RpcError.deserialize(response)
|
|
22
|
+
raise MTProto::RpcError.new(error.error_code, error.error_message)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if constructor == TL::Config::CONSTRUCTOR || constructor == TL::Config::CONSTRUCTOR_ALT
|
|
26
|
+
TL::Config.deserialize(response)
|
|
27
|
+
else
|
|
28
|
+
raise UnexpectedConstructorError.new(constructor)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|