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