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
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ module RPC
6
+ module Help
7
+ class GetConfig
8
+ CONSTRUCTOR = 0xc4f9186b
9
+
10
+ def self.build
11
+ [CONSTRUCTOR].pack('L<')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../serializer'
4
+
5
+ module MTProto
6
+ module TL
7
+ module RPC
8
+ module Messages
9
+ class SendMessage
10
+ CONSTRUCTOR = 0xfe05dc9a
11
+ INPUT_PEER_USER = 0xdde8a54c
12
+
13
+ def self.build(user_id:, access_hash:, message:, random_id: nil)
14
+ raise ArgumentError, 'user_id is required' if user_id.nil?
15
+ raise ArgumentError, 'access_hash is required' if access_hash.nil?
16
+ raise ArgumentError, 'message is required and cannot be empty' if message.nil? || message.empty?
17
+
18
+ random_id ||= SecureRandom.random_number(2**63)
19
+
20
+ query = [CONSTRUCTOR].pack('L<')
21
+
22
+ # flags:# (no optional fields for now)
23
+ flags = 0
24
+ query += Serializer.serialize_int(flags)
25
+
26
+ # peer:InputPeer (inputPeerUser)
27
+ query += [INPUT_PEER_USER].pack('L<')
28
+ query += Serializer.serialize_long(user_id)
29
+ query += Serializer.serialize_long(access_hash)
30
+
31
+ # message:string
32
+ query += Serializer.serialize_string(message)
33
+
34
+ # random_id:long
35
+ query += Serializer.serialize_long(random_id)
36
+
37
+ query
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,87 @@
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 Messages
10
+ class Updates
11
+ CONSTRUCTOR_UPDATES = 0x74ae4240
12
+ CONSTRUCTOR_UPDATE_SHORT = 0x78d4dec1
13
+ CONSTRUCTOR_UPDATE_SHORT_MESSAGE = 0x313bc7f8
14
+ CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE = 0x4d6deea5
15
+ CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE = 0x9015e101
16
+
17
+ def self.parse(response)
18
+ offset = 0
19
+
20
+ constructor = response[offset, 4].unpack1('L<')
21
+ offset += 4
22
+
23
+ if constructor == TL::GzipPacked::CONSTRUCTOR
24
+ response = TL::GzipPacked.unpack(response)
25
+ constructor = response[0, 4].unpack1('L<')
26
+ offset = 4
27
+ end
28
+
29
+ if constructor == TL::RpcError::CONSTRUCTOR
30
+ error = TL::RpcError.deserialize(response)
31
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
32
+ end
33
+
34
+ case constructor
35
+ when CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE
36
+ # updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
37
+ flags = response[offset, 4].unpack1('L<')
38
+ offset += 4
39
+
40
+ message_id = response[offset, 4].unpack1('L<')
41
+ offset += 4
42
+
43
+ pts = response[offset, 4].unpack1('L<')
44
+ offset += 4
45
+
46
+ pts_count = response[offset, 4].unpack1('L<')
47
+ offset += 4
48
+
49
+ date = response[offset, 4].unpack1('L<')
50
+ offset += 4
51
+
52
+ {
53
+ sent: true,
54
+ message_id: message_id,
55
+ date: date,
56
+ pts: pts,
57
+ pts_count: pts_count
58
+ }
59
+
60
+ when CONSTRUCTOR_UPDATE_SHORT_MESSAGE, CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE
61
+ # updateShortMessage#2296d2c8 or updateShortChatMessage#402d5dbb
62
+ # Both have similar structure with flags, id, etc.
63
+ flags = response[offset, 4].unpack1('L<')
64
+ offset += 4
65
+
66
+ message_id = response[offset, 4].unpack1('L<')
67
+
68
+ {
69
+ sent: true,
70
+ message_id: message_id
71
+ }
72
+
73
+ when CONSTRUCTOR_UPDATES, CONSTRUCTOR_UPDATE_SHORT
74
+ # For full updates or updateShort, just confirm success
75
+ {
76
+ sent: true
77
+ }
78
+
79
+ else
80
+ raise "Unknown Updates constructor: 0x#{constructor.to_s(16)}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serializer'
4
+
5
+ module MTProto
6
+ module TL
7
+ module RPC
8
+ class Ping
9
+ CONSTRUCTOR = 0x7abe77ec
10
+
11
+ def self.build(ping_id)
12
+ body = Serializer.serialize_int(CONSTRUCTOR)
13
+ body + [ping_id].pack('Q<')
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ module RPC
6
+ class Pong
7
+ CONSTRUCTOR = 0x347773c5
8
+ CONSTRUCTOR_BAD_MSG_NOTIFICATION = 0xa7eff811
9
+
10
+ def self.parse(body)
11
+ constructor = body[0, 4].unpack1('L<')
12
+
13
+ if constructor == CONSTRUCTOR_BAD_MSG_NOTIFICATION
14
+ bad_msg = BadMsgNotification.deserialize(body)
15
+ 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})"
16
+ end
17
+
18
+ if constructor == MsgContainer::CONSTRUCTOR
19
+ container = MsgContainer.deserialize(body)
20
+ pong_message = container.messages.find do |msg|
21
+ msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR
22
+ end
23
+
24
+ raise 'No pong message found in container' unless pong_message
25
+
26
+ offset = 4
27
+ msg_id = pong_message[:body][offset, 8].unpack1('Q<')
28
+ offset += 8
29
+ ping_id = pong_message[:body][offset, 8].unpack1('Q<')
30
+
31
+ return { msg_id: msg_id, ping_id: ping_id }
32
+ end
33
+
34
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
35
+
36
+ offset = 4
37
+ msg_id = body[offset, 8].unpack1('Q<')
38
+ offset += 8
39
+ ping_id = body[offset, 8].unpack1('Q<')
40
+
41
+ { msg_id: msg_id, ping_id: ping_id }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,332 @@
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 Updates
10
+ class Difference
11
+ CONSTRUCTOR_DIFFERENCE = 0x00f49ca0
12
+ CONSTRUCTOR_DIFFERENCE_EMPTY = 0x5d75a138
13
+ CONSTRUCTOR_DIFFERENCE_SLICE = 0xa8fb1981
14
+ CONSTRUCTOR_DIFFERENCE_TOO_LONG = 0x4afe8f6d
15
+
16
+ # Updates constructors (can be returned instead of Difference)
17
+ CONSTRUCTOR_UPDATE_SHORT = 0x78d4dec1
18
+ CONSTRUCTOR_UPDATES = 0x74ae4240
19
+ CONSTRUCTOR_UPDATE_SHORT_MESSAGE = 0x313bc7f8
20
+ CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE = 0x402d5dbb
21
+ CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE = 0x11f1331c
22
+
23
+ VECTOR_CONSTRUCTOR = 0x1cb5c415
24
+ MESSAGE_CONSTRUCTOR = 0x452c0e65
25
+ MESSAGE_EMPTY_CONSTRUCTOR = 0x83e5de54
26
+
27
+ def self.parse(response)
28
+ offset = 0
29
+
30
+ constructor = response[offset, 4].unpack1('L<')
31
+ offset += 4
32
+
33
+ if constructor == TL::GzipPacked::CONSTRUCTOR
34
+ response = TL::GzipPacked.unpack(response)
35
+ constructor = response[0, 4].unpack1('L<')
36
+ offset = 4
37
+ end
38
+
39
+ if constructor == TL::RpcError::CONSTRUCTOR
40
+ error = TL::RpcError.deserialize(response)
41
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
42
+ end
43
+
44
+ case constructor
45
+ when CONSTRUCTOR_DIFFERENCE_EMPTY
46
+ # updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference;
47
+ date = response[offset, 4].unpack1('L<')
48
+ offset += 4
49
+
50
+ seq = response[offset, 4].unpack1('L<')
51
+
52
+ {
53
+ type: :empty,
54
+ date: date,
55
+ seq: seq,
56
+ new_messages: [],
57
+ state: nil
58
+ }
59
+
60
+ when CONSTRUCTOR_DIFFERENCE, CONSTRUCTOR_DIFFERENCE_SLICE
61
+ # updates.difference#f49ca0 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> state:updates.State = updates.Difference;
62
+ # updates.differenceSlice#a8fb1981 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> intermediate_state:updates.State = updates.Difference;
63
+
64
+ # Parse new_messages vector
65
+ messages, offset = parse_messages_vector(response, offset)
66
+
67
+ # Skip new_encrypted_messages vector
68
+ offset = skip_vector(response, offset)
69
+
70
+ # Skip other_updates vector
71
+ offset = skip_vector(response, offset)
72
+
73
+ # Skip chats vector
74
+ offset = skip_vector(response, offset)
75
+
76
+ # Parse users vector
77
+ users, offset = parse_users_vector(response, offset)
78
+
79
+ {
80
+ type: constructor == CONSTRUCTOR_DIFFERENCE ? :difference : :slice,
81
+ new_messages: messages,
82
+ users: users,
83
+ state: nil # Would need to parse state
84
+ }
85
+
86
+ when CONSTRUCTOR_DIFFERENCE_TOO_LONG
87
+ # updates.differenceTooLong#4afe8f6d pts:int = updates.Difference;
88
+ pts = response[offset, 4].unpack1('L<')
89
+
90
+ {
91
+ type: :too_long,
92
+ pts: pts,
93
+ new_messages: [],
94
+ state: nil
95
+ }
96
+
97
+ when CONSTRUCTOR_UPDATE_SHORT_MESSAGE
98
+ # updateShortMessage#313bc7f8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
99
+
100
+ flags = response[offset, 4].unpack1('L<')
101
+ offset += 4
102
+
103
+ id = response[offset, 4].unpack1('L<')
104
+ offset += 4
105
+
106
+ user_id = response[offset, 8].unpack1('Q<')
107
+ offset += 8
108
+
109
+ # Parse message string
110
+ msg_len = response[offset].unpack1('C')
111
+ offset += 1
112
+ message_text = response[offset, msg_len].force_encoding('UTF-8')
113
+ offset += msg_len
114
+ padding = (4 - ((1 + msg_len) % 4)) % 4
115
+ offset += padding
116
+
117
+ pts = response[offset, 4].unpack1('L<')
118
+ offset += 4
119
+
120
+ pts_count = response[offset, 4].unpack1('L<')
121
+ offset += 4
122
+
123
+ date = response[offset, 4].unpack1('L<')
124
+ offset += 4
125
+
126
+ # Return with the extracted message
127
+ {
128
+ type: :short_message,
129
+ new_messages: [{
130
+ id: id,
131
+ user_id: user_id,
132
+ date: date,
133
+ message: message_text,
134
+ flags: flags
135
+ }],
136
+ state: { pts: pts, pts_count: pts_count }
137
+ }
138
+
139
+ when CONSTRUCTOR_UPDATE_SHORT, CONSTRUCTOR_UPDATES,
140
+ CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE,
141
+ CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE
142
+ # Sometimes getDifference returns Updates instead of Difference
143
+ # This means there are no new differences, treat as empty
144
+ {
145
+ type: :empty,
146
+ date: Time.now.to_i,
147
+ seq: 0,
148
+ new_messages: [],
149
+ state: nil
150
+ }
151
+
152
+ else
153
+ # Unknown constructor - likely an Updates variant we don't handle yet
154
+ # Treat as empty to avoid breaking the polling loop
155
+ warn "Warning: Unknown Difference/Updates constructor: 0x#{constructor.to_s(16)}, treating as empty"
156
+ {
157
+ type: :empty,
158
+ date: Time.now.to_i,
159
+ seq: 0,
160
+ new_messages: [],
161
+ state: nil
162
+ }
163
+ end
164
+ end
165
+
166
+ def self.parse_messages_vector(response, offset)
167
+ # Check for Vector constructor
168
+ vector_constructor = response[offset, 4].unpack1('L<')
169
+ offset += 4
170
+
171
+ return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
172
+
173
+ count = response[offset, 4].unpack1('L<')
174
+ offset += 4
175
+
176
+ messages = []
177
+ count.times do
178
+ message, offset = parse_message(response, offset)
179
+ messages << message if message
180
+ end
181
+
182
+ [messages, offset]
183
+ end
184
+
185
+ def self.parse_message(response, offset)
186
+ msg_constructor = response[offset, 4].unpack1('L<')
187
+ offset += 4
188
+
189
+ return [nil, offset] if msg_constructor == MESSAGE_EMPTY_CONSTRUCTOR
190
+
191
+ return [nil, offset] unless msg_constructor == MESSAGE_CONSTRUCTOR
192
+
193
+ # message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true offline:flags.30?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags.31?long = Message;
194
+
195
+ flags = response[offset, 4].unpack1('L<')
196
+ offset += 4
197
+
198
+ # For simplicity, we'll just extract id, date, and message text
199
+ # A full implementation would parse all fields based on flags
200
+
201
+ id = response[offset, 4].unpack1('L<')
202
+ offset += 4
203
+
204
+ # Skip from_id if present (flags.8)
205
+ if (flags & (1 << 8)) != 0
206
+ # Skip Peer object (constructor + id, could be user/chat/channel)
207
+ offset += 4 # peer constructor
208
+ offset += 8 # peer id (long for user, int for others, but let's assume worst case)
209
+ end
210
+
211
+ # Skip from_boosts_applied if present (flags.29)
212
+ offset += 4 if (flags & (1 << 29)) != 0
213
+
214
+ # peer_id:Peer (always present) - skip it
215
+ offset += 4 # peer constructor
216
+ offset += 8 # peer id
217
+
218
+ # Skip fwd_from if present (flags.2)
219
+ # This is complex, so we'll just skip ahead assuming it's not there for now
220
+
221
+ # Skip via_bot_id if present (flags.11)
222
+ offset += 8 if (flags & (1 << 11)) != 0
223
+
224
+ # Skip reply_to if present (flags.3)
225
+ # Complex object, skip for now
226
+
227
+ # date:int (always present)
228
+ date = response[offset, 4].unpack1('L<')
229
+ offset += 4
230
+
231
+ # message:string (always present)
232
+ msg_len = response[offset].unpack1('C')
233
+ offset += 1
234
+ message_text = response[offset, msg_len].force_encoding('UTF-8')
235
+ offset += msg_len
236
+ padding = (4 - ((1 + msg_len) % 4)) % 4
237
+ offset += padding
238
+
239
+ # Return simplified message
240
+ [{
241
+ id: id,
242
+ date: date,
243
+ message: message_text,
244
+ flags: flags
245
+ }, offset]
246
+ rescue StandardError => e
247
+ # If parsing fails, skip this message
248
+ [nil, offset]
249
+ end
250
+
251
+ def self.skip_vector(response, offset)
252
+ # Read vector constructor
253
+ vector_constructor = response[offset, 4].unpack1('L<')
254
+ offset += 4
255
+
256
+ return offset unless vector_constructor == VECTOR_CONSTRUCTOR
257
+
258
+ # Read count
259
+ count = response[offset, 4].unpack1('L<')
260
+ offset += 4
261
+
262
+ # Skip each element - we can't know the size, so return offset as-is
263
+ # This is a limitation - proper skipping would require parsing each element
264
+ # For now, just read the constructor of each element and skip based on known patterns
265
+ count.times do
266
+ constructor = response[offset, 4].unpack1('L<')
267
+ offset += 4
268
+
269
+ # This is a simplified skip - real implementation would need to parse each type
270
+ # For now, skip a reasonable amount (most TL objects are small)
271
+ # This is fragile but works for common cases
272
+ end
273
+
274
+ offset
275
+ end
276
+
277
+ def self.parse_users_vector(response, offset)
278
+ # Check for Vector constructor
279
+ vector_constructor = response[offset, 4].unpack1('L<')
280
+ offset += 4
281
+
282
+ return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
283
+
284
+ count = response[offset, 4].unpack1('L<')
285
+ offset += 4
286
+
287
+ users = []
288
+ count.times do
289
+ user, offset = parse_user(response, offset)
290
+ users << user if user
291
+ end
292
+
293
+ [users, offset]
294
+ end
295
+
296
+ def self.parse_user(response, offset)
297
+ user_constructor = response[offset, 4].unpack1('L<')
298
+ offset += 4
299
+
300
+ # user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
301
+ return [nil, offset] unless user_constructor == 0x20b1422
302
+
303
+ flags = response[offset, 4].unpack1('L<')
304
+ offset += 4
305
+
306
+ flags2 = response[offset, 4].unpack1('L<')
307
+ offset += 4
308
+
309
+ # id:long (always present)
310
+ user_id = response[offset, 8].unpack1('Q<')
311
+ offset += 8
312
+
313
+ # access_hash:flags.0?long (conditional)
314
+ access_hash = nil
315
+ if (flags & (1 << 0)) != 0
316
+ access_hash = response[offset, 8].unpack1('Q<')
317
+ offset += 8
318
+ end
319
+
320
+ # We have what we need, skip the rest
321
+ [{
322
+ id: user_id,
323
+ access_hash: access_hash
324
+ }, offset]
325
+ rescue StandardError => e
326
+ [nil, offset]
327
+ end
328
+ end
329
+ end
330
+ end
331
+ end
332
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../serializer'
4
+
5
+ module MTProto
6
+ module TL
7
+ module RPC
8
+ module Updates
9
+ class GetDifference
10
+ CONSTRUCTOR = 0x19c2f763
11
+
12
+ def self.build(pts:, pts_limit:, pts_total_limit: nil, date:, qts:, qts_limit: nil)
13
+ raise ArgumentError, 'pts is required' if pts.nil?
14
+ raise ArgumentError, 'date is required' if date.nil?
15
+ raise ArgumentError, 'qts is required' if qts.nil?
16
+
17
+ # updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.0?int pts_total_limit:flags.1?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
18
+ flags = 0
19
+ flags |= (1 << 0) if pts_limit
20
+ flags |= (1 << 1) if pts_total_limit
21
+ flags |= (1 << 2) if qts_limit
22
+
23
+ query = [CONSTRUCTOR].pack('L<')
24
+ query += Serializer.serialize_int(flags)
25
+ query += Serializer.serialize_int(pts)
26
+
27
+ query += Serializer.serialize_int(pts_limit) if pts_limit
28
+
29
+ query += Serializer.serialize_int(pts_total_limit) if pts_total_limit
30
+
31
+ query += Serializer.serialize_int(date)
32
+ query += Serializer.serialize_int(qts)
33
+
34
+ query += Serializer.serialize_int(qts_limit) if qts_limit
35
+
36
+ query
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ module RPC
6
+ module Updates
7
+ class GetState
8
+ CONSTRUCTOR = 0xedd4882a
9
+
10
+ def self.build
11
+ [CONSTRUCTOR].pack('L<')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,59 @@
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 Updates
10
+ class State
11
+ CONSTRUCTOR = 0xa56c2a3e
12
+
13
+ def self.parse(response)
14
+ offset = 0
15
+
16
+ constructor = response[offset, 4].unpack1('L<')
17
+ offset += 4
18
+
19
+ if constructor == TL::GzipPacked::CONSTRUCTOR
20
+ response = TL::GzipPacked.unpack(response)
21
+ constructor = response[0, 4].unpack1('L<')
22
+ offset = 4
23
+ end
24
+
25
+ if constructor == TL::RpcError::CONSTRUCTOR
26
+ error = TL::RpcError.deserialize(response)
27
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
28
+ end
29
+
30
+ raise "Expected State constructor 0x#{CONSTRUCTOR.to_s(16)}, got 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
31
+
32
+ # updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
33
+ pts = response[offset, 4].unpack1('L<')
34
+ offset += 4
35
+
36
+ qts = response[offset, 4].unpack1('L<')
37
+ offset += 4
38
+
39
+ date = response[offset, 4].unpack1('L<')
40
+ offset += 4
41
+
42
+ seq = response[offset, 4].unpack1('L<')
43
+ offset += 4
44
+
45
+ unread_count = response[offset, 4].unpack1('L<')
46
+
47
+ {
48
+ pts: pts,
49
+ qts: qts,
50
+ date: date,
51
+ seq: seq,
52
+ unread_count: unread_count
53
+ }
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ module RPC
6
+ module Users
7
+ class GetUsers
8
+ CONSTRUCTOR = 0x0d91a548
9
+ INPUT_USER_SELF = 0xf7c1b13f
10
+
11
+ def self.build
12
+ query = [CONSTRUCTOR].pack('L<')
13
+
14
+ # Vector of InputUser with one element: inputUserSelf
15
+ query += [0x1cb5c415].pack('L<') # vector constructor
16
+ query += [1].pack('L<') # count = 1
17
+ query += [INPUT_USER_SELF].pack('L<') # inputUserSelf
18
+
19
+ query
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end