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,155 @@
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 Contacts
10
+ class Contacts
11
+ CONSTRUCTOR_CONTACTS = 0xeae87e42
12
+ CONSTRUCTOR_CONTACTS_NOT_MODIFIED = 0xb74ba9d2
13
+ VECTOR_CONSTRUCTOR = 0x1cb5c415
14
+
15
+ def self.parse(response)
16
+ offset = 0
17
+
18
+ constructor = response[offset, 4].unpack1('L<')
19
+ offset += 4
20
+
21
+ if constructor == Type::GzipPacked::CONSTRUCTOR
22
+ response = Type::GzipPacked.unpack(response)
23
+ constructor = response[0, 4].unpack1('L<')
24
+ offset = 4
25
+ end
26
+
27
+ if constructor == Type::RpcError::CONSTRUCTOR
28
+ error = Type::RpcError.deserialize(response)
29
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
30
+ end
31
+
32
+ case constructor
33
+ when CONSTRUCTOR_CONTACTS_NOT_MODIFIED
34
+ # contacts.contactsNotModified#b74ba9d2 = contacts.Contacts;
35
+ {
36
+ contacts: [],
37
+ users: []
38
+ }
39
+
40
+ when CONSTRUCTOR_CONTACTS
41
+ # contacts.contacts#eae87e42 contacts:Vector<Contact> saved_count:int users:Vector<User> = contacts.Contacts;
42
+
43
+ # Parse contacts vector
44
+ contacts, offset = parse_contacts_vector(response, offset)
45
+
46
+ # Parse saved_count
47
+ saved_count = response[offset, 4].unpack1('L<')
48
+ offset += 4
49
+
50
+ # Parse users vector
51
+ users, offset = parse_users_vector(response, offset)
52
+
53
+ {
54
+ contacts: contacts,
55
+ saved_count: saved_count,
56
+ users: users
57
+ }
58
+
59
+ else
60
+ raise "Unknown Contacts constructor: 0x#{constructor.to_s(16)}"
61
+ end
62
+ end
63
+
64
+ def self.parse_contacts_vector(response, offset)
65
+ vector_constructor = response[offset, 4].unpack1('L<')
66
+ offset += 4
67
+
68
+ return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
69
+
70
+ count = response[offset, 4].unpack1('L<')
71
+ offset += 4
72
+
73
+ contacts = []
74
+ count.times do
75
+ contact, offset = parse_contact(response, offset)
76
+ contacts << contact if contact
77
+ end
78
+
79
+ [contacts, offset]
80
+ end
81
+
82
+ def self.parse_contact(response, offset)
83
+ # contact#145ade0b user_id:long mutual:Bool
84
+ constructor = response[offset, 4].unpack1('L<')
85
+ offset += 4
86
+
87
+ return [nil, offset] unless constructor == 0x145ade0b
88
+
89
+ user_id = response[offset, 8].unpack1('Q<')
90
+ offset += 8
91
+
92
+ mutual_constructor = response[offset, 4].unpack1('L<')
93
+ offset += 4
94
+ mutual = mutual_constructor == 0x997275b5 # boolTrue
95
+
96
+ [{
97
+ user_id: user_id,
98
+ mutual: mutual
99
+ }, offset]
100
+ rescue StandardError
101
+ [nil, offset]
102
+ end
103
+
104
+ def self.parse_users_vector(response, offset)
105
+ vector_constructor = response[offset, 4].unpack1('L<')
106
+ offset += 4
107
+
108
+ return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
109
+
110
+ count = response[offset, 4].unpack1('L<')
111
+ offset += 4
112
+
113
+ users = []
114
+ count.times do
115
+ user, offset = parse_user(response, offset)
116
+ users << user if user
117
+ end
118
+
119
+ [users, offset]
120
+ end
121
+
122
+ def self.parse_user(response, offset)
123
+ user_constructor = response[offset, 4].unpack1('L<')
124
+ offset += 4
125
+
126
+ # user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
127
+ return [nil, offset] unless user_constructor == 0x20b1422
128
+
129
+ flags = response[offset, 4].unpack1('L<')
130
+ offset += 4
131
+
132
+ flags2 = response[offset, 4].unpack1('L<')
133
+ offset += 4
134
+
135
+ user_id = response[offset, 8].unpack1('Q<')
136
+ offset += 8
137
+
138
+ access_hash = nil
139
+ if (flags & (1 << 0)) != 0
140
+ access_hash = response[offset, 8].unpack1('Q<')
141
+ offset += 8
142
+ end
143
+
144
+ [{
145
+ id: user_id,
146
+ access_hash: access_hash
147
+ }, offset]
148
+ rescue StandardError
149
+ [nil, offset]
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module RPC
6
+ module Contacts
7
+ class GetContacts
8
+ def self.build(hash: 0)
9
+ # contacts.getContacts#22c6aa08 hash:int = contacts.Contacts;
10
+ data = [0x22c6aa08].pack('L<')
11
+ data += [hash].pack('l<')
12
+ data
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../gzip_packed'
4
+ require_relative '../../rpc_error'
5
+ require_relative '../../config'
6
+
7
+ module MTProto
8
+ module Type
9
+ module RPC
10
+ module Help
11
+ class Config
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::Config::CONSTRUCTOR || constructor == Type::Config::CONSTRUCTOR_ALT
26
+ Type::Config.deserialize(response)
27
+ else
28
+ raise UnexpectedConstructorError.new(constructor)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module RPC
6
+ module Help
7
+ class GetConfig
8
+ CONSTRUCTOR = 0xc4f9186b
9
+
10
+ def self.build
11
+ [CONSTRUCTOR].pack('L<')
12
+ end
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module RPC
8
+ class InitConnection
9
+ CONSTRUCTOR = 0xc1cd5ea9
10
+
11
+ def self.build(api_id:, device_model:, system_version:, app_version:, system_lang_code:, lang_pack:, lang_code:, query:)
12
+ flags = 0
13
+
14
+ body = [CONSTRUCTOR].pack('L<')
15
+ body += Serializer.serialize_int(flags)
16
+ body += Serializer.serialize_int(api_id)
17
+ body += Serializer.serialize_string(device_model)
18
+ body += Serializer.serialize_string(system_version)
19
+ body += Serializer.serialize_string(app_version)
20
+ body += Serializer.serialize_string(system_lang_code)
21
+ body += Serializer.serialize_string(lang_pack)
22
+ body += Serializer.serialize_string(lang_code)
23
+ body + query
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module RPC
8
+ class InvokeWithLayer
9
+ CONSTRUCTOR = 0xda9b0d0d
10
+
11
+ def self.build(layer:, query:)
12
+ body = [CONSTRUCTOR].pack('L<')
13
+ body += Serializer.serialize_int(layer)
14
+ body + query
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module RPC
8
+ module Messages
9
+ class SendMessage
10
+ CONSTRUCTOR = 0xfe05dc9a
11
+ INPUT_PEER_USER = 0xdde8a54c
12
+
13
+ def self.build(user_id:, access_hash:, message:, random_id: nil)
14
+ raise ArgumentError, 'user_id is required' if user_id.nil?
15
+ raise ArgumentError, 'access_hash is required' if access_hash.nil?
16
+ raise ArgumentError, 'message is required and cannot be empty' if message.nil? || message.empty?
17
+
18
+ random_id ||= SecureRandom.random_number(2**63)
19
+
20
+ query = [CONSTRUCTOR].pack('L<')
21
+
22
+ # flags:# (no optional fields for now)
23
+ flags = 0
24
+ query += Serializer.serialize_int(flags)
25
+
26
+ # peer:InputPeer (inputPeerUser)
27
+ query += [INPUT_PEER_USER].pack('L<')
28
+ query += Serializer.serialize_long(user_id)
29
+ query += Serializer.serialize_long(access_hash)
30
+
31
+ # message:string
32
+ query += Serializer.serialize_string(message)
33
+
34
+ # random_id:long
35
+ query += Serializer.serialize_long(random_id)
36
+
37
+ query
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,87 @@
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 Messages
10
+ class Updates
11
+ CONSTRUCTOR_UPDATES = 0x74ae4240
12
+ CONSTRUCTOR_UPDATE_SHORT = 0x78d4dec1
13
+ CONSTRUCTOR_UPDATE_SHORT_MESSAGE = 0x313bc7f8
14
+ CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE = 0x4d6deea5
15
+ CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE = 0x9015e101
16
+
17
+ def self.parse(response)
18
+ offset = 0
19
+
20
+ constructor = response[offset, 4].unpack1('L<')
21
+ offset += 4
22
+
23
+ if constructor == Type::GzipPacked::CONSTRUCTOR
24
+ response = Type::GzipPacked.unpack(response)
25
+ constructor = response[0, 4].unpack1('L<')
26
+ offset = 4
27
+ end
28
+
29
+ if constructor == Type::RpcError::CONSTRUCTOR
30
+ error = Type::RpcError.deserialize(response)
31
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
32
+ end
33
+
34
+ case constructor
35
+ when CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE
36
+ # updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
37
+ flags = response[offset, 4].unpack1('L<')
38
+ offset += 4
39
+
40
+ message_id = response[offset, 4].unpack1('L<')
41
+ offset += 4
42
+
43
+ pts = response[offset, 4].unpack1('L<')
44
+ offset += 4
45
+
46
+ pts_count = response[offset, 4].unpack1('L<')
47
+ offset += 4
48
+
49
+ date = response[offset, 4].unpack1('L<')
50
+ offset += 4
51
+
52
+ {
53
+ sent: true,
54
+ message_id: message_id,
55
+ date: date,
56
+ pts: pts,
57
+ pts_count: pts_count
58
+ }
59
+
60
+ when CONSTRUCTOR_UPDATE_SHORT_MESSAGE, CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE
61
+ # updateShortMessage#2296d2c8 or updateShortChatMessage#402d5dbb
62
+ # Both have similar structure with flags, id, etc.
63
+ flags = response[offset, 4].unpack1('L<')
64
+ offset += 4
65
+
66
+ message_id = response[offset, 4].unpack1('L<')
67
+
68
+ {
69
+ sent: true,
70
+ message_id: message_id
71
+ }
72
+
73
+ when CONSTRUCTOR_UPDATES, CONSTRUCTOR_UPDATE_SHORT
74
+ # For full updates or updateShort, just confirm success
75
+ {
76
+ sent: true
77
+ }
78
+
79
+ else
80
+ raise "Unknown Updates constructor: 0x#{constructor.to_s(16)}"
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../serializer'
4
+
5
+ module MTProto
6
+ module Type
7
+ module RPC
8
+ class Ping
9
+ CONSTRUCTOR = 0x7abe77ec
10
+
11
+ def self.build(ping_id)
12
+ body = Serializer.serialize_int(CONSTRUCTOR)
13
+ body + [ping_id].pack('Q<')
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module Type
5
+ module RPC
6
+ class Pong
7
+ CONSTRUCTOR = 0x347773c5
8
+ CONSTRUCTOR_BAD_MSG_NOTIFICATION = 0xa7eff811
9
+
10
+ def self.parse(body)
11
+ constructor = body[0, 4].unpack1('L<')
12
+
13
+ if constructor == CONSTRUCTOR_BAD_MSG_NOTIFICATION
14
+ bad_msg = BadMsgNotification.deserialize(body)
15
+ raise "Bad message notification: #{bad_msg.error_message} (code: #{bad_msg.error_code}, msg_id: #{bad_msg.bad_msg_id}, seqno: #{bad_msg.bad_msg_seqno})"
16
+ end
17
+
18
+ if constructor == MsgContainer::CONSTRUCTOR
19
+ container = MsgContainer.deserialize(body)
20
+ pong_message = container.messages.find do |msg|
21
+ msg[:body][0, 4].unpack1('L<') == CONSTRUCTOR
22
+ end
23
+
24
+ raise 'No pong message found in container' unless pong_message
25
+
26
+ offset = 4
27
+ msg_id = pong_message[:body][offset, 8].unpack1('Q<')
28
+ offset += 8
29
+ ping_id = pong_message[:body][offset, 8].unpack1('Q<')
30
+
31
+ return { msg_id: msg_id, ping_id: ping_id }
32
+ end
33
+
34
+ raise "Unexpected constructor: 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
35
+
36
+ offset = 4
37
+ msg_id = body[offset, 8].unpack1('Q<')
38
+ offset += 8
39
+ ping_id = body[offset, 8].unpack1('Q<')
40
+
41
+ { msg_id: msg_id, ping_id: ping_id }
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end