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 +4 -4
- data/lib/mtproto/auth_key_generator.rb +3 -1
- data/lib/mtproto/client/api/resolve_username.rb +18 -0
- data/lib/mtproto/client/api.rb +1 -0
- data/lib/mtproto/crypto/dh_key_exchange.rb +2 -1
- data/lib/mtproto/tl/constructors.rb +1 -0
- data/lib/mtproto/tl/objects/authorization.rb +4 -3
- data/lib/mtproto/tl/objects/channel_difference.rb +13 -32
- data/lib/mtproto/tl/objects/contacts.rb +7 -89
- data/lib/mtproto/tl/objects/dialogs.rb +14 -352
- data/lib/mtproto/tl/objects/edit_message.rb +1 -1
- data/lib/mtproto/tl/objects/message.rb +14 -0
- data/lib/mtproto/tl/objects/message_service.rb +40 -0
- data/lib/mtproto/tl/objects/messages.rb +7 -32
- data/lib/mtproto/tl/objects/resolved_peer.rb +29 -0
- data/lib/mtproto/tl/objects/send_message.rb +1 -1
- data/lib/mtproto/tl/objects/update.rb +3 -35
- data/lib/mtproto/tl/objects/updates.rb +4 -6
- data/lib/mtproto/tl/objects/updates_difference.rb +9 -29
- data/lib/mtproto/tl/peers.rb +233 -0
- data/lib/mtproto/updates/event_builder.rb +20 -32
- data/lib/mtproto/updates/parser.rb +9 -8
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +1 -0
- metadata +5 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc4ebf89bbc3d69e0b542b04320af0e74acdd828279411bad647e995a5aad06d
|
|
4
|
+
data.tar.gz: acb092bf151f543578359c1bcd9d81ff1a2f6bc20e5935276a5c8a7b117892ec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
|
|
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
|
data/lib/mtproto/client/api.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
|
@@ -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
|
-
|
|
36
|
-
offset += 4
|
|
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 '../
|
|
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
|
-
|
|
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 =
|
|
83
|
-
users, =
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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 '../
|
|
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
|
|
75
|
-
|
|
76
|
-
|
|
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
|