mtproto 0.0.8 → 0.0.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/data/tl-schema.json +42686 -0
- data/ext/aes_ige/extconf.rb +3 -9
- data/ext/factorization/extconf.rb +2 -0
- data/lib/mtproto/auth_key_generator.rb +68 -105
- data/lib/mtproto/binary.rb +21 -0
- data/lib/mtproto/client/api/check_password.rb +41 -0
- data/lib/mtproto/client/api/export_login_token.rb +27 -0
- 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/get_updates_difference.rb +21 -0
- data/lib/mtproto/client/api/get_updates_state.rb +14 -0
- data/lib/mtproto/client/api/get_users.rb +14 -0
- data/lib/mtproto/client/api/import_login_token.rb +23 -0
- data/lib/mtproto/client/api/send_code.rb +21 -0
- data/lib/mtproto/client/api/sign_in.rb +27 -0
- data/lib/mtproto/client/api.rb +36 -0
- data/lib/mtproto/client/rpc/response.rb +63 -0
- data/lib/mtproto/client/rpc.rb +60 -127
- data/lib/mtproto/client.rb +143 -32
- data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
- data/lib/mtproto/crypto/dh_validator.rb +17 -19
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/crypto/rsa_key.rb +2 -2
- data/lib/mtproto/crypto/srp.rb +117 -0
- data/lib/mtproto/delegate_methods.rb +11 -0
- data/lib/mtproto/errors.rb +8 -0
- data/lib/mtproto/message/message.rb +85 -0
- data/lib/mtproto/session.rb +1 -1
- data/lib/mtproto/tl/constructor_names.rb +2271 -0
- data/lib/mtproto/tl/constructors.rb +99 -0
- data/lib/mtproto/tl/object.rb +25 -0
- data/lib/mtproto/tl/objects/account_password.rb +69 -0
- data/lib/mtproto/tl/objects/authorization.rb +70 -0
- data/lib/mtproto/tl/objects/check_password.rb +43 -0
- data/lib/mtproto/tl/objects/client_dh_inner_data.rb +45 -0
- data/lib/mtproto/tl/objects/dh_gen_response.rb +46 -0
- data/lib/mtproto/tl/objects/dialogs.rb +453 -0
- data/lib/mtproto/tl/objects/export_login_token.rb +48 -0
- data/lib/mtproto/tl/objects/get_config.rb +13 -0
- data/lib/mtproto/tl/objects/get_dialogs.rb +51 -0
- data/lib/mtproto/tl/objects/get_difference.rb +34 -0
- data/lib/mtproto/tl/objects/get_history.rb +49 -0
- data/lib/mtproto/tl/objects/get_password.rb +13 -0
- data/lib/mtproto/tl/objects/get_state.rb +13 -0
- data/lib/mtproto/tl/objects/get_users.rb +16 -0
- data/lib/mtproto/{type → tl/objects}/gzip_packed.rb +6 -6
- data/lib/mtproto/tl/objects/help_config.rb +76 -0
- data/lib/mtproto/tl/objects/import_login_token.rb +37 -0
- data/lib/mtproto/tl/objects/init_connection.rb +57 -0
- data/lib/mtproto/tl/objects/invoke_with_layer.rb +20 -0
- data/lib/mtproto/tl/objects/login_token.rb +78 -0
- data/lib/mtproto/{type → tl/objects}/message.rb +3 -3
- 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 +66 -0
- data/lib/mtproto/tl/objects/req_dh_params.rb +63 -0
- data/lib/mtproto/tl/objects/req_pq_multi.rb +21 -0
- data/lib/mtproto/tl/objects/res_pq.rb +73 -0
- data/lib/mtproto/{type → tl/objects}/rpc_error.rb +1 -4
- data/lib/mtproto/tl/objects/send_code.rb +47 -0
- data/lib/mtproto/tl/objects/sent_code.rb +79 -0
- data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
- data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
- data/lib/mtproto/tl/objects/set_client_dh_params.rb +46 -0
- data/lib/mtproto/tl/objects/sign_in.rb +45 -0
- data/lib/mtproto/tl/objects/update.rb +77 -0
- data/lib/mtproto/tl/objects/update_short.rb +20 -0
- data/lib/mtproto/tl/objects/update_short_message.rb +65 -0
- data/lib/mtproto/tl/objects/updates_difference.rb +152 -0
- data/lib/mtproto/tl/objects/updates_state.rb +35 -0
- data/lib/mtproto/tl/objects/users.rb +83 -0
- data/lib/mtproto/tl/schema.rb +102 -0
- data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
- data/lib/mtproto/transport/connection.rb +23 -0
- data/lib/mtproto/transport/errors.rb +11 -0
- data/lib/mtproto/transport/packet.rb +19 -0
- data/lib/mtproto/transport/tcp_connection.rb +57 -46
- data/lib/mtproto/updates_poller.rb +37 -33
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +17 -27
- data/scripts/generate_constructors.rb +65 -0
- metadata +76 -61
- data/lib/mtproto/async/middleware/base.rb +0 -17
- data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
- data/lib/mtproto/async/request.rb +0 -18
- data/lib/mtproto/async/request_queue.rb +0 -63
- data/lib/mtproto/async_client.rb +0 -201
- data/lib/mtproto/rpc/get_config.rb +0 -34
- data/lib/mtproto/rpc/get_contacts.rb +0 -29
- data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
- data/lib/mtproto/rpc/get_updates_state.rb +0 -29
- data/lib/mtproto/rpc/get_users.rb +0 -29
- data/lib/mtproto/rpc/ping.rb +0 -33
- data/lib/mtproto/rpc/send_code.rb +0 -41
- data/lib/mtproto/rpc/send_message.rb +0 -47
- data/lib/mtproto/rpc/sign_in.rb +0 -48
- data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
- data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
- data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
- data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
- data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
- data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
- 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/code_settings.rb +0 -25
- data/lib/mtproto/type/config.rb +0 -124
- data/lib/mtproto/type/pq_inner_data.rb +0 -41
- data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
- data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
- data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
- data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
- data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
- data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
- data/lib/mtproto/type/rpc/help/config.rb +0 -35
- data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
- data/lib/mtproto/type/rpc/init_connection.rb +0 -28
- data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
- data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
- data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
- data/lib/mtproto/type/rpc/ping.rb +0 -18
- data/lib/mtproto/type/rpc/pong.rb +0 -46
- data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
- data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
- data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
- data/lib/mtproto/type/rpc/updates/state.rb +0 -59
- data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
- data/lib/mtproto/type/rpc/users/users.rb +0 -99
- data/lib/mtproto/type/sent_code.rb +0 -128
- data/lib/mtproto/type/serializer.rb +0 -55
- data/lib/mtproto/type/server_dh_inner_data.rb +0 -85
|
@@ -4,10 +4,8 @@ require 'zlib'
|
|
|
4
4
|
require 'stringio'
|
|
5
5
|
|
|
6
6
|
module MTProto
|
|
7
|
-
module
|
|
7
|
+
module TL
|
|
8
8
|
module GzipPacked
|
|
9
|
-
CONSTRUCTOR = 0x3072cfa1
|
|
10
|
-
|
|
11
9
|
module_function
|
|
12
10
|
|
|
13
11
|
def unpack(data)
|
|
@@ -19,11 +17,11 @@ module MTProto
|
|
|
19
17
|
|
|
20
18
|
if length_byte == 254
|
|
21
19
|
length_bytes = data[offset, 3]
|
|
22
|
-
length =
|
|
20
|
+
length = "#{length_bytes}\u0000".unpack1('L<')
|
|
23
21
|
offset += 3
|
|
24
22
|
puts " [GZIP] Extended length: #{length} bytes" if $DEBUG
|
|
25
23
|
elsif length_byte == 255
|
|
26
|
-
raise
|
|
24
|
+
raise 'Invalid TL string length: 255'
|
|
27
25
|
else
|
|
28
26
|
length = length_byte
|
|
29
27
|
puts " [GZIP] Short length: #{length} bytes" if $DEBUG
|
|
@@ -32,7 +30,9 @@ module MTProto
|
|
|
32
30
|
raise "Invalid length: #{length.inspect}" unless length.is_a?(Integer) && length > 0
|
|
33
31
|
|
|
34
32
|
compressed_data = data[offset, length]
|
|
35
|
-
|
|
33
|
+
if compressed_data.nil? || compressed_data.bytesize < length
|
|
34
|
+
raise "Not enough data: expected #{length}, got #{compressed_data&.bytesize}"
|
|
35
|
+
end
|
|
36
36
|
|
|
37
37
|
Zlib::GzipReader.new(StringIO.new(compressed_data)).read.force_encoding(Encoding::BINARY)
|
|
38
38
|
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class HelpConfig
|
|
6
|
+
attr_reader :flags, :date, :expires, :test_mode, :this_dc, :dc_options
|
|
7
|
+
|
|
8
|
+
def initialize(flags:, date:, expires:, test_mode:, this_dc:, dc_options:)
|
|
9
|
+
@flags = flags
|
|
10
|
+
@date = date
|
|
11
|
+
@expires = expires
|
|
12
|
+
@test_mode = test_mode
|
|
13
|
+
@this_dc = this_dc
|
|
14
|
+
@dc_options = dc_options
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.parse(data)
|
|
18
|
+
constructor = data[0, 4].unpack1('L<')
|
|
19
|
+
unless [Constructors::CONFIG, Constructors::GZIP_PACKED].include?(constructor)
|
|
20
|
+
raise UnexpectedConstructorError, constructor
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
offset = 4
|
|
24
|
+
flags = data[offset, 4].unpack1('L<')
|
|
25
|
+
offset += 4
|
|
26
|
+
date = data[offset, 4].unpack1('L<')
|
|
27
|
+
offset += 4
|
|
28
|
+
expires = data[offset, 4].unpack1('L<')
|
|
29
|
+
offset += 4
|
|
30
|
+
test_mode = data[offset, 4].unpack1('L<') == Constructors::BOOL_TRUE
|
|
31
|
+
offset += 4
|
|
32
|
+
this_dc = data[offset, 4].unpack1('L<')
|
|
33
|
+
offset += 4
|
|
34
|
+
|
|
35
|
+
offset += 4 # vector constructor
|
|
36
|
+
dc_count = data[offset, 4].unpack1('L<')
|
|
37
|
+
offset += 4
|
|
38
|
+
|
|
39
|
+
dc_options = []
|
|
40
|
+
dc_count.times do
|
|
41
|
+
dc_option, offset = parse_dc_option(data, offset)
|
|
42
|
+
dc_options << dc_option
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
new(
|
|
46
|
+
flags: flags, date: date, expires: expires,
|
|
47
|
+
test_mode: test_mode, this_dc: this_dc, dc_options: dc_options
|
|
48
|
+
)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class << self
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def parse_dc_option(data, offset)
|
|
55
|
+
offset += 4 # constructor
|
|
56
|
+
flags = data[offset, 4].unpack1('L<')
|
|
57
|
+
offset += 4
|
|
58
|
+
id = data[offset, 4].unpack1('L<')
|
|
59
|
+
offset += 4
|
|
60
|
+
|
|
61
|
+
ip_len = data.getbyte(offset)
|
|
62
|
+
offset += 1
|
|
63
|
+
ip_address = data[offset, ip_len]
|
|
64
|
+
offset += ip_len
|
|
65
|
+
padding = (4 - ((ip_len + 1) % 4)) % 4
|
|
66
|
+
offset += padding
|
|
67
|
+
|
|
68
|
+
port = data[offset, 4].unpack1('L<')
|
|
69
|
+
offset += 4
|
|
70
|
+
|
|
71
|
+
[{ id: id, ip_address: ip_address, port: port, flags: flags }, offset]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class ImportLoginToken
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
attr_reader :token
|
|
9
|
+
|
|
10
|
+
def initialize(token:)
|
|
11
|
+
@token = token
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def body
|
|
15
|
+
u32_b(Constructors::AUTH_IMPORT_LOGIN_TOKEN) + serialize_tl_bytes(@token)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def serialize_tl_bytes(data)
|
|
21
|
+
bytes = data.bytes
|
|
22
|
+
length = bytes.length
|
|
23
|
+
|
|
24
|
+
if length <= 253
|
|
25
|
+
[length] + bytes + padding(length + 1)
|
|
26
|
+
else
|
|
27
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def padding(current_length)
|
|
32
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
33
|
+
[0] * pad_length
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class InitConnection
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
attr_reader :api_id, :device_model, :system_version, :app_version,
|
|
9
|
+
:system_lang_code, :lang_pack, :lang_code, :query
|
|
10
|
+
|
|
11
|
+
def initialize(api_id:, device_model:, system_version:, app_version:,
|
|
12
|
+
system_lang_code:, lang_pack:, lang_code:, query:)
|
|
13
|
+
@api_id = api_id
|
|
14
|
+
@device_model = device_model
|
|
15
|
+
@system_version = system_version
|
|
16
|
+
@app_version = app_version
|
|
17
|
+
@system_lang_code = system_lang_code
|
|
18
|
+
@lang_pack = lang_pack
|
|
19
|
+
@lang_code = lang_code
|
|
20
|
+
@query = query
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def body
|
|
24
|
+
flags = 0
|
|
25
|
+
|
|
26
|
+
result = u32_b(Constructors::INIT_CONNECTION)
|
|
27
|
+
result += u32_b(flags)
|
|
28
|
+
result += u32_b(@api_id)
|
|
29
|
+
result += serialize_tl_string(@device_model)
|
|
30
|
+
result += serialize_tl_string(@system_version)
|
|
31
|
+
result += serialize_tl_string(@app_version)
|
|
32
|
+
result += serialize_tl_string(@system_lang_code)
|
|
33
|
+
result += serialize_tl_string(@lang_pack)
|
|
34
|
+
result += serialize_tl_string(@lang_code)
|
|
35
|
+
result + @query.body
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def serialize_tl_string(str)
|
|
41
|
+
bytes = str.encode('UTF-8').bytes
|
|
42
|
+
length = bytes.length
|
|
43
|
+
|
|
44
|
+
if length <= 253
|
|
45
|
+
[length] + bytes + padding(length + 1)
|
|
46
|
+
else
|
|
47
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def padding(current_length)
|
|
52
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
53
|
+
[0] * pad_length
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class InvokeWithLayer
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
attr_reader :layer, :query
|
|
9
|
+
|
|
10
|
+
def initialize(layer:, query:)
|
|
11
|
+
@layer = layer
|
|
12
|
+
@query = query
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def body
|
|
16
|
+
u32_b(Constructors::INVOKE_WITH_LAYER) + u32_b(@layer) + @query.body
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'authorization'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
class LoginToken
|
|
8
|
+
attr_reader :type, :expires, :token, :dc_id, :authorization
|
|
9
|
+
|
|
10
|
+
def initialize(type:, expires: nil, token: nil, dc_id: nil, authorization: nil)
|
|
11
|
+
@type = type
|
|
12
|
+
@expires = expires
|
|
13
|
+
@token = token
|
|
14
|
+
@dc_id = dc_id
|
|
15
|
+
@authorization = authorization
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def user_id
|
|
19
|
+
@authorization&.user_id
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def access_hash
|
|
23
|
+
@authorization&.access_hash
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def success?
|
|
27
|
+
@type == :success
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def self.parse(data)
|
|
31
|
+
constructor = data[0, 4].unpack1('L<')
|
|
32
|
+
|
|
33
|
+
case constructor
|
|
34
|
+
when Constructors::AUTH_LOGIN_TOKEN
|
|
35
|
+
parse_token(data)
|
|
36
|
+
when Constructors::AUTH_LOGIN_TOKEN_MIGRATE_TO
|
|
37
|
+
parse_migrate_to(data)
|
|
38
|
+
when Constructors::AUTH_LOGIN_TOKEN_SUCCESS
|
|
39
|
+
new(type: :success, authorization: Authorization.parse(data[4..]))
|
|
40
|
+
else
|
|
41
|
+
raise UnexpectedConstructorError, constructor
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
class << self
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def parse_token(data)
|
|
49
|
+
offset = 4
|
|
50
|
+
expires = data[offset, 4].unpack1('L<')
|
|
51
|
+
offset += 4
|
|
52
|
+
token = read_tl_bytes(data, offset)
|
|
53
|
+
new(type: :token, expires: expires, token: token)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def parse_migrate_to(data)
|
|
57
|
+
offset = 4
|
|
58
|
+
dc_id = data[offset, 4].unpack1('L<')
|
|
59
|
+
offset += 4
|
|
60
|
+
token = read_tl_bytes(data, offset)
|
|
61
|
+
new(type: :migrate_to, dc_id: dc_id, token: token)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def read_tl_bytes(data, offset)
|
|
65
|
+
first_byte = data.getbyte(offset)
|
|
66
|
+
if first_byte < 254
|
|
67
|
+
data[offset + 1, first_byte]
|
|
68
|
+
else
|
|
69
|
+
len = data.getbyte(offset + 1) |
|
|
70
|
+
(data.getbyte(offset + 2) << 8) |
|
|
71
|
+
(data.getbyte(offset + 3) << 16)
|
|
72
|
+
data[offset + 4, len]
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module MTProto
|
|
4
|
-
module
|
|
4
|
+
module TL
|
|
5
5
|
class Message
|
|
6
6
|
attr_reader :auth_key_id, :msg_id, :body
|
|
7
7
|
|
|
@@ -22,8 +22,8 @@ module MTProto
|
|
|
22
22
|
def self.deserialize(data)
|
|
23
23
|
if data.bytesize < 20
|
|
24
24
|
raise(ArgumentError,
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
'Invalid MTProto message: expected at least 20 bytes, ' \
|
|
26
|
+
"got #{data.bytesize} bytes (hex: #{data.unpack1('H*')})")
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
auth_key_id = data[0, 8].unpack1('Q<')
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../schema'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
class Messages
|
|
8
|
+
Message = Struct.new(:id, :from_id, :date, :text, keyword_init: true)
|
|
9
|
+
|
|
10
|
+
MESSAGES_MESSAGES = 0x8c718e87
|
|
11
|
+
MESSAGES_SLICE = 0x762b263d
|
|
12
|
+
MESSAGES_CHANNEL = 0xc776ba4e
|
|
13
|
+
|
|
14
|
+
MESSAGE = Constructors::MESSAGE
|
|
15
|
+
MESSAGE_SERVICE = 0x7a800e0a
|
|
16
|
+
MESSAGE_EMPTY = 0x90a6ca84
|
|
17
|
+
|
|
18
|
+
VALID_CONSTRUCTORS = [MESSAGES_MESSAGES, MESSAGES_SLICE, MESSAGES_CHANNEL].freeze
|
|
19
|
+
|
|
20
|
+
attr_reader :messages, :count
|
|
21
|
+
|
|
22
|
+
def initialize(messages:, count: nil)
|
|
23
|
+
@messages = messages
|
|
24
|
+
@count = count
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def slice?
|
|
28
|
+
!@count.nil?
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.schema
|
|
32
|
+
@schema ||= Schema.new(
|
|
33
|
+
File.expand_path('../../../../data/tl-schema.json', __dir__)
|
|
34
|
+
)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def self.parse(data)
|
|
38
|
+
constructor = data[0, 4].unpack1('L<')
|
|
39
|
+
raise UnexpectedConstructorError, constructor unless VALID_CONSTRUCTORS.include?(constructor)
|
|
40
|
+
|
|
41
|
+
offset = 4
|
|
42
|
+
count = nil
|
|
43
|
+
|
|
44
|
+
case constructor
|
|
45
|
+
when MESSAGES_MESSAGES
|
|
46
|
+
# messages:Vector<Message> chats:Vector<Chat> users:Vector<User>
|
|
47
|
+
when MESSAGES_SLICE
|
|
48
|
+
# flags:# count:int next_rate:flags.0?int offset_id_offset:flags.2?int
|
|
49
|
+
# search_flood:flags.3?SearchPostsFlood messages:Vector<Message> ...
|
|
50
|
+
flags = data[offset, 4].unpack1('L<')
|
|
51
|
+
offset += 4
|
|
52
|
+
count = data[offset, 4].unpack1('L<')
|
|
53
|
+
offset += 4
|
|
54
|
+
offset += 4 if flags.anybits?(1 << 0) # next_rate
|
|
55
|
+
offset += 4 if flags.anybits?(1 << 2) # offset_id_offset
|
|
56
|
+
offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # search_flood
|
|
57
|
+
when MESSAGES_CHANNEL
|
|
58
|
+
# flags:# pts:int count:int offset_id_offset:flags.2?int
|
|
59
|
+
# messages:Vector<Message> topics:Vector<ForumTopic> ...
|
|
60
|
+
flags = data[offset, 4].unpack1('L<')
|
|
61
|
+
offset += 4
|
|
62
|
+
offset += 4 # pts
|
|
63
|
+
count = data[offset, 4].unpack1('L<')
|
|
64
|
+
offset += 4
|
|
65
|
+
offset += 4 if flags.anybits?(1 << 2) # offset_id_offset
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
messages, offset = parse_messages_vector(data, offset)
|
|
69
|
+
|
|
70
|
+
# skip topics vector for channelMessages
|
|
71
|
+
schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if constructor == MESSAGES_CHANNEL
|
|
72
|
+
|
|
73
|
+
# skip chats and users (we don't need them here)
|
|
74
|
+
|
|
75
|
+
new(messages: messages, count: count)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
class << self
|
|
79
|
+
private
|
|
80
|
+
|
|
81
|
+
def parse_messages_vector(data, offset)
|
|
82
|
+
offset += 4 # vector constructor
|
|
83
|
+
count = data[offset, 4].unpack1('L<')
|
|
84
|
+
offset += 4
|
|
85
|
+
|
|
86
|
+
messages = []
|
|
87
|
+
count.times do
|
|
88
|
+
constructor = data[offset, 4].unpack1('L<')
|
|
89
|
+
|
|
90
|
+
if constructor == MESSAGE
|
|
91
|
+
msg, = parse_message(data, offset)
|
|
92
|
+
messages << msg if msg
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
offset = schema.skip(data, offset)
|
|
96
|
+
end
|
|
97
|
+
[messages, offset]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# message#9815cec8 flags:# flags2:# id:int
|
|
101
|
+
# from_id:flags.8?Peer ... date:int message:string ...
|
|
102
|
+
def parse_message(data, offset)
|
|
103
|
+
offset += 4 # constructor
|
|
104
|
+
flags = data[offset, 4].unpack1('L<')
|
|
105
|
+
offset += 4
|
|
106
|
+
flags2 = data[offset, 4].unpack1('L<')
|
|
107
|
+
offset += 4
|
|
108
|
+
id = data[offset, 4].unpack1('l<')
|
|
109
|
+
offset += 4
|
|
110
|
+
|
|
111
|
+
from_id = nil
|
|
112
|
+
_, from_id, offset = parse_peer(data, offset) if flags.anybits?(1 << 8)
|
|
113
|
+
offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
|
|
114
|
+
_, _, offset = parse_peer(data, offset) # peer_id (skip)
|
|
115
|
+
_, _, offset = parse_peer(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
|
|
116
|
+
offset = schema.skip(data, offset) if flags.anybits?(1 << 2) # fwd_from
|
|
117
|
+
offset += 8 if flags.anybits?(1 << 11) # via_bot_id
|
|
118
|
+
offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
|
|
119
|
+
offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
|
|
120
|
+
|
|
121
|
+
date = data[offset, 4].unpack1('L<')
|
|
122
|
+
offset += 4
|
|
123
|
+
|
|
124
|
+
text, offset = read_tl_string(data, offset)
|
|
125
|
+
|
|
126
|
+
msg = Message.new(id: id, from_id: from_id, date: date, text: text)
|
|
127
|
+
[msg, offset]
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_peer(data, offset)
|
|
131
|
+
constructor = data[offset, 4].unpack1('L<')
|
|
132
|
+
offset += 4
|
|
133
|
+
case constructor
|
|
134
|
+
when Constructors::PEER_USER
|
|
135
|
+
[:user, data[offset, 8].unpack1('Q<'), offset + 8]
|
|
136
|
+
when Constructors::PEER_CHAT
|
|
137
|
+
[:chat, data[offset, 8].unpack1('Q<'), offset + 8]
|
|
138
|
+
when Constructors::PEER_CHANNEL
|
|
139
|
+
[:channel, data[offset, 8].unpack1('Q<'), offset + 8]
|
|
140
|
+
else
|
|
141
|
+
raise "Unknown peer constructor: 0x#{constructor.to_s(16)}"
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def read_tl_string(data, offset)
|
|
146
|
+
first_byte = data.getbyte(offset)
|
|
147
|
+
if first_byte == 254
|
|
148
|
+
len = data.getbyte(offset + 1) |
|
|
149
|
+
(data.getbyte(offset + 2) << 8) |
|
|
150
|
+
(data.getbyte(offset + 3) << 16)
|
|
151
|
+
total = 4 + len
|
|
152
|
+
else
|
|
153
|
+
len = first_byte
|
|
154
|
+
total = 1 + len
|
|
155
|
+
end
|
|
156
|
+
padding = (4 - (total % 4)) % 4
|
|
157
|
+
[data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class PQInnerData
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
attr_reader :pq, :p, :q, :nonce, :server_nonce, :new_nonce, :dc, :expires_in
|
|
9
|
+
|
|
10
|
+
def initialize(pq:, p:, q:, nonce:, server_nonce:, new_nonce:, dc:, expires_in: nil)
|
|
11
|
+
@pq = pq
|
|
12
|
+
@p = p
|
|
13
|
+
@q = q
|
|
14
|
+
@nonce = nonce
|
|
15
|
+
@server_nonce = server_nonce
|
|
16
|
+
@new_nonce = new_nonce
|
|
17
|
+
@dc = dc
|
|
18
|
+
@expires_in = expires_in
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def body
|
|
22
|
+
constructor = @expires_in ? Constructors::PQ_INNER_DATA_TEMP_DC : Constructors::PQ_INNER_DATA_DC
|
|
23
|
+
|
|
24
|
+
result = u32_b(constructor)
|
|
25
|
+
result += serialize_tl_bytes(integer_to_bytes(@pq))
|
|
26
|
+
result += serialize_tl_bytes(integer_to_bytes(@p))
|
|
27
|
+
result += serialize_tl_bytes(integer_to_bytes(@q))
|
|
28
|
+
result += @nonce.bytes
|
|
29
|
+
result += @server_nonce.bytes
|
|
30
|
+
result += @new_nonce.bytes
|
|
31
|
+
result += u32_b(@expires_in) if @expires_in
|
|
32
|
+
result += u32_b(@dc)
|
|
33
|
+
|
|
34
|
+
result
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
def serialize_tl_bytes(bytes)
|
|
40
|
+
length = bytes.length
|
|
41
|
+
|
|
42
|
+
if length <= 253
|
|
43
|
+
[length] + bytes + padding(length + 1)
|
|
44
|
+
else
|
|
45
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def padding(current_length)
|
|
50
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
51
|
+
[0] * pad_length
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def integer_to_bytes(int)
|
|
55
|
+
return [0] if int.zero?
|
|
56
|
+
|
|
57
|
+
bytes = []
|
|
58
|
+
while int > 0
|
|
59
|
+
bytes.unshift(int & 0xff)
|
|
60
|
+
int >>= 8
|
|
61
|
+
end
|
|
62
|
+
bytes
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class ReqDHParams
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
attr_reader :nonce, :server_nonce, :p, :q, :public_key_fingerprint, :encrypted_data
|
|
9
|
+
|
|
10
|
+
def initialize(nonce:, server_nonce:, p:, q:, public_key_fingerprint:, encrypted_data:)
|
|
11
|
+
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
12
|
+
raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
|
|
13
|
+
|
|
14
|
+
@nonce = nonce
|
|
15
|
+
@server_nonce = server_nonce
|
|
16
|
+
@p = p
|
|
17
|
+
@q = q
|
|
18
|
+
@public_key_fingerprint = public_key_fingerprint
|
|
19
|
+
@encrypted_data = encrypted_data
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def body
|
|
23
|
+
result = u32_b(Constructors::REQ_DH_PARAMS)
|
|
24
|
+
result += @nonce.bytes
|
|
25
|
+
result += @server_nonce.bytes
|
|
26
|
+
result += serialize_tl_bytes(integer_to_bytes(@p))
|
|
27
|
+
result += serialize_tl_bytes(integer_to_bytes(@q))
|
|
28
|
+
result += u64_b(@public_key_fingerprint)
|
|
29
|
+
result += serialize_tl_bytes(@encrypted_data.bytes)
|
|
30
|
+
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def serialize_tl_bytes(bytes)
|
|
37
|
+
length = bytes.length
|
|
38
|
+
|
|
39
|
+
if length <= 253
|
|
40
|
+
[length] + bytes + padding(length + 1)
|
|
41
|
+
else
|
|
42
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def padding(current_length)
|
|
47
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
48
|
+
[0] * pad_length
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def integer_to_bytes(int)
|
|
52
|
+
return [0] if int.zero?
|
|
53
|
+
|
|
54
|
+
bytes = []
|
|
55
|
+
while int > 0
|
|
56
|
+
bytes.unshift(int & 0xff)
|
|
57
|
+
int >>= 8
|
|
58
|
+
end
|
|
59
|
+
bytes
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class ReqPqMulti
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
attr_reader :nonce
|
|
9
|
+
|
|
10
|
+
def initialize(nonce)
|
|
11
|
+
raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
|
|
12
|
+
|
|
13
|
+
@nonce = nonce
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def body
|
|
17
|
+
u32_b(Constructors::REQ_PQ_MULTI) + @nonce.bytes
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|