mtproto 0.0.7 → 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.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +4 -0
  3. data/lib/mtproto/async/middleware/base.rb +17 -0
  4. data/lib/mtproto/async/middleware/flood_wait.rb +42 -0
  5. data/lib/mtproto/async/request.rb +18 -0
  6. data/lib/mtproto/async/request_queue.rb +63 -0
  7. data/lib/mtproto/async_client.rb +201 -0
  8. data/lib/mtproto/auth_key_generator.rb +21 -21
  9. data/lib/mtproto/client/rpc.rb +44 -20
  10. data/lib/mtproto/client.rb +49 -15
  11. data/lib/mtproto/rpc/get_config.rb +19 -22
  12. data/lib/mtproto/rpc/get_contacts.rb +12 -5
  13. data/lib/mtproto/rpc/get_updates_difference.rb +23 -5
  14. data/lib/mtproto/rpc/get_updates_state.rb +12 -5
  15. data/lib/mtproto/rpc/get_users.rb +12 -5
  16. data/lib/mtproto/rpc/ping.rb +12 -5
  17. data/lib/mtproto/rpc/send_code.rb +19 -22
  18. data/lib/mtproto/rpc/send_message.rb +21 -5
  19. data/lib/mtproto/rpc/sign_in.rb +23 -27
  20. data/lib/mtproto/{tl → type}/auth_key/dh_gen_response.rb +1 -1
  21. data/lib/mtproto/{tl → type}/auth_key/req_dh_params.rb +1 -1
  22. data/lib/mtproto/{tl → type}/auth_key/req_pq_multi.rb +1 -1
  23. data/lib/mtproto/{tl → type}/auth_key/res_pq.rb +1 -1
  24. data/lib/mtproto/{tl → type}/auth_key/server_dh_params.rb +1 -1
  25. data/lib/mtproto/{tl → type}/auth_key/set_client_dh_params.rb +1 -1
  26. data/lib/mtproto/{tl → type}/bad_msg_notification.rb +1 -1
  27. data/lib/mtproto/{tl → type}/client_dh_inner_data.rb +1 -1
  28. data/lib/mtproto/{tl → type}/code_settings.rb +1 -1
  29. data/lib/mtproto/{tl → type}/config.rb +1 -1
  30. data/lib/mtproto/{tl → type}/gzip_packed.rb +1 -1
  31. data/lib/mtproto/{tl → type}/message.rb +1 -1
  32. data/lib/mtproto/{tl → type}/msg_container.rb +1 -1
  33. data/lib/mtproto/{tl → type}/new_session_created.rb +1 -1
  34. data/lib/mtproto/{tl/p_q_inner_data.rb → type/pq_inner_data.rb} +1 -1
  35. data/lib/mtproto/{tl → type}/rpc/auth/authorization.rb +5 -5
  36. data/lib/mtproto/{tl → type}/rpc/auth/send_code.rb +1 -1
  37. data/lib/mtproto/{tl → type}/rpc/auth/sent_code.rb +7 -7
  38. data/lib/mtproto/{tl → type}/rpc/auth/sign_in.rb +1 -1
  39. data/lib/mtproto/{tl → type}/rpc/contacts/contacts.rb +5 -5
  40. data/lib/mtproto/{tl → type}/rpc/contacts/get_contacts.rb +1 -1
  41. data/lib/mtproto/{tl → type}/rpc/help/config.rb +7 -7
  42. data/lib/mtproto/{tl → type}/rpc/help/get_config.rb +1 -1
  43. data/lib/mtproto/type/rpc/init_connection.rb +28 -0
  44. data/lib/mtproto/type/rpc/invoke_with_layer.rb +19 -0
  45. data/lib/mtproto/{tl → type}/rpc/messages/send_message.rb +1 -1
  46. data/lib/mtproto/{tl → type}/rpc/messages/updates.rb +5 -5
  47. data/lib/mtproto/{tl → type}/rpc/ping.rb +1 -1
  48. data/lib/mtproto/{tl → type}/rpc/pong.rb +1 -1
  49. data/lib/mtproto/{tl → type}/rpc/updates/difference.rb +5 -5
  50. data/lib/mtproto/{tl → type}/rpc/updates/get_difference.rb +1 -1
  51. data/lib/mtproto/{tl → type}/rpc/updates/get_state.rb +1 -1
  52. data/lib/mtproto/{tl → type}/rpc/updates/state.rb +5 -5
  53. data/lib/mtproto/{tl → type}/rpc/users/get_users.rb +1 -1
  54. data/lib/mtproto/{tl → type}/rpc/users/users.rb +5 -5
  55. data/lib/mtproto/{tl → type}/rpc_error.rb +1 -1
  56. data/lib/mtproto/{tl → type}/sent_code.rb +1 -1
  57. data/lib/mtproto/{tl → type}/serializer.rb +1 -1
  58. data/lib/mtproto/{tl → type}/server_dh_inner_data.rb +1 -1
  59. data/lib/mtproto/updates_poller.rb +3 -3
  60. data/lib/mtproto/version.rb +1 -1
  61. data/lib/mtproto.rb +18 -13
  62. metadata +59 -39
  63. data/lib/mtproto/tl/method_builder.rb +0 -29
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2744b720c59f90c3e9d18021ce6096a1c8e559dae949fbd6a9b53948c14f8b5c
4
- data.tar.gz: 3b9000cabc247db305244b279bc58474466e1549cca3efa07ca1202eaa2d7874
3
+ metadata.gz: 93bf74e47e4eb9cea99def85727f559cc3fcbe484411ba181b6ac596ed24fcbe
4
+ data.tar.gz: c0b1bc2e59a38eb2042d938692ba1959bbad1633a0594858ce9c1991444f4ef0
5
5
  SHA512:
6
- metadata.gz: 874a17be8f75bb305057d0a0cb2c136441f9e8da883c44a99169ad0975c5b745b4323abc84ab6edb96b72f2c8f500ec40536a1a6724f585730501514a2e7475b
7
- data.tar.gz: 6a693436a7443f789cb73a6fe6ecebf523ee288bb174b297fdfac4274340c9a5a00544299d08f0558028645e4cf80dbfb0c70c844ec47d2bb6e413589a45a5b2
6
+ metadata.gz: 9a5cd8f10a36c676db3ba4e642f9f7d1a209600278b3d571c86dfcf78f489613603919a8c1f950b8e3b71e2a1a721a4b0fab721b2e318eb30ff7960edfe6b4df
7
+ data.tar.gz: e98baafabd0e49ea34d1cc7d92b6761a1b899fec53c19faccd96ada01989b8784ac6114e597f28a1f0a15f34ed95cb600948f89decdee66c0d84c6ec1e716194
data/.env.example CHANGED
@@ -3,3 +3,7 @@ TG_API_HASH=your_api_hash
3
3
  TG_TEST_DC='111.111.111.111:443'
4
4
  TG_TEST_DC_N='1'
5
5
  TG_TEST_DC_KEY='DC public key'
6
+
7
+ # For integration tests with authentication:
8
+ TG_AUTH='auth data 1'
9
+ TG_AUTH_2='auth data 2'
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Async
5
+ module Middleware
6
+ class Base
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def call(request, future, retry_fn, retries)
12
+ @app.call(request, future, retry_fn, retries)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base'
4
+
5
+ module MTProto
6
+ module Async
7
+ module Middleware
8
+ class FloodWait < Base
9
+ def initialize(app, max_retries: 5)
10
+ super(app)
11
+ @max_retries = max_retries
12
+ end
13
+
14
+ def call(request, future, retry_fn, retries)
15
+ wrapped = @app.call(request, future, retry_fn, retries)
16
+
17
+ wrapped.rescue do |error|
18
+ if flood_wait_error?(error) && retries < @max_retries
19
+ delay = extract_delay(error.error_message)
20
+ sleep(delay)
21
+ retry_fn.call.value!
22
+ else
23
+ raise error
24
+ end
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def flood_wait_error?(error)
31
+ error.is_a?(MTProto::RpcError) &&
32
+ error.error_code == 420 &&
33
+ error.error_message.start_with?('FLOOD_WAIT_')
34
+ end
35
+
36
+ def extract_delay(error_message)
37
+ error_message[11..-1].to_i
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Async
5
+ class Request
6
+ attr_reader :body, :metadata
7
+
8
+ def initialize(body:, metadata: {})
9
+ @body = body
10
+ @metadata = metadata
11
+ end
12
+
13
+ def key
14
+ metadata[:key] || :default
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent-ruby'
4
+
5
+ module MTProto
6
+ module Async
7
+ class RequestQueue
8
+ def initialize
9
+ @queues = Concurrent::Hash.new
10
+ @threads = Concurrent::Hash.new
11
+ @running = Concurrent::AtomicBoolean.new(true)
12
+ end
13
+
14
+ def call(key:, &blk)
15
+ ensure_worker_running!(key)
16
+
17
+ future = Concurrent::Promises.resolvable_future
18
+ @queues[key] << [blk, future]
19
+ future
20
+ end
21
+
22
+ def shutdown
23
+ @running.make_false
24
+ @threads.each_value do |thread|
25
+ thread.join(2)
26
+ thread.kill if thread.alive?
27
+ end
28
+ @threads.clear
29
+ @queues.clear
30
+ end
31
+
32
+ private
33
+
34
+ def ensure_worker_running!(key)
35
+ return if @threads.key?(key) && @threads[key].alive?
36
+
37
+ @queues[key] ||= Queue.new
38
+ @threads[key] = Thread.new { worker_loop(key) }
39
+ end
40
+
41
+ def worker_loop(key)
42
+ queue = @queues[key]
43
+ loop do
44
+ break unless @running.true?
45
+
46
+ begin
47
+ blk, future = queue.pop(true)
48
+ result = blk.call
49
+ if result.is_a?(Concurrent::Promises::Future)
50
+ result.then { |v| future.fulfill(v) }.rescue { |e| future.reject(e) }
51
+ else
52
+ future.fulfill(result)
53
+ end
54
+ rescue ThreadError
55
+ sleep 0.01
56
+ rescue => e
57
+ future&.reject(e) if future
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,201 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'concurrent'
4
+ require_relative 'async/request'
5
+ require_relative 'async/request_queue'
6
+ require_relative 'async/middleware/base'
7
+ require_relative 'async/middleware/flood_wait'
8
+
9
+ module MTProto
10
+ class AsyncClient
11
+ attr_reader :client
12
+
13
+ def initialize(client)
14
+ @client = client
15
+ @queue = Async::RequestQueue.new
16
+ @middleware_classes = []
17
+ @pending_requests = Concurrent::Hash.new
18
+ @timeout_threads = Concurrent::Hash.new
19
+ @receiver_thread = nil
20
+ @running = Concurrent::AtomicBoolean.new(false)
21
+
22
+ use(Async::Middleware::FloodWait)
23
+ end
24
+
25
+ def use(middleware_class, **options)
26
+ @middleware_classes << [middleware_class, options]
27
+ self
28
+ end
29
+
30
+ def call(body, metadata: {})
31
+ ensure_receiver_running!
32
+
33
+ request = Async::Request.new(body: body, metadata: metadata)
34
+
35
+ @queue.call(key: request.key) do
36
+ send_and_wrap = lambda do |retries = 0|
37
+ base_future = send_request(request.body)
38
+
39
+ retry_fn = lambda do
40
+ send_and_wrap.call(retries + 1)
41
+ end
42
+
43
+ build_middleware_stack.call(request, base_future, retry_fn, retries)
44
+ end
45
+
46
+ send_and_wrap.call(0)
47
+ end
48
+ end
49
+
50
+ def shutdown
51
+ @running.make_false
52
+
53
+ @client.disconnect! if @client.connected?
54
+
55
+ if @receiver_thread
56
+ @receiver_thread.join(2)
57
+ @receiver_thread.kill if @receiver_thread.alive?
58
+ @receiver_thread = nil
59
+ end
60
+
61
+ @timeout_threads.each_value(&:kill)
62
+ @timeout_threads.clear
63
+
64
+ @pending_requests.each_value do |future|
65
+ future.reject(MTProto::Transport::ConnectionError.new('Client shutting down')) unless future.resolved?
66
+ end
67
+ @pending_requests.clear
68
+
69
+ @queue.shutdown
70
+ end
71
+
72
+ private
73
+
74
+ def send_request(body)
75
+ msg_id = @client.rpc.call(body)
76
+ future = Concurrent::Promises.resolvable_future
77
+ @pending_requests[msg_id] = future
78
+
79
+ timeout_thread = Thread.new do
80
+ sleep @client.timeout
81
+ if @pending_requests.delete(msg_id)
82
+ future.reject(MTProto::Transport::ConnectionError.new('RPC timeout'))
83
+ end
84
+ @timeout_threads.delete(msg_id)
85
+ end
86
+ @timeout_threads[msg_id] = timeout_thread
87
+
88
+ future.on_fulfillment! do
89
+ timeout_thread.kill
90
+ @timeout_threads.delete(msg_id)
91
+ end
92
+ future.on_rejection! do
93
+ timeout_thread.kill
94
+ @timeout_threads.delete(msg_id)
95
+ end
96
+
97
+ future
98
+ end
99
+
100
+ def ensure_receiver_running!
101
+ return if @running.true?
102
+
103
+ @running.make_true
104
+ @receiver_thread = Thread.new { receiver_loop }
105
+ end
106
+
107
+ def receiver_loop
108
+ loop do
109
+ break unless @running.true?
110
+
111
+ begin
112
+ response_data = @client.connection.recv(timeout: 1)
113
+
114
+ decrypted = EncryptedMessage.decrypt(
115
+ auth_key: @client.auth_key,
116
+ encrypted_message_data: response_data,
117
+ sender: :server
118
+ )
119
+
120
+ process_message(decrypted[:body])
121
+ rescue MTProto::Transport::ConnectionError => e
122
+ next if e.message == 'Receive timeout' && @running.true?
123
+ break
124
+ rescue IOError => e
125
+ break
126
+ rescue => e
127
+ warn "AsyncClient receiver error: #{e.class}: #{e.message}"
128
+ warn e.backtrace.join("\n")
129
+ break
130
+ end
131
+ end
132
+ end
133
+
134
+ def process_message(response_body)
135
+ constructor = response_body[0, 4].unpack1('L<')
136
+
137
+ case constructor
138
+ when Client::RPC::CONSTRUCTOR_BAD_SERVER_SALT
139
+ handle_bad_server_salt(response_body)
140
+ when Type::NewSessionCreated::CONSTRUCTOR
141
+ handle_new_session(response_body)
142
+ when Type::MsgContainer::CONSTRUCTOR
143
+ handle_container(response_body)
144
+ when Client::RPC::CONSTRUCTOR_RPC_RESULT
145
+ handle_rpc_result(response_body)
146
+ end
147
+ end
148
+
149
+ def handle_bad_server_salt(response_body)
150
+ offset = 4
151
+ offset += 8
152
+ offset += 4
153
+ offset += 4
154
+ new_server_salt = response_body[offset, 8].unpack1('Q<')
155
+
156
+ @client.instance_variable_set(:@server_salt, new_server_salt)
157
+ end
158
+
159
+ def handle_new_session(response_body)
160
+ session_info = Type::NewSessionCreated.deserialize(response_body)
161
+ @client.instance_variable_set(:@server_salt, session_info.server_salt)
162
+ end
163
+
164
+ def handle_container(response_body)
165
+ container = Type::MsgContainer.deserialize(response_body)
166
+
167
+ container.messages.each do |msg|
168
+ process_message(msg[:body])
169
+ end
170
+ end
171
+
172
+ def handle_rpc_result(response_body)
173
+ offset = 4
174
+ req_msg_id = response_body[offset, 8].unpack1('Q<')
175
+ offset += 8
176
+ result = response_body[offset..]
177
+
178
+ future = @pending_requests.delete(req_msg_id)
179
+ return unless future # No pending request for this msg_id
180
+
181
+ # Check if result is an RPC error
182
+ result_constructor = result[0, 4].unpack1('L<')
183
+ if result_constructor == Type::RpcError::CONSTRUCTOR
184
+ error = Type::RpcError.deserialize(result)
185
+ future.reject(MTProto::RpcError.new(error.error_code, error.error_message))
186
+ else
187
+ future.fulfill(result)
188
+ end
189
+ end
190
+
191
+ def build_middleware_stack
192
+ # Middleware now wraps Futures and can trigger retries
193
+ # The base "app" just returns the Future as-is
194
+ app = ->(request, future, retry_fn, retries) { future }
195
+
196
+ @middleware_classes.reverse.reduce(app) do |stack, (middleware_class, options)|
197
+ middleware_class.new(stack, **options)
198
+ end
199
+ end
200
+ end
201
+ end
@@ -3,12 +3,12 @@
3
3
  require 'securerandom'
4
4
  require 'digest'
5
5
  require_relative 'message_id'
6
- require_relative 'tl/auth_key/req_pq_multi'
7
- require_relative 'tl/auth_key/res_pq'
8
- require_relative 'tl/auth_key/req_dh_params'
9
- require_relative 'tl/auth_key/server_dh_params'
10
- require_relative 'tl/auth_key/set_client_dh_params'
11
- require_relative 'tl/auth_key/dh_gen_response'
6
+ require_relative 'type/auth_key/req_pq_multi'
7
+ require_relative 'type/auth_key/res_pq'
8
+ require_relative 'type/auth_key/req_dh_params'
9
+ require_relative 'type/auth_key/server_dh_params'
10
+ require_relative 'type/auth_key/set_client_dh_params'
11
+ require_relative 'type/auth_key/dh_gen_response'
12
12
 
13
13
  module MTProto
14
14
  class AuthKeyGenerator
@@ -87,13 +87,13 @@ module MTProto
87
87
 
88
88
  def req_pq_multi
89
89
  nonce = SecureRandom.random_bytes(16)
90
- body = TL::AuthKey::ReqPqMulti.build(nonce)
91
- message = TL::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
90
+ body = Type::AuthKey::ReqPqMulti.build(nonce)
91
+ message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
92
92
  @connection.send(message.serialize)
93
93
 
94
94
  response_data = @connection.recv(timeout: @timeout)
95
- response_message = TL::Message.deserialize(response_data)
96
- res_pq = TL::AuthKey::ResPq.parse(response_message.body)
95
+ response_message = Type::Message.deserialize(response_data)
96
+ res_pq = Type::AuthKey::ResPq.parse(response_message.body)
97
97
 
98
98
  raise 'Nonce mismatch!' unless res_pq[:nonce] == nonce
99
99
 
@@ -114,7 +114,7 @@ module MTProto
114
114
  # For production DCs, use the DC number as-is
115
115
  dc_value = @test_mode ? (@dc_number + 10000) : @dc_number
116
116
 
117
- inner_data = TL::PQInnerData.new(
117
+ inner_data = Type::PQInnerData.new(
118
118
  pq: Crypto::Factorization.bytes_to_integer(res_pq[:pq]),
119
119
  p: p,
120
120
  q: q,
@@ -128,7 +128,7 @@ module MTProto
128
128
  end
129
129
 
130
130
  def send_req_dh_params(res_pq, p, q, server_key, encrypted_data)
131
- body = TL::AuthKey::ReqDHParams.build(
131
+ body = Type::AuthKey::ReqDHParams.build(
132
132
  nonce: res_pq[:nonce],
133
133
  server_nonce: res_pq[:server_nonce],
134
134
  p: p,
@@ -136,13 +136,13 @@ module MTProto
136
136
  public_key_fingerprint: server_key.fingerprint,
137
137
  encrypted_data: encrypted_data
138
138
  )
139
- message = TL::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
139
+ message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
140
140
 
141
141
  @connection.send(message.serialize)
142
142
 
143
143
  response_data = @connection.recv(timeout: @timeout)
144
- response_message = TL::Message.deserialize(response_data)
145
- server_dh_params = TL::AuthKey::ServerDHParams.parse(response_message.body)
144
+ response_message = Type::Message.deserialize(response_data)
145
+ server_dh_params = Type::AuthKey::ServerDHParams.parse(response_message.body)
146
146
 
147
147
  raise 'Nonce mismatch!' unless server_dh_params[:nonce] == res_pq[:nonce]
148
148
  raise 'Server nonce mismatch!' unless server_dh_params[:server_nonce] == res_pq[:server_nonce]
@@ -172,7 +172,7 @@ module MTProto
172
172
  answer = answer[0..-2]
173
173
  end
174
174
 
175
- server_dh_inner_data = TL::ServerDHInnerData.deserialize(answer)
175
+ server_dh_inner_data = Type::ServerDHInnerData.deserialize(answer)
176
176
 
177
177
  raise 'Nonce mismatch in DH inner data!' unless server_dh_inner_data.nonce == res_pq[:nonce]
178
178
  raise 'Server nonce mismatch in DH inner data!' unless server_dh_inner_data.server_nonce == res_pq[:server_nonce]
@@ -181,7 +181,7 @@ module MTProto
181
181
  end
182
182
 
183
183
  def send_client_dh_params(res_pq, new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
184
- client_dh_inner_data = TL::ClientDHInnerData.new(
184
+ client_dh_inner_data = Type::ClientDHInnerData.new(
185
185
  nonce: res_pq[:nonce],
186
186
  server_nonce: res_pq[:server_nonce],
187
187
  retry_id: 0,
@@ -200,18 +200,18 @@ module MTProto
200
200
  tmp_aes_iv
201
201
  )
202
202
 
203
- body = TL::AuthKey::SetClientDHParams.build(
203
+ body = Type::AuthKey::SetClientDHParams.build(
204
204
  nonce: res_pq[:nonce],
205
205
  server_nonce: res_pq[:server_nonce],
206
206
  encrypted_data: client_dh_encrypted
207
207
  )
208
- message = TL::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
208
+ message = Type::Message.new(auth_key_id: 0, msg_id: MessageId.generate, body: body)
209
209
 
210
210
  @connection.send(message.serialize)
211
211
 
212
212
  response_data = @connection.recv(timeout: @timeout)
213
- response_message = TL::Message.deserialize(response_data)
214
- TL::AuthKey::DHGenResponse.parse(response_message.body)
213
+ response_message = Type::Message.deserialize(response_data)
214
+ Type::AuthKey::DHGenResponse.parse(response_message.body)
215
215
  end
216
216
 
217
217
  def verify_dh_gen_response(dh_gen_response, res_pq, new_nonce, auth_key)
@@ -32,6 +32,27 @@ module MTProto
32
32
 
33
33
  @client.connection.send(encrypted_msg.serialize)
34
34
 
35
+ msg_id
36
+ end
37
+
38
+ def call_sync(body, content_related: true)
39
+ raise 'Auth key not generated' unless @client.auth_key
40
+ raise 'Session not initialized' unless @client.session
41
+
42
+ msg_id = MessageId.generate(time_offset: @client.time_offset)
43
+ seq_no = @client.session.next_seq_no(content_related: content_related)
44
+
45
+ encrypted_msg = EncryptedMessage.encrypt(
46
+ auth_key: @client.auth_key,
47
+ server_salt: @client.server_salt,
48
+ session_id: @client.session.session_id,
49
+ msg_id: msg_id,
50
+ seq_no: seq_no,
51
+ body: body
52
+ )
53
+
54
+ @client.connection.send(encrypted_msg.serialize)
55
+
35
56
  response_data = @client.connection.recv(timeout: @client.timeout)
36
57
 
37
58
  decrypted = EncryptedMessage.decrypt(
@@ -44,25 +65,19 @@ module MTProto
44
65
 
45
66
  constructor = response_body[0, 4].unpack1('L<')
46
67
 
47
- # Handle bad_server_salt
48
68
  if constructor == CONSTRUCTOR_BAD_SERVER_SALT
49
- # bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long
50
69
  offset = 4
51
- bad_msg_id = response_body[offset, 8].unpack1('Q<')
52
70
  offset += 8
53
- bad_msg_seqno = response_body[offset, 4].unpack1('L<')
54
71
  offset += 4
55
- error_code = response_body[offset, 4].unpack1('L<')
56
72
  offset += 4
57
73
  new_server_salt = response_body[offset, 8].unpack1('Q<')
58
74
 
59
- # Update server salt and retry
60
75
  update_server_salt(new_server_salt)
61
- return call(body, content_related: content_related)
76
+ return call_sync(body, content_related: content_related)
62
77
  end
63
78
 
64
- if constructor == TL::NewSessionCreated::CONSTRUCTOR
65
- session_info = TL::NewSessionCreated.deserialize(response_body)
79
+ if constructor == Type::NewSessionCreated::CONSTRUCTOR
80
+ session_info = Type::NewSessionCreated.deserialize(response_body)
66
81
  update_server_salt(session_info.server_salt)
67
82
 
68
83
  response_data = @client.connection.recv(timeout: @client.timeout)
@@ -75,26 +90,26 @@ module MTProto
75
90
  constructor = response_body[0, 4].unpack1('L<')
76
91
  end
77
92
 
78
- if constructor == TL::MsgContainer::CONSTRUCTOR
79
- container = TL::MsgContainer.deserialize(response_body)
93
+ if constructor == Type::MsgContainer::CONSTRUCTOR
94
+ container = Type::MsgContainer.deserialize(response_body)
80
95
 
81
96
  rpc_result = container.messages.find do |msg|
82
97
  msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
83
98
  end
84
99
 
85
- return rpc_result[:body][12..] if rpc_result
100
+ return extract_rpc_result(rpc_result[:body][12..]) if rpc_result
86
101
 
87
102
  new_session = container.messages.find do |msg|
88
- msg[:body][0, 4].unpack1('L<') == TL::NewSessionCreated::CONSTRUCTOR
103
+ msg[:body][0, 4].unpack1('L<') == Type::NewSessionCreated::CONSTRUCTOR
89
104
  end
90
105
  if new_session
91
- session_info = TL::NewSessionCreated.deserialize(new_session[:body])
106
+ session_info = Type::NewSessionCreated.deserialize(new_session[:body])
92
107
  update_server_salt(session_info.server_salt)
93
108
 
94
109
  other_messages = container.messages.reject do |msg|
95
110
  constructor = msg[:body][0, 4].unpack1('L<')
96
111
  (
97
- constructor == TL::NewSessionCreated::CONSTRUCTOR ||
112
+ constructor == Type::NewSessionCreated::CONSTRUCTOR ||
98
113
  constructor == CONSTRUCTOR_MSGS_ACK
99
114
  )
100
115
  end
@@ -110,14 +125,14 @@ module MTProto
110
125
  response_body = decrypted[:body]
111
126
  constructor = response_body[0, 4].unpack1('L<')
112
127
 
113
- return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
128
+ return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
114
129
 
115
- if constructor == TL::MsgContainer::CONSTRUCTOR
116
- container = TL::MsgContainer.deserialize(response_body)
130
+ if constructor == Type::MsgContainer::CONSTRUCTOR
131
+ container = Type::MsgContainer.deserialize(response_body)
117
132
  rpc_result = container.messages.find do |msg|
118
133
  msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR_RPC_RESULT
119
134
  end
120
- return rpc_result[:body][12..] if rpc_result
135
+ return extract_rpc_result(rpc_result[:body][12..]) if rpc_result
121
136
  end
122
137
 
123
138
  return response_body
@@ -126,13 +141,22 @@ module MTProto
126
141
  return container.messages.first[:body]
127
142
  end
128
143
 
129
- return response_body[12..] if constructor == CONSTRUCTOR_RPC_RESULT
144
+ return extract_rpc_result(response_body[12..]) if constructor == CONSTRUCTOR_RPC_RESULT
130
145
 
131
146
  response_body
132
147
  end
133
148
 
134
149
  private
135
150
 
151
+ def extract_rpc_result(result)
152
+ result_constructor = result[0, 4].unpack1('L<')
153
+ if result_constructor == Type::RpcError::CONSTRUCTOR
154
+ error = Type::RpcError.deserialize(result)
155
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
156
+ end
157
+ result
158
+ end
159
+
136
160
  def update_server_salt(server_salt)
137
161
  @client.instance_variable_set(:@server_salt, server_salt)
138
162
  end