mtproto 0.0.32 → 0.0.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/mtproto/auth_key_generator.rb +3 -1
- data/lib/mtproto/client/api/resolve_username.rb +18 -0
- data/lib/mtproto/client/api.rb +1 -0
- data/lib/mtproto/crypto/dh_key_exchange.rb +2 -1
- data/lib/mtproto/tl/constructors.rb +1 -0
- data/lib/mtproto/tl/objects/authorization.rb +4 -3
- data/lib/mtproto/tl/objects/channel_difference.rb +13 -32
- data/lib/mtproto/tl/objects/contacts.rb +7 -89
- data/lib/mtproto/tl/objects/dialogs.rb +14 -352
- data/lib/mtproto/tl/objects/edit_message.rb +1 -1
- data/lib/mtproto/tl/objects/message.rb +14 -0
- data/lib/mtproto/tl/objects/message_service.rb +40 -0
- data/lib/mtproto/tl/objects/messages.rb +7 -32
- data/lib/mtproto/tl/objects/resolved_peer.rb +29 -0
- data/lib/mtproto/tl/objects/send_message.rb +1 -1
- data/lib/mtproto/tl/objects/update.rb +3 -35
- data/lib/mtproto/tl/objects/updates.rb +4 -6
- data/lib/mtproto/tl/objects/updates_difference.rb +9 -29
- data/lib/mtproto/tl/peers.rb +233 -0
- data/lib/mtproto/updates/event_builder.rb +20 -32
- data/lib/mtproto/updates/parser.rb +9 -8
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +1 -0
- metadata +5 -1
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../
|
|
3
|
+
require_relative '../reader'
|
|
4
|
+
require_relative '../peers'
|
|
5
|
+
require_relative 'message'
|
|
6
|
+
require_relative 'message_service'
|
|
4
7
|
|
|
5
8
|
module MTProto
|
|
6
9
|
module TL
|
|
7
10
|
class Dialogs
|
|
8
11
|
Dialog = Struct.new(:peer_type, :peer_id, :top_message, :unread_count, :pts, keyword_init: true)
|
|
9
|
-
Chat = Struct.new(:id, :title, :type, :access_hash, :username, keyword_init: true)
|
|
10
|
-
|
|
11
|
-
SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
|
|
12
12
|
|
|
13
13
|
attr_reader :dialogs, :chats, :users, :count, :message_dates
|
|
14
14
|
|
|
@@ -24,10 +24,6 @@ module MTProto
|
|
|
24
24
|
!@count.nil?
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def self.schema
|
|
28
|
-
@schema ||= Schema.new(SCHEMA_PATH)
|
|
29
|
-
end
|
|
30
|
-
|
|
31
27
|
def self.deserialize(data)
|
|
32
28
|
constructor = data[0, 4].unpack1('L<')
|
|
33
29
|
|
|
@@ -45,22 +41,12 @@ module MTProto
|
|
|
45
41
|
|
|
46
42
|
dialogs, offset = parse_dialogs_vector(data, offset)
|
|
47
43
|
message_dates, offset = parse_messages_vector(data, offset)
|
|
48
|
-
chats, offset =
|
|
49
|
-
users, =
|
|
44
|
+
chats, offset = Peers.chats_at(data, offset)
|
|
45
|
+
users, = Peers.users_at(data, offset)
|
|
50
46
|
|
|
51
47
|
new(dialogs: dialogs, chats: chats, users: users, count: count, message_dates: message_dates)
|
|
52
48
|
end
|
|
53
49
|
|
|
54
|
-
# Parse a chats:Vector<Chat> / users:Vector<User> at an arbitrary offset.
|
|
55
|
-
# Reused by UpdatesDifference — the same vectors appear in updates.difference.
|
|
56
|
-
def self.chats_at(data, offset)
|
|
57
|
-
parse_chats_vector(data, offset)
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def self.users_at(data, offset)
|
|
61
|
-
parse_users_vector(data, offset)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
50
|
class << self
|
|
65
51
|
private
|
|
66
52
|
|
|
@@ -84,14 +70,14 @@ module MTProto
|
|
|
84
70
|
offset += 4
|
|
85
71
|
|
|
86
72
|
unless constructor == Constructors::DIALOG
|
|
87
|
-
offset = schema.skip_params(data, offset, constructor)
|
|
73
|
+
offset = Reader.schema.skip_params(data, offset, constructor)
|
|
88
74
|
return [nil, offset]
|
|
89
75
|
end
|
|
90
76
|
|
|
91
77
|
flags = data[offset, 4].unpack1('L<')
|
|
92
78
|
offset += 4
|
|
93
79
|
|
|
94
|
-
peer_type, peer_id, offset = parse_peer(data, offset)
|
|
80
|
+
peer_type, peer_id, offset = Reader.parse_peer(data, offset)
|
|
95
81
|
|
|
96
82
|
top_message = data[offset, 4].unpack1('L<')
|
|
97
83
|
offset += 4
|
|
@@ -106,14 +92,14 @@ module MTProto
|
|
|
106
92
|
offset += 4 # unread_reactions_count
|
|
107
93
|
offset += 4 # unread_poll_votes_count (added in layer 227)
|
|
108
94
|
|
|
109
|
-
offset = schema.skip(data, offset) # notify_settings (PeerNotifySettings)
|
|
95
|
+
offset = Reader.schema.skip(data, offset) # notify_settings (PeerNotifySettings)
|
|
110
96
|
|
|
111
97
|
pts = nil
|
|
112
98
|
if flags.anybits?(1 << 0)
|
|
113
99
|
pts = data[offset, 4].unpack1('L<')
|
|
114
100
|
offset += 4
|
|
115
101
|
end
|
|
116
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 1) # draft (DraftMessage)
|
|
102
|
+
offset = Reader.schema.skip(data, offset) if flags.anybits?(1 << 1) # draft (DraftMessage)
|
|
117
103
|
offset += 4 if flags.anybits?(1 << 4) # folder_id
|
|
118
104
|
offset += 4 if flags.anybits?(1 << 5) # ttl_period
|
|
119
105
|
|
|
@@ -121,39 +107,8 @@ module MTProto
|
|
|
121
107
|
top_message: top_message, unread_count: unread_count, pts: pts), offset]
|
|
122
108
|
end
|
|
123
109
|
|
|
124
|
-
def parse_peer(data, offset)
|
|
125
|
-
constructor = data[offset, 4].unpack1('L<')
|
|
126
|
-
offset += 4
|
|
127
|
-
|
|
128
|
-
case constructor
|
|
129
|
-
when Constructors::PEER_USER
|
|
130
|
-
[:user, data[offset, 8].unpack1('Q<'), offset + 8]
|
|
131
|
-
when Constructors::PEER_CHAT
|
|
132
|
-
[:chat, data[offset, 8].unpack1('Q<'), offset + 8]
|
|
133
|
-
when Constructors::PEER_CHANNEL
|
|
134
|
-
[:channel, data[offset, 8].unpack1('Q<'), offset + 8]
|
|
135
|
-
else
|
|
136
|
-
raise "Unknown peer constructor: 0x#{constructor.to_s(16)}"
|
|
137
|
-
end
|
|
138
|
-
end
|
|
139
|
-
|
|
140
110
|
# --- Messages vector ---
|
|
141
111
|
|
|
142
|
-
# message#7600b9d3 flags:# flags2:#
|
|
143
|
-
# id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int
|
|
144
|
-
# from_rank:flags2.12?string peer_id:Peer saved_peer_id:flags.28?Peer
|
|
145
|
-
# fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long
|
|
146
|
-
# via_business_bot_id:flags2.0?long guestchat_via_from:flags2.19?Peer
|
|
147
|
-
# reply_to:flags.3?MessageReplyHeader date:int ...
|
|
148
|
-
#
|
|
149
|
-
# messageService#2b085862 flags:#
|
|
150
|
-
# id:int from_id:flags.8?Peer peer_id:Peer
|
|
151
|
-
# saved_peer_id:flags.28?Peer reply_to:flags.3?MessageReplyHeader
|
|
152
|
-
# date:int ...
|
|
153
|
-
MESSAGE_CONSTRUCTOR = Constructors::MESSAGE
|
|
154
|
-
MESSAGE_SERVICE = 0x7a800e0a
|
|
155
|
-
MESSAGE_EMPTY = 0x90a6ca84
|
|
156
|
-
|
|
157
112
|
def parse_messages_vector(data, offset)
|
|
158
113
|
offset += 4 # vector constructor
|
|
159
114
|
count = data[offset, 4].unpack1('L<')
|
|
@@ -165,308 +120,15 @@ module MTProto
|
|
|
165
120
|
id_date = extract_message_id_date(data, offset, constructor)
|
|
166
121
|
dates[id_date[0]] = id_date[1] if id_date
|
|
167
122
|
|
|
168
|
-
offset = schema.skip(data, offset)
|
|
123
|
+
offset = Reader.schema.skip(data, offset)
|
|
169
124
|
end
|
|
170
125
|
[dates, offset]
|
|
171
126
|
end
|
|
172
127
|
|
|
173
128
|
def extract_message_id_date(data, offset, constructor)
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
when MESSAGE_SERVICE
|
|
178
|
-
extract_message_service_date(data, offset)
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def extract_message_date(data, offset)
|
|
183
|
-
offset += 4 # constructor
|
|
184
|
-
flags = data[offset, 4].unpack1('L<')
|
|
185
|
-
offset += 4
|
|
186
|
-
flags2 = data[offset, 4].unpack1('L<')
|
|
187
|
-
offset += 4
|
|
188
|
-
id = data[offset, 4].unpack1('l<')
|
|
189
|
-
offset += 4
|
|
190
|
-
|
|
191
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
|
|
192
|
-
offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
|
|
193
|
-
_, offset = read_tl_string(data, offset) if flags2.anybits?(1 << 12) # from_rank (layer 227)
|
|
194
|
-
offset = schema.skip(data, offset) # peer_id (Peer, always present)
|
|
195
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
|
|
196
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 2) # fwd_from
|
|
197
|
-
offset += 8 if flags.anybits?(1 << 11) # via_bot_id
|
|
198
|
-
offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
|
|
199
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 19) # guestchat_via_from (layer 227, Peer)
|
|
200
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
|
|
201
|
-
|
|
202
|
-
date = data[offset, 4].unpack1('L<')
|
|
203
|
-
[id, date]
|
|
204
|
-
end
|
|
205
|
-
|
|
206
|
-
def extract_message_service_date(data, offset)
|
|
207
|
-
offset += 4 # constructor
|
|
208
|
-
flags = data[offset, 4].unpack1('L<')
|
|
209
|
-
offset += 4
|
|
210
|
-
id = data[offset, 4].unpack1('l<')
|
|
211
|
-
offset += 4
|
|
212
|
-
|
|
213
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
|
|
214
|
-
offset = schema.skip(data, offset) # peer_id (Peer, always present)
|
|
215
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
|
|
216
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
|
|
217
|
-
|
|
218
|
-
date = data[offset, 4].unpack1('L<')
|
|
219
|
-
[id, date]
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# --- Chats vector ---
|
|
223
|
-
|
|
224
|
-
def parse_chats_vector(data, offset)
|
|
225
|
-
offset += 4 # vector constructor
|
|
226
|
-
count = data[offset, 4].unpack1('L<')
|
|
227
|
-
offset += 4
|
|
228
|
-
|
|
229
|
-
chats = []
|
|
230
|
-
count.times do
|
|
231
|
-
chat, offset = parse_chat(data, offset)
|
|
232
|
-
chats << chat if chat
|
|
233
|
-
end
|
|
234
|
-
[chats, offset]
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def parse_chat(data, offset)
|
|
238
|
-
constructor = data[offset, 4].unpack1('L<')
|
|
239
|
-
offset += 4
|
|
240
|
-
|
|
241
|
-
case constructor
|
|
242
|
-
when Constructors::CHAT
|
|
243
|
-
parse_basic_chat(data, offset)
|
|
244
|
-
when Constructors::CHANNEL
|
|
245
|
-
parse_channel(data, offset)
|
|
246
|
-
when Constructors::CHAT_FORBIDDEN
|
|
247
|
-
parse_chat_forbidden(data, offset)
|
|
248
|
-
when Constructors::CHANNEL_FORBIDDEN
|
|
249
|
-
parse_channel_forbidden(data, offset)
|
|
250
|
-
when CHAT_EMPTY
|
|
251
|
-
id = data[offset, 8].unpack1('Q<')
|
|
252
|
-
offset += 8
|
|
253
|
-
[Chat.new(id: id, title: '(empty)', type: :chat_empty), offset]
|
|
254
|
-
else
|
|
255
|
-
raise "Unknown Chat constructor: 0x#{constructor.to_s(16).rjust(8, '0')}"
|
|
256
|
-
end
|
|
257
|
-
end
|
|
258
|
-
|
|
259
|
-
CHAT_EMPTY = 0x29562865
|
|
260
|
-
|
|
261
|
-
# chat#41cbf256 flags:#
|
|
262
|
-
# id:long title:string photo:ChatPhoto
|
|
263
|
-
# participants_count:int date:int version:int
|
|
264
|
-
# migrated_to:flags.6?InputChannel
|
|
265
|
-
# admin_rights:flags.14?ChatAdminRights
|
|
266
|
-
# default_banned_rights:flags.18?ChatBannedRights
|
|
267
|
-
def parse_basic_chat(data, offset)
|
|
268
|
-
flags = data[offset, 4].unpack1('L<')
|
|
269
|
-
offset += 4
|
|
270
|
-
id = data[offset, 8].unpack1('Q<')
|
|
271
|
-
offset += 8
|
|
272
|
-
title, offset = read_tl_string(data, offset)
|
|
273
|
-
offset = schema.skip(data, offset) # photo (ChatPhoto)
|
|
274
|
-
offset += 4 # participants_count
|
|
275
|
-
offset += 4 # date
|
|
276
|
-
offset += 4 # version
|
|
277
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 6) # migrated_to (InputChannel)
|
|
278
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 14) # admin_rights
|
|
279
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 18) # default_banned_rights
|
|
280
|
-
|
|
281
|
-
[Chat.new(id: id, title: title, type: :chat), offset]
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
# channel#1c32b11c flags:# flags2:#
|
|
285
|
-
# id:long access_hash:flags.13?long title:string
|
|
286
|
-
# username:flags.6?string photo:ChatPhoto date:int
|
|
287
|
-
# restriction_reason:flags.9?Vector<RestrictionReason>
|
|
288
|
-
# admin_rights:flags.14?ChatAdminRights
|
|
289
|
-
# banned_rights:flags.15?ChatBannedRights
|
|
290
|
-
# default_banned_rights:flags.18?ChatBannedRights
|
|
291
|
-
# participants_count:flags.17?int
|
|
292
|
-
# usernames:flags2.0?Vector<Username>
|
|
293
|
-
# stories_max_id:flags2.4?int
|
|
294
|
-
# color:flags2.7?PeerColor profile_color:flags2.8?PeerColor
|
|
295
|
-
# emoji_status:flags2.9?EmojiStatus
|
|
296
|
-
# level:flags2.10?int subscription_until_date:flags2.11?int
|
|
297
|
-
# bot_verification_icon:flags2.13?long
|
|
298
|
-
# send_paid_messages_stars:flags2.14?long
|
|
299
|
-
# linked_monoforum_id:flags2.18?long
|
|
300
|
-
def parse_channel(data, offset)
|
|
301
|
-
flags = data[offset, 4].unpack1('L<')
|
|
302
|
-
offset += 4
|
|
303
|
-
flags2 = data[offset, 4].unpack1('L<')
|
|
304
|
-
offset += 4
|
|
305
|
-
id = data[offset, 8].unpack1('Q<')
|
|
306
|
-
offset += 8
|
|
307
|
-
access_hash = nil
|
|
308
|
-
if flags.anybits?(1 << 13)
|
|
309
|
-
access_hash = data[offset, 8].unpack1('Q<')
|
|
310
|
-
offset += 8
|
|
311
|
-
end
|
|
312
|
-
title, offset = read_tl_string(data, offset)
|
|
313
|
-
username = nil
|
|
314
|
-
username, offset = read_tl_string(data, offset) if flags.anybits?(1 << 6) # username
|
|
315
|
-
offset = schema.skip(data, offset) # photo (ChatPhoto)
|
|
316
|
-
offset += 4 # date
|
|
317
|
-
if flags.anybits?(1 << 9) # restriction_reason Vector<RestrictionReason>
|
|
318
|
-
offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) }
|
|
319
|
-
end
|
|
320
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 14) # admin_rights
|
|
321
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 15) # banned_rights
|
|
322
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 18) # default_banned_rights
|
|
323
|
-
offset += 4 if flags.anybits?(1 << 17) # participants_count
|
|
324
|
-
if flags2.anybits?(1 << 0) # usernames Vector<Username>
|
|
325
|
-
offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) }
|
|
326
|
-
end
|
|
327
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 4) # stories_max_id (layer 227: RecentStory)
|
|
328
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 7) # color (PeerColor)
|
|
329
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 8) # profile_color (PeerColor)
|
|
330
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 9) # emoji_status
|
|
331
|
-
offset += 4 if flags2.anybits?(1 << 10) # level
|
|
332
|
-
offset += 4 if flags2.anybits?(1 << 11) # subscription_until_date
|
|
333
|
-
offset += 8 if flags2.anybits?(1 << 13) # bot_verification_icon (long)
|
|
334
|
-
offset += 8 if flags2.anybits?(1 << 14) # send_paid_messages_stars (long)
|
|
335
|
-
offset += 8 if flags2.anybits?(1 << 18) # linked_monoforum_id (long)
|
|
336
|
-
|
|
337
|
-
type = flags.anybits?(1 << 8) ? :supergroup : :channel
|
|
338
|
-
[Chat.new(id: id, title: title, type: type, access_hash: access_hash, username: username), offset]
|
|
339
|
-
end
|
|
340
|
-
|
|
341
|
-
# chatForbidden#6592a1a7 id:long title:string
|
|
342
|
-
def parse_chat_forbidden(data, offset)
|
|
343
|
-
id = data[offset, 8].unpack1('Q<')
|
|
344
|
-
offset += 8
|
|
345
|
-
title, offset = read_tl_string(data, offset)
|
|
346
|
-
[Chat.new(id: id, title: title, type: :chat_forbidden), offset]
|
|
347
|
-
end
|
|
348
|
-
|
|
349
|
-
# channelForbidden#17d493d5 flags:#
|
|
350
|
-
# id:long access_hash:long title:string until_date:flags.16?int
|
|
351
|
-
def parse_channel_forbidden(data, offset)
|
|
352
|
-
flags = data[offset, 4].unpack1('L<')
|
|
353
|
-
offset += 4
|
|
354
|
-
id = data[offset, 8].unpack1('Q<')
|
|
355
|
-
offset += 8
|
|
356
|
-
access_hash = data[offset, 8].unpack1('Q<')
|
|
357
|
-
offset += 8
|
|
358
|
-
title, offset = read_tl_string(data, offset)
|
|
359
|
-
offset += 4 if flags.anybits?(1 << 16) # until_date
|
|
360
|
-
[Chat.new(id: id, title: title, type: :channel_forbidden, access_hash: access_hash), offset]
|
|
361
|
-
end
|
|
362
|
-
|
|
363
|
-
# --- Users vector ---
|
|
364
|
-
|
|
365
|
-
def parse_users_vector(data, offset)
|
|
366
|
-
offset += 4 # vector constructor
|
|
367
|
-
count = data[offset, 4].unpack1('L<')
|
|
368
|
-
offset += 4
|
|
369
|
-
|
|
370
|
-
users = []
|
|
371
|
-
count.times do
|
|
372
|
-
user, offset = parse_user(data, offset)
|
|
373
|
-
users << user if user
|
|
374
|
-
end
|
|
375
|
-
[users, offset]
|
|
376
|
-
end
|
|
377
|
-
|
|
378
|
-
USER_EMPTY = 0xd3bc4b7a
|
|
379
|
-
|
|
380
|
-
# user#31774388 flags:# flags2:#
|
|
381
|
-
# id:long access_hash:flags.0?long
|
|
382
|
-
# first_name:flags.1?string last_name:flags.2?string
|
|
383
|
-
# username:flags.3?string phone:flags.4?string
|
|
384
|
-
# photo:flags.5?UserProfilePhoto status:flags.6?UserStatus
|
|
385
|
-
# bot_info_version:flags.14?int
|
|
386
|
-
# restriction_reason:flags.18?Vector<RestrictionReason>
|
|
387
|
-
# bot_inline_placeholder:flags.19?string
|
|
388
|
-
# lang_code:flags.22?string
|
|
389
|
-
# emoji_status:flags.30?EmojiStatus
|
|
390
|
-
# usernames:flags2.0?Vector<Username>
|
|
391
|
-
# stories_max_id:flags2.5?int
|
|
392
|
-
# color:flags2.8?PeerColor profile_color:flags2.9?PeerColor
|
|
393
|
-
# bot_active_users:flags2.12?int
|
|
394
|
-
# bot_verification_icon:flags2.14?long
|
|
395
|
-
# send_paid_messages_stars:flags2.15?long
|
|
396
|
-
def parse_user(data, offset)
|
|
397
|
-
constructor = data[offset, 4].unpack1('L<')
|
|
398
|
-
offset += 4
|
|
399
|
-
|
|
400
|
-
if constructor == USER_EMPTY
|
|
401
|
-
offset += 8 # id (long)
|
|
402
|
-
return [nil, offset]
|
|
403
|
-
end
|
|
404
|
-
|
|
405
|
-
raise "Unknown User constructor: 0x#{constructor.to_s(16)}" unless constructor == Constructors::USER
|
|
406
|
-
|
|
407
|
-
flags = data[offset, 4].unpack1('L<')
|
|
408
|
-
offset += 4
|
|
409
|
-
flags2 = data[offset, 4].unpack1('L<')
|
|
410
|
-
offset += 4
|
|
411
|
-
id = data[offset, 8].unpack1('Q<')
|
|
412
|
-
offset += 8
|
|
413
|
-
access_hash = nil
|
|
414
|
-
if flags.anybits?(1 << 0)
|
|
415
|
-
access_hash = data[offset, 8].unpack1('Q<')
|
|
416
|
-
offset += 8
|
|
417
|
-
end
|
|
418
|
-
|
|
419
|
-
first_name = nil
|
|
420
|
-
first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
|
|
421
|
-
|
|
422
|
-
last_name = nil
|
|
423
|
-
last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
|
|
424
|
-
|
|
425
|
-
username = nil
|
|
426
|
-
username, offset = read_tl_string(data, offset) if flags.anybits?(1 << 3) # username
|
|
427
|
-
|
|
428
|
-
offset = skip_tl_string(data, offset) if flags.anybits?(1 << 4) # phone
|
|
429
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 5) # photo
|
|
430
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 6) # status
|
|
431
|
-
offset += 4 if flags.anybits?(1 << 14) # bot_info_version
|
|
432
|
-
if flags.anybits?(1 << 18) # restriction_reason
|
|
433
|
-
offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) }
|
|
434
|
-
end
|
|
435
|
-
offset = skip_tl_string(data, offset) if flags.anybits?(1 << 19) # bot_inline_placeholder
|
|
436
|
-
offset = skip_tl_string(data, offset) if flags.anybits?(1 << 22) # lang_code
|
|
437
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 30) # emoji_status
|
|
438
|
-
offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if flags2.anybits?(1 << 0) # usernames
|
|
439
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 5) # stories_max_id (layer 227: RecentStory)
|
|
440
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 8) # color
|
|
441
|
-
offset = schema.skip(data, offset) if flags2.anybits?(1 << 9) # profile_color
|
|
442
|
-
offset += 4 if flags2.anybits?(1 << 12) # bot_active_users
|
|
443
|
-
offset += 8 if flags2.anybits?(1 << 14) # bot_verification_icon
|
|
444
|
-
offset += 8 if flags2.anybits?(1 << 15) # send_paid_messages_stars
|
|
445
|
-
|
|
446
|
-
[{ id: id, first_name: first_name, last_name: last_name, username: username,
|
|
447
|
-
is_bot: flags.anybits?(1 << 14), access_hash: access_hash }, offset]
|
|
448
|
-
end
|
|
449
|
-
|
|
450
|
-
# --- TL string ---
|
|
451
|
-
|
|
452
|
-
def read_tl_string(data, offset)
|
|
453
|
-
first_byte = data.getbyte(offset)
|
|
454
|
-
if first_byte == 254
|
|
455
|
-
len = data.getbyte(offset + 1) |
|
|
456
|
-
(data.getbyte(offset + 2) << 8) |
|
|
457
|
-
(data.getbyte(offset + 3) << 16)
|
|
458
|
-
total = 4 + len
|
|
459
|
-
else
|
|
460
|
-
len = first_byte
|
|
461
|
-
total = 1 + len
|
|
462
|
-
end
|
|
463
|
-
padding = (4 - (total % 4)) % 4
|
|
464
|
-
[data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
|
|
465
|
-
end
|
|
466
|
-
|
|
467
|
-
def skip_tl_string(data, offset)
|
|
468
|
-
_, new_offset = read_tl_string(data, offset)
|
|
469
|
-
new_offset
|
|
129
|
+
msg = Message.deserialize(data, offset, constructor) ||
|
|
130
|
+
MessageService.deserialize(data, offset, constructor)
|
|
131
|
+
[msg.id, msg.date] if msg
|
|
470
132
|
end
|
|
471
133
|
end
|
|
472
134
|
end
|
|
@@ -92,6 +92,20 @@ module MTProto
|
|
|
92
92
|
fwd_channel_post: fwd_channel_post)
|
|
93
93
|
end
|
|
94
94
|
|
|
95
|
+
def vector_at(data, offset)
|
|
96
|
+
offset += 4 # vector constructor
|
|
97
|
+
count = data[offset, 4].unpack1('L<')
|
|
98
|
+
offset += 4
|
|
99
|
+
|
|
100
|
+
messages = []
|
|
101
|
+
count.times do
|
|
102
|
+
msg = deserialize(data, offset, data[offset, 4].unpack1('L<'))
|
|
103
|
+
messages << msg if msg
|
|
104
|
+
offset = Reader.schema.skip(data, offset)
|
|
105
|
+
end
|
|
106
|
+
[messages, offset]
|
|
107
|
+
end
|
|
108
|
+
|
|
95
109
|
private
|
|
96
110
|
|
|
97
111
|
# entities (flags.7) sit past media (flags.9) and reply_markup (flags.6) in the
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../reader'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
# The header of `messageService#7a800e0a` — the chat event (a join, a leave, a
|
|
8
|
+
# title change) that rides the same Vector<Message> as a plain `message`. The
|
|
9
|
+
# walk stops at the MessageAction, whose meaning belongs to the caller: it reads
|
|
10
|
+
# the action itself, from `action_offset`.
|
|
11
|
+
module MessageService
|
|
12
|
+
module_function
|
|
13
|
+
|
|
14
|
+
MESSAGE_SERVICE = 0x7a800e0a
|
|
15
|
+
|
|
16
|
+
Header = Struct.new(:id, :date, :from_id, :peer_type, :peer_id, :action_offset, keyword_init: true)
|
|
17
|
+
|
|
18
|
+
def deserialize(data, offset, constructor)
|
|
19
|
+
return unless constructor == MESSAGE_SERVICE
|
|
20
|
+
|
|
21
|
+
offset += 4 # constructor
|
|
22
|
+
flags = data[offset, 4].unpack1('L<')
|
|
23
|
+
offset += 4
|
|
24
|
+
id = data[offset, 4].unpack1('l<')
|
|
25
|
+
offset += 4
|
|
26
|
+
|
|
27
|
+
from_id = nil
|
|
28
|
+
_from_type, from_id, offset = Reader.parse_peer(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
|
|
29
|
+
peer_type, peer_id, offset = Reader.parse_peer(data, offset)
|
|
30
|
+
offset = Reader.schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
|
|
31
|
+
offset = Reader.schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
|
|
32
|
+
|
|
33
|
+
date = data[offset, 4].unpack1('L<')
|
|
34
|
+
|
|
35
|
+
Header.new(id: id, date: date, from_id: from_id,
|
|
36
|
+
peer_type: peer_type, peer_id: peer_id, action_offset: offset + 4)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative '../
|
|
3
|
+
require_relative '../reader'
|
|
4
|
+
require_relative '../peers'
|
|
4
5
|
require_relative 'message'
|
|
5
|
-
require_relative 'dialogs'
|
|
6
6
|
|
|
7
7
|
module MTProto
|
|
8
8
|
module TL
|
|
@@ -29,12 +29,6 @@ module MTProto
|
|
|
29
29
|
!@count.nil?
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
def self.schema
|
|
33
|
-
@schema ||= Schema.new(
|
|
34
|
-
File.expand_path('../../../../data/tl-schema.json', __dir__)
|
|
35
|
-
)
|
|
36
|
-
end
|
|
37
|
-
|
|
38
32
|
def self.deserialize(data)
|
|
39
33
|
constructor = data[0, 4].unpack1('L<')
|
|
40
34
|
raise UnexpectedConstructorError, constructor unless VALID_CONSTRUCTORS.include?(constructor)
|
|
@@ -54,7 +48,7 @@ module MTProto
|
|
|
54
48
|
offset += 4
|
|
55
49
|
offset += 4 if flags.anybits?(1 << 0) # next_rate
|
|
56
50
|
offset += 4 if flags.anybits?(1 << 2) # offset_id_offset
|
|
57
|
-
offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # search_flood
|
|
51
|
+
offset = Reader.schema.skip(data, offset) if flags.anybits?(1 << 3) # search_flood
|
|
58
52
|
when MESSAGES_CHANNEL
|
|
59
53
|
# flags:# pts:int count:int offset_id_offset:flags.2?int
|
|
60
54
|
# messages:Vector<Message> topics:Vector<ForumTopic> ...
|
|
@@ -66,35 +60,16 @@ module MTProto
|
|
|
66
60
|
offset += 4 if flags.anybits?(1 << 2) # offset_id_offset
|
|
67
61
|
end
|
|
68
62
|
|
|
69
|
-
messages, offset =
|
|
63
|
+
messages, offset = Message.vector_at(data, offset)
|
|
70
64
|
|
|
71
65
|
# topics:Vector<ForumTopic> precedes chats/users in all three constructors
|
|
72
|
-
offset = schema.skip_vector(data, offset)
|
|
66
|
+
offset = Reader.schema.skip_vector(data, offset)
|
|
73
67
|
|
|
74
|
-
chats, offset =
|
|
75
|
-
users, =
|
|
68
|
+
chats, offset = Peers.chats_at(data, offset)
|
|
69
|
+
users, = Peers.users_at(data, offset)
|
|
76
70
|
|
|
77
71
|
new(messages: messages, count: count, users: users, chats: chats)
|
|
78
72
|
end
|
|
79
|
-
|
|
80
|
-
class << self
|
|
81
|
-
private
|
|
82
|
-
|
|
83
|
-
def parse_messages_vector(data, offset)
|
|
84
|
-
offset += 4 # vector constructor
|
|
85
|
-
count = data[offset, 4].unpack1('L<')
|
|
86
|
-
offset += 4
|
|
87
|
-
|
|
88
|
-
messages = []
|
|
89
|
-
count.times do
|
|
90
|
-
constructor = data[offset, 4].unpack1('L<')
|
|
91
|
-
msg = Message.deserialize(data, offset, constructor)
|
|
92
|
-
messages << msg if msg
|
|
93
|
-
offset = schema.skip(data, offset)
|
|
94
|
-
end
|
|
95
|
-
[messages, offset]
|
|
96
|
-
end
|
|
97
|
-
end
|
|
98
73
|
end
|
|
99
74
|
end
|
|
100
75
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../reader'
|
|
4
|
+
require_relative '../peers'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
module TL
|
|
8
|
+
class ResolvedPeer
|
|
9
|
+
def self.deserialize(data)
|
|
10
|
+
constructor = data[0, 4].unpack1('L<')
|
|
11
|
+
raise UnexpectedConstructorError, constructor unless constructor == Constructors::CONTACTS_RESOLVED_PEER
|
|
12
|
+
|
|
13
|
+
type, id, offset = Reader.parse_peer(data, 4)
|
|
14
|
+
chats, offset = Peers.chats_at(data, offset)
|
|
15
|
+
users, = Peers.users_at(data, offset)
|
|
16
|
+
|
|
17
|
+
{ type: type, id: id, access_hash: access_hash_for(type, id, chats, users) }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def self.access_hash_for(type, id, chats, users)
|
|
21
|
+
return chats.find { |chat| chat.id == id }&.access_hash unless type == :user
|
|
22
|
+
|
|
23
|
+
user = users.find { |candidate| candidate[:id] == id }
|
|
24
|
+
user && user[:access_hash]
|
|
25
|
+
end
|
|
26
|
+
private_class_method :access_hash_for
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'message'
|
|
4
|
+
|
|
3
5
|
module MTProto
|
|
4
6
|
module TL
|
|
5
7
|
class Update
|
|
@@ -50,41 +52,7 @@ module MTProto
|
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
def extract_new_message_text(bytes)
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
msg_constructor = bytes[offset, 4].unpack1('L<')
|
|
56
|
-
return nil unless msg_constructor == Constructors::MESSAGE
|
|
57
|
-
|
|
58
|
-
offset += 4
|
|
59
|
-
flags = bytes[offset, 4].unpack1('L<')
|
|
60
|
-
offset += 4
|
|
61
|
-
flags2 = bytes[offset, 4].unpack1('L<')
|
|
62
|
-
offset += 4
|
|
63
|
-
offset += 4 # id
|
|
64
|
-
|
|
65
|
-
offset += 12 if flags & (1 << 8) != 0 # from_id: Peer
|
|
66
|
-
offset += 12 # peer_id: Peer
|
|
67
|
-
offset += 12 if flags & (1 << 28) != 0 # saved_peer_id: Peer
|
|
68
|
-
return nil if flags & (1 << 2) != 0 # fwd_from: variable length
|
|
69
|
-
|
|
70
|
-
offset += 8 if flags & (1 << 11) != 0 # via_bot_id: long
|
|
71
|
-
offset += 8 if flags2 & 1 != 0 # via_business_bot_id: long
|
|
72
|
-
return nil if flags & (1 << 3) != 0 # reply_to: variable length
|
|
73
|
-
|
|
74
|
-
offset += 4 # date
|
|
75
|
-
read_tl_string(bytes, offset)
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def read_tl_string(bytes, offset)
|
|
79
|
-
first_byte = bytes.getbyte(offset)
|
|
80
|
-
if first_byte == 254
|
|
81
|
-
len = bytes.getbyte(offset + 1) |
|
|
82
|
-
(bytes.getbyte(offset + 2) << 8) |
|
|
83
|
-
(bytes.getbyte(offset + 3) << 16)
|
|
84
|
-
bytes[offset + 4, len]
|
|
85
|
-
else
|
|
86
|
-
bytes[offset + 1, first_byte]
|
|
87
|
-
end
|
|
55
|
+
Message.deserialize(bytes, 4, bytes[4, 4].unpack1('L<'))&.text
|
|
88
56
|
end
|
|
89
57
|
end
|
|
90
58
|
end
|