mtproto 0.0.14 → 0.0.15
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/client/rpc.rb +33 -0
- data/lib/mtproto/client.rb +53 -0
- data/lib/mtproto/tl/constructor_names.rb +10 -0
- data/lib/mtproto/tl/objects/create_bot.rb +54 -0
- data/lib/mtproto/tl/objects/dialogs.rb +10 -0
- data/lib/mtproto/tl/objects/edit_access_settings.rb +46 -0
- data/lib/mtproto/tl/objects/export_bot_token.rb +32 -0
- data/lib/mtproto/tl/objects/exported_bot_token.rb +27 -0
- data/lib/mtproto/tl/objects/forward_messages.rb +8 -2
- data/lib/mtproto/tl/objects/get_access_settings.rb +28 -0
- data/lib/mtproto/tl/objects/input_keyboard_button_request_peer.rb +54 -0
- data/lib/mtproto/tl/objects/messages_get_messages.rb +26 -0
- data/lib/mtproto/tl/objects/reply_keyboard_markup.rb +35 -0
- data/lib/mtproto/tl/objects/request_peer_type_create_bot.rb +47 -0
- data/lib/mtproto/tl/objects/send_bot_requested_peer.rb +51 -0
- data/lib/mtproto/tl/objects/send_media.rb +13 -2
- data/lib/mtproto/tl/objects/send_message.rb +4 -1
- data/lib/mtproto/tl/objects/set_bot_commands.rb +50 -0
- data/lib/mtproto/tl/objects/set_bot_info.rb +64 -0
- data/lib/mtproto/tl/objects/updates_difference.rb +91 -12
- data/lib/mtproto/tl/objects/upload_profile_photo.rb +65 -0
- data/lib/mtproto/version.rb +1 -1
- metadata +14 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a52707d10eeb5e41cf6e1dcb01374d92092031ceba5f5f92dde31ce4965d174c
|
|
4
|
+
data.tar.gz: ddf47dbcf9612d686806ec83fd16470de46595300b76c733fc8af37be851f1ff
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c97f61c1f21c9b2221b90333dd12156b77617e7debd04424a0a4996dd7c50c7b570ba0777d043b966032ad2827db4d70498cc362cd9966c1342bd2b4c711d91c
|
|
7
|
+
data.tar.gz: 5f235da4dac488f6c9853affca03de93bfecf2b35eacaedf13e933a4400a099e55d1831e2002c63a0687fc326e74c38e86c1e82da9bd9c95c5f07bd41ee846f7
|
data/lib/mtproto/client/rpc.rb
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'securerandom'
|
|
3
4
|
require_relative '../message_id'
|
|
4
5
|
require_relative 'rpc/response'
|
|
5
6
|
|
|
6
7
|
module MTProto
|
|
7
8
|
class Client
|
|
8
9
|
class RPC
|
|
10
|
+
MSGS_ACK = 0x62d6b459
|
|
11
|
+
VECTOR = 0x1cb5c415
|
|
12
|
+
PING_DELAY_DISCONNECT = 0xf3427b8c
|
|
13
|
+
|
|
9
14
|
attr_reader :pending_requests
|
|
10
15
|
|
|
11
16
|
def initialize(client)
|
|
@@ -66,6 +71,34 @@ module MTProto
|
|
|
66
71
|
@pending_requests.clear
|
|
67
72
|
end
|
|
68
73
|
|
|
74
|
+
# Acknowledge received server messages (msgs_ack). Without this the server
|
|
75
|
+
# keeps the unacknowledged messages, resends them, and drops the connection
|
|
76
|
+
# after a fixed window. Fire-and-forget — no rpc_result is expected.
|
|
77
|
+
def send_ack(msg_ids)
|
|
78
|
+
return if msg_ids.nil? || msg_ids.empty?
|
|
79
|
+
|
|
80
|
+
send_encrypted(self.class.ack_body(msg_ids), content_related: false)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Keepalive: ping_delay_disconnect asks the server to disconnect us only if
|
|
84
|
+
# we stop pinging within disconnect_delay seconds. Fire-and-forget (the pong
|
|
85
|
+
# is ignored by process_message).
|
|
86
|
+
def send_ping(disconnect_delay = 75)
|
|
87
|
+
send_encrypted(self.class.ping_body(SecureRandom.random_number(2**63), disconnect_delay),
|
|
88
|
+
content_related: false)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Serialized msgs_ack body (no envelope) — extracted for unit testing.
|
|
92
|
+
def self.ack_body(msg_ids)
|
|
93
|
+
[MSGS_ACK].pack('L<') + [VECTOR].pack('L<') + [msg_ids.length].pack('L<') +
|
|
94
|
+
msg_ids.map { |id| [id].pack('Q<') }.join
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Serialized ping_delay_disconnect body (no envelope) — extracted for testing.
|
|
98
|
+
def self.ping_body(ping_id, disconnect_delay)
|
|
99
|
+
[PING_DELAY_DISCONNECT].pack('L<') + [ping_id].pack('Q<') + [disconnect_delay].pack('l<')
|
|
100
|
+
end
|
|
101
|
+
|
|
69
102
|
private
|
|
70
103
|
|
|
71
104
|
def serialize_request(request)
|
data/lib/mtproto/client.rb
CHANGED
|
@@ -77,10 +77,17 @@ module MTProto
|
|
|
77
77
|
@lang_code = 'en'
|
|
78
78
|
|
|
79
79
|
@receiver_task = nil
|
|
80
|
+
@keepalive_task = nil
|
|
81
|
+
@ack_ids = []
|
|
80
82
|
@running = false
|
|
81
83
|
@on_update_callbacks = []
|
|
82
84
|
end
|
|
83
85
|
|
|
86
|
+
# Keepalive cadence: flush pending acks every ACK_INTERVAL seconds, send a
|
|
87
|
+
# ping every PING_INTERVAL seconds.
|
|
88
|
+
ACK_INTERVAL = 5
|
|
89
|
+
PING_INTERVAL = 30
|
|
90
|
+
|
|
84
91
|
def on_update(&block)
|
|
85
92
|
@on_update_callbacks << block
|
|
86
93
|
end
|
|
@@ -111,6 +118,7 @@ module MTProto
|
|
|
111
118
|
raise 'Mainloop already running' if @running
|
|
112
119
|
|
|
113
120
|
@running = true
|
|
121
|
+
@ack_ids = []
|
|
114
122
|
@receiver_task = Async do
|
|
115
123
|
@connection.receive do |packet, error|
|
|
116
124
|
if error
|
|
@@ -124,15 +132,20 @@ module MTProto
|
|
|
124
132
|
sender: :server
|
|
125
133
|
)
|
|
126
134
|
|
|
135
|
+
collect_ack(decrypted[:msg_id], decrypted[:seq_no], decrypted[:body])
|
|
127
136
|
process_message(decrypted[:body])
|
|
128
137
|
end
|
|
129
138
|
end
|
|
139
|
+
@keepalive_task = Async { keepalive_loop }
|
|
130
140
|
end
|
|
131
141
|
|
|
132
142
|
def disconnect!
|
|
133
143
|
@running = false
|
|
144
|
+
@keepalive_task&.stop
|
|
145
|
+
@keepalive_task = nil
|
|
134
146
|
@receiver_task&.stop
|
|
135
147
|
@receiver_task = nil
|
|
148
|
+
@ack_ids = []
|
|
136
149
|
|
|
137
150
|
rpc.signal_all_error(Transport::ConnectionError.new('Client shutting down'))
|
|
138
151
|
|
|
@@ -232,6 +245,46 @@ module MTProto
|
|
|
232
245
|
|
|
233
246
|
private
|
|
234
247
|
|
|
248
|
+
# Periodically acknowledge received messages and ping, so the server doesn't
|
|
249
|
+
# drop a long-lived connection. Ends when the connection dies (the wrapper
|
|
250
|
+
# reconnects) or when the task is stopped on disconnect!.
|
|
251
|
+
def keepalive_loop
|
|
252
|
+
elapsed = 0
|
|
253
|
+
loop do
|
|
254
|
+
sleep ACK_INTERVAL
|
|
255
|
+
flush_acks
|
|
256
|
+
elapsed += ACK_INTERVAL
|
|
257
|
+
next if elapsed < PING_INTERVAL
|
|
258
|
+
|
|
259
|
+
elapsed = 0
|
|
260
|
+
rpc.send_ping
|
|
261
|
+
end
|
|
262
|
+
rescue StandardError
|
|
263
|
+
nil
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# Queue msg_ids of received content-related server messages for msgs_ack.
|
|
267
|
+
# A container carries its own inner msg_ids; content-related messages have an
|
|
268
|
+
# odd seq_no and must be acknowledged (acks/pongs are even and need none).
|
|
269
|
+
def collect_ack(msg_id, seq_no, body)
|
|
270
|
+
constructor = body[0, 4].unpack1('L<')
|
|
271
|
+
if constructor == TL::Constructors::MSG_CONTAINER
|
|
272
|
+
TL::MsgContainer.deserialize(body).messages.each do |msg|
|
|
273
|
+
collect_ack(msg[:msg_id], msg[:seqno], msg[:body])
|
|
274
|
+
end
|
|
275
|
+
elsif seq_no.odd?
|
|
276
|
+
@ack_ids << msg_id
|
|
277
|
+
end
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def flush_acks
|
|
281
|
+
return if @ack_ids.empty?
|
|
282
|
+
|
|
283
|
+
ids = @ack_ids
|
|
284
|
+
@ack_ids = []
|
|
285
|
+
rpc.send_ack(ids)
|
|
286
|
+
end
|
|
287
|
+
|
|
235
288
|
def process_message(response_body)
|
|
236
289
|
constructor = response_body[0, 4].unpack1('L<')
|
|
237
290
|
|
|
@@ -192,6 +192,7 @@ module MTProto
|
|
|
192
192
|
0x16115a96 => 'pageBlockRelatedArticles',
|
|
193
193
|
0x161d9628 => 'topPeerCategoryChannels',
|
|
194
194
|
0x16484857 => 'account.chatThemes',
|
|
195
|
+
0x16605e3e => 'messageActionManagedBotCreated',
|
|
195
196
|
0x1662af0b => 'messages.historyImport',
|
|
196
197
|
0x167bd90b => 'payments.starGiftUpgradePreview',
|
|
197
198
|
0x167fc0a1 => 'channels.toggleAutotranslation',
|
|
@@ -304,6 +305,7 @@ module MTProto
|
|
|
304
305
|
0x21108ff7 => 'businessRecipients',
|
|
305
306
|
0x211a1788 => 'webPageEmpty',
|
|
306
307
|
0x21202222 => 'messages.getDialogUnreadMarks',
|
|
308
|
+
0x213853a3 => 'bots.getAccessSettings',
|
|
307
309
|
0x2144ca19 => 'rpc_error',
|
|
308
310
|
0x21461b5d => 'privacyValueAllowBots',
|
|
309
311
|
0x219c34e6 => 'phone.toggleGroupCallStartSubscription',
|
|
@@ -428,6 +430,8 @@ module MTProto
|
|
|
428
430
|
0x31518e9b => 'messageActionRequestedPeer',
|
|
429
431
|
0x315a4974 => 'users.usersSlice',
|
|
430
432
|
0x316ce548 => 'account.setReactionsNotifySettings',
|
|
433
|
+
0x31774388 => 'user',
|
|
434
|
+
0x31813cd8 => 'bots.editAccessSettings',
|
|
431
435
|
0x31bb5d52 => 'channelAdminLogEventActionChangeWallpaper',
|
|
432
436
|
0x31bd492d => 'messages.messageReactionsList',
|
|
433
437
|
0x31c1c44f => 'messages.getMessageReadParticipants',
|
|
@@ -525,6 +529,7 @@ module MTProto
|
|
|
525
529
|
0x3c479971 => 'phone.declineConferenceCallInvite',
|
|
526
530
|
0x3c4f04d8 => 'botCommandScopeUsers',
|
|
527
531
|
0x3c5693e9 => 'inputTheme',
|
|
532
|
+
0x3c60b621 => 'bots.exportedBotToken',
|
|
528
533
|
0x3cbc93f8 => 'chatParticipants',
|
|
529
534
|
0x3cc04740 => 'messages.deleteQuickReplyShortcut',
|
|
530
535
|
0x3cd930b7 => 'channels.setEmojiStickers',
|
|
@@ -616,6 +621,7 @@ module MTProto
|
|
|
616
621
|
0x47dd8079 => 'messageActionWebViewDataSentMe',
|
|
617
622
|
0x481eadfa => 'emojiListNotModified',
|
|
618
623
|
0x48222faf => 'inputGeoPoint',
|
|
624
|
+
0x4880ed9a => 'updateManagedBot',
|
|
619
625
|
0x48870999 => 'pageBlockFooter',
|
|
620
626
|
0x4899484e => 'messages.votesList',
|
|
621
627
|
0x48a30254 => 'replyInlineMarkup',
|
|
@@ -918,6 +924,7 @@ module MTProto
|
|
|
918
924
|
0x6c47ac9f => 'langPackStringPluralized',
|
|
919
925
|
0x6c50051c => 'messages.importChatInvite',
|
|
920
926
|
0x6c5a5b37 => 'account.saveWallPaper',
|
|
927
|
+
0x6c5cf2a7 => 'messages.sendBotRequestedPeer',
|
|
921
928
|
0x6c6274fa => 'messageActionGiftPremium',
|
|
922
929
|
0x6c750de1 => 'messages.sendQuickReplyMessages',
|
|
923
930
|
0x6c8e1e06 => 'birthday',
|
|
@@ -1651,6 +1658,7 @@ module MTProto
|
|
|
1651
1658
|
0xbc799737 => 'boolFalse',
|
|
1652
1659
|
0xbcf22685 => 'phone.inviteConferenceCallParticipant',
|
|
1653
1660
|
0xbd0415c4 => 'stories.togglePeerStoriesHidden',
|
|
1661
|
+
0xbd0d99eb => 'bots.exportBotToken',
|
|
1654
1662
|
0xbd17a14a => 'topPeerCategoryGroups',
|
|
1655
1663
|
0xbd1efd3e => 'payments.getStarsGiveawayOptions',
|
|
1656
1664
|
0xbd2a0840 => 'inputPeerChannelFromMessage',
|
|
@@ -1927,6 +1935,7 @@ module MTProto
|
|
|
1927
1935
|
0xdcdf8607 => 'stats.getMegagroupStats',
|
|
1928
1936
|
0xdd0c66f2 => 'starRefProgram',
|
|
1929
1937
|
0xdd18782e => 'help.appConfig',
|
|
1938
|
+
0xdd1fbf93 => 'bots.accessSettings',
|
|
1930
1939
|
0xdd289f8e => 'invokeWithBusinessConnection',
|
|
1931
1940
|
0xdd6a8f48 => 'sendMessageGamePlayAction',
|
|
1932
1941
|
0xdde8a54c => 'inputPeerUser',
|
|
@@ -1999,6 +2008,7 @@ module MTProto
|
|
|
1999
2008
|
0xe56dbf05 => 'dialogPeer',
|
|
2000
2009
|
0xe581e4e9 => 'requirementToContactPremium',
|
|
2001
2010
|
0xe58e95d2 => 'messages.deleteMessages',
|
|
2011
|
+
0xe5b17f2b => 'bots.createBot',
|
|
2002
2012
|
0xe5bbfe1a => 'inputMediaPhotoExternal',
|
|
2003
2013
|
0xe5bdf8de => 'updateUserStatus',
|
|
2004
2014
|
0xe5bfffcd => 'auth.exportAuthorization',
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class CreateBot
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0xe5b17f2b
|
|
9
|
+
INPUT_USER = 0xf21158c6
|
|
10
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
11
|
+
|
|
12
|
+
# manager: { id:, access_hash: } or { type: :self }
|
|
13
|
+
def initialize(name:, username:, manager:, via_deeplink: false)
|
|
14
|
+
@name = name
|
|
15
|
+
@username = username
|
|
16
|
+
@manager = manager
|
|
17
|
+
@via_deeplink = via_deeplink
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def serialize
|
|
21
|
+
flags = 0
|
|
22
|
+
flags |= (1 << 0) if @via_deeplink
|
|
23
|
+
|
|
24
|
+
result = u32_b(CONSTRUCTOR)
|
|
25
|
+
result += u32_b(flags)
|
|
26
|
+
result += serialize_tl_string(@name)
|
|
27
|
+
result += serialize_tl_string(@username)
|
|
28
|
+
result += if @manager[:type] == :self
|
|
29
|
+
u32_b(INPUT_USER_SELF)
|
|
30
|
+
else
|
|
31
|
+
u32_b(INPUT_USER) + u64_b(@manager[:id]) + u64_b(@manager[:access_hash] || 0)
|
|
32
|
+
end
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def serialize_tl_string(str)
|
|
39
|
+
bytes = str.encode('UTF-8').bytes
|
|
40
|
+
length = bytes.length
|
|
41
|
+
if length <= 253
|
|
42
|
+
[length] + bytes + padding(length + 1)
|
|
43
|
+
else
|
|
44
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def padding(current_length)
|
|
49
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
50
|
+
[0] * pad_length
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -51,6 +51,16 @@ module MTProto
|
|
|
51
51
|
new(dialogs: dialogs, chats: chats, users: users, count: count, message_dates: message_dates)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# Parse a chats:Vector<Chat> / users:Vector<User> at an arbitrary offset.
|
|
55
|
+
# Reused by UpdatesDifference — the same vectors appear in updates.difference.
|
|
56
|
+
def self.chats_at(data, offset)
|
|
57
|
+
parse_chats_vector(data, offset)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def self.users_at(data, offset)
|
|
61
|
+
parse_users_vector(data, offset)
|
|
62
|
+
end
|
|
63
|
+
|
|
54
64
|
class << self
|
|
55
65
|
private
|
|
56
66
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class EditAccessSettings
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x31813cd8
|
|
9
|
+
INPUT_USER = 0xf21158c6
|
|
10
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
11
|
+
|
|
12
|
+
# bot / add_users entries: { id:, access_hash: } or { type: :self }
|
|
13
|
+
def initialize(bot:, restricted: false, add_users: [])
|
|
14
|
+
@bot = bot
|
|
15
|
+
@restricted = restricted
|
|
16
|
+
@add_users = add_users
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def serialize
|
|
20
|
+
flags = 0
|
|
21
|
+
flags |= (1 << 0) if @restricted
|
|
22
|
+
flags |= (1 << 1) unless @add_users.empty?
|
|
23
|
+
|
|
24
|
+
result = u32_b(CONSTRUCTOR)
|
|
25
|
+
result += u32_b(flags)
|
|
26
|
+
result += serialize_input_user(@bot)
|
|
27
|
+
unless @add_users.empty?
|
|
28
|
+
result += u32_b(Constructors::VECTOR)
|
|
29
|
+
result += u32_b(@add_users.size)
|
|
30
|
+
@add_users.each { |u| result += serialize_input_user(u) }
|
|
31
|
+
end
|
|
32
|
+
result
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def serialize_input_user(user)
|
|
38
|
+
if user[:type] == :self
|
|
39
|
+
u32_b(INPUT_USER_SELF)
|
|
40
|
+
else
|
|
41
|
+
u32_b(INPUT_USER) + u64_b(user[:id]) + u64_b(user[:access_hash] || 0)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class ExportBotToken
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0xbd0d99eb
|
|
9
|
+
INPUT_USER = 0xf21158c6
|
|
10
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
11
|
+
BOOL_TRUE = 0x997275b5
|
|
12
|
+
BOOL_FALSE = 0xbc799737
|
|
13
|
+
|
|
14
|
+
# bot: { id:, access_hash: } or { type: :self }
|
|
15
|
+
def initialize(bot:, revoke: false)
|
|
16
|
+
@bot = bot
|
|
17
|
+
@revoke = revoke
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def serialize
|
|
21
|
+
result = u32_b(CONSTRUCTOR)
|
|
22
|
+
result += if @bot[:type] == :self
|
|
23
|
+
u32_b(INPUT_USER_SELF)
|
|
24
|
+
else
|
|
25
|
+
u32_b(INPUT_USER) + u64_b(@bot[:id]) + u64_b(@bot[:access_hash] || 0)
|
|
26
|
+
end
|
|
27
|
+
result += u32_b(@revoke ? BOOL_TRUE : BOOL_FALSE)
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class ExportedBotToken
|
|
6
|
+
CONSTRUCTOR = 0x3c60b621
|
|
7
|
+
|
|
8
|
+
attr_reader :token
|
|
9
|
+
|
|
10
|
+
def initialize(token:)
|
|
11
|
+
@token = token
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.deserialize(bytes)
|
|
15
|
+
body = bytes[4..]
|
|
16
|
+
first = body.getbyte(0)
|
|
17
|
+
token = if first == 254
|
|
18
|
+
len = body.getbyte(1) | (body.getbyte(2) << 8) | (body.getbyte(3) << 16)
|
|
19
|
+
body[4, len]
|
|
20
|
+
else
|
|
21
|
+
body[1, first]
|
|
22
|
+
end
|
|
23
|
+
new(token: token.force_encoding('UTF-8'))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -9,16 +9,22 @@ module MTProto
|
|
|
9
9
|
|
|
10
10
|
CONSTRUCTOR = 0x978928ca
|
|
11
11
|
|
|
12
|
-
def initialize(from_peer:, to_peer:, ids:, random_ids: nil)
|
|
12
|
+
def initialize(from_peer:, to_peer:, ids:, random_ids: nil, drop_author: false, drop_media_captions: false)
|
|
13
13
|
@from_peer = from_peer
|
|
14
14
|
@to_peer = to_peer
|
|
15
15
|
@ids = ids
|
|
16
16
|
@random_ids = random_ids || Array.new(ids.length) { SecureRandom.random_number(2**63) }
|
|
17
|
+
@drop_author = drop_author
|
|
18
|
+
@drop_media_captions = drop_media_captions
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
def serialize
|
|
22
|
+
flags = 0
|
|
23
|
+
flags |= (1 << 11) if @drop_author
|
|
24
|
+
flags |= (1 << 12) if @drop_media_captions
|
|
25
|
+
|
|
20
26
|
result = u32_b(CONSTRUCTOR)
|
|
21
|
-
result += u32_b(
|
|
27
|
+
result += u32_b(flags)
|
|
22
28
|
result += serialize_input_peer(@from_peer)
|
|
23
29
|
result += u32_b(Constructors::VECTOR) + u32_b(@ids.length)
|
|
24
30
|
@ids.each { |id| result += u32_b(id) }
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class GetAccessSettings
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x213853a3
|
|
9
|
+
INPUT_USER = 0xf21158c6
|
|
10
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
11
|
+
|
|
12
|
+
# bot: { id:, access_hash: } or { type: :self }
|
|
13
|
+
def initialize(bot:)
|
|
14
|
+
@bot = bot
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def serialize
|
|
18
|
+
result = u32_b(CONSTRUCTOR)
|
|
19
|
+
result += if @bot[:type] == :self
|
|
20
|
+
u32_b(INPUT_USER_SELF)
|
|
21
|
+
else
|
|
22
|
+
u32_b(INPUT_USER) + u64_b(@bot[:id]) + u64_b(@bot[:access_hash] || 0)
|
|
23
|
+
end
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class InputKeyboardButtonRequestPeer
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x02b78156
|
|
9
|
+
|
|
10
|
+
def initialize(text:, button_id:, peer_type:, max_quantity: 1,
|
|
11
|
+
name_requested: false, username_requested: false, photo_requested: false)
|
|
12
|
+
@text = text
|
|
13
|
+
@button_id = button_id
|
|
14
|
+
@peer_type = peer_type
|
|
15
|
+
@max_quantity = max_quantity
|
|
16
|
+
@name_requested = name_requested
|
|
17
|
+
@username_requested = username_requested
|
|
18
|
+
@photo_requested = photo_requested
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def serialize
|
|
22
|
+
flags = 0
|
|
23
|
+
flags |= (1 << 0) if @name_requested
|
|
24
|
+
flags |= (1 << 1) if @username_requested
|
|
25
|
+
flags |= (1 << 2) if @photo_requested
|
|
26
|
+
|
|
27
|
+
result = u32_b(CONSTRUCTOR)
|
|
28
|
+
result += u32_b(flags)
|
|
29
|
+
result += serialize_tl_string(@text)
|
|
30
|
+
result += u32_b(@button_id)
|
|
31
|
+
result += @peer_type.serialize
|
|
32
|
+
result += u32_b(@max_quantity)
|
|
33
|
+
result
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def serialize_tl_string(str)
|
|
39
|
+
bytes = str.encode('UTF-8').bytes
|
|
40
|
+
length = bytes.length
|
|
41
|
+
if length <= 253
|
|
42
|
+
[length] + bytes + padding(length + 1)
|
|
43
|
+
else
|
|
44
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def padding(current_length)
|
|
49
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
50
|
+
[0] * pad_length
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
# messages.getMessages#63c66506 id:Vector<InputMessage> — fetch messages by id
|
|
6
|
+
# from a basic group or private chat (the non-channel counterpart of
|
|
7
|
+
# channels.getMessages). Returns messages.messages / messagesSlice.
|
|
8
|
+
class MessagesGetMessages
|
|
9
|
+
include Binary
|
|
10
|
+
|
|
11
|
+
CONSTRUCTOR = 0x63c66506
|
|
12
|
+
INPUT_MESSAGE_ID = 0xa676a322
|
|
13
|
+
|
|
14
|
+
def initialize(ids:)
|
|
15
|
+
@ids = ids
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def serialize
|
|
19
|
+
result = u32_b(CONSTRUCTOR)
|
|
20
|
+
result += u32_b(Constructors::VECTOR) + u32_b(@ids.length)
|
|
21
|
+
@ids.each { |id| result += u32_b(INPUT_MESSAGE_ID) + u32_b(id) }
|
|
22
|
+
result
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class ReplyKeyboardMarkup
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x85dd99d1
|
|
9
|
+
KEYBOARD_BUTTON_ROW = 0x77608b83
|
|
10
|
+
|
|
11
|
+
# buttons: a single row of button objects (each responds to #serialize)
|
|
12
|
+
def initialize(buttons:, resize: true, single_use: false)
|
|
13
|
+
@buttons = buttons
|
|
14
|
+
@resize = resize
|
|
15
|
+
@single_use = single_use
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def serialize
|
|
19
|
+
flags = 0
|
|
20
|
+
flags |= (1 << 0) if @resize
|
|
21
|
+
flags |= (1 << 1) if @single_use
|
|
22
|
+
|
|
23
|
+
result = u32_b(CONSTRUCTOR)
|
|
24
|
+
result += u32_b(flags)
|
|
25
|
+
result += u32_b(Constructors::VECTOR)
|
|
26
|
+
result += u32_b(1)
|
|
27
|
+
result += u32_b(KEYBOARD_BUTTON_ROW)
|
|
28
|
+
result += u32_b(Constructors::VECTOR)
|
|
29
|
+
result += u32_b(@buttons.size)
|
|
30
|
+
@buttons.each { |button| result += button.serialize }
|
|
31
|
+
result
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class RequestPeerTypeCreateBot
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x3e81e078
|
|
9
|
+
|
|
10
|
+
def initialize(bot_managed: true, suggested_name: nil, suggested_username: nil)
|
|
11
|
+
@bot_managed = bot_managed
|
|
12
|
+
@suggested_name = suggested_name
|
|
13
|
+
@suggested_username = suggested_username
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def serialize
|
|
17
|
+
flags = 0
|
|
18
|
+
flags |= (1 << 0) if @bot_managed
|
|
19
|
+
flags |= (1 << 1) if @suggested_name
|
|
20
|
+
flags |= (1 << 2) if @suggested_username
|
|
21
|
+
|
|
22
|
+
result = u32_b(CONSTRUCTOR)
|
|
23
|
+
result += u32_b(flags)
|
|
24
|
+
result += serialize_tl_string(@suggested_name) if @suggested_name
|
|
25
|
+
result += serialize_tl_string(@suggested_username) if @suggested_username
|
|
26
|
+
result
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def serialize_tl_string(str)
|
|
32
|
+
bytes = str.encode('UTF-8').bytes
|
|
33
|
+
length = bytes.length
|
|
34
|
+
if length <= 253
|
|
35
|
+
[length] + bytes + padding(length + 1)
|
|
36
|
+
else
|
|
37
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def padding(current_length)
|
|
42
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
43
|
+
[0] * pad_length
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class SendBotRequestedPeer
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x6c5cf2a7
|
|
9
|
+
|
|
10
|
+
# peer / requested_peers entries: { type:, id:, access_hash: } or { type: :self }
|
|
11
|
+
def initialize(peer:, button_id:, msg_id: nil, requested_peers: [])
|
|
12
|
+
@peer = peer
|
|
13
|
+
@button_id = button_id
|
|
14
|
+
@msg_id = msg_id
|
|
15
|
+
@requested_peers = requested_peers
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def serialize
|
|
19
|
+
flags = 0
|
|
20
|
+
flags |= (1 << 0) if @msg_id
|
|
21
|
+
|
|
22
|
+
result = u32_b(CONSTRUCTOR)
|
|
23
|
+
result += u32_b(flags)
|
|
24
|
+
result += serialize_input_peer(@peer)
|
|
25
|
+
result += u32_b(@msg_id) if @msg_id
|
|
26
|
+
result += u32_b(@button_id)
|
|
27
|
+
result += u32_b(Constructors::VECTOR)
|
|
28
|
+
result += u32_b(@requested_peers.size)
|
|
29
|
+
@requested_peers.each { |peer| result += serialize_input_peer(peer) }
|
|
30
|
+
result
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def serialize_input_peer(peer)
|
|
36
|
+
case peer[:type]
|
|
37
|
+
when :self
|
|
38
|
+
u32_b(Constructors::INPUT_PEER_SELF)
|
|
39
|
+
when :user
|
|
40
|
+
u32_b(Constructors::INPUT_PEER_USER) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
|
|
41
|
+
when :chat
|
|
42
|
+
u32_b(Constructors::INPUT_PEER_CHAT) + u64_b(peer[:id])
|
|
43
|
+
when :channel
|
|
44
|
+
u32_b(Constructors::INPUT_PEER_CHANNEL) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
|
|
45
|
+
else
|
|
46
|
+
raise "Unknown peer type: #{peer[:type]}"
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -7,17 +7,24 @@ module MTProto
|
|
|
7
7
|
class SendMedia
|
|
8
8
|
include Binary
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
INPUT_REPLY_TO_MESSAGE = 0x869fbe10
|
|
11
|
+
|
|
12
|
+
def initialize(peer:, media:, message: '', random_id: nil, reply_to: nil)
|
|
11
13
|
@peer = peer
|
|
12
14
|
@media = media
|
|
13
15
|
@message = message
|
|
14
16
|
@random_id = random_id || SecureRandom.random_number(2**63)
|
|
17
|
+
@reply_to = reply_to
|
|
15
18
|
end
|
|
16
19
|
|
|
17
20
|
def serialize
|
|
21
|
+
flags = 0
|
|
22
|
+
flags |= (1 << 0) if @reply_to
|
|
23
|
+
|
|
18
24
|
result = u32_b(Constructors::MESSAGES_SEND_MEDIA)
|
|
19
|
-
result += u32_b(
|
|
25
|
+
result += u32_b(flags)
|
|
20
26
|
result += serialize_input_peer
|
|
27
|
+
result += serialize_reply_to if @reply_to
|
|
21
28
|
result += serialize_input_media
|
|
22
29
|
result += serialize_tl_string(@message)
|
|
23
30
|
result += u64_b(@random_id)
|
|
@@ -26,6 +33,10 @@ module MTProto
|
|
|
26
33
|
|
|
27
34
|
private
|
|
28
35
|
|
|
36
|
+
def serialize_reply_to
|
|
37
|
+
u32_b(INPUT_REPLY_TO_MESSAGE) + u32_b(0) + u32_b(@reply_to)
|
|
38
|
+
end
|
|
39
|
+
|
|
29
40
|
def serialize_input_peer
|
|
30
41
|
case @peer[:type]
|
|
31
42
|
when :self
|
|
@@ -9,16 +9,18 @@ module MTProto
|
|
|
9
9
|
|
|
10
10
|
INPUT_REPLY_TO_MESSAGE = 0x869fbe10
|
|
11
11
|
|
|
12
|
-
def initialize(peer:, message:, random_id: nil, reply_to: nil)
|
|
12
|
+
def initialize(peer:, message:, random_id: nil, reply_to: nil, reply_markup: nil)
|
|
13
13
|
@peer = peer
|
|
14
14
|
@message = message
|
|
15
15
|
@random_id = random_id || SecureRandom.random_number(2**63)
|
|
16
16
|
@reply_to = reply_to
|
|
17
|
+
@reply_markup = reply_markup
|
|
17
18
|
end
|
|
18
19
|
|
|
19
20
|
def serialize
|
|
20
21
|
flags = 0
|
|
21
22
|
flags |= (1 << 0) if @reply_to
|
|
23
|
+
flags |= (1 << 2) if @reply_markup
|
|
22
24
|
|
|
23
25
|
result = u32_b(Constructors::MESSAGES_SEND_MESSAGE)
|
|
24
26
|
result += u32_b(flags)
|
|
@@ -26,6 +28,7 @@ module MTProto
|
|
|
26
28
|
result += serialize_reply_to if @reply_to
|
|
27
29
|
result += serialize_tl_string(@message)
|
|
28
30
|
result += u64_b(@random_id)
|
|
31
|
+
result += @reply_markup.serialize if @reply_markup
|
|
29
32
|
result
|
|
30
33
|
end
|
|
31
34
|
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class SetBotCommands
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x0517165a
|
|
9
|
+
BOT_COMMAND_SCOPE_DEFAULT = 0x2f6cb2ab
|
|
10
|
+
BOT_COMMAND = 0xc27ac8c7
|
|
11
|
+
|
|
12
|
+
# commands: array of { command:, description: }
|
|
13
|
+
def initialize(commands:, lang_code: '')
|
|
14
|
+
@commands = commands
|
|
15
|
+
@lang_code = lang_code
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def serialize
|
|
19
|
+
result = u32_b(CONSTRUCTOR)
|
|
20
|
+
result += u32_b(BOT_COMMAND_SCOPE_DEFAULT)
|
|
21
|
+
result += serialize_tl_string(@lang_code)
|
|
22
|
+
result += u32_b(Constructors::VECTOR)
|
|
23
|
+
result += u32_b(@commands.size)
|
|
24
|
+
@commands.each do |cmd|
|
|
25
|
+
result += u32_b(BOT_COMMAND)
|
|
26
|
+
result += serialize_tl_string(cmd[:command])
|
|
27
|
+
result += serialize_tl_string(cmd[:description])
|
|
28
|
+
end
|
|
29
|
+
result
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
private
|
|
33
|
+
|
|
34
|
+
def serialize_tl_string(str)
|
|
35
|
+
bytes = str.to_s.encode('UTF-8').bytes
|
|
36
|
+
length = bytes.length
|
|
37
|
+
if length <= 253
|
|
38
|
+
[length] + bytes + padding(length + 1)
|
|
39
|
+
else
|
|
40
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def padding(current_length)
|
|
45
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
46
|
+
[0] * pad_length
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class SetBotInfo
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x10cf3123
|
|
9
|
+
INPUT_USER = 0xf21158c6
|
|
10
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
11
|
+
|
|
12
|
+
# bot: { id:, access_hash: } or { type: :self } (nil = the calling bot)
|
|
13
|
+
def initialize(bot: nil, lang_code: '', name: nil, about: nil, description: nil)
|
|
14
|
+
@bot = bot
|
|
15
|
+
@lang_code = lang_code
|
|
16
|
+
@name = name
|
|
17
|
+
@about = about
|
|
18
|
+
@description = description
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def serialize
|
|
22
|
+
flags = 0
|
|
23
|
+
flags |= (1 << 2) if @bot
|
|
24
|
+
flags |= (1 << 3) if @name
|
|
25
|
+
flags |= (1 << 0) if @about
|
|
26
|
+
flags |= (1 << 1) if @description
|
|
27
|
+
|
|
28
|
+
result = u32_b(CONSTRUCTOR)
|
|
29
|
+
result += u32_b(flags)
|
|
30
|
+
result += serialize_input_user(@bot) if @bot
|
|
31
|
+
result += serialize_tl_string(@lang_code)
|
|
32
|
+
result += serialize_tl_string(@name) if @name
|
|
33
|
+
result += serialize_tl_string(@about) if @about
|
|
34
|
+
result += serialize_tl_string(@description) if @description
|
|
35
|
+
result
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
private
|
|
39
|
+
|
|
40
|
+
def serialize_input_user(user)
|
|
41
|
+
if user[:type] == :self
|
|
42
|
+
u32_b(INPUT_USER_SELF)
|
|
43
|
+
else
|
|
44
|
+
u32_b(INPUT_USER) + u64_b(user[:id]) + u64_b(user[:access_hash] || 0)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def serialize_tl_string(str)
|
|
49
|
+
bytes = str.to_s.encode('UTF-8').bytes
|
|
50
|
+
length = bytes.length
|
|
51
|
+
if length <= 253
|
|
52
|
+
[length] + bytes + padding(length + 1)
|
|
53
|
+
else
|
|
54
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def padding(current_length)
|
|
59
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
60
|
+
[0] * pad_length
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../schema'
|
|
4
|
+
require_relative 'dialogs'
|
|
4
5
|
|
|
5
6
|
module MTProto
|
|
6
7
|
module TL
|
|
7
8
|
class UpdatesDifference
|
|
8
|
-
attr_reader :type, :date, :seq, :new_messages, :pts, :state
|
|
9
|
+
attr_reader :type, :date, :seq, :new_messages, :pts, :state, :chats, :users
|
|
9
10
|
|
|
10
11
|
SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
|
|
11
12
|
|
|
12
|
-
def initialize(type:, date: nil, seq: nil, new_messages: [], pts: nil, state: nil)
|
|
13
|
+
def initialize(type:, date: nil, seq: nil, new_messages: [], pts: nil, state: nil, chats: [], users: [])
|
|
13
14
|
@type = type
|
|
14
15
|
@date = date
|
|
15
16
|
@seq = seq
|
|
16
17
|
@new_messages = new_messages
|
|
17
18
|
@pts = pts
|
|
18
19
|
@state = state
|
|
20
|
+
@chats = chats
|
|
21
|
+
@users = users
|
|
19
22
|
end
|
|
20
23
|
|
|
21
24
|
def self.schema
|
|
@@ -41,6 +44,11 @@ module MTProto
|
|
|
41
44
|
MESSAGE_CONSTRUCTOR = Constructors::MESSAGE
|
|
42
45
|
MESSAGE_SERVICE = 0x7a800e0a
|
|
43
46
|
MESSAGE_EMPTY = 0x90a6ca84
|
|
47
|
+
MESSAGE_REPLY_HEADER = 0x6917560b
|
|
48
|
+
MEDIA_PHOTO = 0x695150d7
|
|
49
|
+
MEDIA_DOCUMENT = 0x52d8ccd9
|
|
50
|
+
UPDATE_NEW_MESSAGE = 0x1f2b0afd
|
|
51
|
+
UPDATE_NEW_CHANNEL_MESSAGE = 0x62ba04d9
|
|
44
52
|
|
|
45
53
|
class << self
|
|
46
54
|
private
|
|
@@ -53,24 +61,48 @@ module MTProto
|
|
|
53
61
|
)
|
|
54
62
|
end
|
|
55
63
|
|
|
56
|
-
# updates.difference: new_messages new_encrypted_messages other_updates chats users state
|
|
64
|
+
# updates.difference: new_messages new_encrypted_messages other_updates chats users state.
|
|
65
|
+
# Channel/supergroup messages arrive in other_updates as
|
|
66
|
+
# updateNewChannelMessage (not in new_messages), so we mine those too; and
|
|
67
|
+
# chats/users carry the peers' access_hashes for the bot's peer cache.
|
|
57
68
|
def parse_difference(data, constructor)
|
|
58
69
|
offset = 4
|
|
59
70
|
|
|
60
71
|
messages, offset = parse_messages_vector(data, offset)
|
|
61
72
|
offset = schema.skip_vector(data, offset) # new_encrypted_messages
|
|
62
|
-
offset =
|
|
63
|
-
offset =
|
|
64
|
-
offset =
|
|
73
|
+
other_messages, offset = parse_other_updates(data, offset)
|
|
74
|
+
chats, offset = Dialogs.chats_at(data, offset)
|
|
75
|
+
users, offset = Dialogs.users_at(data, offset)
|
|
65
76
|
state = UpdatesState.deserialize(data[offset..])
|
|
66
77
|
|
|
67
78
|
new(
|
|
68
79
|
type: constructor == Constructors::UPDATES_DIFFERENCE ? :difference : :slice,
|
|
69
|
-
new_messages: messages,
|
|
70
|
-
state: state
|
|
80
|
+
new_messages: messages + other_messages,
|
|
81
|
+
chats: chats, users: users, state: state
|
|
71
82
|
)
|
|
72
83
|
end
|
|
73
84
|
|
|
85
|
+
# other_updates: Vector<Update>. Mine the message-bearing updates
|
|
86
|
+
# (updateNewMessage / updateNewChannelMessage — each is the update ctor,
|
|
87
|
+
# then an inner Message, then pts/pts_count); advance the rest via schema.
|
|
88
|
+
def parse_other_updates(data, offset)
|
|
89
|
+
offset += 4 # vector constructor
|
|
90
|
+
count = data[offset, 4].unpack1('L<')
|
|
91
|
+
offset += 4
|
|
92
|
+
|
|
93
|
+
messages = []
|
|
94
|
+
count.times do
|
|
95
|
+
ctor = data[offset, 4].unpack1('L<')
|
|
96
|
+
if [UPDATE_NEW_MESSAGE, UPDATE_NEW_CHANNEL_MESSAGE].include?(ctor)
|
|
97
|
+
inner_off = offset + 4
|
|
98
|
+
msg = extract_message(data, inner_off, data[inner_off, 4].unpack1('L<'))
|
|
99
|
+
messages << msg if msg
|
|
100
|
+
end
|
|
101
|
+
offset = schema.skip(data, offset)
|
|
102
|
+
end
|
|
103
|
+
[messages, offset]
|
|
104
|
+
end
|
|
105
|
+
|
|
74
106
|
def parse_messages_vector(data, offset)
|
|
75
107
|
offset += 4 # vector constructor
|
|
76
108
|
count = data[offset, 4].unpack1('L<')
|
|
@@ -97,20 +129,30 @@ module MTProto
|
|
|
97
129
|
id = data[offset, 4].unpack1('l<')
|
|
98
130
|
offset += 4
|
|
99
131
|
|
|
100
|
-
|
|
132
|
+
from_type = from_id = nil
|
|
133
|
+
from_type, from_id, offset = parse_peer(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
|
|
101
134
|
offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
|
|
102
135
|
peer_type, peer_id, offset = parse_peer(data, offset)
|
|
103
136
|
offset = schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
|
|
104
137
|
offset = schema.skip(data, offset) if flags.anybits?(1 << 2) # fwd_from
|
|
105
138
|
offset += 8 if flags.anybits?(1 << 11) # via_bot_id
|
|
106
139
|
offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
|
|
107
|
-
|
|
140
|
+
|
|
141
|
+
is_reply = false
|
|
142
|
+
reply_to_msg_id = nil
|
|
143
|
+
if flags.anybits?(1 << 3) # reply_to
|
|
144
|
+
reply_to_msg_id, is_reply = parse_reply_to(data, offset)
|
|
145
|
+
offset = schema.skip(data, offset)
|
|
146
|
+
end
|
|
108
147
|
|
|
109
148
|
date = data[offset, 4].unpack1('L<')
|
|
110
149
|
offset += 4
|
|
111
|
-
message_text, = read_tl_string(data, offset)
|
|
150
|
+
message_text, offset = read_tl_string(data, offset)
|
|
151
|
+
media = flags.anybits?(1 << 9) ? parse_media(data, offset) : nil
|
|
112
152
|
|
|
113
|
-
{ id: id, date: date, message: message_text, peer_type: peer_type, peer_id: peer_id
|
|
153
|
+
{ id: id, date: date, message: message_text, peer_type: peer_type, peer_id: peer_id,
|
|
154
|
+
from_type: from_type, from_id: from_id, out: flags.anybits?(1 << 1),
|
|
155
|
+
media: media, is_reply: is_reply, reply_to_msg_id: reply_to_msg_id }
|
|
114
156
|
end
|
|
115
157
|
|
|
116
158
|
def parse_peer(data, offset)
|
|
@@ -129,6 +171,43 @@ module MTProto
|
|
|
129
171
|
end
|
|
130
172
|
end
|
|
131
173
|
|
|
174
|
+
# messageReplyHeader: ctor, flags, reply_to_msg_id:flags.4?int (first payload
|
|
175
|
+
# field). A plain message in a forum topic carries this header (forum_topic
|
|
176
|
+
# flags.3) but is a reply only when it targets a message in the topic
|
|
177
|
+
# (reply_to_top_id flags.1). => [reply_to_msg_id, is_reply]
|
|
178
|
+
def parse_reply_to(data, offset)
|
|
179
|
+
return [nil, false] unless data[offset, 4].unpack1('L<') == MESSAGE_REPLY_HEADER
|
|
180
|
+
|
|
181
|
+
io = offset + 4
|
|
182
|
+
flags = data[io, 4].unpack1('L<')
|
|
183
|
+
io += 4
|
|
184
|
+
forum_topic = flags.anybits?(1 << 3)
|
|
185
|
+
msg_id = flags.anybits?(1 << 4) ? data[io, 4].unpack1('l<') : nil
|
|
186
|
+
[msg_id, forum_topic ? flags.anybits?(1 << 1) : true]
|
|
187
|
+
rescue StandardError
|
|
188
|
+
[nil, false]
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Classify message media into :photo / :video / :audio / :document, or nil.
|
|
192
|
+
def parse_media(data, offset)
|
|
193
|
+
case data[offset, 4].unpack1('L<')
|
|
194
|
+
when MEDIA_PHOTO
|
|
195
|
+
:photo
|
|
196
|
+
when MEDIA_DOCUMENT
|
|
197
|
+
document_kind(data[offset + 4, 4].unpack1('L<'))
|
|
198
|
+
end
|
|
199
|
+
rescue StandardError
|
|
200
|
+
nil
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# messageMediaDocument flags: video flags.6 / round flags.7 / voice flags.8.
|
|
204
|
+
def document_kind(flags)
|
|
205
|
+
return :video if flags.anybits?(1 << 6) || flags.anybits?(1 << 7)
|
|
206
|
+
return :audio if flags.anybits?(1 << 8)
|
|
207
|
+
|
|
208
|
+
:document
|
|
209
|
+
end
|
|
210
|
+
|
|
132
211
|
def read_tl_string(data, offset)
|
|
133
212
|
first_byte = data.getbyte(offset)
|
|
134
213
|
if first_byte == 254
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module MTProto
|
|
4
|
+
module TL
|
|
5
|
+
class UploadProfilePhoto
|
|
6
|
+
include Binary
|
|
7
|
+
|
|
8
|
+
CONSTRUCTOR = 0x0388a3b5
|
|
9
|
+
INPUT_USER = 0xf21158c6
|
|
10
|
+
INPUT_USER_SELF = 0xf7c1b13f
|
|
11
|
+
INPUT_FILE = 0xf52ff27f
|
|
12
|
+
|
|
13
|
+
# bot: { id:, access_hash: } or { type: :self }; nil targets the calling bot
|
|
14
|
+
# file: { id:, parts:, name:, md5_checksum: }
|
|
15
|
+
def initialize(file:, bot: nil)
|
|
16
|
+
@bot = bot
|
|
17
|
+
@file = file
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def serialize
|
|
21
|
+
flags = (1 << 0) # file
|
|
22
|
+
flags |= (1 << 5) if @bot
|
|
23
|
+
|
|
24
|
+
result = u32_b(CONSTRUCTOR)
|
|
25
|
+
result += u32_b(flags)
|
|
26
|
+
result += serialize_input_user(@bot) if @bot
|
|
27
|
+
result += serialize_input_file(@file)
|
|
28
|
+
result
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def serialize_input_user(user)
|
|
34
|
+
if user[:type] == :self
|
|
35
|
+
u32_b(INPUT_USER_SELF)
|
|
36
|
+
else
|
|
37
|
+
u32_b(INPUT_USER) + u64_b(user[:id]) + u64_b(user[:access_hash] || 0)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def serialize_input_file(file)
|
|
42
|
+
u32_b(INPUT_FILE) +
|
|
43
|
+
u64_b(file[:id]) +
|
|
44
|
+
u32_b(file[:parts]) +
|
|
45
|
+
serialize_tl_string(file[:name].to_s) +
|
|
46
|
+
serialize_tl_string(file[:md5_checksum].to_s)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def serialize_tl_string(str)
|
|
50
|
+
bytes = str.encode('UTF-8').bytes
|
|
51
|
+
length = bytes.length
|
|
52
|
+
if length <= 253
|
|
53
|
+
[length] + bytes + padding(length + 1)
|
|
54
|
+
else
|
|
55
|
+
[254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def padding(current_length)
|
|
60
|
+
pad_length = (4 - (current_length % 4)) % 4
|
|
61
|
+
[0] * pad_length
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
data/lib/mtproto/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: mtproto
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.15
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Artem Levenkov
|
|
@@ -108,12 +108,17 @@ files:
|
|
|
108
108
|
- lib/mtproto/tl/objects/check_password.rb
|
|
109
109
|
- lib/mtproto/tl/objects/client_dh_inner_data.rb
|
|
110
110
|
- lib/mtproto/tl/objects/contacts.rb
|
|
111
|
+
- lib/mtproto/tl/objects/create_bot.rb
|
|
111
112
|
- lib/mtproto/tl/objects/delete_messages.rb
|
|
112
113
|
- lib/mtproto/tl/objects/dh_gen_response.rb
|
|
113
114
|
- lib/mtproto/tl/objects/dialogs.rb
|
|
115
|
+
- lib/mtproto/tl/objects/edit_access_settings.rb
|
|
114
116
|
- lib/mtproto/tl/objects/edit_message.rb
|
|
117
|
+
- lib/mtproto/tl/objects/export_bot_token.rb
|
|
115
118
|
- lib/mtproto/tl/objects/export_login_token.rb
|
|
119
|
+
- lib/mtproto/tl/objects/exported_bot_token.rb
|
|
116
120
|
- lib/mtproto/tl/objects/forward_messages.rb
|
|
121
|
+
- lib/mtproto/tl/objects/get_access_settings.rb
|
|
117
122
|
- lib/mtproto/tl/objects/get_channel_difference.rb
|
|
118
123
|
- lib/mtproto/tl/objects/get_config.rb
|
|
119
124
|
- lib/mtproto/tl/objects/get_contacts.rb
|
|
@@ -132,21 +137,26 @@ files:
|
|
|
132
137
|
- lib/mtproto/tl/objects/import_bot_authorization.rb
|
|
133
138
|
- lib/mtproto/tl/objects/import_login_token.rb
|
|
134
139
|
- lib/mtproto/tl/objects/init_connection.rb
|
|
140
|
+
- lib/mtproto/tl/objects/input_keyboard_button_request_peer.rb
|
|
135
141
|
- lib/mtproto/tl/objects/invite_to_channel.rb
|
|
136
142
|
- lib/mtproto/tl/objects/invoke_with_layer.rb
|
|
137
143
|
- lib/mtproto/tl/objects/login_token.rb
|
|
138
144
|
- lib/mtproto/tl/objects/message.rb
|
|
139
145
|
- lib/mtproto/tl/objects/messages.rb
|
|
146
|
+
- lib/mtproto/tl/objects/messages_get_messages.rb
|
|
140
147
|
- lib/mtproto/tl/objects/msg_container.rb
|
|
141
148
|
- lib/mtproto/tl/objects/new_session_created.rb
|
|
142
149
|
- lib/mtproto/tl/objects/pq_inner_data.rb
|
|
143
150
|
- lib/mtproto/tl/objects/raw_response.rb
|
|
151
|
+
- lib/mtproto/tl/objects/reply_keyboard_markup.rb
|
|
144
152
|
- lib/mtproto/tl/objects/req_dh_params.rb
|
|
145
153
|
- lib/mtproto/tl/objects/req_pq_multi.rb
|
|
154
|
+
- lib/mtproto/tl/objects/request_peer_type_create_bot.rb
|
|
146
155
|
- lib/mtproto/tl/objects/res_pq.rb
|
|
147
156
|
- lib/mtproto/tl/objects/resolve_username.rb
|
|
148
157
|
- lib/mtproto/tl/objects/rpc_error.rb
|
|
149
158
|
- lib/mtproto/tl/objects/save_file_part.rb
|
|
159
|
+
- lib/mtproto/tl/objects/send_bot_requested_peer.rb
|
|
150
160
|
- lib/mtproto/tl/objects/send_code.rb
|
|
151
161
|
- lib/mtproto/tl/objects/send_media.rb
|
|
152
162
|
- lib/mtproto/tl/objects/send_message.rb
|
|
@@ -154,6 +164,8 @@ files:
|
|
|
154
164
|
- lib/mtproto/tl/objects/sent_code.rb
|
|
155
165
|
- lib/mtproto/tl/objects/server_dh_inner_data.rb
|
|
156
166
|
- lib/mtproto/tl/objects/server_dh_params.rb
|
|
167
|
+
- lib/mtproto/tl/objects/set_bot_commands.rb
|
|
168
|
+
- lib/mtproto/tl/objects/set_bot_info.rb
|
|
157
169
|
- lib/mtproto/tl/objects/set_client_dh_params.rb
|
|
158
170
|
- lib/mtproto/tl/objects/sign_in.rb
|
|
159
171
|
- lib/mtproto/tl/objects/update.rb
|
|
@@ -163,6 +175,7 @@ files:
|
|
|
163
175
|
- lib/mtproto/tl/objects/update_username.rb
|
|
164
176
|
- lib/mtproto/tl/objects/updates_difference.rb
|
|
165
177
|
- lib/mtproto/tl/objects/updates_state.rb
|
|
178
|
+
- lib/mtproto/tl/objects/upload_profile_photo.rb
|
|
166
179
|
- lib/mtproto/tl/objects/users.rb
|
|
167
180
|
- lib/mtproto/tl/schema.rb
|
|
168
181
|
- lib/mtproto/transport/abridged_packet_codec.rb
|