mtproto 0.0.6 → 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 (69) 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 +22 -12
  9. data/lib/mtproto/client/rpc.rb +165 -0
  10. data/lib/mtproto/client.rb +65 -176
  11. data/lib/mtproto/crypto/aes_ige.rb +1 -1
  12. data/lib/mtproto/crypto/factorization.rb +1 -1
  13. data/lib/mtproto/message_id.rb +13 -0
  14. data/lib/mtproto/rpc/get_config.rb +34 -0
  15. data/lib/mtproto/rpc/get_contacts.rb +29 -0
  16. data/lib/mtproto/rpc/get_updates_difference.rb +51 -0
  17. data/lib/mtproto/rpc/get_updates_state.rb +29 -0
  18. data/lib/mtproto/rpc/get_users.rb +29 -0
  19. data/lib/mtproto/rpc/ping.rb +33 -0
  20. data/lib/mtproto/rpc/send_code.rb +41 -0
  21. data/lib/mtproto/rpc/send_message.rb +47 -0
  22. data/lib/mtproto/rpc/sign_in.rb +48 -0
  23. data/lib/mtproto/type/auth_key/dh_gen_response.rb +37 -0
  24. data/lib/mtproto/type/auth_key/req_dh_params.rb +31 -0
  25. data/lib/mtproto/type/auth_key/req_pq_multi.rb +18 -0
  26. data/lib/mtproto/type/auth_key/res_pq.rb +62 -0
  27. data/lib/mtproto/type/auth_key/server_dh_params.rb +43 -0
  28. data/lib/mtproto/type/auth_key/set_client_dh_params.rb +25 -0
  29. data/lib/mtproto/{tl → type}/bad_msg_notification.rb +1 -1
  30. data/lib/mtproto/{tl → type}/client_dh_inner_data.rb +1 -1
  31. data/lib/mtproto/{tl → type}/code_settings.rb +1 -1
  32. data/lib/mtproto/{tl → type}/config.rb +1 -1
  33. data/lib/mtproto/{tl → type}/gzip_packed.rb +1 -1
  34. data/lib/mtproto/type/message.rb +38 -0
  35. data/lib/mtproto/{tl → type}/msg_container.rb +1 -1
  36. data/lib/mtproto/{tl → type}/new_session_created.rb +1 -1
  37. data/lib/mtproto/{tl/p_q_inner_data.rb → type/pq_inner_data.rb} +1 -1
  38. data/lib/mtproto/type/rpc/auth/authorization.rb +107 -0
  39. data/lib/mtproto/type/rpc/auth/send_code.rb +28 -0
  40. data/lib/mtproto/type/rpc/auth/sent_code.rb +36 -0
  41. data/lib/mtproto/type/rpc/auth/sign_in.rb +32 -0
  42. data/lib/mtproto/type/rpc/contacts/contacts.rb +155 -0
  43. data/lib/mtproto/type/rpc/contacts/get_contacts.rb +18 -0
  44. data/lib/mtproto/type/rpc/help/config.rb +35 -0
  45. data/lib/mtproto/type/rpc/help/get_config.rb +17 -0
  46. data/lib/mtproto/type/rpc/init_connection.rb +28 -0
  47. data/lib/mtproto/type/rpc/invoke_with_layer.rb +19 -0
  48. data/lib/mtproto/type/rpc/messages/send_message.rb +43 -0
  49. data/lib/mtproto/type/rpc/messages/updates.rb +87 -0
  50. data/lib/mtproto/type/rpc/ping.rb +18 -0
  51. data/lib/mtproto/type/rpc/pong.rb +46 -0
  52. data/lib/mtproto/type/rpc/updates/difference.rb +332 -0
  53. data/lib/mtproto/type/rpc/updates/get_difference.rb +42 -0
  54. data/lib/mtproto/type/rpc/updates/get_state.rb +17 -0
  55. data/lib/mtproto/type/rpc/updates/state.rb +59 -0
  56. data/lib/mtproto/type/rpc/users/get_users.rb +25 -0
  57. data/lib/mtproto/type/rpc/users/users.rb +99 -0
  58. data/lib/mtproto/{tl → type}/rpc_error.rb +1 -1
  59. data/lib/mtproto/{tl → type}/sent_code.rb +1 -1
  60. data/lib/mtproto/{tl → type}/serializer.rb +1 -1
  61. data/lib/mtproto/{tl → type}/server_dh_inner_data.rb +1 -1
  62. data/lib/mtproto/updates_poller.rb +111 -0
  63. data/lib/mtproto/version.rb +1 -1
  64. data/lib/mtproto.rb +28 -14
  65. metadata +86 -18
  66. data/ext/aes_ige/Makefile +0 -273
  67. data/ext/factorization/Makefile +0 -273
  68. data/lib/mtproto/connection.rb +0 -103
  69. data/lib/mtproto/tl/message.rb +0 -252
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module AuthKey
6
+ class DHGenResponse
7
+ CONSTRUCTOR_DH_GEN_OK = 0x3bcbf734
8
+ CONSTRUCTOR_DH_GEN_RETRY = 0x46dc1fb9
9
+ CONSTRUCTOR_DH_GEN_FAIL = 0xa69dae02
10
+
11
+ def self.parse(body)
12
+ constructor = body[0, 4].unpack1('L<')
13
+
14
+ offset = 4
15
+ nonce = body[offset, 16]
16
+ offset += 16
17
+
18
+ server_nonce = body[offset, 16]
19
+ offset += 16
20
+
21
+ new_nonce_hash = body[offset, 16]
22
+
23
+ case constructor
24
+ when CONSTRUCTOR_DH_GEN_OK
25
+ { status: :ok, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
26
+ when CONSTRUCTOR_DH_GEN_RETRY
27
+ { status: :retry, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
28
+ when CONSTRUCTOR_DH_GEN_FAIL
29
+ { status: :fail, nonce: nonce, server_nonce: server_nonce, new_nonce_hash: new_nonce_hash }
30
+ else
31
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module AuthKey
8
+ class ReqDHParams
9
+ CONSTRUCTOR = 0xd712e4be
10
+
11
+ def self.build(nonce:, server_nonce:, p:, q:, public_key_fingerprint:, encrypted_data:)
12
+ raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
13
+ raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
14
+
15
+ p_bytes = Serializer.integer_to_bytes(p)
16
+ q_bytes = Serializer.integer_to_bytes(q)
17
+
18
+ body = Serializer.serialize_int(CONSTRUCTOR)
19
+ body += nonce
20
+ body += server_nonce
21
+ body += Serializer.serialize_bytes(p_bytes)
22
+ body += Serializer.serialize_bytes(q_bytes)
23
+ body += Serializer.serialize_long(public_key_fingerprint)
24
+ body += Serializer.serialize_bytes(encrypted_data)
25
+
26
+ body
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module AuthKey
6
+ class ReqPqMulti
7
+ CONSTRUCTOR = 0xbe7e8ef1
8
+
9
+ def self.build(nonce)
10
+ raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
11
+
12
+ constructor = [CONSTRUCTOR].pack('L<')
13
+ constructor + nonce
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module AuthKey
6
+ class ResPq
7
+ CONSTRUCTOR = 0x05162463
8
+
9
+ def self.parse(body)
10
+ constructor = body[0, 4].unpack1('L<')
11
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
12
+
13
+ offset = 4
14
+
15
+ nonce = body[offset, 16]
16
+ offset += 16
17
+
18
+ server_nonce = body[offset, 16]
19
+ offset += 16
20
+
21
+ pq_length_byte = body[offset].unpack1('C')
22
+ offset += 1
23
+
24
+ pq_length = if pq_length_byte == 254
25
+ body[offset, 3].unpack1('L<') & 0xffffff
26
+ offset += 3
27
+ else
28
+ pq_length_byte
29
+ end
30
+
31
+ pq = body[offset, pq_length]
32
+ offset += pq_length
33
+ offset += padding_length(pq_length + 1)
34
+
35
+ vector_constructor = body[offset, 4].unpack1('L<')
36
+ offset += 4
37
+ raise 'Expected vector constructor' unless vector_constructor == 0x1cb5c415
38
+
39
+ fingerprints_count = body[offset, 4].unpack1('L<')
40
+ offset += 4
41
+
42
+ fingerprints = []
43
+ fingerprints_count.times do
44
+ fingerprints << body[offset, 8].unpack1('Q<')
45
+ offset += 8
46
+ end
47
+
48
+ {
49
+ nonce: nonce,
50
+ server_nonce: server_nonce,
51
+ pq: pq,
52
+ fingerprints: fingerprints
53
+ }
54
+ end
55
+
56
+ def self.padding_length(length)
57
+ (4 - (length % 4)) % 4
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module AuthKey
6
+ class ServerDHParams
7
+ CONSTRUCTOR = 0xd0e8075c
8
+
9
+ def self.parse(body)
10
+ constructor = body[0, 4].unpack1('L<')
11
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
12
+
13
+ offset = 4
14
+
15
+ nonce = body[offset, 16]
16
+ offset += 16
17
+
18
+ server_nonce = body[offset, 16]
19
+ offset += 16
20
+
21
+ length_byte = body[offset].ord
22
+ offset += 1
23
+
24
+ if length_byte == 254
25
+ length_bytes = body[offset, 3].bytes
26
+ encrypted_answer_length = length_bytes[0] | (length_bytes[1] << 8) | (length_bytes[2] << 16)
27
+ offset += 3
28
+ else
29
+ encrypted_answer_length = length_byte
30
+ end
31
+
32
+ encrypted_answer = body[offset, encrypted_answer_length]
33
+
34
+ {
35
+ nonce: nonce,
36
+ server_nonce: server_nonce,
37
+ encrypted_answer: encrypted_answer
38
+ }
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module AuthKey
8
+ class SetClientDHParams
9
+ CONSTRUCTOR = 0xf5045f1f
10
+
11
+ def self.build(nonce:, server_nonce:, encrypted_data:)
12
+ raise ArgumentError, 'Nonce must be 16 bytes' unless nonce.bytesize == 16
13
+ raise ArgumentError, 'Server nonce must be 16 bytes' unless server_nonce.bytesize == 16
14
+
15
+ body = Serializer.serialize_int(CONSTRUCTOR)
16
+ body += nonce
17
+ body += server_nonce
18
+ body += Serializer.serialize_bytes(encrypted_data)
19
+
20
+ body
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module TL
4
+ module Type
5
5
  class BadMsgNotification
6
6
  CONSTRUCTOR = 0xa7eff811
7
7
 
@@ -3,7 +3,7 @@
3
3
  require_relative 'serializer'
4
4
 
5
5
  module MTProto
6
- module TL
6
+ module Type
7
7
  class ClientDHInnerData
8
8
  CONSTRUCTOR = 0x6643b654
9
9
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module TL
4
+ module Type
5
5
  class CodeSettings
6
6
  CONSTRUCTOR = 0xad253d78
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module TL
4
+ module Type
5
5
  class Config
6
6
  CONSTRUCTOR = 0xcc1a241e
7
7
  CONSTRUCTOR_ALT = 0x3072cfa1
@@ -4,7 +4,7 @@ require 'zlib'
4
4
  require 'stringio'
5
5
 
6
6
  module MTProto
7
- module TL
7
+ module Type
8
8
  module GzipPacked
9
9
  CONSTRUCTOR = 0x3072cfa1
10
10
 
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ class Message
6
+ attr_reader :auth_key_id, :msg_id, :body
7
+
8
+ def initialize(auth_key_id:, msg_id:, body:)
9
+ @auth_key_id = auth_key_id
10
+ @msg_id = msg_id
11
+ @body = body
12
+ end
13
+
14
+ def serialize
15
+ auth_key_id_bytes = [@auth_key_id].pack('Q<')
16
+ msg_id_bytes = [@msg_id].pack('Q<')
17
+ body_length = [@body.bytesize].pack('L<')
18
+
19
+ auth_key_id_bytes + msg_id_bytes + body_length + @body
20
+ end
21
+
22
+ def self.deserialize(data)
23
+ if data.bytesize < 20
24
+ raise(ArgumentError,
25
+ "Invalid MTProto message: expected at least 20 bytes, got #{data.bytesize} bytes (hex: #{data.unpack1('H*')})",
26
+ )
27
+ end
28
+
29
+ auth_key_id = data[0, 8].unpack1('Q<')
30
+ msg_id = data[8, 8].unpack1('Q<')
31
+ body_length = data[16, 4].unpack1('L<')
32
+ body = data[20, body_length]
33
+
34
+ new(auth_key_id: auth_key_id, msg_id: msg_id, body: body)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module TL
4
+ module Type
5
5
  class MsgContainer
6
6
  CONSTRUCTOR = 0x73f1f8dc
7
7
 
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- module TL
4
+ module Type
5
5
  class NewSessionCreated
6
6
  CONSTRUCTOR = 0x9ec20908
7
7
 
@@ -3,7 +3,7 @@
3
3
  require_relative 'serializer'
4
4
 
5
5
  module MTProto
6
- module TL
6
+ module Type
7
7
  class PQInnerData
8
8
  CONSTRUCTOR_DC = 0xa9f55f95
9
9
  CONSTRUCTOR_TEMP_DC = 0x56fddf88
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../gzip_packed'
4
+ require_relative '../../rpc_error'
5
+
6
+ module MTProto
7
+ module Type
8
+ module RPC
9
+ module Auth
10
+ class Authorization
11
+ CONSTRUCTOR = 0x2ea2c0d4
12
+ CONSTRUCTOR_SIGN_UP_REQUIRED = 0x44747e9a
13
+
14
+ def self.parse(response)
15
+ constructor = response[0, 4].unpack1('L<')
16
+
17
+ if constructor == Type::GzipPacked::CONSTRUCTOR
18
+ response = Type::GzipPacked.unpack(response)
19
+ constructor = response[0, 4].unpack1('L<')
20
+ end
21
+
22
+ if constructor == Type::RpcError::CONSTRUCTOR
23
+ error = Type::RpcError.deserialize(response)
24
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
25
+ end
26
+
27
+ if constructor == CONSTRUCTOR_SIGN_UP_REQUIRED
28
+ return { authorization: nil, sign_up_required: true }
29
+ end
30
+
31
+ if constructor == CONSTRUCTOR
32
+ offset = 4
33
+
34
+ # Parse flags
35
+ flags = response[offset, 4].unpack1('L<')
36
+ offset += 4
37
+
38
+ # tmp_sessions:flags.0?int
39
+ if (flags & (1 << 0)) != 0
40
+ offset += 4
41
+ end
42
+
43
+ # otherwise_relogin_days:flags.1?int
44
+ if (flags & (1 << 1)) != 0
45
+ offset += 4
46
+ end
47
+
48
+ # future_auth_token:flags.2?bytes
49
+ if (flags & (1 << 2)) != 0
50
+ first_byte = response[offset].unpack1('C')
51
+
52
+ if first_byte < 254
53
+ # Short format: 1 byte length + data + padding
54
+ token_len = first_byte
55
+ offset += 1 + token_len
56
+ padding = (4 - ((1 + token_len) % 4)) % 4
57
+ offset += padding
58
+ else
59
+ # Long format: 0xfe + 3 bytes length + data + padding
60
+ offset += 1
61
+ token_len = response[offset, 3].unpack('CCC').inject(0) {|sum, b| (sum << 8) + b}
62
+ offset += 3 + token_len
63
+ padding = (4 - ((4 + token_len) % 4)) % 4
64
+ offset += padding
65
+ end
66
+ end
67
+
68
+ # Parse User object
69
+ # user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
70
+ user_constructor = response[offset, 4].unpack1('L<')
71
+ offset += 4
72
+
73
+ user_flags = response[offset, 4].unpack1('L<')
74
+ offset += 4
75
+
76
+ user_flags2 = response[offset, 4].unpack1('L<')
77
+ offset += 4
78
+
79
+ # id:long (always present)
80
+ user_id = response[offset, 8].unpack1('Q<')
81
+ offset += 8
82
+
83
+ # access_hash:flags.0?long (conditional on flags.0)
84
+ access_hash = nil
85
+ if (user_flags & (1 << 0)) != 0
86
+ access_hash = response[offset, 8].unpack1('Q<')
87
+ offset += 8
88
+ end
89
+
90
+ # We have what we need, skip the rest of User fields
91
+
92
+ return {
93
+ authorization: true,
94
+ flags: flags,
95
+ sign_up_required: false,
96
+ user_id: user_id,
97
+ access_hash: access_hash
98
+ }
99
+ end
100
+
101
+ raise UnexpectedConstructorError.new(constructor)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../serializer'
4
+ require_relative '../../code_settings'
5
+
6
+ module MTProto
7
+ module Type
8
+ module RPC
9
+ module Auth
10
+ class SendCode
11
+ CONSTRUCTOR = 0xa677244f
12
+
13
+ def self.build(phone_number:, api_id:, api_hash:, code_settings: {})
14
+ raise ArgumentError, 'phone_number is required' if phone_number.nil? || phone_number.empty?
15
+
16
+ query = [CONSTRUCTOR].pack('L<')
17
+ query += Serializer.serialize_string(phone_number)
18
+ query += Serializer.serialize_int(api_id)
19
+ query += Serializer.serialize_string(api_hash)
20
+ query += CodeSettings.serialize(code_settings)
21
+
22
+ query
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../gzip_packed'
4
+ require_relative '../../rpc_error'
5
+ require_relative '../../sent_code'
6
+
7
+ module MTProto
8
+ module Type
9
+ module RPC
10
+ module Auth
11
+ class SentCode
12
+ def self.parse(response)
13
+ constructor = response[0, 4].unpack1('L<')
14
+
15
+ if constructor == Type::GzipPacked::CONSTRUCTOR
16
+ response = Type::GzipPacked.unpack(response)
17
+ constructor = response[0, 4].unpack1('L<')
18
+ end
19
+
20
+ if constructor == Type::RpcError::CONSTRUCTOR
21
+ error = Type::RpcError.deserialize(response)
22
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
23
+ end
24
+
25
+ if constructor == Type::SentCode::CONSTRUCTOR
26
+ sent_code = Type::SentCode.deserialize(response)
27
+ sent_code.to_h
28
+ else
29
+ raise UnexpectedConstructorError.new(constructor)
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module RPC
8
+ module Auth
9
+ class SignIn
10
+ CONSTRUCTOR = 0x8d52a951
11
+
12
+ def self.build(phone_number:, phone_code_hash:, phone_code:)
13
+ raise ArgumentError, 'phone_number is required' if phone_number.nil? || phone_number.empty?
14
+ raise ArgumentError, 'phone_code_hash is required' if phone_code_hash.nil? || phone_code_hash.empty?
15
+ raise ArgumentError, 'phone_code is required' if phone_code.nil? || phone_code.empty?
16
+
17
+ flags = 0
18
+ flags |= (1 << 0) # phone_code present
19
+
20
+ query = [CONSTRUCTOR].pack('L<')
21
+ query += Serializer.serialize_int(flags)
22
+ query += Serializer.serialize_string(phone_number)
23
+ query += Serializer.serialize_string(phone_code_hash)
24
+ query += Serializer.serialize_string(phone_code)
25
+
26
+ query
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end