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