mtproto 0.0.9 → 0.0.11

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 (63) hide show
  1. checksums.yaml +4 -4
  2. data/data/tl-schema.json +42686 -0
  3. data/ext/aes_ige/extconf.rb +1 -1
  4. data/ext/factorization/extconf.rb +1 -1
  5. data/lib/mtproto/client/api/get_dialogs.rb +21 -0
  6. data/lib/mtproto/client/api/get_history.rb +20 -0
  7. data/lib/mtproto/client/api.rb +8 -0
  8. data/lib/mtproto/client/rpc.rb +4 -7
  9. data/lib/mtproto/client.rb +7 -7
  10. data/lib/mtproto/errors.rb +8 -0
  11. data/lib/mtproto/tl/constructor_names.rb +2271 -0
  12. data/lib/mtproto/tl/constructors.rb +91 -2261
  13. data/lib/mtproto/tl/object.rb +1 -1
  14. data/lib/mtproto/tl/objects/account_password.rb +2 -5
  15. data/lib/mtproto/tl/objects/authorization.rb +2 -5
  16. data/lib/mtproto/tl/objects/check_password.rb +2 -5
  17. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +1 -3
  18. data/lib/mtproto/tl/objects/dh_gen_response.rb +3 -7
  19. data/lib/mtproto/tl/objects/dialogs.rb +453 -0
  20. data/lib/mtproto/tl/objects/export_login_token.rb +2 -5
  21. data/lib/mtproto/tl/objects/get_config.rb +1 -3
  22. data/lib/mtproto/tl/objects/get_dialogs.rb +51 -0
  23. data/lib/mtproto/tl/objects/get_difference.rb +1 -3
  24. data/lib/mtproto/tl/objects/get_history.rb +49 -0
  25. data/lib/mtproto/tl/objects/get_password.rb +1 -3
  26. data/lib/mtproto/tl/objects/get_state.rb +1 -3
  27. data/lib/mtproto/tl/objects/get_users.rb +3 -7
  28. data/lib/mtproto/{type → tl/objects}/gzip_packed.rb +1 -3
  29. data/lib/mtproto/tl/objects/help_config.rb +4 -5
  30. data/lib/mtproto/tl/objects/import_login_token.rb +1 -3
  31. data/lib/mtproto/tl/objects/init_connection.rb +1 -3
  32. data/lib/mtproto/tl/objects/invoke_with_layer.rb +1 -3
  33. data/lib/mtproto/tl/objects/login_token.rb +3 -7
  34. data/lib/mtproto/{type → tl/objects}/message.rb +1 -1
  35. data/lib/mtproto/tl/objects/messages.rb +162 -0
  36. data/lib/mtproto/{type → tl/objects}/msg_container.rb +1 -3
  37. data/lib/mtproto/{type → tl/objects}/new_session_created.rb +1 -3
  38. data/lib/mtproto/tl/objects/pq_inner_data.rb +1 -4
  39. data/lib/mtproto/tl/objects/req_dh_params.rb +1 -3
  40. data/lib/mtproto/tl/objects/req_pq_multi.rb +1 -3
  41. data/lib/mtproto/tl/objects/res_pq.rb +2 -4
  42. data/lib/mtproto/{type → tl/objects}/rpc_error.rb +1 -3
  43. data/lib/mtproto/tl/objects/send_code.rb +2 -5
  44. data/lib/mtproto/tl/objects/sent_code.rb +4 -4
  45. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +3 -3
  46. data/lib/mtproto/tl/objects/server_dh_params.rb +3 -3
  47. data/lib/mtproto/tl/objects/set_client_dh_params.rb +1 -3
  48. data/lib/mtproto/tl/objects/sign_in.rb +1 -3
  49. data/lib/mtproto/tl/objects/update.rb +3 -6
  50. data/lib/mtproto/tl/objects/update_short.rb +0 -2
  51. data/lib/mtproto/tl/objects/update_short_message.rb +0 -2
  52. data/lib/mtproto/tl/objects/updates_difference.rb +6 -11
  53. data/lib/mtproto/tl/objects/updates_state.rb +1 -3
  54. data/lib/mtproto/tl/objects/users.rb +2 -5
  55. data/lib/mtproto/tl/schema.rb +102 -0
  56. data/lib/mtproto/version.rb +1 -1
  57. data/lib/mtproto.rb +6 -10
  58. metadata +15 -11
  59. data/lib/mtproto/type/bad_msg_notification.rb +0 -46
  60. data/lib/mtproto/type/client_dh_inner_data.rb +0 -29
  61. data/lib/mtproto/type/pq_inner_data.rb +0 -41
  62. data/lib/mtproto/type/serializer.rb +0 -55
  63. data/lib/mtproto/type/server_dh_inner_data.rb +0 -85
@@ -15,7 +15,7 @@ module MTProto
15
15
  def self.parse(message)
16
16
  body = message.body
17
17
  constructor_id = b_u32(body[0, 4])
18
- constructor = Constructors::NAMES.fetch(constructor_id)
18
+ constructor = ConstructorNames::NAMES.fetch(constructor_id)
19
19
  payload = body[4..]
20
20
 
21
21
  new(constructor, payload)
@@ -3,9 +3,6 @@
3
3
  module MTProto
4
4
  module TL
5
5
  class AccountPassword
6
- CONSTRUCTOR = 0x957b50fb
7
- ALGO_SHA256_MOD_POW = 0x3a912d4a
8
-
9
6
  attr_reader :has_password, :current_algo, :srp_b, :srp_id
10
7
 
11
8
  def initialize(has_password:, current_algo: nil, srp_b: nil, srp_id: nil)
@@ -17,7 +14,7 @@ module MTProto
17
14
 
18
15
  def self.parse(data)
19
16
  constructor = data[0, 4].unpack1('L<')
20
- raise UnexpectedConstructorError, constructor unless constructor == CONSTRUCTOR
17
+ raise UnexpectedConstructorError, constructor unless constructor == Constructors::ACCOUNT_PASSWORD
21
18
 
22
19
  offset = 4
23
20
  flags = data[offset, 4].unpack1('L<')
@@ -31,7 +28,7 @@ module MTProto
31
28
  offset += 4
32
29
 
33
30
  current_algo = nil
34
- if algo_constructor == ALGO_SHA256_MOD_POW
31
+ if algo_constructor == Constructors::PASSWORD_KDF_ALGO_SHA256_MOD_POW
35
32
  salt1, offset = read_tl_bytes(data, offset)
36
33
  salt2, offset = read_tl_bytes(data, offset)
37
34
  g = data[offset, 4].unpack1('l<')
@@ -3,9 +3,6 @@
3
3
  module MTProto
4
4
  module TL
5
5
  class Authorization
6
- CONSTRUCTOR = 0x2ea2c0d4
7
- CONSTRUCTOR_SIGN_UP_REQUIRED = 0x44747e9a
8
-
9
6
  attr_reader :user_id, :access_hash, :flags, :sign_up_required
10
7
 
11
8
  def initialize(user_id: nil, access_hash: nil, flags: 0, sign_up_required: false)
@@ -22,8 +19,8 @@ module MTProto
22
19
  def self.parse(data)
23
20
  constructor = data[0, 4].unpack1('L<')
24
21
 
25
- return new(sign_up_required: true) if constructor == CONSTRUCTOR_SIGN_UP_REQUIRED
26
- raise UnexpectedConstructorError, constructor unless constructor == CONSTRUCTOR
22
+ return new(sign_up_required: true) if constructor == Constructors::AUTH_AUTHORIZATION_SIGN_UP_REQUIRED
23
+ raise UnexpectedConstructorError, constructor unless constructor == Constructors::AUTH_AUTHORIZATION
27
24
 
28
25
  offset = 4
29
26
 
@@ -5,9 +5,6 @@ module MTProto
5
5
  class CheckPassword
6
6
  include Binary
7
7
 
8
- CONSTRUCTOR = 0xd18b4d16
9
- INPUT_CHECK_PASSWORD_SRP = 0xd27ff082
10
-
11
8
  attr_reader :srp_id, :a, :m1
12
9
 
13
10
  def initialize(srp_id:, a:, m1:)
@@ -17,8 +14,8 @@ module MTProto
17
14
  end
18
15
 
19
16
  def body
20
- u32_b(CONSTRUCTOR) +
21
- u32_b(INPUT_CHECK_PASSWORD_SRP) +
17
+ u32_b(Constructors::AUTH_CHECK_PASSWORD) +
18
+ u32_b(Constructors::INPUT_CHECK_PASSWORD_SRP) +
22
19
  u64_b(@srp_id) +
23
20
  serialize_tl_bytes(@a) +
24
21
  serialize_tl_bytes(@m1)
@@ -5,8 +5,6 @@ module MTProto
5
5
  class ClientDHInnerData
6
6
  include Binary
7
7
 
8
- CONSTRUCTOR = 0x6643b654
9
-
10
8
  attr_reader :nonce, :server_nonce, :retry_id, :g_b
11
9
 
12
10
  def initialize(nonce:, server_nonce:, retry_id:, g_b:)
@@ -17,7 +15,7 @@ module MTProto
17
15
  end
18
16
 
19
17
  def body
20
- result = u32_b(CONSTRUCTOR)
18
+ result = u32_b(Constructors::CLIENT_DH_INNER_DATA)
21
19
  result += @nonce.bytes
22
20
  result += @server_nonce.bytes
23
21
  result += u64_b(@retry_id)
@@ -5,10 +5,6 @@ module MTProto
5
5
  class DHGenResponse
6
6
  extend Binary
7
7
 
8
- CONSTRUCTOR_OK = 0x3bcbf734
9
- CONSTRUCTOR_RETRY = 0x46dc1fb9
10
- CONSTRUCTOR_FAIL = 0xa69dae02
11
-
12
8
  attr_reader :status, :nonce, :server_nonce, :new_nonce_hash
13
9
 
14
10
  def self.parse(message)
@@ -16,9 +12,9 @@ module MTProto
16
12
  constructor = b_u32(data[0, 4])
17
13
 
18
14
  status = case constructor
19
- when CONSTRUCTOR_OK then :ok
20
- when CONSTRUCTOR_RETRY then :retry
21
- when CONSTRUCTOR_FAIL then :fail
15
+ when Constructors::DH_GEN_OK then :ok
16
+ when Constructors::DH_GEN_RETRY then :retry
17
+ when Constructors::DH_GEN_FAIL then :fail
22
18
  else raise "Unexpected constructor: 0x#{constructor.to_s(16)}"
23
19
  end
24
20
 
@@ -0,0 +1,453 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../schema'
4
+
5
+ module MTProto
6
+ module TL
7
+ class Dialogs
8
+ Dialog = Struct.new(:peer_type, :peer_id, :top_message, :unread_count, keyword_init: true)
9
+ Chat = Struct.new(:id, :title, :type, :access_hash, keyword_init: true)
10
+
11
+ SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
12
+
13
+ attr_reader :dialogs, :chats, :users, :count, :message_dates
14
+
15
+ def initialize(dialogs:, chats:, users:, count: nil, message_dates: {})
16
+ @dialogs = dialogs
17
+ @chats = chats
18
+ @users = users
19
+ @count = count
20
+ @message_dates = message_dates
21
+ end
22
+
23
+ def slice?
24
+ !@count.nil?
25
+ end
26
+
27
+ def self.schema
28
+ @schema ||= Schema.new(SCHEMA_PATH)
29
+ end
30
+
31
+ def self.parse(data)
32
+ constructor = data[0, 4].unpack1('L<')
33
+
34
+ unless [Constructors::MESSAGES_DIALOGS, Constructors::MESSAGES_DIALOGS_SLICE].include?(constructor)
35
+ raise UnexpectedConstructorError, constructor
36
+ end
37
+
38
+ offset = 4
39
+
40
+ count = nil
41
+ if constructor == Constructors::MESSAGES_DIALOGS_SLICE
42
+ count = data[offset, 4].unpack1('L<')
43
+ offset += 4
44
+ end
45
+
46
+ dialogs, offset = parse_dialogs_vector(data, offset)
47
+ message_dates, offset = parse_messages_vector(data, offset)
48
+ chats, offset = parse_chats_vector(data, offset)
49
+ users, = parse_users_vector(data, offset)
50
+
51
+ new(dialogs: dialogs, chats: chats, users: users, count: count, message_dates: message_dates)
52
+ end
53
+
54
+ class << self
55
+ private
56
+
57
+ # --- Dialogs vector ---
58
+
59
+ def parse_dialogs_vector(data, offset)
60
+ offset += 4 # vector constructor
61
+ count = data[offset, 4].unpack1('L<')
62
+ offset += 4
63
+
64
+ dialogs = []
65
+ count.times do
66
+ dialog, offset = parse_dialog(data, offset)
67
+ dialogs << dialog if dialog
68
+ end
69
+ [dialogs, offset]
70
+ end
71
+
72
+ def parse_dialog(data, offset)
73
+ constructor = data[offset, 4].unpack1('L<')
74
+ offset += 4
75
+
76
+ unless constructor == Constructors::DIALOG
77
+ offset = schema.skip_params(data, offset, constructor)
78
+ return [nil, offset]
79
+ end
80
+
81
+ flags = data[offset, 4].unpack1('L<')
82
+ offset += 4
83
+
84
+ peer_type, peer_id, offset = parse_peer(data, offset)
85
+
86
+ top_message = data[offset, 4].unpack1('L<')
87
+ offset += 4
88
+
89
+ offset += 4 # read_inbox_max_id
90
+ offset += 4 # read_outbox_max_id
91
+
92
+ unread_count = data[offset, 4].unpack1('L<')
93
+ offset += 4
94
+
95
+ offset += 4 # unread_mentions_count
96
+ offset += 4 # unread_reactions_count
97
+
98
+ offset = schema.skip(data, offset) # notify_settings (PeerNotifySettings)
99
+
100
+ offset += 4 if flags.anybits?(1 << 0) # pts
101
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 1) # draft (DraftMessage)
102
+ offset += 4 if flags.anybits?(1 << 4) # folder_id
103
+ offset += 4 if flags.anybits?(1 << 5) # ttl_period
104
+
105
+ [Dialog.new(peer_type: peer_type, peer_id: peer_id,
106
+ top_message: top_message, unread_count: unread_count), offset]
107
+ end
108
+
109
+ def parse_peer(data, offset)
110
+ constructor = data[offset, 4].unpack1('L<')
111
+ offset += 4
112
+
113
+ case constructor
114
+ when Constructors::PEER_USER
115
+ [:user, data[offset, 8].unpack1('Q<'), offset + 8]
116
+ when Constructors::PEER_CHAT
117
+ [:chat, data[offset, 8].unpack1('Q<'), offset + 8]
118
+ when Constructors::PEER_CHANNEL
119
+ [:channel, data[offset, 8].unpack1('Q<'), offset + 8]
120
+ else
121
+ raise "Unknown peer constructor: 0x#{constructor.to_s(16)}"
122
+ end
123
+ end
124
+
125
+ # --- Messages vector ---
126
+
127
+ # message#9815cec8 flags:# flags2:#
128
+ # id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int
129
+ # peer_id:Peer saved_peer_id:flags.28?Peer
130
+ # fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long
131
+ # via_business_bot_id:flags2.0?long reply_to:flags.3?MessageReplyHeader
132
+ # date:int ...
133
+ #
134
+ # messageService#2b085862 flags:#
135
+ # id:int from_id:flags.8?Peer peer_id:Peer
136
+ # saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader
137
+ # date:int ...
138
+ MESSAGE_CONSTRUCTOR = Constructors::MESSAGE
139
+ MESSAGE_SERVICE = 0x7a800e0a
140
+ MESSAGE_EMPTY = 0x90a6ca84
141
+
142
+ def parse_messages_vector(data, offset)
143
+ offset += 4 # vector constructor
144
+ count = data[offset, 4].unpack1('L<')
145
+ offset += 4
146
+
147
+ dates = {}
148
+ count.times do
149
+ constructor = data[offset, 4].unpack1('L<')
150
+ id_date = extract_message_id_date(data, offset, constructor)
151
+ dates[id_date[0]] = id_date[1] if id_date
152
+
153
+ offset = schema.skip(data, offset)
154
+ end
155
+ [dates, offset]
156
+ end
157
+
158
+ def extract_message_id_date(data, offset, constructor)
159
+ case constructor
160
+ when MESSAGE_CONSTRUCTOR
161
+ extract_message_date(data, offset)
162
+ when MESSAGE_SERVICE
163
+ extract_message_service_date(data, offset)
164
+ end
165
+ end
166
+
167
+ def extract_message_date(data, offset)
168
+ offset += 4 # constructor
169
+ flags = data[offset, 4].unpack1('L<')
170
+ offset += 4
171
+ flags2 = data[offset, 4].unpack1('L<')
172
+ offset += 4
173
+ id = data[offset, 4].unpack1('l<')
174
+ offset += 4
175
+
176
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
177
+ offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
178
+ offset = schema.skip(data, offset) # peer_id (Peer, always present)
179
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
180
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 2) # fwd_from
181
+ offset += 8 if flags.anybits?(1 << 11) # via_bot_id
182
+ offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
183
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
184
+
185
+ date = data[offset, 4].unpack1('L<')
186
+ [id, date]
187
+ end
188
+
189
+ def extract_message_service_date(data, offset)
190
+ offset += 4 # constructor
191
+ flags = data[offset, 4].unpack1('L<')
192
+ offset += 4
193
+ id = data[offset, 4].unpack1('l<')
194
+ offset += 4
195
+
196
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
197
+ offset = schema.skip(data, offset) # peer_id (Peer, always present)
198
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
199
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
200
+
201
+ date = data[offset, 4].unpack1('L<')
202
+ [id, date]
203
+ end
204
+
205
+ # --- Chats vector ---
206
+
207
+ def parse_chats_vector(data, offset)
208
+ offset += 4 # vector constructor
209
+ count = data[offset, 4].unpack1('L<')
210
+ offset += 4
211
+
212
+ chats = []
213
+ count.times do
214
+ chat, offset = parse_chat(data, offset)
215
+ chats << chat if chat
216
+ end
217
+ [chats, offset]
218
+ end
219
+
220
+ def parse_chat(data, offset)
221
+ constructor = data[offset, 4].unpack1('L<')
222
+ offset += 4
223
+
224
+ case constructor
225
+ when Constructors::CHAT
226
+ parse_basic_chat(data, offset)
227
+ when Constructors::CHANNEL
228
+ parse_channel(data, offset)
229
+ when Constructors::CHAT_FORBIDDEN
230
+ parse_chat_forbidden(data, offset)
231
+ when Constructors::CHANNEL_FORBIDDEN
232
+ parse_channel_forbidden(data, offset)
233
+ when CHAT_EMPTY
234
+ id = data[offset, 8].unpack1('Q<')
235
+ offset += 8
236
+ [Chat.new(id: id, title: '(empty)', type: :chat_empty), offset]
237
+ else
238
+ raise "Unknown Chat constructor: 0x#{constructor.to_s(16).rjust(8, '0')}"
239
+ end
240
+ end
241
+
242
+ CHAT_EMPTY = 0x29562865
243
+
244
+ # chat#41cbf256 flags:#
245
+ # id:long title:string photo:ChatPhoto
246
+ # participants_count:int date:int version:int
247
+ # migrated_to:flags.6?InputChannel
248
+ # admin_rights:flags.14?ChatAdminRights
249
+ # default_banned_rights:flags.18?ChatBannedRights
250
+ def parse_basic_chat(data, offset)
251
+ flags = data[offset, 4].unpack1('L<')
252
+ offset += 4
253
+ id = data[offset, 8].unpack1('Q<')
254
+ offset += 8
255
+ title, offset = read_tl_string(data, offset)
256
+ offset = schema.skip(data, offset) # photo (ChatPhoto)
257
+ offset += 4 # participants_count
258
+ offset += 4 # date
259
+ offset += 4 # version
260
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 6) # migrated_to (InputChannel)
261
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 14) # admin_rights
262
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 18) # default_banned_rights
263
+
264
+ [Chat.new(id: id, title: title, type: :chat), offset]
265
+ end
266
+
267
+ # channel#fe685355 flags:# flags2:#
268
+ # id:long access_hash:flags.13?long title:string
269
+ # username:flags.6?string photo:ChatPhoto date:int
270
+ # restriction_reason:flags.9?Vector<RestrictionReason>
271
+ # admin_rights:flags.14?ChatAdminRights
272
+ # banned_rights:flags.15?ChatBannedRights
273
+ # default_banned_rights:flags.18?ChatBannedRights
274
+ # participants_count:flags.17?int
275
+ # usernames:flags2.0?Vector<Username>
276
+ # stories_max_id:flags2.4?int
277
+ # color:flags2.7?PeerColor profile_color:flags2.8?PeerColor
278
+ # emoji_status:flags2.9?EmojiStatus
279
+ # level:flags2.10?int subscription_until_date:flags2.11?int
280
+ # bot_verification_icon:flags2.13?long
281
+ # send_paid_messages_stars:flags2.14?long
282
+ # linked_monoforum_id:flags2.18?long
283
+ def parse_channel(data, offset)
284
+ flags = data[offset, 4].unpack1('L<')
285
+ offset += 4
286
+ flags2 = data[offset, 4].unpack1('L<')
287
+ offset += 4
288
+ id = data[offset, 8].unpack1('Q<')
289
+ offset += 8
290
+ access_hash = nil
291
+ if flags.anybits?(1 << 13)
292
+ access_hash = data[offset, 8].unpack1('Q<')
293
+ offset += 8
294
+ end
295
+ title, offset = read_tl_string(data, offset)
296
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 6) # username
297
+ offset = schema.skip(data, offset) # photo (ChatPhoto)
298
+ offset += 4 # date
299
+ if flags.anybits?(1 << 9) # restriction_reason Vector<RestrictionReason>
300
+ offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) }
301
+ end
302
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 14) # admin_rights
303
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 15) # banned_rights
304
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 18) # default_banned_rights
305
+ offset += 4 if flags.anybits?(1 << 17) # participants_count
306
+ if flags2.anybits?(1 << 0) # usernames Vector<Username>
307
+ offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) }
308
+ end
309
+ offset += 4 if flags2.anybits?(1 << 4) # stories_max_id
310
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 7) # color (PeerColor)
311
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 8) # profile_color (PeerColor)
312
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 9) # emoji_status
313
+ offset += 4 if flags2.anybits?(1 << 10) # level
314
+ offset += 4 if flags2.anybits?(1 << 11) # subscription_until_date
315
+ offset += 8 if flags2.anybits?(1 << 13) # bot_verification_icon (long)
316
+ offset += 8 if flags2.anybits?(1 << 14) # send_paid_messages_stars (long)
317
+ offset += 8 if flags2.anybits?(1 << 18) # linked_monoforum_id (long)
318
+
319
+ type = flags.anybits?(1 << 8) ? :supergroup : :channel
320
+ [Chat.new(id: id, title: title, type: type, access_hash: access_hash), offset]
321
+ end
322
+
323
+ # chatForbidden#6592a1a7 id:long title:string
324
+ def parse_chat_forbidden(data, offset)
325
+ id = data[offset, 8].unpack1('Q<')
326
+ offset += 8
327
+ title, offset = read_tl_string(data, offset)
328
+ [Chat.new(id: id, title: title, type: :chat_forbidden), offset]
329
+ end
330
+
331
+ # channelForbidden#17d493d5 flags:#
332
+ # id:long access_hash:long title:string until_date:flags.16?int
333
+ def parse_channel_forbidden(data, offset)
334
+ flags = data[offset, 4].unpack1('L<')
335
+ offset += 4
336
+ id = data[offset, 8].unpack1('Q<')
337
+ offset += 8
338
+ access_hash = data[offset, 8].unpack1('Q<')
339
+ offset += 8
340
+ title, offset = read_tl_string(data, offset)
341
+ offset += 4 if flags.anybits?(1 << 16) # until_date
342
+ [Chat.new(id: id, title: title, type: :channel_forbidden, access_hash: access_hash), offset]
343
+ end
344
+
345
+ # --- Users vector ---
346
+
347
+ def parse_users_vector(data, offset)
348
+ offset += 4 # vector constructor
349
+ count = data[offset, 4].unpack1('L<')
350
+ offset += 4
351
+
352
+ users = []
353
+ count.times do
354
+ user, offset = parse_user(data, offset)
355
+ users << user if user
356
+ end
357
+ [users, offset]
358
+ end
359
+
360
+ USER_EMPTY = 0xd3bc4b7a
361
+
362
+ # user#020b1422 flags:# flags2:#
363
+ # id:long access_hash:flags.0?long
364
+ # first_name:flags.1?string last_name:flags.2?string
365
+ # username:flags.3?string phone:flags.4?string
366
+ # photo:flags.5?UserProfilePhoto status:flags.6?UserStatus
367
+ # bot_info_version:flags.14?int
368
+ # restriction_reason:flags.18?Vector<RestrictionReason>
369
+ # bot_inline_placeholder:flags.19?string
370
+ # lang_code:flags.22?string
371
+ # emoji_status:flags.30?EmojiStatus
372
+ # usernames:flags2.0?Vector<Username>
373
+ # stories_max_id:flags2.5?int
374
+ # color:flags2.8?PeerColor profile_color:flags2.9?PeerColor
375
+ # bot_active_users:flags2.12?int
376
+ # bot_verification_icon:flags2.14?long
377
+ # send_paid_messages_stars:flags2.15?long
378
+ def parse_user(data, offset)
379
+ constructor = data[offset, 4].unpack1('L<')
380
+ offset += 4
381
+
382
+ if constructor == USER_EMPTY
383
+ offset += 8 # id (long)
384
+ return [nil, offset]
385
+ end
386
+
387
+ raise "Unknown User constructor: 0x#{constructor.to_s(16)}" unless constructor == Constructors::USER
388
+
389
+ flags = data[offset, 4].unpack1('L<')
390
+ offset += 4
391
+ flags2 = data[offset, 4].unpack1('L<')
392
+ offset += 4
393
+ id = data[offset, 8].unpack1('Q<')
394
+ offset += 8
395
+ access_hash = nil
396
+ if flags.anybits?(1 << 0)
397
+ access_hash = data[offset, 8].unpack1('Q<')
398
+ offset += 8
399
+ end
400
+
401
+ first_name = nil
402
+ first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
403
+
404
+ last_name = nil
405
+ last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
406
+
407
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 3) # username
408
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 4) # phone
409
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 5) # photo
410
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 6) # status
411
+ offset += 4 if flags.anybits?(1 << 14) # bot_info_version
412
+ if flags.anybits?(1 << 18) # restriction_reason
413
+ offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) }
414
+ end
415
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 19) # bot_inline_placeholder
416
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 22) # lang_code
417
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 30) # emoji_status
418
+ offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if flags2.anybits?(1 << 0) # usernames
419
+ offset += 4 if flags2.anybits?(1 << 5) # stories_max_id
420
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 8) # color
421
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 9) # profile_color
422
+ offset += 4 if flags2.anybits?(1 << 12) # bot_active_users
423
+ offset += 8 if flags2.anybits?(1 << 14) # bot_verification_icon
424
+ offset += 8 if flags2.anybits?(1 << 15) # send_paid_messages_stars
425
+
426
+ [{ id: id, first_name: first_name, last_name: last_name, access_hash: access_hash }, offset]
427
+ end
428
+
429
+ # --- TL string ---
430
+
431
+ def read_tl_string(data, offset)
432
+ first_byte = data.getbyte(offset)
433
+ if first_byte == 254
434
+ len = data.getbyte(offset + 1) |
435
+ (data.getbyte(offset + 2) << 8) |
436
+ (data.getbyte(offset + 3) << 16)
437
+ total = 4 + len
438
+ else
439
+ len = first_byte
440
+ total = 1 + len
441
+ end
442
+ padding = (4 - (total % 4)) % 4
443
+ [data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
444
+ end
445
+
446
+ def skip_tl_string(data, offset)
447
+ _, new_offset = read_tl_string(data, offset)
448
+ new_offset
449
+ end
450
+ end
451
+ end
452
+ end
453
+ end
@@ -5,9 +5,6 @@ module MTProto
5
5
  class ExportLoginToken
6
6
  include Binary
7
7
 
8
- CONSTRUCTOR = 0xb7e085fe
9
- VECTOR_CONSTRUCTOR = 0x1cb5c415
10
-
11
8
  attr_reader :api_id, :api_hash, :except_ids
12
9
 
13
10
  def initialize(api_id:, api_hash:, except_ids: [])
@@ -17,7 +14,7 @@ module MTProto
17
14
  end
18
15
 
19
16
  def body
20
- u32_b(CONSTRUCTOR) +
17
+ u32_b(Constructors::AUTH_EXPORT_LOGIN_TOKEN) +
21
18
  u32_b(@api_id) +
22
19
  serialize_tl_string(@api_hash) +
23
20
  serialize_vector_long(@except_ids)
@@ -26,7 +23,7 @@ module MTProto
26
23
  private
27
24
 
28
25
  def serialize_vector_long(ids)
29
- result = u32_b(VECTOR_CONSTRUCTOR) + u32_b(ids.length)
26
+ result = u32_b(Constructors::VECTOR) + u32_b(ids.length)
30
27
  ids.each { |id| result += u64_b(id) }
31
28
  result
32
29
  end
@@ -5,10 +5,8 @@ module MTProto
5
5
  class GetConfig
6
6
  include Binary
7
7
 
8
- CONSTRUCTOR = 0xc4f9186b
9
-
10
8
  def body
11
- u32_b(CONSTRUCTOR)
9
+ u32_b(Constructors::HELP_GET_CONFIG)
12
10
  end
13
11
  end
14
12
  end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetDialogs
6
+ include Binary
7
+
8
+ attr_reader :offset_date, :offset_id, :offset_peer, :limit, :exclude_pinned, :folder_id
9
+
10
+ def initialize(offset_date: 0, offset_id: 0, offset_peer: nil, limit: 100, exclude_pinned: false, folder_id: nil)
11
+ @offset_date = offset_date
12
+ @offset_id = offset_id
13
+ @offset_peer = offset_peer
14
+ @limit = limit
15
+ @exclude_pinned = exclude_pinned
16
+ @folder_id = folder_id
17
+ end
18
+
19
+ def body
20
+ flags = 0
21
+ flags |= (1 << 0) if @exclude_pinned
22
+ flags |= (1 << 1) if @folder_id
23
+
24
+ result = u32_b(Constructors::MESSAGES_GET_DIALOGS) + u32_b(flags)
25
+ result += u32_b(@folder_id) if @folder_id
26
+ result += u32_b(@offset_date) + u32_b(@offset_id)
27
+ result += serialize_input_peer
28
+ result += u32_b(@limit)
29
+ result += u64_b(0) # hash
30
+ result
31
+ end
32
+
33
+ private
34
+
35
+ def serialize_input_peer
36
+ return u32_b(Constructors::INPUT_PEER_EMPTY) unless @offset_peer
37
+
38
+ case @offset_peer[:type]
39
+ when :user
40
+ u32_b(Constructors::INPUT_PEER_USER) + u64_b(@offset_peer[:id]) + u64_b(@offset_peer[:access_hash] || 0)
41
+ when :chat
42
+ u32_b(Constructors::INPUT_PEER_CHAT) + u64_b(@offset_peer[:id])
43
+ when :channel
44
+ u32_b(Constructors::INPUT_PEER_CHANNEL) + u64_b(@offset_peer[:id]) + u64_b(@offset_peer[:access_hash] || 0)
45
+ else
46
+ u32_b(Constructors::INPUT_PEER_EMPTY)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -5,8 +5,6 @@ module MTProto
5
5
  class GetDifference
6
6
  include Binary
7
7
 
8
- CONSTRUCTOR = 0x19c2f763
9
-
10
8
  attr_reader :pts, :date, :qts, :pts_limit, :pts_total_limit, :qts_limit
11
9
 
12
10
  def initialize(pts:, date:, qts:, pts_limit: nil, pts_total_limit: nil, qts_limit: nil)
@@ -24,7 +22,7 @@ module MTProto
24
22
  flags |= (1 << 1) if @pts_total_limit
25
23
  flags |= (1 << 2) if @qts_limit
26
24
 
27
- result = u32_b(CONSTRUCTOR) + u32_b(flags) + u32_b(@pts)
25
+ result = u32_b(Constructors::UPDATES_GET_DIFFERENCE) + u32_b(flags) + u32_b(@pts)
28
26
  result += u32_b(@pts_limit) if @pts_limit
29
27
  result += u32_b(@pts_total_limit) if @pts_total_limit
30
28
  result += u32_b(@date) + u32_b(@qts)