mtproto 0.0.32 → 0.0.33

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6f20076eea4c2e79508740e13b6f8405437f47ce5ea04f7bffcb55eef0134026
4
- data.tar.gz: 3e0ad205973f1f54e8ba60b1b97c4cac1c4a67ea5e59d2b7c797950f943953e0
3
+ metadata.gz: fc4ebf89bbc3d69e0b542b04320af0e74acdd828279411bad647e995a5aad06d
4
+ data.tar.gz: acb092bf151f543578359c1bcd9d81ff1a2f6bc20e5935276a5c8a7b117892ec
5
5
  SHA512:
6
- metadata.gz: 815dbe344577826f6ef0e6d4fac25c0a56eb58f8372f5612c3c77c8f71b9b2684624f3152b3ba4c7f62046dcb239b1bff6ee95b0af98ab249f79a30928abb6cb
7
- data.tar.gz: daaf36af66d6cff257881b8ebb3b19ab6c7b6c4bdaa8d7b9ef9f277a7da53539525039380714555948f80112fd46815985c324cbbd6c3075bef8738cf1ef8296
6
+ metadata.gz: da18a3d97b307eee7bc93ce71efe179a571c4a6385c94a81b43e6dc8e950aabc002fe31b4312b51a1d91157bb3e102b11ce213409eb4ebafbc37db998dd528ab
7
+ data.tar.gz: cdc1d6a7bd55e8ca66841e43e6f40ec8bdfcd950a7c2bad5325dc8dff2f5d4aecb9eb948d76b7787f3c36a255fc5301830145820037c7e9fc5660459747dadd1
@@ -193,7 +193,9 @@ module MTProto
193
193
  Digest::SHA1.digest(new_nonce + "\x03".b + auth_key_aux_hash)[-16..]
194
194
  end
195
195
 
196
- raise 'new_nonce_hash mismatch!' unless dh_gen_response.new_nonce_hash == expected_new_nonce_hash
196
+ unless dh_gen_response.new_nonce_hash == expected_new_nonce_hash
197
+ raise "new_nonce_hash mismatch! (dh_gen #{dh_gen_response.status}, auth_key #{auth_key.bytesize} bytes)"
198
+ end
197
199
 
198
200
  case dh_gen_response.status
199
201
  when :retry
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/resolve_username'
4
+ require_relative '../../tl/objects/resolved_peer'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def resolve_username(username:)
10
+ rpc_call(TL::ResolveUsername.new(username: username), TL::ResolvedPeer).body
11
+ rescue RpcError => e
12
+ raise unless e.error_message == 'USERNAME_NOT_OCCUPIED'
13
+
14
+ nil
15
+ end
16
+ end
17
+ end
18
+ end
@@ -18,6 +18,7 @@ require_relative 'api/get_bot_callback_answer'
18
18
  require_relative 'api/send_message'
19
19
  require_relative 'api/set_typing'
20
20
  require_relative 'api/get_contacts'
21
+ require_relative 'api/resolve_username'
21
22
  require_relative 'api/export_bot_token'
22
23
  require_relative 'api/pin_message'
23
24
  require_relative 'api/unpin_all_messages'
@@ -29,7 +29,8 @@ module MTProto
29
29
  dh_prime = OpenSSL::BN.new(dh_prime_bytes, 2)
30
30
 
31
31
  auth_key_bn = g_a.mod_exp(b, dh_prime)
32
- auth_key_bn.to_s(2)
32
+ # BN#to_s(2) drops leading zero bytes, and roughly one shared secret in 256 has one.
33
+ auth_key_bn.to_s(2).rjust(dh_prime_bytes.bytesize, "\x00".b)
33
34
  end
34
35
 
35
36
  def generate_random_2048_bit_number
@@ -89,6 +89,7 @@ module MTProto
89
89
  CONTACTS_CONTACTS = 0xeae87e42
90
90
  CONTACTS_CONTACTS_NOT_MODIFIED = 0xb74ba9d2
91
91
  CONTACT = 0x145ade0b
92
+ CONTACTS_RESOLVED_PEER = 0x7f077ad9
92
93
 
93
94
  # Messages
94
95
  MESSAGES_GET_DIALOGS = 0xa0f4cb4f
@@ -27,13 +27,14 @@ module MTProto
27
27
  flags = data[offset, 4].unpack1('L<')
28
28
  offset += 4
29
29
 
30
- offset += 4 if flags.anybits?(1 << 0) # tmp_sessions
31
30
  offset += 4 if flags.anybits?(1 << 1) # otherwise_relogin_days
31
+ offset += 4 if flags.anybits?(1 << 0) # tmp_sessions
32
32
 
33
33
  offset = skip_tl_bytes(data, offset) if flags.anybits?(1 << 2) # future_auth_token
34
34
 
35
- # user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
36
- offset += 4 # user constructor
35
+ user_constructor = data[offset, 4].unpack1('L<')
36
+ offset += 4
37
+ raise UnexpectedConstructorError, user_constructor unless user_constructor == Constructors::USER
37
38
 
38
39
  user_flags = data[offset, 4].unpack1('L<')
39
40
  offset += 4
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../schema'
3
+ require_relative '../reader'
4
+ require_relative '../peers'
4
5
  require_relative 'message'
5
- require_relative 'dialogs'
6
6
 
7
7
  module MTProto
8
8
  module TL
@@ -18,8 +18,6 @@ module MTProto
18
18
  class ChannelDifference
19
19
  attr_reader :type, :final, :pts, :timeout, :new_messages, :chats, :users
20
20
 
21
- SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
22
-
23
21
  # Channel messages ride in new_messages, but message-bearing updates can also
24
22
  # appear in other_updates — mine those too, like UpdatesDifference.
25
23
  UPDATE_NEW_MESSAGE = 0x1f2b0afd
@@ -35,10 +33,6 @@ module MTProto
35
33
  @users = users
36
34
  end
37
35
 
38
- def self.schema
39
- @schema ||= Schema.new(SCHEMA_PATH)
40
- end
41
-
42
36
  def self.deserialize(data)
43
37
  constructor = data[0, 4].unpack1('L<')
44
38
 
@@ -77,10 +71,11 @@ module MTProto
77
71
  offset += 4
78
72
  end
79
73
 
80
- messages, offset = parse_messages_vector(data, offset)
74
+ raw_messages, offset = Message.vector_at(data, offset)
75
+ messages = raw_messages.map(&:to_h)
81
76
  other_messages, offset = parse_other_updates(data, offset)
82
- chats, offset = Dialogs.chats_at(data, offset)
83
- users, = Dialogs.users_at(data, offset)
77
+ chats, offset = Peers.chats_at(data, offset)
78
+ users, = Peers.users_at(data, offset)
84
79
 
85
80
  new(
86
81
  type: :difference,
@@ -107,10 +102,11 @@ module MTProto
107
102
  offset += 4
108
103
  end
109
104
 
110
- offset = schema.skip(data, offset) # dialog:Dialog
111
- messages, offset = parse_messages_vector(data, offset)
112
- chats, offset = Dialogs.chats_at(data, offset)
113
- users, = Dialogs.users_at(data, offset)
105
+ offset = Reader.schema.skip(data, offset) # dialog:Dialog
106
+ raw_messages, offset = Message.vector_at(data, offset)
107
+ messages = raw_messages.map(&:to_h)
108
+ chats, offset = Peers.chats_at(data, offset)
109
+ users, = Peers.users_at(data, offset)
114
110
 
115
111
  new(
116
112
  type: :too_long,
@@ -122,24 +118,9 @@ module MTProto
122
118
  )
123
119
  end
124
120
 
125
- def parse_messages_vector(data, offset)
126
- offset += 4 # vector constructor
127
- count = data[offset, 4].unpack1('L<')
128
- offset += 4
129
-
130
- messages = []
131
- count.times do
132
- constructor = data[offset, 4].unpack1('L<')
133
- msg = Message.deserialize(data, offset, constructor)
134
- messages << msg.to_h if msg
135
- offset = schema.skip(data, offset)
136
- end
137
- [messages, offset]
138
- end
139
-
140
121
  # other_updates:Vector<Update> — mine message-bearing updates
141
122
  # (updateNewMessage/updateNewChannelMessage: the ctor, then an inner Message,
142
- # then pts/pts_count), and advance the rest via schema.
123
+ # then pts/pts_count), and advance the rest via Reader.schema.
143
124
  def parse_other_updates(data, offset)
144
125
  offset += 4 # vector constructor
145
126
  count = data[offset, 4].unpack1('L<')
@@ -153,7 +134,7 @@ module MTProto
153
134
  msg = Message.deserialize(data, inner_off, data[inner_off, 4].unpack1('L<'))
154
135
  messages << msg.to_h if msg
155
136
  end
156
- offset = schema.skip(data, offset)
137
+ offset = Reader.schema.skip(data, offset)
157
138
  end
158
139
  [messages, offset]
159
140
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../schema'
3
+ require_relative '../reader'
4
+ require_relative '../peers'
4
5
 
5
6
  module MTProto
6
7
  module TL
@@ -8,8 +9,6 @@ module MTProto
8
9
  Contact = Struct.new(:user_id, :mutual, keyword_init: true)
9
10
  User = Struct.new(:id, :first_name, :last_name, :access_hash, keyword_init: true)
10
11
 
11
- SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
12
-
13
12
  attr_reader :contacts, :saved_count, :users, :not_modified
14
13
 
15
14
  def initialize(contacts: [], saved_count: 0, users: [], not_modified: false)
@@ -23,10 +22,6 @@ module MTProto
23
22
  @not_modified
24
23
  end
25
24
 
26
- def self.schema
27
- @schema ||= Schema.new(SCHEMA_PATH)
28
- end
29
-
30
25
  def self.deserialize(data)
31
26
  constructor = data[0, 4].unpack1('L<')
32
27
  return new(not_modified: true) if constructor == Constructors::CONTACTS_CONTACTS_NOT_MODIFIED
@@ -41,9 +36,6 @@ module MTProto
41
36
  new(contacts: contacts, saved_count: saved_count, users: users)
42
37
  end
43
38
 
44
- USER_EMPTY = 0xd3bc4b7a
45
- private_constant :USER_EMPTY
46
-
47
39
  class << self
48
40
  private
49
41
 
@@ -68,87 +60,13 @@ module MTProto
68
60
  [contacts, offset]
69
61
  end
70
62
 
71
- # User parsing mirrors TL::Dialogs.parse_user (user#31774388). Kept private here
72
- # so this class can be used without instantiating Dialogs.
73
63
  def parse_users_vector(data, offset)
74
- offset += 4 # vector tag
75
- count = data[offset, 4].unpack1('L<')
76
- offset += 4
77
-
78
- users = []
79
- count.times do
80
- user, offset = parse_user(data, offset)
81
- users << user if user
82
- end
83
- [users, offset]
84
- end
85
-
86
- def parse_user(data, offset)
87
- constructor = data[offset, 4].unpack1('L<')
88
- offset += 4
89
-
90
- if constructor == USER_EMPTY
91
- offset += 8
92
- return [nil, offset]
64
+ records, offset = Peers.users_at(data, offset)
65
+ mapped = records.map do |user|
66
+ User.new(id: user[:id], first_name: user[:first_name],
67
+ last_name: user[:last_name], access_hash: user[:access_hash])
93
68
  end
94
-
95
- raise "Unknown User constructor: 0x#{constructor.to_s(16)}" unless constructor == Constructors::USER
96
-
97
- flags = data[offset, 4].unpack1('L<')
98
- offset += 4
99
- flags2 = data[offset, 4].unpack1('L<')
100
- offset += 4
101
- id = data[offset, 8].unpack1('Q<')
102
- offset += 8
103
- access_hash = nil
104
- if flags.anybits?(1 << 0)
105
- access_hash = data[offset, 8].unpack1('Q<')
106
- offset += 8
107
- end
108
-
109
- first_name = nil
110
- first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
111
- last_name = nil
112
- last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
113
-
114
- offset = skip_tl_string(data, offset) if flags.anybits?(1 << 3) # username
115
- offset = skip_tl_string(data, offset) if flags.anybits?(1 << 4) # phone
116
- offset = schema.skip(data, offset) if flags.anybits?(1 << 5) # photo
117
- offset = schema.skip(data, offset) if flags.anybits?(1 << 6) # status
118
- offset += 4 if flags.anybits?(1 << 14) # bot_info_version
119
- offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if flags.anybits?(1 << 18)
120
- offset = skip_tl_string(data, offset) if flags.anybits?(1 << 19) # bot_inline_placeholder
121
- offset = skip_tl_string(data, offset) if flags.anybits?(1 << 22) # lang_code
122
- offset = schema.skip(data, offset) if flags.anybits?(1 << 30) # emoji_status
123
- offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if flags2.anybits?(1 << 0)
124
- offset += 4 if flags2.anybits?(1 << 5) # stories_max_id
125
- offset = schema.skip(data, offset) if flags2.anybits?(1 << 8) # color
126
- offset = schema.skip(data, offset) if flags2.anybits?(1 << 9) # profile_color
127
- offset += 4 if flags2.anybits?(1 << 12) # bot_active_users
128
- offset += 8 if flags2.anybits?(1 << 14) # bot_verification_icon
129
- offset += 8 if flags2.anybits?(1 << 15) # send_paid_messages_stars
130
-
131
- [User.new(id: id, first_name: first_name, last_name: last_name, access_hash: access_hash), offset]
132
- end
133
-
134
- def read_tl_string(data, offset)
135
- first_byte = data.getbyte(offset)
136
- if first_byte == 254
137
- len = data.getbyte(offset + 1) |
138
- (data.getbyte(offset + 2) << 8) |
139
- (data.getbyte(offset + 3) << 16)
140
- total = 4 + len
141
- else
142
- len = first_byte
143
- total = 1 + len
144
- end
145
- padding = (4 - (total % 4)) % 4
146
- [data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
147
- end
148
-
149
- def skip_tl_string(data, offset)
150
- _, new_offset = read_tl_string(data, offset)
151
- new_offset
69
+ [mapped, offset]
152
70
  end
153
71
  end
154
72
  end