mtproto 0.0.8 → 0.0.9
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/ext/aes_ige/extconf.rb +3 -9
- data/ext/factorization/extconf.rb +2 -0
- data/lib/mtproto/auth_key_generator.rb +68 -105
- data/lib/mtproto/binary.rb +21 -0
- data/lib/mtproto/client/api/check_password.rb +41 -0
- data/lib/mtproto/client/api/export_login_token.rb +27 -0
- data/lib/mtproto/client/api/get_updates_difference.rb +21 -0
- data/lib/mtproto/client/api/get_updates_state.rb +14 -0
- data/lib/mtproto/client/api/get_users.rb +14 -0
- data/lib/mtproto/client/api/import_login_token.rb +23 -0
- data/lib/mtproto/client/api/send_code.rb +21 -0
- data/lib/mtproto/client/api/sign_in.rb +27 -0
- data/lib/mtproto/client/api.rb +28 -0
- data/lib/mtproto/client/rpc/response.rb +63 -0
- data/lib/mtproto/client/rpc.rb +61 -125
- data/lib/mtproto/client.rb +142 -31
- data/lib/mtproto/crypto/dh_key_exchange.rb +1 -2
- data/lib/mtproto/crypto/dh_validator.rb +17 -19
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/crypto/rsa_key.rb +2 -2
- data/lib/mtproto/crypto/srp.rb +117 -0
- data/lib/mtproto/delegate_methods.rb +11 -0
- data/lib/mtproto/message/message.rb +85 -0
- data/lib/mtproto/session.rb +1 -1
- data/lib/mtproto/tl/constructors.rb +2269 -0
- data/lib/mtproto/tl/object.rb +25 -0
- data/lib/mtproto/tl/objects/account_password.rb +72 -0
- data/lib/mtproto/tl/objects/authorization.rb +73 -0
- data/lib/mtproto/tl/objects/check_password.rb +46 -0
- data/lib/mtproto/tl/objects/client_dh_inner_data.rb +47 -0
- data/lib/mtproto/tl/objects/dh_gen_response.rb +50 -0
- data/lib/mtproto/tl/objects/export_login_token.rb +51 -0
- data/lib/mtproto/tl/objects/get_config.rb +15 -0
- data/lib/mtproto/tl/objects/get_difference.rb +36 -0
- data/lib/mtproto/tl/objects/get_password.rb +15 -0
- data/lib/mtproto/tl/objects/get_state.rb +15 -0
- data/lib/mtproto/tl/objects/get_users.rb +20 -0
- data/lib/mtproto/tl/objects/help_config.rb +77 -0
- data/lib/mtproto/tl/objects/import_login_token.rb +39 -0
- data/lib/mtproto/tl/objects/init_connection.rb +59 -0
- data/lib/mtproto/tl/objects/invoke_with_layer.rb +22 -0
- data/lib/mtproto/tl/objects/login_token.rb +82 -0
- data/lib/mtproto/tl/objects/pq_inner_data.rb +69 -0
- data/lib/mtproto/tl/objects/req_dh_params.rb +65 -0
- data/lib/mtproto/tl/objects/req_pq_multi.rb +23 -0
- data/lib/mtproto/tl/objects/res_pq.rb +75 -0
- data/lib/mtproto/tl/objects/send_code.rb +50 -0
- data/lib/mtproto/tl/objects/sent_code.rb +79 -0
- data/lib/mtproto/tl/objects/server_dh_inner_data.rb +74 -0
- data/lib/mtproto/tl/objects/server_dh_params.rb +53 -0
- data/lib/mtproto/tl/objects/set_client_dh_params.rb +48 -0
- data/lib/mtproto/tl/objects/sign_in.rb +47 -0
- data/lib/mtproto/tl/objects/update.rb +80 -0
- data/lib/mtproto/tl/objects/update_short.rb +22 -0
- data/lib/mtproto/tl/objects/update_short_message.rb +67 -0
- data/lib/mtproto/tl/objects/updates_difference.rb +157 -0
- data/lib/mtproto/tl/objects/updates_state.rb +37 -0
- data/lib/mtproto/tl/objects/users.rb +86 -0
- data/lib/mtproto/transport/abridged_packet_codec.rb +35 -12
- data/lib/mtproto/transport/connection.rb +23 -0
- data/lib/mtproto/transport/errors.rb +11 -0
- data/lib/mtproto/transport/packet.rb +19 -0
- data/lib/mtproto/transport/tcp_connection.rb +57 -46
- data/lib/mtproto/type/bad_msg_notification.rb +10 -10
- data/lib/mtproto/type/gzip_packed.rb +5 -3
- data/lib/mtproto/type/message.rb +2 -2
- data/lib/mtproto/type/rpc_error.rb +0 -1
- data/lib/mtproto/updates_poller.rb +37 -33
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +11 -17
- data/scripts/generate_constructors.rb +65 -0
- metadata +62 -51
- data/lib/mtproto/async/middleware/base.rb +0 -17
- data/lib/mtproto/async/middleware/flood_wait.rb +0 -42
- data/lib/mtproto/async/request.rb +0 -18
- data/lib/mtproto/async/request_queue.rb +0 -63
- data/lib/mtproto/async_client.rb +0 -201
- data/lib/mtproto/rpc/get_config.rb +0 -34
- data/lib/mtproto/rpc/get_contacts.rb +0 -29
- data/lib/mtproto/rpc/get_updates_difference.rb +0 -51
- data/lib/mtproto/rpc/get_updates_state.rb +0 -29
- data/lib/mtproto/rpc/get_users.rb +0 -29
- data/lib/mtproto/rpc/ping.rb +0 -33
- data/lib/mtproto/rpc/send_code.rb +0 -41
- data/lib/mtproto/rpc/send_message.rb +0 -47
- data/lib/mtproto/rpc/sign_in.rb +0 -48
- data/lib/mtproto/type/auth_key/dh_gen_response.rb +0 -37
- data/lib/mtproto/type/auth_key/req_dh_params.rb +0 -31
- data/lib/mtproto/type/auth_key/req_pq_multi.rb +0 -18
- data/lib/mtproto/type/auth_key/res_pq.rb +0 -62
- data/lib/mtproto/type/auth_key/server_dh_params.rb +0 -43
- data/lib/mtproto/type/auth_key/set_client_dh_params.rb +0 -25
- data/lib/mtproto/type/code_settings.rb +0 -25
- data/lib/mtproto/type/config.rb +0 -124
- data/lib/mtproto/type/rpc/auth/authorization.rb +0 -107
- data/lib/mtproto/type/rpc/auth/send_code.rb +0 -28
- data/lib/mtproto/type/rpc/auth/sent_code.rb +0 -36
- data/lib/mtproto/type/rpc/auth/sign_in.rb +0 -32
- data/lib/mtproto/type/rpc/contacts/contacts.rb +0 -155
- data/lib/mtproto/type/rpc/contacts/get_contacts.rb +0 -18
- data/lib/mtproto/type/rpc/help/config.rb +0 -35
- data/lib/mtproto/type/rpc/help/get_config.rb +0 -17
- data/lib/mtproto/type/rpc/init_connection.rb +0 -28
- data/lib/mtproto/type/rpc/invoke_with_layer.rb +0 -19
- data/lib/mtproto/type/rpc/messages/send_message.rb +0 -43
- data/lib/mtproto/type/rpc/messages/updates.rb +0 -87
- data/lib/mtproto/type/rpc/ping.rb +0 -18
- data/lib/mtproto/type/rpc/pong.rb +0 -46
- data/lib/mtproto/type/rpc/updates/difference.rb +0 -332
- data/lib/mtproto/type/rpc/updates/get_difference.rb +0 -42
- data/lib/mtproto/type/rpc/updates/get_state.rb +0 -17
- data/lib/mtproto/type/rpc/updates/state.rb +0 -59
- data/lib/mtproto/type/rpc/users/get_users.rb +0 -25
- data/lib/mtproto/type/rpc/users/users.rb +0 -99
- data/lib/mtproto/type/sent_code.rb +0 -128
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class Update
|
|
6
|
+
CONSTRUCTOR_NEW_MESSAGE = 0x1f2b0afd
|
|
7
|
+
MESSAGE_CONSTRUCTOR = 0x9815cec8
|
|
8
|
+
|
|
9
|
+
attr_reader :type, :text
|
|
10
|
+
|
|
11
|
+
def initialize(type, text: nil)
|
|
12
|
+
@type = type
|
|
13
|
+
@text = text
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.parse(bytes)
|
|
17
|
+
constructor_id = bytes[0, 4].unpack1('L<')
|
|
18
|
+
type = type_name(constructor_id)
|
|
19
|
+
|
|
20
|
+
case constructor_id
|
|
21
|
+
when CONSTRUCTOR_NEW_MESSAGE
|
|
22
|
+
text = extract_new_message_text(bytes)
|
|
23
|
+
new(type, text: text)
|
|
24
|
+
else
|
|
25
|
+
new(type)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def type_name(constructor_id)
|
|
33
|
+
name = Constructors::NAMES.fetch(constructor_id)
|
|
34
|
+
name.sub(/^update/, '')
|
|
35
|
+
.gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
|
|
36
|
+
.gsub(/([a-z\d])([A-Z])/, '\1_\2')
|
|
37
|
+
.downcase
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def extract_new_message_text(bytes)
|
|
41
|
+
offset = 4 # skip updateNewMessage constructor
|
|
42
|
+
|
|
43
|
+
msg_constructor = bytes[offset, 4].unpack1('L<')
|
|
44
|
+
return nil unless msg_constructor == MESSAGE_CONSTRUCTOR
|
|
45
|
+
|
|
46
|
+
offset += 4
|
|
47
|
+
flags = bytes[offset, 4].unpack1('L<')
|
|
48
|
+
offset += 4
|
|
49
|
+
flags2 = bytes[offset, 4].unpack1('L<')
|
|
50
|
+
offset += 4
|
|
51
|
+
offset += 4 # id
|
|
52
|
+
|
|
53
|
+
offset += 12 if flags & (1 << 8) != 0 # from_id: Peer
|
|
54
|
+
offset += 12 # peer_id: Peer
|
|
55
|
+
offset += 12 if flags & (1 << 28) != 0 # saved_peer_id: Peer
|
|
56
|
+
return nil if flags & (1 << 2) != 0 # fwd_from: variable length
|
|
57
|
+
|
|
58
|
+
offset += 8 if flags & (1 << 11) != 0 # via_bot_id: long
|
|
59
|
+
offset += 8 if flags2 & 1 != 0 # via_business_bot_id: long
|
|
60
|
+
return nil if flags & (1 << 3) != 0 # reply_to: variable length
|
|
61
|
+
|
|
62
|
+
offset += 4 # date
|
|
63
|
+
read_tl_string(bytes, offset)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def read_tl_string(bytes, offset)
|
|
67
|
+
first_byte = bytes.getbyte(offset)
|
|
68
|
+
if first_byte == 254
|
|
69
|
+
len = bytes.getbyte(offset + 1) |
|
|
70
|
+
(bytes.getbyte(offset + 2) << 8) |
|
|
71
|
+
(bytes.getbyte(offset + 3) << 16)
|
|
72
|
+
bytes[offset + 4, len]
|
|
73
|
+
else
|
|
74
|
+
bytes[offset + 1, first_byte]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UpdateShort
|
|
6
|
+
CONSTRUCTOR = 0x78d4dec1
|
|
7
|
+
|
|
8
|
+
attr_reader :update, :date
|
|
9
|
+
|
|
10
|
+
def initialize(update, date)
|
|
11
|
+
@update = update
|
|
12
|
+
@date = date
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def self.parse(bytes)
|
|
16
|
+
date = bytes[-4, 4].unpack1('l<')
|
|
17
|
+
update = Update.parse(bytes[4...-4])
|
|
18
|
+
new(update, date)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UpdateShortMessage
|
|
6
|
+
CONSTRUCTOR = 0x313bc7f8
|
|
7
|
+
|
|
8
|
+
attr_reader :flags, :id, :user_id, :text, :pts, :pts_count, :date
|
|
9
|
+
|
|
10
|
+
def initialize(flags:, id:, user_id:, text:, pts:, pts_count:, date:)
|
|
11
|
+
@flags = flags
|
|
12
|
+
@id = id
|
|
13
|
+
@user_id = user_id
|
|
14
|
+
@text = text
|
|
15
|
+
@pts = pts
|
|
16
|
+
@pts_count = pts_count
|
|
17
|
+
@date = date
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def out?
|
|
21
|
+
flags & (1 << 1) != 0
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def mentioned?
|
|
25
|
+
flags & (1 << 4) != 0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.parse(bytes)
|
|
29
|
+
offset = 4 # skip constructor
|
|
30
|
+
flags = bytes[offset, 4].unpack1('L<')
|
|
31
|
+
offset += 4
|
|
32
|
+
id = bytes[offset, 4].unpack1('l<')
|
|
33
|
+
offset += 4
|
|
34
|
+
user_id = bytes[offset, 8].unpack1('q<')
|
|
35
|
+
offset += 8
|
|
36
|
+
text, offset = read_tl_string(bytes, offset)
|
|
37
|
+
pts = bytes[offset, 4].unpack1('l<')
|
|
38
|
+
offset += 4
|
|
39
|
+
pts_count = bytes[offset, 4].unpack1('l<')
|
|
40
|
+
offset += 4
|
|
41
|
+
date = bytes[offset, 4].unpack1('l<')
|
|
42
|
+
|
|
43
|
+
new(flags: flags, id: id, user_id: user_id, text: text,
|
|
44
|
+
pts: pts, pts_count: pts_count, date: date)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
class << self
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def read_tl_string(bytes, offset)
|
|
51
|
+
first_byte = bytes.getbyte(offset)
|
|
52
|
+
if first_byte == 254
|
|
53
|
+
len = bytes.getbyte(offset + 1) |
|
|
54
|
+
(bytes.getbyte(offset + 2) << 8) |
|
|
55
|
+
(bytes.getbyte(offset + 3) << 16)
|
|
56
|
+
total = 4 + len
|
|
57
|
+
else
|
|
58
|
+
len = first_byte
|
|
59
|
+
total = 1 + len
|
|
60
|
+
end
|
|
61
|
+
padding = (4 - (total % 4)) % 4
|
|
62
|
+
[bytes[offset + (total - len), len], offset + total + padding]
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UpdatesDifference
|
|
6
|
+
CONSTRUCTOR_EMPTY = 0x5d75a138
|
|
7
|
+
CONSTRUCTOR_DIFFERENCE = 0x00f49ca0
|
|
8
|
+
CONSTRUCTOR_SLICE = 0xa8fb1981
|
|
9
|
+
CONSTRUCTOR_TOO_LONG = 0x4afe8f6d
|
|
10
|
+
|
|
11
|
+
attr_reader :type, :date, :seq, :new_messages, :users, :pts, :state
|
|
12
|
+
|
|
13
|
+
def initialize(type:, date: nil, seq: nil, new_messages: [], users: [], pts: nil, state: nil)
|
|
14
|
+
@type = type
|
|
15
|
+
@date = date
|
|
16
|
+
@seq = seq
|
|
17
|
+
@new_messages = new_messages
|
|
18
|
+
@users = users
|
|
19
|
+
@pts = pts
|
|
20
|
+
@state = state
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.parse(data)
|
|
24
|
+
constructor = data[0, 4].unpack1('L<')
|
|
25
|
+
|
|
26
|
+
case constructor
|
|
27
|
+
when CONSTRUCTOR_EMPTY
|
|
28
|
+
parse_empty(data)
|
|
29
|
+
when CONSTRUCTOR_DIFFERENCE, CONSTRUCTOR_SLICE
|
|
30
|
+
parse_difference(data, constructor)
|
|
31
|
+
when CONSTRUCTOR_TOO_LONG
|
|
32
|
+
new(type: :too_long, pts: data[4, 4].unpack1('L<'))
|
|
33
|
+
else
|
|
34
|
+
warn "Unknown Difference constructor: 0x#{constructor.to_s(16)}, treating as empty"
|
|
35
|
+
new(type: :empty, date: Time.now.to_i, seq: 0)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class << self
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def parse_empty(data)
|
|
43
|
+
new(
|
|
44
|
+
type: :empty,
|
|
45
|
+
date: data[4, 4].unpack1('L<'),
|
|
46
|
+
seq: data[8, 4].unpack1('L<')
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def parse_difference(data, constructor)
|
|
51
|
+
offset = 4
|
|
52
|
+
|
|
53
|
+
messages, offset = parse_messages_vector(data, offset)
|
|
54
|
+
offset = skip_vector(data, offset) # encrypted_messages
|
|
55
|
+
offset = skip_vector(data, offset) # other_updates
|
|
56
|
+
offset = skip_vector(data, offset) # chats
|
|
57
|
+
users, = parse_users_vector(data, offset)
|
|
58
|
+
|
|
59
|
+
new(
|
|
60
|
+
type: constructor == CONSTRUCTOR_DIFFERENCE ? :difference : :slice,
|
|
61
|
+
new_messages: messages,
|
|
62
|
+
users: users
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def parse_messages_vector(data, offset)
|
|
67
|
+
offset += 4 # vector constructor
|
|
68
|
+
count = data[offset, 4].unpack1('L<')
|
|
69
|
+
offset += 4
|
|
70
|
+
|
|
71
|
+
messages = []
|
|
72
|
+
count.times do
|
|
73
|
+
msg, offset = parse_message(data, offset)
|
|
74
|
+
messages << msg if msg
|
|
75
|
+
end
|
|
76
|
+
[messages, offset]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def parse_message(data, offset)
|
|
80
|
+
msg_constructor = data[offset, 4].unpack1('L<')
|
|
81
|
+
offset += 4
|
|
82
|
+
|
|
83
|
+
return [nil, offset] unless msg_constructor == 0x452c0e65
|
|
84
|
+
|
|
85
|
+
flags = data[offset, 4].unpack1('L<')
|
|
86
|
+
offset += 4
|
|
87
|
+
id = data[offset, 4].unpack1('L<')
|
|
88
|
+
offset += 4
|
|
89
|
+
offset += 12 if flags.anybits?(1 << 8) # from_id
|
|
90
|
+
offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
|
|
91
|
+
offset += 12 # peer_id
|
|
92
|
+
offset += 8 if flags.anybits?(1 << 11) # via_bot_id
|
|
93
|
+
date = data[offset, 4].unpack1('L<')
|
|
94
|
+
offset += 4
|
|
95
|
+
message_text, offset = read_tl_string(data, offset)
|
|
96
|
+
|
|
97
|
+
[{ id: id, date: date, message: message_text, flags: flags }, offset]
|
|
98
|
+
rescue StandardError
|
|
99
|
+
[nil, offset]
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def parse_users_vector(data, offset)
|
|
103
|
+
offset += 4 # vector constructor
|
|
104
|
+
count = data[offset, 4].unpack1('L<')
|
|
105
|
+
offset += 4
|
|
106
|
+
|
|
107
|
+
users = []
|
|
108
|
+
count.times do
|
|
109
|
+
user_constructor = data[offset, 4].unpack1('L<')
|
|
110
|
+
offset += 4
|
|
111
|
+
next unless user_constructor == 0x20b1422
|
|
112
|
+
|
|
113
|
+
flags = data[offset, 4].unpack1('L<')
|
|
114
|
+
offset += 4
|
|
115
|
+
offset += 4 # flags2
|
|
116
|
+
user_id = data[offset, 8].unpack1('Q<')
|
|
117
|
+
offset += 8
|
|
118
|
+
|
|
119
|
+
access_hash = nil
|
|
120
|
+
if flags.anybits?(1 << 0)
|
|
121
|
+
access_hash = data[offset, 8].unpack1('Q<')
|
|
122
|
+
offset += 8
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
users << { id: user_id, access_hash: access_hash }
|
|
126
|
+
rescue StandardError
|
|
127
|
+
break
|
|
128
|
+
end
|
|
129
|
+
[users, offset]
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def skip_vector(data, offset)
|
|
133
|
+
offset += 4 # vector constructor
|
|
134
|
+
count = data[offset, 4].unpack1('L<')
|
|
135
|
+
offset += 4
|
|
136
|
+
count.times { offset += 4 }
|
|
137
|
+
offset
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def read_tl_string(data, offset)
|
|
141
|
+
first_byte = data.getbyte(offset)
|
|
142
|
+
if first_byte == 254
|
|
143
|
+
len = data.getbyte(offset + 1) |
|
|
144
|
+
(data.getbyte(offset + 2) << 8) |
|
|
145
|
+
(data.getbyte(offset + 3) << 16)
|
|
146
|
+
total = 4 + len
|
|
147
|
+
else
|
|
148
|
+
len = first_byte
|
|
149
|
+
total = 1 + len
|
|
150
|
+
end
|
|
151
|
+
padding = (4 - (total % 4)) % 4
|
|
152
|
+
[data[offset + (total - len), len], offset + total + padding]
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UpdatesState
|
|
6
|
+
CONSTRUCTOR = 0xa56c2a3e
|
|
7
|
+
|
|
8
|
+
attr_accessor :pts, :qts, :date, :seq, :unread_count
|
|
9
|
+
|
|
10
|
+
def initialize(pts:, qts:, date:, seq:, unread_count:)
|
|
11
|
+
@pts = pts
|
|
12
|
+
@qts = qts
|
|
13
|
+
@date = date
|
|
14
|
+
@seq = seq
|
|
15
|
+
@unread_count = unread_count
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.parse(data)
|
|
19
|
+
constructor = data[0, 4].unpack1('L<')
|
|
20
|
+
raise UnexpectedConstructorError, constructor unless constructor == CONSTRUCTOR
|
|
21
|
+
|
|
22
|
+
offset = 4
|
|
23
|
+
pts = data[offset, 4].unpack1('L<')
|
|
24
|
+
offset += 4
|
|
25
|
+
qts = data[offset, 4].unpack1('L<')
|
|
26
|
+
offset += 4
|
|
27
|
+
date = data[offset, 4].unpack1('L<')
|
|
28
|
+
offset += 4
|
|
29
|
+
seq = data[offset, 4].unpack1('L<')
|
|
30
|
+
offset += 4
|
|
31
|
+
unread_count = data[offset, 4].unpack1('L<')
|
|
32
|
+
|
|
33
|
+
new(pts: pts, qts: qts, date: date, seq: seq, unread_count: unread_count)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class Users
|
|
6
|
+
VECTOR_CONSTRUCTOR = 0x1cb5c415
|
|
7
|
+
USER_CONSTRUCTOR = 0x020b1422
|
|
8
|
+
|
|
9
|
+
User = Struct.new(:id, :access_hash, :first_name, :last_name, :username, :phone, :flags, :flags2,
|
|
10
|
+
keyword_init: true)
|
|
11
|
+
|
|
12
|
+
def self.parse(data)
|
|
13
|
+
constructor = data[0, 4].unpack1('L<')
|
|
14
|
+
raise "Expected Vector constructor, got 0x#{constructor.to_s(16)}" unless constructor == VECTOR_CONSTRUCTOR
|
|
15
|
+
|
|
16
|
+
offset = 4
|
|
17
|
+
count = data[offset, 4].unpack1('L<')
|
|
18
|
+
offset += 4
|
|
19
|
+
|
|
20
|
+
users = []
|
|
21
|
+
count.times do
|
|
22
|
+
user_constructor = data[offset, 4].unpack1('L<')
|
|
23
|
+
offset += 4
|
|
24
|
+
next unless user_constructor == USER_CONSTRUCTOR
|
|
25
|
+
|
|
26
|
+
flags = data[offset, 4].unpack1('L<')
|
|
27
|
+
offset += 4
|
|
28
|
+
|
|
29
|
+
flags2 = data[offset, 4].unpack1('L<')
|
|
30
|
+
offset += 4
|
|
31
|
+
|
|
32
|
+
id = data[offset, 8].unpack1('Q<')
|
|
33
|
+
offset += 8
|
|
34
|
+
|
|
35
|
+
access_hash = nil
|
|
36
|
+
if flags.anybits?(1 << 0)
|
|
37
|
+
access_hash = data[offset, 8].unpack1('Q<')
|
|
38
|
+
offset += 8
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
first_name = nil
|
|
42
|
+
first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
|
|
43
|
+
|
|
44
|
+
last_name = nil
|
|
45
|
+
last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
|
|
46
|
+
|
|
47
|
+
username = nil
|
|
48
|
+
username, offset = read_tl_string(data, offset) if flags.anybits?(1 << 3)
|
|
49
|
+
|
|
50
|
+
phone = nil
|
|
51
|
+
phone, offset = read_tl_string(data, offset) if flags.anybits?(1 << 4)
|
|
52
|
+
|
|
53
|
+
users << User.new(
|
|
54
|
+
id: id, access_hash: access_hash,
|
|
55
|
+
first_name: first_name, last_name: last_name,
|
|
56
|
+
username: username, phone: phone,
|
|
57
|
+
flags: flags, flags2: flags2
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
break
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
users
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
class << self
|
|
67
|
+
private
|
|
68
|
+
|
|
69
|
+
def read_tl_string(data, offset)
|
|
70
|
+
first_byte = data.getbyte(offset)
|
|
71
|
+
if first_byte == 254
|
|
72
|
+
len = data.getbyte(offset + 1) |
|
|
73
|
+
(data.getbyte(offset + 2) << 8) |
|
|
74
|
+
(data.getbyte(offset + 3) << 16)
|
|
75
|
+
total = 4 + len
|
|
76
|
+
else
|
|
77
|
+
len = first_byte
|
|
78
|
+
total = 1 + len
|
|
79
|
+
end
|
|
80
|
+
padding = (4 - (total % 4)) % 4
|
|
81
|
+
[data[offset + (total - len), len], offset + total + padding]
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -6,29 +6,52 @@ module MTProto
|
|
|
6
6
|
TAG = "\xef".b
|
|
7
7
|
OBFUSCATE_TAG = "\xef\xef\xef\xef".b
|
|
8
8
|
|
|
9
|
-
def
|
|
10
|
-
|
|
9
|
+
def initialize(stream)
|
|
10
|
+
@stream = stream
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def send(packet)
|
|
14
|
+
length = packet.size >> 2
|
|
11
15
|
|
|
12
|
-
|
|
13
|
-
[length].pack('C')
|
|
16
|
+
if length < 127
|
|
17
|
+
@stream.write [length].pack('C')
|
|
14
18
|
else
|
|
15
|
-
"\x7f".b + [length].pack('L<')[0, 3]
|
|
19
|
+
@stream.write "\x7f".b + [length].pack('L<')[0, 3]
|
|
16
20
|
end
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
@stream.write packet.data.pack('C*')
|
|
19
23
|
end
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
25
|
+
MAX_PACKET_SIZE = 1_048_576
|
|
26
|
+
|
|
27
|
+
def scan
|
|
28
|
+
first_byte = read_exactly(1)
|
|
29
|
+
length = first_byte.unpack1('C')
|
|
24
30
|
|
|
25
31
|
if length >= 127
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
length_bytes = read_exactly(3)
|
|
33
|
+
length = "#{length_bytes}\x00".unpack1('L<')
|
|
28
34
|
end
|
|
29
35
|
|
|
30
36
|
actual_length = length << 2
|
|
31
|
-
|
|
37
|
+
|
|
38
|
+
raise PacketReadError, "Packet too large: #{actual_length} bytes" if actual_length > MAX_PACKET_SIZE
|
|
39
|
+
|
|
40
|
+
data = read_exactly(actual_length)
|
|
41
|
+
Packet.new(data.bytes)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def read_exactly(bytes_needed)
|
|
47
|
+
result = ''.b
|
|
48
|
+
while result.bytesize < bytes_needed
|
|
49
|
+
chunk = @stream.read(bytes_needed - result.bytesize)
|
|
50
|
+
raise IOError, 'EOF while reading' if chunk.nil? || chunk.empty?
|
|
51
|
+
|
|
52
|
+
result += chunk
|
|
53
|
+
end
|
|
54
|
+
result
|
|
32
55
|
end
|
|
33
56
|
end
|
|
34
57
|
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Transport
|
|
5
|
+
class Connection
|
|
6
|
+
extend DelegateMethods
|
|
7
|
+
|
|
8
|
+
attr_reader :transport
|
|
9
|
+
|
|
10
|
+
def initialize(transport, wait_timeout: 60, read_timeout: 30)
|
|
11
|
+
@transport = transport
|
|
12
|
+
@transport.wait_timeout = wait_timeout
|
|
13
|
+
@transport.read_timeout = read_timeout
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
delegate :connect!, :disconnect!, :connected?, :not_connected?,
|
|
17
|
+
:send, :receive,
|
|
18
|
+
:wait_timeout, :wait_timeout=,
|
|
19
|
+
:read_timeout, :read_timeout=,
|
|
20
|
+
to: :transport
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Transport
|
|
5
|
+
class ConnectionError < StandardError; end
|
|
6
|
+
class NotConnectedError < ConnectionError; end
|
|
7
|
+
class ConnectionClosedError < ConnectionError; end
|
|
8
|
+
class ReceiveTimeoutError < ConnectionError; end
|
|
9
|
+
class PacketReadError < ConnectionError; end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module Transport
|
|
5
|
+
class Packet
|
|
6
|
+
attr_reader :data
|
|
7
|
+
|
|
8
|
+
def initialize(data)
|
|
9
|
+
raise ArgumentError, 'data must be an Array' unless data.is_a?(Array)
|
|
10
|
+
|
|
11
|
+
@data = data
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def size
|
|
15
|
+
data.length
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|