mtproto 0.0.5 → 0.0.7

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/.env.example +5 -0
  3. data/Rakefile +13 -0
  4. data/lib/mtproto/auth_key_generator.rb +36 -13
  5. data/lib/mtproto/client/rpc.rb +141 -0
  6. data/lib/mtproto/client.rb +60 -185
  7. data/lib/mtproto/crypto/aes_ige.rb +1 -1
  8. data/lib/mtproto/crypto/factorization.rb +1 -1
  9. data/lib/mtproto/crypto/rsa_key.rb +9 -15
  10. data/lib/mtproto/errors.rb +33 -0
  11. data/lib/mtproto/message_id.rb +13 -0
  12. data/lib/mtproto/rpc/get_config.rb +37 -0
  13. data/lib/mtproto/rpc/get_contacts.rb +22 -0
  14. data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
  15. data/lib/mtproto/rpc/get_updates_state.rb +22 -0
  16. data/lib/mtproto/rpc/get_users.rb +22 -0
  17. data/lib/mtproto/rpc/ping.rb +26 -0
  18. data/lib/mtproto/rpc/send_code.rb +44 -0
  19. data/lib/mtproto/rpc/send_message.rb +31 -0
  20. data/lib/mtproto/rpc/sign_in.rb +52 -0
  21. data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
  22. data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
  23. data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
  24. data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
  25. data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
  26. data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
  27. data/lib/mtproto/tl/code_settings.rb +25 -0
  28. data/lib/mtproto/tl/config.rb +4 -2
  29. data/lib/mtproto/tl/gzip_packed.rb +1 -1
  30. data/lib/mtproto/tl/message.rb +8 -216
  31. data/lib/mtproto/tl/method_builder.rb +29 -0
  32. data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
  33. data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
  34. data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
  35. data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
  36. data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
  37. data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
  38. data/lib/mtproto/tl/rpc/help/config.rb +35 -0
  39. data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
  40. data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
  41. data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
  42. data/lib/mtproto/tl/rpc/ping.rb +18 -0
  43. data/lib/mtproto/tl/rpc/pong.rb +46 -0
  44. data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
  45. data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
  46. data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
  47. data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
  48. data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
  49. data/lib/mtproto/tl/rpc/users/users.rb +99 -0
  50. data/lib/mtproto/tl/sent_code.rb +128 -0
  51. data/lib/mtproto/transport/tcp_connection.rb +1 -1
  52. data/lib/mtproto/updates_poller.rb +111 -0
  53. data/lib/mtproto/version.rb +1 -1
  54. data/lib/mtproto.rb +13 -0
  55. metadata +57 -6
  56. data/ext/aes_ige/Makefile +0 -273
  57. data/ext/aes_ige/aes_ige.bundle +0 -0
  58. data/ext/factorization/Makefile +0 -273
  59. data/ext/factorization/factorization.bundle +0 -0
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../gzip_packed'
4
+ require_relative '../../rpc_error'
5
+
6
+ module MTProto
7
+ module TL
8
+ module RPC
9
+ module Users
10
+ class Users
11
+ VECTOR_CONSTRUCTOR = 0x1cb5c415
12
+ USER_CONSTRUCTOR = 0x020b1422
13
+
14
+ def self.parse(response)
15
+ offset = 0
16
+
17
+ constructor = response[offset, 4].unpack1('L<')
18
+ offset += 4
19
+
20
+ if constructor == TL::GzipPacked::CONSTRUCTOR
21
+ response = TL::GzipPacked.unpack(response)
22
+ constructor = response[0, 4].unpack1('L<')
23
+ offset = 4
24
+ end
25
+
26
+ if constructor == TL::RpcError::CONSTRUCTOR
27
+ error = TL::RpcError.deserialize(response)
28
+ raise MTProto::RpcError.new(error.error_code, error.error_message)
29
+ end
30
+
31
+ # Expect Vector<User>
32
+ raise "Expected Vector constructor, got 0x#{constructor.to_s(16)}" unless constructor == VECTOR_CONSTRUCTOR
33
+
34
+ count = response[offset, 4].unpack1('L<')
35
+ offset += 4
36
+
37
+ users = []
38
+ count.times do
39
+ user_constructor = response[offset, 4].unpack1('L<')
40
+ offset += 4
41
+
42
+ next unless user_constructor == USER_CONSTRUCTOR
43
+
44
+ # Parse User: flags:# flags2:# id:long access_hash:flags.0?long first_name:flags.1?string ...
45
+ flags = response[offset, 4].unpack1('L<')
46
+ offset += 4
47
+
48
+ flags2 = response[offset, 4].unpack1('L<')
49
+ offset += 4
50
+
51
+ id = response[offset, 8].unpack1('Q<')
52
+ offset += 8
53
+
54
+ access_hash = nil
55
+ if (flags & (1 << 0)) != 0
56
+ access_hash = response[offset, 8].unpack1('Q<')
57
+ offset += 8
58
+ end
59
+
60
+ first_name = nil
61
+ if (flags & (1 << 1)) != 0
62
+ len = response[offset].unpack1('C')
63
+ offset += 1
64
+ first_name = response[offset, len]
65
+ offset += len
66
+ padding = (4 - ((1 + len) % 4)) % 4
67
+ offset += padding
68
+ end
69
+
70
+ last_name = nil
71
+ if (flags & (1 << 2)) != 0
72
+ len = response[offset].unpack1('C')
73
+ offset += 1
74
+ last_name = response[offset, len]
75
+ offset += len
76
+ padding = (4 - ((1 + len) % 4)) % 4
77
+ offset += padding
78
+ end
79
+
80
+ users << {
81
+ id: id,
82
+ access_hash: access_hash,
83
+ first_name: first_name,
84
+ last_name: last_name,
85
+ flags: flags,
86
+ flags2: flags2
87
+ }
88
+
89
+ # Skip remaining fields - we don't need them for this test
90
+ break
91
+ end
92
+
93
+ users
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class SentCode
6
+ CONSTRUCTOR = 0x5e002502
7
+
8
+ attr_reader :flags, :type, :phone_code_hash, :next_type, :timeout
9
+
10
+ def self.deserialize(data)
11
+ offset = 4
12
+
13
+ flags = data[offset, 4].unpack1('L<')
14
+ offset += 4
15
+
16
+ type, type_bytes_read = SentCodeType.deserialize_from(data[offset..])
17
+ offset += type_bytes_read
18
+
19
+ phone_code_hash_length = data[offset].ord
20
+ offset += 1
21
+
22
+ phone_code_hash = data[offset, phone_code_hash_length]
23
+ offset += phone_code_hash_length
24
+
25
+ padding = (4 - ((phone_code_hash_length + 1) % 4)) % 4
26
+ offset += padding
27
+
28
+ next_type = nil
29
+ if (flags & (1 << 1)) != 0
30
+ next_type, next_type_bytes_read = CodeType.deserialize_from(data[offset..])
31
+ offset += next_type_bytes_read
32
+ end
33
+
34
+ timeout = nil
35
+ if (flags & (1 << 2)) != 0
36
+ timeout = data[offset, 4].unpack1('L<')
37
+ end
38
+
39
+ new(
40
+ flags: flags,
41
+ type: type,
42
+ phone_code_hash: phone_code_hash,
43
+ next_type: next_type,
44
+ timeout: timeout
45
+ )
46
+ end
47
+
48
+ def initialize(flags:, type:, phone_code_hash:, next_type: nil, timeout: nil)
49
+ @flags = flags
50
+ @type = type
51
+ @phone_code_hash = phone_code_hash
52
+ @next_type = next_type
53
+ @timeout = timeout
54
+ end
55
+
56
+ def to_h
57
+ {
58
+ phone_code_hash: @phone_code_hash,
59
+ type: @type,
60
+ next_type: @next_type,
61
+ timeout: @timeout
62
+ }.compact
63
+ end
64
+ end
65
+
66
+ module SentCodeType
67
+ def self.deserialize_from(data)
68
+ constructor = data[0, 4].unpack1('L<')
69
+ offset = 4
70
+
71
+ case constructor
72
+ when 0x3dbb5986 # auth.sentCodeTypeApp
73
+ length = data[offset, 4].unpack1('L<')
74
+ offset += 4
75
+ [{ _: :sent_code_type_app, length: length }, offset]
76
+ when 0xc000bba2 # auth.sentCodeTypeSms
77
+ length = data[offset, 4].unpack1('L<')
78
+ offset += 4
79
+ [{ _: :sent_code_type_sms, length: length }, offset]
80
+ when 0x5353e5a7 # auth.sentCodeTypeCall
81
+ length = data[offset, 4].unpack1('L<')
82
+ offset += 4
83
+ [{ _: :sent_code_type_call, length: length }, offset]
84
+ when 0xab03c6d9 # auth.sentCodeTypeFlashCall
85
+ pattern_length = data[offset].ord
86
+ offset += 1
87
+ pattern = data[offset, pattern_length]
88
+ offset += pattern_length
89
+ padding = (4 - ((pattern_length + 1) % 4)) % 4
90
+ offset += padding
91
+ [{ _: :sent_code_type_flash_call, pattern: pattern }, offset]
92
+ when 0x82006484 # auth.sentCodeTypeMissedCall
93
+ prefix_length = data[offset].ord
94
+ offset += 1
95
+ prefix = data[offset, prefix_length]
96
+ offset += prefix_length
97
+ padding = (4 - ((prefix_length + 1) % 4)) % 4
98
+ offset += padding
99
+ length = data[offset, 4].unpack1('L<')
100
+ offset += 4
101
+ [{ _: :sent_code_type_missed_call, prefix: prefix, length: length }, offset]
102
+ else
103
+ raise "Unknown SentCodeType constructor: 0x#{constructor.to_s(16)}"
104
+ end
105
+ end
106
+ end
107
+
108
+ module CodeType
109
+ def self.deserialize_from(data)
110
+ constructor = data[0, 4].unpack1('L<')
111
+ offset = 4
112
+
113
+ case constructor
114
+ when 0x72a3158c # codeTypeSms
115
+ [{ _: :code_type_sms }, offset]
116
+ when 0x741cd3e3 # codeTypeCall
117
+ [{ _: :code_type_call }, offset]
118
+ when 0x226ccefb # codeTypeFlashCall
119
+ [{ _: :code_type_flash_call }, offset]
120
+ when 0xd61ad6ee # codeTypeMissedCall
121
+ [{ _: :code_type_missed_call }, offset]
122
+ else
123
+ raise "Unknown CodeType constructor: 0x#{constructor.to_s(16)}"
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
@@ -17,7 +17,7 @@ module MTProto
17
17
  @socket = nil
18
18
  end
19
19
 
20
- def connect
20
+ def connect!
21
21
  return if connected?
22
22
 
23
23
  @socket = TCPSocket.new(@host, @port)
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'rpc/get_updates_state'
4
+ require_relative 'rpc/get_updates_difference'
5
+
6
+ module MTProto
7
+ class UpdatesPoller
8
+ attr_reader :state, :running
9
+
10
+ def initialize(client, poll_interval: 1.0)
11
+ @client = client
12
+ @poll_interval = poll_interval
13
+ @state = nil
14
+ @running = false
15
+ @on_message_callbacks = []
16
+ @on_update_callbacks = []
17
+ @users_cache = {} # Cache user_id => access_hash
18
+ end
19
+
20
+ def on_message(&block)
21
+ @on_message_callbacks << block
22
+ end
23
+
24
+ def on_update(&block)
25
+ @on_update_callbacks << block
26
+ end
27
+
28
+ def start
29
+ raise 'Already running' if @running
30
+ raise 'Auth key not generated' unless @client.auth_key
31
+
32
+ @running = true
33
+
34
+ @state = RPC::GetUpdatesState.new(@client).call
35
+
36
+ poll_loop
37
+ end
38
+
39
+ def stop
40
+ @running = false
41
+ end
42
+
43
+ private
44
+
45
+ def poll_loop
46
+ while @running
47
+ begin
48
+ difference = RPC::GetUpdatesDifference.new(@client).call(
49
+ pts: @state[:pts],
50
+ date: @state[:date],
51
+ qts: @state[:qts]
52
+ )
53
+
54
+ process_difference(difference)
55
+
56
+ if difference[:state]
57
+ @state = @state.merge(difference[:state])
58
+ elsif difference[:type] == :too_long
59
+ @state = RPC::GetUpdatesState.new(@client).call
60
+ elsif difference[:date]
61
+ @state[:date] = difference[:date]
62
+ end
63
+
64
+ sleep @poll_interval
65
+ rescue MTProto::RpcError => e
66
+ puts "RPC Error during polling: #{e.message}"
67
+ sleep @poll_interval
68
+ rescue StandardError => e
69
+ puts "Error during polling: #{e.class} - #{e.message}"
70
+ puts e.backtrace.first(5)
71
+ sleep @poll_interval
72
+ end
73
+ end
74
+ end
75
+
76
+ def process_difference(difference)
77
+ case difference[:type]
78
+ when :empty
79
+ nil
80
+ when :difference, :slice, :short_message
81
+ if difference[:users]
82
+ difference[:users].each do |user|
83
+ if user[:access_hash]
84
+ @users_cache[user[:id]] = user[:access_hash]
85
+ end
86
+ end
87
+ end
88
+
89
+ if difference[:new_messages] && !difference[:new_messages].empty?
90
+ difference[:new_messages].each do |message|
91
+ process_message(message)
92
+ end
93
+ end
94
+
95
+ @on_update_callbacks.each { |callback| callback.call(difference) }
96
+ when :too_long
97
+ puts "Update gap too long (pts=#{difference[:pts]}), refreshing state..."
98
+ end
99
+ end
100
+
101
+ def process_message(message)
102
+ return unless message
103
+
104
+ if message[:user_id] && @users_cache[message[:user_id]]
105
+ message[:access_hash] = @users_cache[message[:user_id]]
106
+ end
107
+
108
+ @on_message_callbacks.each { |callback| callback.call(message) }
109
+ end
110
+ end
111
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MTProto
4
- VERSION = '0.0.5'
4
+ VERSION = '0.0.7'
5
5
  end
data/lib/mtproto.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'mtproto/version'
4
+ require_relative 'mtproto/errors'
4
5
  require_relative 'mtproto/transport/abridged_packet_codec'
5
6
  require_relative 'mtproto/transport/tcp_connection'
6
7
  require_relative 'mtproto/tl/message'
@@ -14,6 +15,8 @@ require_relative 'mtproto/tl/new_session_created'
14
15
  require_relative 'mtproto/tl/rpc_error'
15
16
  require_relative 'mtproto/tl/gzip_packed'
16
17
  require_relative 'mtproto/tl/config'
18
+ require_relative 'mtproto/tl/code_settings'
19
+ require_relative 'mtproto/tl/sent_code'
17
20
  require_relative 'mtproto/crypto/rsa_key'
18
21
  require_relative 'mtproto/crypto/factorization'
19
22
  require_relative 'mtproto/crypto/aes_ige'
@@ -26,6 +29,16 @@ require_relative 'mtproto/auth_key_generator'
26
29
  require_relative 'mtproto/session'
27
30
  require_relative 'mtproto/encrypted_message'
28
31
  require_relative 'mtproto/client'
32
+ require_relative 'mtproto/rpc/ping'
33
+ require_relative 'mtproto/rpc/get_config'
34
+ require_relative 'mtproto/rpc/send_code'
35
+ require_relative 'mtproto/rpc/sign_in'
36
+ require_relative 'mtproto/rpc/get_users'
37
+ require_relative 'mtproto/rpc/send_message'
38
+ require_relative 'mtproto/rpc/get_updates_state'
39
+ require_relative 'mtproto/rpc/get_updates_difference'
40
+ require_relative 'mtproto/rpc/get_contacts'
41
+ require_relative 'mtproto/updates_poller'
29
42
 
30
43
  module MTProto
31
44
  end
metadata CHANGED
@@ -1,14 +1,28 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mtproto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.5
4
+ version: 0.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Artem Levenkov
8
8
  bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
- dependencies: []
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: base64
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '0.2'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '0.2'
12
26
  description: A Ruby library for Telegram MTProto protocol with TCP abridged transport
13
27
  support
14
28
  email:
@@ -17,19 +31,17 @@ executables: []
17
31
  extensions: []
18
32
  extra_rdoc_files: []
19
33
  files:
34
+ - ".env.example"
20
35
  - ".ruby-version"
21
36
  - Rakefile
22
- - ext/aes_ige/Makefile
23
- - ext/aes_ige/aes_ige.bundle
24
37
  - ext/aes_ige/aes_ige.c
25
38
  - ext/aes_ige/extconf.rb
26
- - ext/factorization/Makefile
27
39
  - ext/factorization/extconf.rb
28
- - ext/factorization/factorization.bundle
29
40
  - ext/factorization/factorization.c
30
41
  - lib/mtproto.rb
31
42
  - lib/mtproto/auth_key_generator.rb
32
43
  - lib/mtproto/client.rb
44
+ - lib/mtproto/client/rpc.rb
33
45
  - lib/mtproto/crypto/aes_ige.rb
34
46
  - lib/mtproto/crypto/auth_key_helper.rb
35
47
  - lib/mtproto/crypto/dh_key_exchange.rb
@@ -39,20 +51,59 @@ files:
39
51
  - lib/mtproto/crypto/rsa_key.rb
40
52
  - lib/mtproto/crypto/rsa_pad.rb
41
53
  - lib/mtproto/encrypted_message.rb
54
+ - lib/mtproto/errors.rb
55
+ - lib/mtproto/message_id.rb
56
+ - lib/mtproto/rpc/get_config.rb
57
+ - lib/mtproto/rpc/get_contacts.rb
58
+ - lib/mtproto/rpc/get_updates_difference.rb
59
+ - lib/mtproto/rpc/get_updates_state.rb
60
+ - lib/mtproto/rpc/get_users.rb
61
+ - lib/mtproto/rpc/ping.rb
62
+ - lib/mtproto/rpc/send_code.rb
63
+ - lib/mtproto/rpc/send_message.rb
64
+ - lib/mtproto/rpc/sign_in.rb
42
65
  - lib/mtproto/session.rb
66
+ - lib/mtproto/tl/auth_key/dh_gen_response.rb
67
+ - lib/mtproto/tl/auth_key/req_dh_params.rb
68
+ - lib/mtproto/tl/auth_key/req_pq_multi.rb
69
+ - lib/mtproto/tl/auth_key/res_pq.rb
70
+ - lib/mtproto/tl/auth_key/server_dh_params.rb
71
+ - lib/mtproto/tl/auth_key/set_client_dh_params.rb
43
72
  - lib/mtproto/tl/bad_msg_notification.rb
44
73
  - lib/mtproto/tl/client_dh_inner_data.rb
74
+ - lib/mtproto/tl/code_settings.rb
45
75
  - lib/mtproto/tl/config.rb
46
76
  - lib/mtproto/tl/gzip_packed.rb
47
77
  - lib/mtproto/tl/message.rb
78
+ - lib/mtproto/tl/method_builder.rb
48
79
  - lib/mtproto/tl/msg_container.rb
49
80
  - lib/mtproto/tl/new_session_created.rb
50
81
  - lib/mtproto/tl/p_q_inner_data.rb
82
+ - lib/mtproto/tl/rpc/auth/authorization.rb
83
+ - lib/mtproto/tl/rpc/auth/send_code.rb
84
+ - lib/mtproto/tl/rpc/auth/sent_code.rb
85
+ - lib/mtproto/tl/rpc/auth/sign_in.rb
86
+ - lib/mtproto/tl/rpc/contacts/contacts.rb
87
+ - lib/mtproto/tl/rpc/contacts/get_contacts.rb
88
+ - lib/mtproto/tl/rpc/help/config.rb
89
+ - lib/mtproto/tl/rpc/help/get_config.rb
90
+ - lib/mtproto/tl/rpc/messages/send_message.rb
91
+ - lib/mtproto/tl/rpc/messages/updates.rb
92
+ - lib/mtproto/tl/rpc/ping.rb
93
+ - lib/mtproto/tl/rpc/pong.rb
94
+ - lib/mtproto/tl/rpc/updates/difference.rb
95
+ - lib/mtproto/tl/rpc/updates/get_difference.rb
96
+ - lib/mtproto/tl/rpc/updates/get_state.rb
97
+ - lib/mtproto/tl/rpc/updates/state.rb
98
+ - lib/mtproto/tl/rpc/users/get_users.rb
99
+ - lib/mtproto/tl/rpc/users/users.rb
51
100
  - lib/mtproto/tl/rpc_error.rb
101
+ - lib/mtproto/tl/sent_code.rb
52
102
  - lib/mtproto/tl/serializer.rb
53
103
  - lib/mtproto/tl/server_dh_inner_data.rb
54
104
  - lib/mtproto/transport/abridged_packet_codec.rb
55
105
  - lib/mtproto/transport/tcp_connection.rb
106
+ - lib/mtproto/updates_poller.rb
56
107
  - lib/mtproto/version.rb
57
108
  - tmp/.keep
58
109
  homepage: https://github.com/alev-pro/mtproto-ruby