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.
- checksums.yaml +4 -4
- data/.env.example +5 -0
- data/Rakefile +13 -0
- data/lib/mtproto/auth_key_generator.rb +36 -13
- data/lib/mtproto/client/rpc.rb +141 -0
- data/lib/mtproto/client.rb +60 -185
- data/lib/mtproto/crypto/aes_ige.rb +1 -1
- data/lib/mtproto/crypto/factorization.rb +1 -1
- data/lib/mtproto/crypto/rsa_key.rb +9 -15
- data/lib/mtproto/errors.rb +33 -0
- data/lib/mtproto/message_id.rb +13 -0
- data/lib/mtproto/rpc/get_config.rb +37 -0
- data/lib/mtproto/rpc/get_contacts.rb +22 -0
- data/lib/mtproto/rpc/get_updates_difference.rb +33 -0
- data/lib/mtproto/rpc/get_updates_state.rb +22 -0
- data/lib/mtproto/rpc/get_users.rb +22 -0
- data/lib/mtproto/rpc/ping.rb +26 -0
- data/lib/mtproto/rpc/send_code.rb +44 -0
- data/lib/mtproto/rpc/send_message.rb +31 -0
- data/lib/mtproto/rpc/sign_in.rb +52 -0
- data/lib/mtproto/tl/auth_key/dh_gen_response.rb +37 -0
- data/lib/mtproto/tl/auth_key/req_dh_params.rb +31 -0
- data/lib/mtproto/tl/auth_key/req_pq_multi.rb +18 -0
- data/lib/mtproto/tl/auth_key/res_pq.rb +62 -0
- data/lib/mtproto/tl/auth_key/server_dh_params.rb +43 -0
- data/lib/mtproto/tl/auth_key/set_client_dh_params.rb +25 -0
- data/lib/mtproto/tl/code_settings.rb +25 -0
- data/lib/mtproto/tl/config.rb +4 -2
- data/lib/mtproto/tl/gzip_packed.rb +1 -1
- data/lib/mtproto/tl/message.rb +8 -216
- data/lib/mtproto/tl/method_builder.rb +29 -0
- data/lib/mtproto/tl/rpc/auth/authorization.rb +107 -0
- data/lib/mtproto/tl/rpc/auth/send_code.rb +28 -0
- data/lib/mtproto/tl/rpc/auth/sent_code.rb +36 -0
- data/lib/mtproto/tl/rpc/auth/sign_in.rb +32 -0
- data/lib/mtproto/tl/rpc/contacts/contacts.rb +155 -0
- data/lib/mtproto/tl/rpc/contacts/get_contacts.rb +18 -0
- data/lib/mtproto/tl/rpc/help/config.rb +35 -0
- data/lib/mtproto/tl/rpc/help/get_config.rb +17 -0
- data/lib/mtproto/tl/rpc/messages/send_message.rb +43 -0
- data/lib/mtproto/tl/rpc/messages/updates.rb +87 -0
- data/lib/mtproto/tl/rpc/ping.rb +18 -0
- data/lib/mtproto/tl/rpc/pong.rb +46 -0
- data/lib/mtproto/tl/rpc/updates/difference.rb +332 -0
- data/lib/mtproto/tl/rpc/updates/get_difference.rb +42 -0
- data/lib/mtproto/tl/rpc/updates/get_state.rb +17 -0
- data/lib/mtproto/tl/rpc/updates/state.rb +59 -0
- data/lib/mtproto/tl/rpc/users/get_users.rb +25 -0
- data/lib/mtproto/tl/rpc/users/users.rb +99 -0
- data/lib/mtproto/tl/sent_code.rb +128 -0
- data/lib/mtproto/transport/tcp_connection.rb +1 -1
- data/lib/mtproto/updates_poller.rb +111 -0
- data/lib/mtproto/version.rb +1 -1
- data/lib/mtproto.rb +13 -0
- metadata +57 -6
- data/ext/aes_ige/Makefile +0 -273
- data/ext/aes_ige/aes_ige.bundle +0 -0
- data/ext/factorization/Makefile +0 -273
- data/ext/factorization/factorization.bundle +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
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 TL
|
|
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 == TL::GzipPacked::CONSTRUCTOR
|
|
24
|
+
response = TL::GzipPacked.unpack(response)
|
|
25
|
+
constructor = response[0, 4].unpack1('L<')
|
|
26
|
+
offset = 4
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
30
|
+
error = TL::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 TL
|
|
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 TL
|
|
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
|
|
@@ -0,0 +1,332 @@
|
|
|
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 Updates
|
|
10
|
+
class Difference
|
|
11
|
+
CONSTRUCTOR_DIFFERENCE = 0x00f49ca0
|
|
12
|
+
CONSTRUCTOR_DIFFERENCE_EMPTY = 0x5d75a138
|
|
13
|
+
CONSTRUCTOR_DIFFERENCE_SLICE = 0xa8fb1981
|
|
14
|
+
CONSTRUCTOR_DIFFERENCE_TOO_LONG = 0x4afe8f6d
|
|
15
|
+
|
|
16
|
+
# Updates constructors (can be returned instead of Difference)
|
|
17
|
+
CONSTRUCTOR_UPDATE_SHORT = 0x78d4dec1
|
|
18
|
+
CONSTRUCTOR_UPDATES = 0x74ae4240
|
|
19
|
+
CONSTRUCTOR_UPDATE_SHORT_MESSAGE = 0x313bc7f8
|
|
20
|
+
CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE = 0x402d5dbb
|
|
21
|
+
CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE = 0x11f1331c
|
|
22
|
+
|
|
23
|
+
VECTOR_CONSTRUCTOR = 0x1cb5c415
|
|
24
|
+
MESSAGE_CONSTRUCTOR = 0x452c0e65
|
|
25
|
+
MESSAGE_EMPTY_CONSTRUCTOR = 0x83e5de54
|
|
26
|
+
|
|
27
|
+
def self.parse(response)
|
|
28
|
+
offset = 0
|
|
29
|
+
|
|
30
|
+
constructor = response[offset, 4].unpack1('L<')
|
|
31
|
+
offset += 4
|
|
32
|
+
|
|
33
|
+
if constructor == TL::GzipPacked::CONSTRUCTOR
|
|
34
|
+
response = TL::GzipPacked.unpack(response)
|
|
35
|
+
constructor = response[0, 4].unpack1('L<')
|
|
36
|
+
offset = 4
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
40
|
+
error = TL::RpcError.deserialize(response)
|
|
41
|
+
raise MTProto::RpcError.new(error.error_code, error.error_message)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
case constructor
|
|
45
|
+
when CONSTRUCTOR_DIFFERENCE_EMPTY
|
|
46
|
+
# updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference;
|
|
47
|
+
date = response[offset, 4].unpack1('L<')
|
|
48
|
+
offset += 4
|
|
49
|
+
|
|
50
|
+
seq = response[offset, 4].unpack1('L<')
|
|
51
|
+
|
|
52
|
+
{
|
|
53
|
+
type: :empty,
|
|
54
|
+
date: date,
|
|
55
|
+
seq: seq,
|
|
56
|
+
new_messages: [],
|
|
57
|
+
state: nil
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
when CONSTRUCTOR_DIFFERENCE, CONSTRUCTOR_DIFFERENCE_SLICE
|
|
61
|
+
# updates.difference#f49ca0 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> state:updates.State = updates.Difference;
|
|
62
|
+
# updates.differenceSlice#a8fb1981 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> intermediate_state:updates.State = updates.Difference;
|
|
63
|
+
|
|
64
|
+
# Parse new_messages vector
|
|
65
|
+
messages, offset = parse_messages_vector(response, offset)
|
|
66
|
+
|
|
67
|
+
# Skip new_encrypted_messages vector
|
|
68
|
+
offset = skip_vector(response, offset)
|
|
69
|
+
|
|
70
|
+
# Skip other_updates vector
|
|
71
|
+
offset = skip_vector(response, offset)
|
|
72
|
+
|
|
73
|
+
# Skip chats vector
|
|
74
|
+
offset = skip_vector(response, offset)
|
|
75
|
+
|
|
76
|
+
# Parse users vector
|
|
77
|
+
users, offset = parse_users_vector(response, offset)
|
|
78
|
+
|
|
79
|
+
{
|
|
80
|
+
type: constructor == CONSTRUCTOR_DIFFERENCE ? :difference : :slice,
|
|
81
|
+
new_messages: messages,
|
|
82
|
+
users: users,
|
|
83
|
+
state: nil # Would need to parse state
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
when CONSTRUCTOR_DIFFERENCE_TOO_LONG
|
|
87
|
+
# updates.differenceTooLong#4afe8f6d pts:int = updates.Difference;
|
|
88
|
+
pts = response[offset, 4].unpack1('L<')
|
|
89
|
+
|
|
90
|
+
{
|
|
91
|
+
type: :too_long,
|
|
92
|
+
pts: pts,
|
|
93
|
+
new_messages: [],
|
|
94
|
+
state: nil
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
when CONSTRUCTOR_UPDATE_SHORT_MESSAGE
|
|
98
|
+
# updateShortMessage#313bc7f8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;
|
|
99
|
+
|
|
100
|
+
flags = response[offset, 4].unpack1('L<')
|
|
101
|
+
offset += 4
|
|
102
|
+
|
|
103
|
+
id = response[offset, 4].unpack1('L<')
|
|
104
|
+
offset += 4
|
|
105
|
+
|
|
106
|
+
user_id = response[offset, 8].unpack1('Q<')
|
|
107
|
+
offset += 8
|
|
108
|
+
|
|
109
|
+
# Parse message string
|
|
110
|
+
msg_len = response[offset].unpack1('C')
|
|
111
|
+
offset += 1
|
|
112
|
+
message_text = response[offset, msg_len].force_encoding('UTF-8')
|
|
113
|
+
offset += msg_len
|
|
114
|
+
padding = (4 - ((1 + msg_len) % 4)) % 4
|
|
115
|
+
offset += padding
|
|
116
|
+
|
|
117
|
+
pts = response[offset, 4].unpack1('L<')
|
|
118
|
+
offset += 4
|
|
119
|
+
|
|
120
|
+
pts_count = response[offset, 4].unpack1('L<')
|
|
121
|
+
offset += 4
|
|
122
|
+
|
|
123
|
+
date = response[offset, 4].unpack1('L<')
|
|
124
|
+
offset += 4
|
|
125
|
+
|
|
126
|
+
# Return with the extracted message
|
|
127
|
+
{
|
|
128
|
+
type: :short_message,
|
|
129
|
+
new_messages: [{
|
|
130
|
+
id: id,
|
|
131
|
+
user_id: user_id,
|
|
132
|
+
date: date,
|
|
133
|
+
message: message_text,
|
|
134
|
+
flags: flags
|
|
135
|
+
}],
|
|
136
|
+
state: { pts: pts, pts_count: pts_count }
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
when CONSTRUCTOR_UPDATE_SHORT, CONSTRUCTOR_UPDATES,
|
|
140
|
+
CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE,
|
|
141
|
+
CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE
|
|
142
|
+
# Sometimes getDifference returns Updates instead of Difference
|
|
143
|
+
# This means there are no new differences, treat as empty
|
|
144
|
+
{
|
|
145
|
+
type: :empty,
|
|
146
|
+
date: Time.now.to_i,
|
|
147
|
+
seq: 0,
|
|
148
|
+
new_messages: [],
|
|
149
|
+
state: nil
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
else
|
|
153
|
+
# Unknown constructor - likely an Updates variant we don't handle yet
|
|
154
|
+
# Treat as empty to avoid breaking the polling loop
|
|
155
|
+
warn "Warning: Unknown Difference/Updates constructor: 0x#{constructor.to_s(16)}, treating as empty"
|
|
156
|
+
{
|
|
157
|
+
type: :empty,
|
|
158
|
+
date: Time.now.to_i,
|
|
159
|
+
seq: 0,
|
|
160
|
+
new_messages: [],
|
|
161
|
+
state: nil
|
|
162
|
+
}
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def self.parse_messages_vector(response, offset)
|
|
167
|
+
# Check for Vector constructor
|
|
168
|
+
vector_constructor = response[offset, 4].unpack1('L<')
|
|
169
|
+
offset += 4
|
|
170
|
+
|
|
171
|
+
return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
|
|
172
|
+
|
|
173
|
+
count = response[offset, 4].unpack1('L<')
|
|
174
|
+
offset += 4
|
|
175
|
+
|
|
176
|
+
messages = []
|
|
177
|
+
count.times do
|
|
178
|
+
message, offset = parse_message(response, offset)
|
|
179
|
+
messages << message if message
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
[messages, offset]
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def self.parse_message(response, offset)
|
|
186
|
+
msg_constructor = response[offset, 4].unpack1('L<')
|
|
187
|
+
offset += 4
|
|
188
|
+
|
|
189
|
+
return [nil, offset] if msg_constructor == MESSAGE_EMPTY_CONSTRUCTOR
|
|
190
|
+
|
|
191
|
+
return [nil, offset] unless msg_constructor == MESSAGE_CONSTRUCTOR
|
|
192
|
+
|
|
193
|
+
# message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true offline:flags.30?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags.31?long = Message;
|
|
194
|
+
|
|
195
|
+
flags = response[offset, 4].unpack1('L<')
|
|
196
|
+
offset += 4
|
|
197
|
+
|
|
198
|
+
# For simplicity, we'll just extract id, date, and message text
|
|
199
|
+
# A full implementation would parse all fields based on flags
|
|
200
|
+
|
|
201
|
+
id = response[offset, 4].unpack1('L<')
|
|
202
|
+
offset += 4
|
|
203
|
+
|
|
204
|
+
# Skip from_id if present (flags.8)
|
|
205
|
+
if (flags & (1 << 8)) != 0
|
|
206
|
+
# Skip Peer object (constructor + id, could be user/chat/channel)
|
|
207
|
+
offset += 4 # peer constructor
|
|
208
|
+
offset += 8 # peer id (long for user, int for others, but let's assume worst case)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Skip from_boosts_applied if present (flags.29)
|
|
212
|
+
offset += 4 if (flags & (1 << 29)) != 0
|
|
213
|
+
|
|
214
|
+
# peer_id:Peer (always present) - skip it
|
|
215
|
+
offset += 4 # peer constructor
|
|
216
|
+
offset += 8 # peer id
|
|
217
|
+
|
|
218
|
+
# Skip fwd_from if present (flags.2)
|
|
219
|
+
# This is complex, so we'll just skip ahead assuming it's not there for now
|
|
220
|
+
|
|
221
|
+
# Skip via_bot_id if present (flags.11)
|
|
222
|
+
offset += 8 if (flags & (1 << 11)) != 0
|
|
223
|
+
|
|
224
|
+
# Skip reply_to if present (flags.3)
|
|
225
|
+
# Complex object, skip for now
|
|
226
|
+
|
|
227
|
+
# date:int (always present)
|
|
228
|
+
date = response[offset, 4].unpack1('L<')
|
|
229
|
+
offset += 4
|
|
230
|
+
|
|
231
|
+
# message:string (always present)
|
|
232
|
+
msg_len = response[offset].unpack1('C')
|
|
233
|
+
offset += 1
|
|
234
|
+
message_text = response[offset, msg_len].force_encoding('UTF-8')
|
|
235
|
+
offset += msg_len
|
|
236
|
+
padding = (4 - ((1 + msg_len) % 4)) % 4
|
|
237
|
+
offset += padding
|
|
238
|
+
|
|
239
|
+
# Return simplified message
|
|
240
|
+
[{
|
|
241
|
+
id: id,
|
|
242
|
+
date: date,
|
|
243
|
+
message: message_text,
|
|
244
|
+
flags: flags
|
|
245
|
+
}, offset]
|
|
246
|
+
rescue StandardError => e
|
|
247
|
+
# If parsing fails, skip this message
|
|
248
|
+
[nil, offset]
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def self.skip_vector(response, offset)
|
|
252
|
+
# Read vector constructor
|
|
253
|
+
vector_constructor = response[offset, 4].unpack1('L<')
|
|
254
|
+
offset += 4
|
|
255
|
+
|
|
256
|
+
return offset unless vector_constructor == VECTOR_CONSTRUCTOR
|
|
257
|
+
|
|
258
|
+
# Read count
|
|
259
|
+
count = response[offset, 4].unpack1('L<')
|
|
260
|
+
offset += 4
|
|
261
|
+
|
|
262
|
+
# Skip each element - we can't know the size, so return offset as-is
|
|
263
|
+
# This is a limitation - proper skipping would require parsing each element
|
|
264
|
+
# For now, just read the constructor of each element and skip based on known patterns
|
|
265
|
+
count.times do
|
|
266
|
+
constructor = response[offset, 4].unpack1('L<')
|
|
267
|
+
offset += 4
|
|
268
|
+
|
|
269
|
+
# This is a simplified skip - real implementation would need to parse each type
|
|
270
|
+
# For now, skip a reasonable amount (most TL objects are small)
|
|
271
|
+
# This is fragile but works for common cases
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
offset
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def self.parse_users_vector(response, offset)
|
|
278
|
+
# Check for Vector constructor
|
|
279
|
+
vector_constructor = response[offset, 4].unpack1('L<')
|
|
280
|
+
offset += 4
|
|
281
|
+
|
|
282
|
+
return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR
|
|
283
|
+
|
|
284
|
+
count = response[offset, 4].unpack1('L<')
|
|
285
|
+
offset += 4
|
|
286
|
+
|
|
287
|
+
users = []
|
|
288
|
+
count.times do
|
|
289
|
+
user, offset = parse_user(response, offset)
|
|
290
|
+
users << user if user
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
[users, offset]
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def self.parse_user(response, offset)
|
|
297
|
+
user_constructor = response[offset, 4].unpack1('L<')
|
|
298
|
+
offset += 4
|
|
299
|
+
|
|
300
|
+
# user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
|
|
301
|
+
return [nil, offset] unless user_constructor == 0x20b1422
|
|
302
|
+
|
|
303
|
+
flags = response[offset, 4].unpack1('L<')
|
|
304
|
+
offset += 4
|
|
305
|
+
|
|
306
|
+
flags2 = response[offset, 4].unpack1('L<')
|
|
307
|
+
offset += 4
|
|
308
|
+
|
|
309
|
+
# id:long (always present)
|
|
310
|
+
user_id = response[offset, 8].unpack1('Q<')
|
|
311
|
+
offset += 8
|
|
312
|
+
|
|
313
|
+
# access_hash:flags.0?long (conditional)
|
|
314
|
+
access_hash = nil
|
|
315
|
+
if (flags & (1 << 0)) != 0
|
|
316
|
+
access_hash = response[offset, 8].unpack1('Q<')
|
|
317
|
+
offset += 8
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
# We have what we need, skip the rest
|
|
321
|
+
[{
|
|
322
|
+
id: user_id,
|
|
323
|
+
access_hash: access_hash
|
|
324
|
+
}, offset]
|
|
325
|
+
rescue StandardError => e
|
|
326
|
+
[nil, offset]
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../serializer'
|
|
4
|
+
|
|
5
|
+
module MTProto
|
|
6
|
+
module TL
|
|
7
|
+
module RPC
|
|
8
|
+
module Updates
|
|
9
|
+
class GetDifference
|
|
10
|
+
CONSTRUCTOR = 0x19c2f763
|
|
11
|
+
|
|
12
|
+
def self.build(pts:, pts_limit:, pts_total_limit: nil, date:, qts:, qts_limit: nil)
|
|
13
|
+
raise ArgumentError, 'pts is required' if pts.nil?
|
|
14
|
+
raise ArgumentError, 'date is required' if date.nil?
|
|
15
|
+
raise ArgumentError, 'qts is required' if qts.nil?
|
|
16
|
+
|
|
17
|
+
# updates.getDifference#19c2f763 flags:# pts:int pts_limit:flags.0?int pts_total_limit:flags.1?int date:int qts:int qts_limit:flags.2?int = updates.Difference;
|
|
18
|
+
flags = 0
|
|
19
|
+
flags |= (1 << 0) if pts_limit
|
|
20
|
+
flags |= (1 << 1) if pts_total_limit
|
|
21
|
+
flags |= (1 << 2) if qts_limit
|
|
22
|
+
|
|
23
|
+
query = [CONSTRUCTOR].pack('L<')
|
|
24
|
+
query += Serializer.serialize_int(flags)
|
|
25
|
+
query += Serializer.serialize_int(pts)
|
|
26
|
+
|
|
27
|
+
query += Serializer.serialize_int(pts_limit) if pts_limit
|
|
28
|
+
|
|
29
|
+
query += Serializer.serialize_int(pts_total_limit) if pts_total_limit
|
|
30
|
+
|
|
31
|
+
query += Serializer.serialize_int(date)
|
|
32
|
+
query += Serializer.serialize_int(qts)
|
|
33
|
+
|
|
34
|
+
query += Serializer.serialize_int(qts_limit) if qts_limit
|
|
35
|
+
|
|
36
|
+
query
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
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 Updates
|
|
10
|
+
class State
|
|
11
|
+
CONSTRUCTOR = 0xa56c2a3e
|
|
12
|
+
|
|
13
|
+
def self.parse(response)
|
|
14
|
+
offset = 0
|
|
15
|
+
|
|
16
|
+
constructor = response[offset, 4].unpack1('L<')
|
|
17
|
+
offset += 4
|
|
18
|
+
|
|
19
|
+
if constructor == TL::GzipPacked::CONSTRUCTOR
|
|
20
|
+
response = TL::GzipPacked.unpack(response)
|
|
21
|
+
constructor = response[0, 4].unpack1('L<')
|
|
22
|
+
offset = 4
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
if constructor == TL::RpcError::CONSTRUCTOR
|
|
26
|
+
error = TL::RpcError.deserialize(response)
|
|
27
|
+
raise MTProto::RpcError.new(error.error_code, error.error_message)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
raise "Expected State constructor 0x#{CONSTRUCTOR.to_s(16)}, got 0x#{constructor.to_s(16)}" unless constructor == CONSTRUCTOR
|
|
31
|
+
|
|
32
|
+
# updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
|
33
|
+
pts = response[offset, 4].unpack1('L<')
|
|
34
|
+
offset += 4
|
|
35
|
+
|
|
36
|
+
qts = response[offset, 4].unpack1('L<')
|
|
37
|
+
offset += 4
|
|
38
|
+
|
|
39
|
+
date = response[offset, 4].unpack1('L<')
|
|
40
|
+
offset += 4
|
|
41
|
+
|
|
42
|
+
seq = response[offset, 4].unpack1('L<')
|
|
43
|
+
offset += 4
|
|
44
|
+
|
|
45
|
+
unread_count = response[offset, 4].unpack1('L<')
|
|
46
|
+
|
|
47
|
+
{
|
|
48
|
+
pts: pts,
|
|
49
|
+
qts: qts,
|
|
50
|
+
date: date,
|
|
51
|
+
seq: seq,
|
|
52
|
+
unread_count: unread_count
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
module RPC
|
|
6
|
+
module Users
|
|
7
|
+
class GetUsers
|
|
8
|
+
CONSTRUCTOR = 0x0d91a548
|
|
9
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
10
|
+
|
|
11
|
+
def self.build
|
|
12
|
+
query = [CONSTRUCTOR].pack('L<')
|
|
13
|
+
|
|
14
|
+
# Vector of InputUser with one element: inputUserSelf
|
|
15
|
+
query += [0x1cb5c415].pack('L<') # vector constructor
|
|
16
|
+
query += [1].pack('L<') # count = 1
|
|
17
|
+
query += [INPUT_USER_SELF].pack('L<') # inputUserSelf
|
|
18
|
+
|
|
19
|
+
query
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|