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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/Rakefile +13 -0
  4. data/lib/mtproto/auth_key_generator.rb +36 -13
  5. data/lib/mtproto/client/rpc.rb +141 -0
  6. data/lib/mtproto/client.rb +60 -185
  7. data/lib/mtproto/crypto/aes_ige.rb +1 -1
  8. data/lib/mtproto/crypto/factorization.rb +1 -1
  9. data/lib/mtproto/crypto/rsa_key.rb +9 -15
  10. data/lib/mtproto/errors.rb +33 -0
  11. data/lib/mtproto/message_id.rb +13 -0
  12. data/lib/mtproto/rpc/get_config.rb +37 -0
  13. data/lib/mtproto/rpc/get_contacts.rb +22 -0
  14. data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
  15. data/lib/mtproto/rpc/get_updates_state.rb +22 -0
  16. data/lib/mtproto/rpc/get_users.rb +22 -0
  17. data/lib/mtproto/rpc/ping.rb +26 -0
  18. data/lib/mtproto/rpc/send_code.rb +44 -0
  19. data/lib/mtproto/rpc/send_message.rb +31 -0
  20. data/lib/mtproto/rpc/sign_in.rb +52 -0
  21. data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
  22. data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
  23. data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
  24. data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
  25. data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
  26. data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
  27. data/lib/mtproto/tl/code_settings.rb +25 -0
  28. data/lib/mtproto/tl/config.rb +4 -2
  29. data/lib/mtproto/tl/gzip_packed.rb +1 -1
  30. data/lib/mtproto/tl/message.rb +8 -216
  31. data/lib/mtproto/tl/method_builder.rb +29 -0
  32. data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
  33. data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
  34. data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
  35. data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
  36. data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
  37. data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
  38. data/lib/mtproto/tl/rpc/help/config.rb +35 -0
  39. data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
  40. data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
  41. data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
  42. data/lib/mtproto/tl/rpc/ping.rb +18 -0
  43. data/lib/mtproto/tl/rpc/pong.rb +46 -0
  44. data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
  45. data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
  46. data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
  47. data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
  48. data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
  49. data/lib/mtproto/tl/rpc/users/users.rb +99 -0
  50. data/lib/mtproto/tl/sent_code.rb +128 -0
  51. data/lib/mtproto/transport/tcp_connection.rb +1 -1
  52. data/lib/mtproto/updates_poller.rb +111 -0
  53. data/lib/mtproto/version.rb +1 -1
  54. data/lib/mtproto.rb +13 -0
  55. metadata +57 -6
  56. data/ext/aes_ige/Makefile +0 -273
  57. data/ext/aes_ige/aes_ige.bundle +0 -0
  58. data/ext/factorization/Makefile +0 -273
  59. data/ext/factorization/factorization.bundle +0 -0
@@ -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: 0, msg_id: nil, body: '')
8
+ def initialize(auth_key_id:, msg_id:, body:)
24
9
  @auth_key_id = auth_key_id
25
- @msg_id = msg_id || generate_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