mtproto 0.0.32 → 0.0.34
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/docs/test_architecture_level1.md +23 -107
- data/lib/mtproto/auth_key_generator.rb +3 -1
- data/lib/mtproto/client/api/resolve_username.rb +18 -0
- data/lib/mtproto/client/api/send_message.rb +5 -2
- data/lib/mtproto/client/api/set_typing.rb +4 -4
- data/lib/mtproto/client/api/stream_draft.rb +13 -0
- data/lib/mtproto/client/api.rb +2 -0
- data/lib/mtproto/crypto/dh_key_exchange.rb +2 -1
- data/lib/mtproto/draft_stream.rb +52 -0
- 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/media_parts.rb +68 -0
- 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/reply_keyboard_force_reply.rb +46 -0
- 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/send_message_text_draft_action.rb +52 -0
- data/lib/mtproto/tl/objects/send_multi_media.rb +52 -0
- 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/objects/upload_media.rb +26 -0
- data/lib/mtproto/tl/peers.rb +236 -0
- data/lib/mtproto/tl/reader.rb +29 -2
- data/lib/mtproto/updates/event_builder.rb +20 -32
- data/lib/mtproto/updates/parser.rb +9 -8
- data/lib/mtproto/updates/state_machine.rb +7 -7
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +1 -0
- metadata +12 -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
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
# The pieces every media-carrying call writes the same way: who it is addressed to,
|
|
6
|
+
# the file just uploaded, and the strings around it.
|
|
7
|
+
#
|
|
8
|
+
# Held together here because an album is sent in two calls — one to turn each upload
|
|
9
|
+
# into a photo Telegram holds, one to send them as a group — and both write the same
|
|
10
|
+
# peer and the same file.
|
|
11
|
+
module MediaParts
|
|
12
|
+
include Binary
|
|
13
|
+
|
|
14
|
+
INPUT_MEDIA_PHOTO = 0xe3af4434
|
|
15
|
+
INPUT_PHOTO = 0x3bb3b94a
|
|
16
|
+
|
|
17
|
+
def serialize_peer(peer)
|
|
18
|
+
case peer[:type]
|
|
19
|
+
when :self
|
|
20
|
+
u32_b(Constructors::INPUT_PEER_SELF)
|
|
21
|
+
when :user
|
|
22
|
+
u32_b(Constructors::INPUT_PEER_USER) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
|
|
23
|
+
when :chat
|
|
24
|
+
u32_b(Constructors::INPUT_PEER_CHAT) + u64_b(peer[:id])
|
|
25
|
+
when :channel
|
|
26
|
+
u32_b(Constructors::INPUT_PEER_CHANNEL) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
|
|
27
|
+
else
|
|
28
|
+
raise "Unknown peer type: #{peer[:type]}"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def serialize_uploaded_photo(file)
|
|
33
|
+
u32_b(Constructors::INPUT_MEDIA_UPLOADED_PHOTO) + u32_b(0) + serialize_file(file)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# A photo Telegram already holds, named by what it answered when the upload was
|
|
37
|
+
# handed to it.
|
|
38
|
+
def serialize_held_photo(photo)
|
|
39
|
+
u32_b(INPUT_MEDIA_PHOTO) + u32_b(0) +
|
|
40
|
+
u32_b(INPUT_PHOTO) + u64_b(photo[:id]) + u64_b(photo[:access_hash]) +
|
|
41
|
+
serialize_raw(photo[:file_reference])
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def serialize_file(file)
|
|
45
|
+
return u32_b(Constructors::INPUT_FILE_BIG) + u64_b(file[:id]) + u32_b(file[:parts]) +
|
|
46
|
+
serialize_text(file[:name].to_s) if file[:big]
|
|
47
|
+
|
|
48
|
+
u32_b(Constructors::INPUT_FILE) + u64_b(file[:id]) + u32_b(file[:parts]) +
|
|
49
|
+
serialize_text(file[:name].to_s) + serialize_text(file[:md5_checksum].to_s)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def serialize_text(str)
|
|
53
|
+
serialize_byte_array(str.encode('UTF-8').bytes)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def serialize_raw(raw)
|
|
57
|
+
serialize_byte_array(raw.to_s.b.bytes)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def serialize_byte_array(bytes)
|
|
61
|
+
length = bytes.length
|
|
62
|
+
head = length <= 253 ? [length] : [254] + u32_b(length)[0, 3]
|
|
63
|
+
padded = head + bytes
|
|
64
|
+
padded + [0] * ((4 - (padded.length % 4)) % 4)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
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
|