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.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/data/tl-schema.json +47158 -42684
  3. data/lib/mtproto/auth_key_generator.rb +2 -2
  4. data/lib/mtproto/client/api/export_authorization.rb +17 -0
  5. data/lib/mtproto/client/api/import_authorization.rb +23 -0
  6. data/lib/mtproto/client/api.rb +2 -0
  7. data/lib/mtproto/client.rb +1 -1
  8. data/lib/mtproto/file_downloader.rb +122 -0
  9. data/lib/mtproto/tl/constructor_names.rb +314 -109
  10. data/lib/mtproto/tl/constructors.rb +16 -8
  11. data/lib/mtproto/tl/objects/bot_command_scope.rb +81 -0
  12. data/lib/mtproto/tl/objects/channels_join_channel.rb +1 -1
  13. data/lib/mtproto/tl/objects/channels_update_username.rb +42 -0
  14. data/lib/mtproto/tl/objects/contacts.rb +1 -1
  15. data/lib/mtproto/tl/objects/dialogs.rb +15 -10
  16. data/lib/mtproto/tl/objects/export_authorization.rb +17 -0
  17. data/lib/mtproto/tl/objects/exported_authorization.rb +32 -0
  18. data/lib/mtproto/tl/objects/forward_messages.rb +5 -2
  19. data/lib/mtproto/tl/objects/get_bot_callback_answer.rb +67 -0
  20. data/lib/mtproto/tl/objects/get_bot_commands.rb +46 -0
  21. data/lib/mtproto/tl/objects/get_file.rb +10 -2
  22. data/lib/mtproto/tl/objects/import_authorization.rb +38 -0
  23. data/lib/mtproto/tl/objects/keyboard_button_callback.rb +50 -0
  24. data/lib/mtproto/tl/objects/message.rb +97 -21
  25. data/lib/mtproto/tl/objects/messages.rb +7 -74
  26. data/lib/mtproto/tl/objects/reply_inline_markup.rb +30 -0
  27. data/lib/mtproto/tl/objects/reset_bot_commands.rb +45 -0
  28. data/lib/mtproto/tl/objects/send_media.rb +76 -4
  29. data/lib/mtproto/tl/objects/send_message.rb +4 -2
  30. data/lib/mtproto/tl/objects/send_message_action.rb +48 -0
  31. data/lib/mtproto/tl/objects/set_bot_callback_answer.rb +54 -0
  32. data/lib/mtproto/tl/objects/set_bot_commands.rb +6 -3
  33. data/lib/mtproto/tl/objects/set_bot_guest_chat_result.rb +84 -0
  34. data/lib/mtproto/tl/objects/set_typing.rb +49 -0
  35. data/lib/mtproto/tl/objects/update_status.rb +24 -0
  36. data/lib/mtproto/tl/objects/updates.rb +117 -0
  37. data/lib/mtproto/tl/objects/updates_difference.rb +16 -119
  38. data/lib/mtproto/tl/reader.rb +188 -0
  39. data/lib/mtproto/transport/abridged_packet_codec.rb +5 -1
  40. data/lib/mtproto/unencrypted_message.rb +39 -0
  41. data/lib/mtproto/version.rb +1 -1
  42. data/lib/mtproto.rb +4 -1
  43. data/scripts/gen_constructor_names.rb +72 -0
  44. data/scripts/tl_to_json.rb +72 -0
  45. data/scripts/verify_ids.rb +33 -0
  46. metadata +25 -2
  47. 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 = Message.new(rpc.serialize)
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(Message.parse(response_packet))
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
@@ -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'
@@ -20,7 +20,7 @@ module MTProto
20
20
  class Client
21
21
  extend DelegateMethods
22
22
 
23
- API_LAYER = 214
23
+ API_LAYER = 227
24
24
 
25
25
  attr_reader :connection, :server_key, :auth_key, :server_salt, :time_offset, :session,
26
26
  :timeout, :user_id, :access_hash, :dc_number
@@ -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