mtproto 0.0.15 → 0.0.17
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 +47158 -42684
- data/lib/mtproto/auth_key_generator.rb +2 -2
- data/lib/mtproto/client/api/export_authorization.rb +17 -0
- data/lib/mtproto/client/api/import_authorization.rb +23 -0
- data/lib/mtproto/client/api.rb +2 -0
- data/lib/mtproto/client.rb +1 -1
- data/lib/mtproto/file_downloader.rb +122 -0
- data/lib/mtproto/tl/constructor_names.rb +314 -109
- data/lib/mtproto/tl/constructors.rb +16 -8
- data/lib/mtproto/tl/objects/bot_command_scope.rb +81 -0
- data/lib/mtproto/tl/objects/channels_join_channel.rb +1 -1
- data/lib/mtproto/tl/objects/channels_update_username.rb +42 -0
- data/lib/mtproto/tl/objects/contacts.rb +1 -1
- data/lib/mtproto/tl/objects/dialogs.rb +15 -10
- data/lib/mtproto/tl/objects/export_authorization.rb +17 -0
- data/lib/mtproto/tl/objects/exported_authorization.rb +32 -0
- data/lib/mtproto/tl/objects/forward_messages.rb +5 -2
- data/lib/mtproto/tl/objects/get_bot_callback_answer.rb +67 -0
- data/lib/mtproto/tl/objects/get_bot_commands.rb +46 -0
- data/lib/mtproto/tl/objects/get_file.rb +10 -2
- data/lib/mtproto/tl/objects/import_authorization.rb +38 -0
- data/lib/mtproto/tl/objects/keyboard_button_callback.rb +50 -0
- data/lib/mtproto/tl/objects/message.rb +97 -21
- data/lib/mtproto/tl/objects/messages.rb +7 -74
- data/lib/mtproto/tl/objects/reply_inline_markup.rb +30 -0
- data/lib/mtproto/tl/objects/reset_bot_commands.rb +45 -0
- data/lib/mtproto/tl/objects/send_media.rb +76 -4
- data/lib/mtproto/tl/objects/send_message.rb +4 -2
- data/lib/mtproto/tl/objects/send_message_action.rb +48 -0
- data/lib/mtproto/tl/objects/set_bot_callback_answer.rb +54 -0
- data/lib/mtproto/tl/objects/set_bot_commands.rb +6 -3
- data/lib/mtproto/tl/objects/set_bot_guest_chat_result.rb +84 -0
- data/lib/mtproto/tl/objects/set_typing.rb +49 -0
- data/lib/mtproto/tl/objects/update_status.rb +24 -0
- data/lib/mtproto/tl/objects/updates.rb +117 -0
- data/lib/mtproto/tl/objects/updates_difference.rb +16 -119
- data/lib/mtproto/tl/reader.rb +188 -0
- data/lib/mtproto/transport/abridged_packet_codec.rb +5 -1
- data/lib/mtproto/unencrypted_message.rb +39 -0
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +4 -1
- data/scripts/gen_constructor_names.rb +72 -0
- data/scripts/tl_to_json.rb +72 -0
- data/scripts/verify_ids.rb +33 -0
- metadata +25 -2
- data/lib/mtproto/message/message.rb +0 -85
|
@@ -86,13 +86,13 @@ module MTProto
|
|
|
86
86
|
private
|
|
87
87
|
|
|
88
88
|
def send_and_receive(rpc, response_class)
|
|
89
|
-
message =
|
|
89
|
+
message = UnencryptedMessage.new(rpc.serialize)
|
|
90
90
|
@connection.send(Transport::Packet.new(message.bytes))
|
|
91
91
|
|
|
92
92
|
response_packet = @connection.receive
|
|
93
93
|
raise 'Receive timeout' if response_packet.nil?
|
|
94
94
|
|
|
95
|
-
result = response_class.deserialize(
|
|
95
|
+
result = response_class.deserialize(UnencryptedMessage.parse(response_packet))
|
|
96
96
|
|
|
97
97
|
raise 'Nonce mismatch!' if rpc.respond_to?(:nonce) && result.nonce != rpc.nonce
|
|
98
98
|
raise 'Server nonce mismatch!' if rpc.respond_to?(:server_nonce) && result.server_nonce != rpc.server_nonce
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../tl/objects/export_authorization'
|
|
4
|
+
require_relative '../../tl/objects/exported_authorization'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
class Client
|
|
8
|
+
class API
|
|
9
|
+
def export_authorization(dc_id:)
|
|
10
|
+
rpc_call(
|
|
11
|
+
TL::ExportAuthorization.new(dc_id: dc_id),
|
|
12
|
+
TL::ExportedAuthorization
|
|
13
|
+
).body
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../tl/objects/import_authorization'
|
|
4
|
+
require_relative '../../tl/objects/authorization'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
class Client
|
|
8
|
+
class API
|
|
9
|
+
def import_authorization(id:, bytes:)
|
|
10
|
+
result = rpc_call(
|
|
11
|
+
TL::ImportAuthorization.new(id: id, bytes: bytes),
|
|
12
|
+
TL::Authorization
|
|
13
|
+
).body
|
|
14
|
+
|
|
15
|
+
if result.authorization? && result.user_id
|
|
16
|
+
@client.update_user(user_id: result.user_id, access_hash: result.access_hash)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
result
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
data/lib/mtproto/client/api.rb
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'api/send_code'
|
|
4
4
|
require_relative 'api/sign_in'
|
|
5
|
+
require_relative 'api/export_authorization'
|
|
6
|
+
require_relative 'api/import_authorization'
|
|
5
7
|
require_relative 'api/export_login_token'
|
|
6
8
|
require_relative 'api/import_login_token'
|
|
7
9
|
require_relative 'api/check_password'
|
data/lib/mtproto/client.rb
CHANGED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'tl/objects/get_file'
|
|
4
|
+
require_relative 'tl/objects/raw_response'
|
|
5
|
+
|
|
6
|
+
module MTProto
|
|
7
|
+
# Downloads a file's bytes via upload.getFile, handling the case where the file
|
|
8
|
+
# lives on a DC other than the home client's. For a foreign DC it opens one
|
|
9
|
+
# extra connection to that DC, transfers the home authorization there
|
|
10
|
+
# (auth.exportAuthorization on the home client + auth.importAuthorization on the
|
|
11
|
+
# new connection) and runs getFile on it. A FILE_MIGRATE_X reply is honoured the
|
|
12
|
+
# same way. DC addresses come from help.getConfig — never hardcoded, since test
|
|
13
|
+
# DC addresses and ports differ from prod and change over time.
|
|
14
|
+
class FileDownloader
|
|
15
|
+
CHUNK = 1024 * 1024
|
|
16
|
+
DC_OPTION_IPV6 = 1 << 0
|
|
17
|
+
DC_OPTION_CDN = 1 << 3
|
|
18
|
+
|
|
19
|
+
# home: a connected, authorized Client whose receiver loop is already running.
|
|
20
|
+
# dc_options: help.getConfig dc_options (e.g. Client#init_connection! result).
|
|
21
|
+
# public_key / test_mode: used to handshake fresh connections to other DCs.
|
|
22
|
+
def initialize(home, dc_options:, public_key:, test_mode: false)
|
|
23
|
+
@home = home
|
|
24
|
+
@dc_options = dc_options
|
|
25
|
+
@public_key = public_key
|
|
26
|
+
@test_mode = test_mode
|
|
27
|
+
@subs = {}
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# location: { id:, access_hash:, file_reference:, thumb_size: }.
|
|
31
|
+
# dc_id: the DC that stores the file. type: :photo or :document.
|
|
32
|
+
def download(location, dc_id:, type: :document)
|
|
33
|
+
client = client_for(dc_id)
|
|
34
|
+
read_all(client, location, type)
|
|
35
|
+
rescue RpcError => e
|
|
36
|
+
migrate = e.error_message.to_s[/\AFILE_MIGRATE_(\d+)\z/, 1]
|
|
37
|
+
raise unless migrate
|
|
38
|
+
|
|
39
|
+
# client_for, not connection_to: a migrate back to the home DC must reuse
|
|
40
|
+
# @home — exporting authorization to your own DC answers DC_ID_INVALID.
|
|
41
|
+
read_all(client_for(migrate.to_i), location, type)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Tear down the extra DC connections opened for downloads.
|
|
45
|
+
def close
|
|
46
|
+
@subs.each_value(&:disconnect!)
|
|
47
|
+
@subs.clear
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def client_for(dc_id)
|
|
53
|
+
dc_id == @home.dc_number ? @home : connection_to(dc_id)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def read_all(client, location, type)
|
|
57
|
+
data = +''
|
|
58
|
+
offset = 0
|
|
59
|
+
loop do
|
|
60
|
+
req = TL::GetFile.new(location: location, type: type, offset: offset, limit: CHUNK)
|
|
61
|
+
resp = client.rpc.call(req, TL::RawResponse).wait!(client.timeout)
|
|
62
|
+
unless resp.constructor_name == 'upload.file'
|
|
63
|
+
raise UnexpectedConstructorError, resp.raw_bytes[0, 4].unpack1('L<')
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
chunk = upload_file_bytes(resp.raw_bytes)
|
|
67
|
+
data << chunk
|
|
68
|
+
break if chunk.bytesize < CHUNK
|
|
69
|
+
|
|
70
|
+
offset += chunk.bytesize
|
|
71
|
+
end
|
|
72
|
+
data
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# One authorized connection per foreign DC, reused across calls.
|
|
76
|
+
def connection_to(dc_id)
|
|
77
|
+
@subs[dc_id] ||= begin
|
|
78
|
+
sub = Client.new(
|
|
79
|
+
api_id: @home.api_id, api_hash: @home.api_hash,
|
|
80
|
+
host: dc_host(dc_id), port: dc_port(dc_id),
|
|
81
|
+
public_key: @public_key, dc_number: dc_id, test_mode: @test_mode
|
|
82
|
+
)
|
|
83
|
+
sub.connect!
|
|
84
|
+
sub.exchange_keys!
|
|
85
|
+
sub.start_receiving!
|
|
86
|
+
sub.init_connection!
|
|
87
|
+
exported = @home.api.export_authorization(dc_id: dc_id)
|
|
88
|
+
sub.api.import_authorization(id: exported.id, bytes: exported.bytes)
|
|
89
|
+
sub
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def dc_option(dc_id)
|
|
94
|
+
@dc_options.find { |o| o[:id] == dc_id && o[:flags].to_i.nobits?(DC_OPTION_IPV6 | DC_OPTION_CDN) } ||
|
|
95
|
+
@dc_options.find { |o| o[:id] == dc_id } ||
|
|
96
|
+
raise(ArgumentError, "no DC option for dc_id=#{dc_id}")
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def dc_host(dc_id)
|
|
100
|
+
dc_option(dc_id)[:ip_address]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def dc_port(dc_id)
|
|
104
|
+
dc_option(dc_id)[:port]
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# upload.file#096a18d5 type:storage.FileType mtime:int bytes:bytes — the type
|
|
108
|
+
# is a bare constructor, so the bytes string starts at a fixed offset.
|
|
109
|
+
def upload_file_bytes(raw)
|
|
110
|
+
offset = 12
|
|
111
|
+
first = raw.getbyte(offset)
|
|
112
|
+
if first == 254
|
|
113
|
+
len = raw.getbyte(offset + 1) | (raw.getbyte(offset + 2) << 8) | (raw.getbyte(offset + 3) << 16)
|
|
114
|
+
start = offset + 4
|
|
115
|
+
else
|
|
116
|
+
len = first
|
|
117
|
+
start = offset + 1
|
|
118
|
+
end
|
|
119
|
+
raw[start, len]
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|