Class: MTProto::Client

Inherits:
Object
  • Object
show all
Extended by:
DelegateMethods
Defined in:
lib/mtproto/client.rb,
lib/mtproto/client/api.rb,
lib/mtproto/client/rpc.rb,
lib/mtproto/client/api/sign_in.rb,
lib/mtproto/client/rpc/response.rb,
lib/mtproto/client/api/get_users.rb,
lib/mtproto/client/api/send_code.rb,
lib/mtproto/client/api/get_dialogs.rb,
lib/mtproto/client/api/get_history.rb,
lib/mtproto/client/api/get_contacts.rb,
lib/mtproto/client/api/send_message.rb,
lib/mtproto/client/api/check_password.rb,
lib/mtproto/client/api/get_updates_state.rb,
lib/mtproto/client/api/export_login_token.rb,
lib/mtproto/client/api/import_login_token.rb,
lib/mtproto/client/api/export_authorization.rb,
lib/mtproto/client/api/import_authorization.rb,
lib/mtproto/client/api/get_channel_difference.rb,
lib/mtproto/client/api/get_updates_difference.rb,
lib/mtproto/client/api/get_bot_callback_answer.rb,
lib/mtproto/client/api/import_bot_authorization.rb

Defined Under Namespace

Classes: API, RPC

Constant Summary collapse

API_LAYER =
227
ACK_INTERVAL =

Keepalive cadence: flush pending acks every ACK_INTERVAL seconds, send a ping every PING_INTERVAL seconds.

5
PING_INTERVAL =
30
RECONNECT_BACKOFF_BASE =

Reconnect backoff: wait RECONNECT_BACKOFF_BASE seconds after the first failed attempt, doubling each retry, capped at RECONNECT_BACKOFF_MAX.

1
RECONNECT_BACKOFF_MAX =
30

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from DelegateMethods

delegate

Constructor Details

#initialize(host:, api_id:, api_hash:, port: 443, public_key: nil, dc_number: nil, test_mode: false, timeout: 10) ⇒ Client

Returns a new instance of Client.

Raises:

  • (ArgumentError)


41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/mtproto/client.rb', line 41

def initialize(
  host:,
  api_id:,
  api_hash:,
  port: 443,
  public_key: nil,
  dc_number: nil,
  test_mode: false,
  timeout: 10
)
  raise ArgumentError, 'host is required' if host.nil? || host.empty?

  raise ArgumentError, 'dc_number must be positive. Use test_mode: true for test DCs' if dc_number && dc_number < 0

  @api_id = api_id
  @api_hash = api_hash

  transport = Transport::TCPConnection.new(host, port)
  @connection = Transport::Connection.new(transport)
  @public_key = public_key
  @dc_number = dc_number
  @test_mode = test_mode
  @timeout = timeout
  @server_key = nil
  @auth_key = nil
  @server_salt = nil
  @time_offset = 0
  @session = nil
  @connection_initialized = false
  @user_id = nil
  @access_hash = nil

  @device_model = 'Ruby MTProto'
  @system_version = RUBY_DESCRIPTION
  @app_version = '0.1.0'
  @system_lang_code = 'en'
  @lang_pack = ''
  @lang_code = 'en'

  @receiver_task = nil
  @keepalive_task = nil
  @ack_ids = []
  @running = false
  @on_update_callbacks = []
end

Instance Attribute Details

#access_hashObject (readonly)

Returns the value of attribute access_hash.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def access_hash
  @access_hash
end

#api_hashObject

Returns the value of attribute api_hash.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def api_hash
  @api_hash
end

#api_idObject

Returns the value of attribute api_id.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def api_id
  @api_id
end

#app_versionObject

Returns the value of attribute app_version.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def app_version
  @app_version
end

#auth_keyObject (readonly)

Returns the value of attribute auth_key.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def auth_key
  @auth_key
end

#connectionObject (readonly)

Returns the value of attribute connection.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def connection
  @connection
end

#dc_numberObject (readonly)

Returns the value of attribute dc_number.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def dc_number
  @dc_number
end

#device_modelObject

Returns the value of attribute device_model.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def device_model
  @device_model
end

#lang_codeObject

Returns the value of attribute lang_code.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def lang_code
  @lang_code
end

#lang_packObject

Returns the value of attribute lang_pack.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def lang_pack
  @lang_pack
end

#server_keyObject (readonly)

Returns the value of attribute server_key.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def server_key
  @server_key
end

#server_saltObject (readonly)

Returns the value of attribute server_salt.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def server_salt
  @server_salt
end

#sessionObject (readonly)

Returns the value of attribute session.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def session
  @session
end

#system_lang_codeObject

Returns the value of attribute system_lang_code.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def system_lang_code
  @system_lang_code
end

#system_versionObject

Returns the value of attribute system_version.



28
29
30
# File 'lib/mtproto/client.rb', line 28

def system_version
  @system_version
end

#time_offsetObject (readonly)

Returns the value of attribute time_offset.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def time_offset
  @time_offset
end

#timeoutObject (readonly)

Returns the value of attribute timeout.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def timeout
  @timeout
end

#user_idObject (readonly)

Returns the value of attribute user_id.



26
27
28
# File 'lib/mtproto/client.rb', line 26

def user_id
  @user_id
end

Instance Method Details

#apiObject



169
170
171
# File 'lib/mtproto/client.rb', line 169

def api
  @api ||= API.new(self)
end

#auth_key?Boolean

Returns:

  • (Boolean)


33
34
35
# File 'lib/mtproto/client.rb', line 33

def auth_key?
  !@auth_key.nil?
end

#disconnect!Object



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/mtproto/client.rb', line 132

def disconnect!
  @running = false
  @keepalive_task&.stop
  @keepalive_task = nil
  @receiver_task&.stop
  @receiver_task = nil
  @ack_ids = []

  rpc.signal_all_error(Transport::ConnectionError.new('Client shutting down'))

  @connection.disconnect! if @connection&.connected?
end

#exchange_keys!Object

Raises:

  • (ArgumentError)


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/mtproto/client.rb', line 145

def exchange_keys!
  raise ArgumentError, 'public_key is required for auth key generation' if @public_key.nil? || @public_key.empty?

  generator = AuthKeyGenerator.new(
    @connection,
    @public_key,
    @dc_number,
    test_mode: @test_mode,
    timeout: @timeout
  )
  result = generator.generate

  @auth_key = generator.auth_key
  @server_salt = generator.server_salt
  @time_offset = generator.time_offset
  @session = Session.new

  result
end

#init_connection!Object



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# File 'lib/mtproto/client.rb', line 173

def init_connection!
  return if @connection_initialized

  query = TL::InvokeWithLayer.new(
    layer: API_LAYER,
    query: TL::InitConnection.new(
      api_id: api_id,
      device_model: device_model,
      system_version: system_version,
      app_version: app_version,
      system_lang_code: system_lang_code,
      lang_pack: lang_pack,
      lang_code: lang_code,
      query: TL::GetConfig.new
    )
  )

  response = rpc.call(query, TL::HelpConfig)
  response.wait!(timeout)

  @connection_initialized = true

  response.body
end

#load_auth_data(auth_data) ⇒ Object

Raises:

  • (ArgumentError)


219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/mtproto/client.rb', line 219

def load_auth_data(auth_data)
  raise ArgumentError, 'auth_data must be a Hash' unless auth_data.is_a?(Hash)
  raise ArgumentError, 'auth_key is required' unless auth_data[:auth_key]
  raise ArgumentError, 'server_salt is required' unless auth_data[:server_salt]

  @auth_key = Base64.strict_decode64(auth_data[:auth_key])
  @server_salt = auth_data[:server_salt]
  @time_offset = auth_data[:time_offset] || 0
  @user_id = auth_data[:user_id]
  @access_hash = auth_data[:access_hash]

  @session = Session.new

  true
end

#mainloop_running?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/mtproto/client.rb', line 37

def mainloop_running?
  @running
end

#on_update(&block) ⇒ Object



97
98
99
# File 'lib/mtproto/client.rb', line 97

def on_update(&block)
  @on_update_callbacks << block
end

#reinit_connection!Object

Force a fresh initConnection on the current connection. Used to recover when the server reports CONNECTION_NOT_INITED (it lost our connection state): reset the guard and run init again.



201
202
203
204
# File 'lib/mtproto/client.rb', line 201

def reinit_connection!
  @connection_initialized = false
  init_connection!
end

#rpcObject



165
166
167
# File 'lib/mtproto/client.rb', line 165

def rpc
  @rpc ||= RPC.new(self)
end

#run_mainloopObject

Raises:

  • (ArgumentError)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/mtproto/client.rb', line 101

def run_mainloop
  raise 'Auth key not set' unless auth_key?
  raise 'Mainloop already running' if @running
  raise ArgumentError, 'Block is required' unless block_given?

  begin
    Async do
      start_receiving!
      yield self
    ensure
      disconnect!
    end
  rescue Interrupt
    # Ctrl+C
  end
end

#save_auth_dataObject



206
207
208
209
210
211
212
213
214
215
216
217
# File 'lib/mtproto/client.rb', line 206

def save_auth_data
  raise 'Cannot save auth_data: auth_key not set' unless @auth_key

  {
    auth_key: Base64.strict_encode64(@auth_key),
    server_salt: @server_salt,
    user_id: @user_id,
    access_hash: @access_hash,
    dc_number: @dc_number,
    time_offset: @time_offset
  }
end

#start_receiving!Object

Start the receiver task without taking over the current Async reactor. Use when orchestrating multiple clients in a shared Async block — call from inside an Async do ... end. The caller is responsible for disconnect! at the end.



122
123
124
125
126
127
128
129
130
# File 'lib/mtproto/client.rb', line 122

def start_receiving!
  raise 'Auth key not set' unless auth_key?
  raise 'Mainloop already running' if @running

  @running = true
  @ack_ids = []
  @receiver_task = Async { receiver_loop }
  @keepalive_task = Async { keepalive_loop }
end

#update_server_salt(new_salt) ⇒ Object



240
241
242
# File 'lib/mtproto/client.rb', line 240

def update_server_salt(new_salt)
  @server_salt = new_salt
end

#update_user(user_id:, access_hash: nil) ⇒ Object



235
236
237
238
# File 'lib/mtproto/client.rb', line 235

def update_user(user_id:, access_hash: nil)
  @user_id = user_id
  @access_hash = access_hash
end