mtproto 0.0.8 → 0.0.10

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 (132) hide show
  1. checksums.yaml +4 -4
  2. data/data/tl-schema.json +42686 -0
  3. data/ext/aes_ige/extconf.rb +3 -9
  4. data/ext/factorization/extconf.rb +2 -0
  5. data/lib/mtproto/auth_key_generator.rb +68 -105
  6. data/lib/mtproto/binary.rb +21 -0
  7. data/lib/mtproto/client/api/check_password.rb +41 -0
  8. data/lib/mtproto/client/api/export_login_token.rb +27 -0
  9. data/lib/mtproto/client/api/get_dialogs.rb +21 -0
  10. data/lib/mtproto/client/api/get_history.rb +20 -0
  11. data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
  12. data/lib/mtproto/client/api/get_updates_state.rb +14 -0
  13. data/lib/mtproto/client/api/get_users.rb +14 -0
  14. data/lib/mtproto/client/api/import_login_token.rb +23 -0
  15. data/lib/mtproto/client/api/send_code.rb +21 -0
  16. data/lib/mtproto/client/api/sign_in.rb +27 -0
  17. data/lib/mtproto/client/api.rb +36 -0
  18. data/lib/mtproto/client/rpc/response.rb +63 -0
  19. data/lib/mtproto/client/rpc.rb +60 -127
  20. data/lib/mtproto/client.rb +143 -32
  21. data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
  22. data/lib/mtproto/crypto/dh_validator.rb +17 -19
  23. data/lib/mtproto/crypto/factorization.rb +1 -1
  24. data/lib/mtproto/crypto/rsa_key.rb +2 -2
  25. data/lib/mtproto/crypto/srp.rb +117 -0
  26. data/lib/mtproto/delegate_methods.rb +11 -0
  27. data/lib/mtproto/errors.rb +8 -0
  28. data/lib/mtproto/message/message.rb +85 -0
  29. data/lib/mtproto/session.rb +1 -1
  30. data/lib/mtproto/tl/constructor_names.rb +2271 -0
  31. data/lib/mtproto/tl/constructors.rb +99 -0
  32. data/lib/mtproto/tl/object.rb +25 -0
  33. data/lib/mtproto/tl/objects/account_password.rb +69 -0
  34. data/lib/mtproto/tl/objects/authorization.rb +70 -0
  35. data/lib/mtproto/tl/objects/check_password.rb +43 -0
  36. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +45 -0
  37. data/lib/mtproto/tl/objects/dh_gen_response.rb +46 -0
  38. data/lib/mtproto/tl/objects/dialogs.rb +453 -0
  39. data/lib/mtproto/tl/objects/export_login_token.rb +48 -0
  40. data/lib/mtproto/tl/objects/get_config.rb +13 -0
  41. data/lib/mtproto/tl/objects/get_dialogs.rb +51 -0
  42. data/lib/mtproto/tl/objects/get_difference.rb +34 -0
  43. data/lib/mtproto/tl/objects/get_history.rb +49 -0
  44. data/lib/mtproto/tl/objects/get_password.rb +13 -0
  45. data/lib/mtproto/tl/objects/get_state.rb +13 -0
  46. data/lib/mtproto/tl/objects/get_users.rb +16 -0
  47. data/lib/mtproto/{type → tl/objects}/gzip_packed.rb +6 -6
  48. data/lib/mtproto/tl/objects/help_config.rb +76 -0
  49. data/lib/mtproto/tl/objects/import_login_token.rb +37 -0
  50. data/lib/mtproto/tl/objects/init_connection.rb +57 -0
  51. data/lib/mtproto/tl/objects/invoke_with_layer.rb +20 -0
  52. data/lib/mtproto/tl/objects/login_token.rb +78 -0
  53. data/lib/mtproto/{type → tl/objects}/message.rb +3 -3
  54. data/lib/mtproto/tl/objects/messages.rb +162 -0
  55. data/lib/mtproto/{type → tl/objects}/msg_container.rb +1 -3
  56. data/lib/mtproto/{type → tl/objects}/new_session_created.rb +1 -3
  57. data/lib/mtproto/tl/objects/pq_inner_data.rb +66 -0
  58. data/lib/mtproto/tl/objects/req_dh_params.rb +63 -0
  59. data/lib/mtproto/tl/objects/req_pq_multi.rb +21 -0
  60. data/lib/mtproto/tl/objects/res_pq.rb +73 -0
  61. data/lib/mtproto/{type → tl/objects}/rpc_error.rb +1 -4
  62. data/lib/mtproto/tl/objects/send_code.rb +47 -0
  63. data/lib/mtproto/tl/objects/sent_code.rb +79 -0
  64. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
  65. data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
  66. data/lib/mtproto/tl/objects/set_client_dh_params.rb +46 -0
  67. data/lib/mtproto/tl/objects/sign_in.rb +45 -0
  68. data/lib/mtproto/tl/objects/update.rb +77 -0
  69. data/lib/mtproto/tl/objects/update_short.rb +20 -0
  70. data/lib/mtproto/tl/objects/update_short_message.rb +65 -0
  71. data/lib/mtproto/tl/objects/updates_difference.rb +152 -0
  72. data/lib/mtproto/tl/objects/updates_state.rb +35 -0
  73. data/lib/mtproto/tl/objects/users.rb +83 -0
  74. data/lib/mtproto/tl/schema.rb +102 -0
  75. data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
  76. data/lib/mtproto/transport/connection.rb +23 -0
  77. data/lib/mtproto/transport/errors.rb +11 -0
  78. data/lib/mtproto/transport/packet.rb +19 -0
  79. data/lib/mtproto/transport/tcp_connection.rb +57 -46
  80. data/lib/mtproto/updates_poller.rb +37 -33
  81. data/lib/mtproto/version.rb +1 -1
  82. data/lib/mtproto.rb +17 -27
  83. data/scripts/generate_constructors.rb +65 -0
  84. metadata +76 -61
  85. data/lib/mtproto/async/middleware/base.rb +0 -17
  86. data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
  87. data/lib/mtproto/async/request.rb +0 -18
  88. data/lib/mtproto/async/request_queue.rb +0 -63
  89. data/lib/mtproto/async_client.rb +0 -201
  90. data/lib/mtproto/rpc/get_config.rb +0 -34
  91. data/lib/mtproto/rpc/get_contacts.rb +0 -29
  92. data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
  93. data/lib/mtproto/rpc/get_updates_state.rb +0 -29
  94. data/lib/mtproto/rpc/get_users.rb +0 -29
  95. data/lib/mtproto/rpc/ping.rb +0 -33
  96. data/lib/mtproto/rpc/send_code.rb +0 -41
  97. data/lib/mtproto/rpc/send_message.rb +0 -47
  98. data/lib/mtproto/rpc/sign_in.rb +0 -48
  99. data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
  100. data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
  101. data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
  102. data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
  103. data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
  104. data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
  105. data/lib/mtproto/type/bad_msg_notification.rb +0 -46
  106. data/lib/mtproto/type/client_dh_inner_data.rb +0 -29
  107. data/lib/mtproto/type/code_settings.rb +0 -25
  108. data/lib/mtproto/type/config.rb +0 -124
  109. data/lib/mtproto/type/pq_inner_data.rb +0 -41
  110. data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
  111. data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
  112. data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
  113. data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
  114. data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
  115. data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
  116. data/lib/mtproto/type/rpc/help/config.rb +0 -35
  117. data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
  118. data/lib/mtproto/type/rpc/init_connection.rb +0 -28
  119. data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
  120. data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
  121. data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
  122. data/lib/mtproto/type/rpc/ping.rb +0 -18
  123. data/lib/mtproto/type/rpc/pong.rb +0 -46
  124. data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
  125. data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
  126. data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
  127. data/lib/mtproto/type/rpc/updates/state.rb +0 -59
  128. data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
  129. data/lib/mtproto/type/rpc/users/users.rb +0 -99
  130. data/lib/mtproto/type/sent_code.rb +0 -128
  131. data/lib/mtproto/type/serializer.rb +0 -55
  132. data/lib/mtproto/type/server_dh_inner_data.rb +0 -85
@@ -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
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ExportLoginToken
6
+ include Binary
7
+
8
+ attr_reader :api_id, :api_hash, :except_ids
9
+
10
+ def initialize(api_id:, api_hash:, except_ids: [])
11
+ @api_id = api_id
12
+ @api_hash = api_hash
13
+ @except_ids = except_ids
14
+ end
15
+
16
+ def body
17
+ u32_b(Constructors::AUTH_EXPORT_LOGIN_TOKEN) +
18
+ u32_b(@api_id) +
19
+ serialize_tl_string(@api_hash) +
20
+ serialize_vector_long(@except_ids)
21
+ end
22
+
23
+ private
24
+
25
+ def serialize_vector_long(ids)
26
+ result = u32_b(Constructors::VECTOR) + u32_b(ids.length)
27
+ ids.each { |id| result += u64_b(id) }
28
+ result
29
+ end
30
+
31
+ def serialize_tl_string(str)
32
+ bytes = str.encode('UTF-8').bytes
33
+ length = bytes.length
34
+
35
+ if length <= 253
36
+ [length] + bytes + padding(length + 1)
37
+ else
38
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
39
+ end
40
+ end
41
+
42
+ def padding(current_length)
43
+ pad_length = (4 - (current_length % 4)) % 4
44
+ [0] * pad_length
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetConfig
6
+ include Binary
7
+
8
+ def body
9
+ u32_b(Constructors::HELP_GET_CONFIG)
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetDifference
6
+ include Binary
7
+
8
+ attr_reader :pts, :date, :qts, :pts_limit, :pts_total_limit, :qts_limit
9
+
10
+ def initialize(pts:, date:, qts:, pts_limit: nil, pts_total_limit: nil, qts_limit: nil)
11
+ @pts = pts
12
+ @date = date
13
+ @qts = qts
14
+ @pts_limit = pts_limit
15
+ @pts_total_limit = pts_total_limit
16
+ @qts_limit = qts_limit
17
+ end
18
+
19
+ def body
20
+ flags = 0
21
+ flags |= (1 << 0) if @pts_limit
22
+ flags |= (1 << 1) if @pts_total_limit
23
+ flags |= (1 << 2) if @qts_limit
24
+
25
+ result = u32_b(Constructors::UPDATES_GET_DIFFERENCE) + u32_b(flags) + u32_b(@pts)
26
+ result += u32_b(@pts_limit) if @pts_limit
27
+ result += u32_b(@pts_total_limit) if @pts_total_limit
28
+ result += u32_b(@date) + u32_b(@qts)
29
+ result += u32_b(@qts_limit) if @qts_limit
30
+ result
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetHistory
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0x4423e6c5
9
+
10
+ def initialize(peer:, offset_id: 0, offset_date: 0, add_offset: 0, limit: 100, max_id: 0, min_id: 0)
11
+ @peer = peer
12
+ @offset_id = offset_id
13
+ @offset_date = offset_date
14
+ @add_offset = add_offset
15
+ @limit = limit
16
+ @max_id = max_id
17
+ @min_id = min_id
18
+ end
19
+
20
+ def body
21
+ result = u32_b(CONSTRUCTOR)
22
+ result += serialize_input_peer
23
+ result += u32_b(@offset_id)
24
+ result += u32_b(@offset_date)
25
+ result += u32_b(@add_offset)
26
+ result += u32_b(@limit)
27
+ result += u32_b(@max_id)
28
+ result += u32_b(@min_id)
29
+ result += u64_b(0) # hash
30
+ result
31
+ end
32
+
33
+ private
34
+
35
+ def serialize_input_peer
36
+ case @peer[:type]
37
+ when :user
38
+ u32_b(Constructors::INPUT_PEER_USER) + u64_b(@peer[:id]) + u64_b(@peer[:access_hash] || 0)
39
+ when :chat
40
+ u32_b(Constructors::INPUT_PEER_CHAT) + u64_b(@peer[:id])
41
+ when :channel
42
+ u32_b(Constructors::INPUT_PEER_CHANNEL) + u64_b(@peer[:id]) + u64_b(@peer[:access_hash] || 0)
43
+ else
44
+ raise "Unknown peer type: #{@peer[:type]}"
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetPassword
6
+ include Binary
7
+
8
+ def body
9
+ u32_b(Constructors::ACCOUNT_GET_PASSWORD)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetState
6
+ include Binary
7
+
8
+ def body
9
+ u32_b(Constructors::UPDATES_GET_STATE)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class GetUsers
6
+ include Binary
7
+
8
+ def body
9
+ u32_b(Constructors::USERS_GET_USERS) +
10
+ u32_b(Constructors::VECTOR) +
11
+ u32_b(1) +
12
+ u32_b(Constructors::INPUT_USER_SELF)
13
+ end
14
+ end
15
+ end
16
+ end