mtproto 0.0.14 → 0.0.17

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.
Files changed (60) hide show
  1. checksums.yaml +4 -4
  2. data/data/tl-schema.json +47158 -42684
  3. data/lib/mtproto/auth_key_generator.rb +2 -2
  4. data/lib/mtproto/client/api/export_authorization.rb +17 -0
  5. data/lib/mtproto/client/api/import_authorization.rb +23 -0
  6. data/lib/mtproto/client/api.rb +2 -0
  7. data/lib/mtproto/client/rpc.rb +33 -0
  8. data/lib/mtproto/client.rb +54 -1
  9. data/lib/mtproto/file_downloader.rb +122 -0
  10. data/lib/mtproto/tl/constructor_names.rb +324 -109
  11. data/lib/mtproto/tl/constructors.rb +16 -8
  12. data/lib/mtproto/tl/objects/bot_command_scope.rb +81 -0
  13. data/lib/mtproto/tl/objects/channels_join_channel.rb +1 -1
  14. data/lib/mtproto/tl/objects/channels_update_username.rb +42 -0
  15. data/lib/mtproto/tl/objects/contacts.rb +1 -1
  16. data/lib/mtproto/tl/objects/create_bot.rb +54 -0
  17. data/lib/mtproto/tl/objects/dialogs.rb +25 -10
  18. data/lib/mtproto/tl/objects/edit_access_settings.rb +46 -0
  19. data/lib/mtproto/tl/objects/export_authorization.rb +17 -0
  20. data/lib/mtproto/tl/objects/export_bot_token.rb +32 -0
  21. data/lib/mtproto/tl/objects/exported_authorization.rb +32 -0
  22. data/lib/mtproto/tl/objects/exported_bot_token.rb +27 -0
  23. data/lib/mtproto/tl/objects/forward_messages.rb +12 -3
  24. data/lib/mtproto/tl/objects/get_access_settings.rb +28 -0
  25. data/lib/mtproto/tl/objects/get_bot_callback_answer.rb +67 -0
  26. data/lib/mtproto/tl/objects/get_bot_commands.rb +46 -0
  27. data/lib/mtproto/tl/objects/get_file.rb +10 -2
  28. data/lib/mtproto/tl/objects/import_authorization.rb +38 -0
  29. data/lib/mtproto/tl/objects/input_keyboard_button_request_peer.rb +54 -0
  30. data/lib/mtproto/tl/objects/keyboard_button_callback.rb +50 -0
  31. data/lib/mtproto/tl/objects/message.rb +97 -21
  32. data/lib/mtproto/tl/objects/messages.rb +7 -74
  33. data/lib/mtproto/tl/objects/messages_get_messages.rb +26 -0
  34. data/lib/mtproto/tl/objects/reply_inline_markup.rb +30 -0
  35. data/lib/mtproto/tl/objects/reply_keyboard_markup.rb +35 -0
  36. data/lib/mtproto/tl/objects/request_peer_type_create_bot.rb +47 -0
  37. data/lib/mtproto/tl/objects/reset_bot_commands.rb +45 -0
  38. data/lib/mtproto/tl/objects/send_bot_requested_peer.rb +51 -0
  39. data/lib/mtproto/tl/objects/send_media.rb +87 -4
  40. data/lib/mtproto/tl/objects/send_message.rb +7 -2
  41. data/lib/mtproto/tl/objects/send_message_action.rb +48 -0
  42. data/lib/mtproto/tl/objects/set_bot_callback_answer.rb +54 -0
  43. data/lib/mtproto/tl/objects/set_bot_commands.rb +53 -0
  44. data/lib/mtproto/tl/objects/set_bot_guest_chat_result.rb +84 -0
  45. data/lib/mtproto/tl/objects/set_bot_info.rb +64 -0
  46. data/lib/mtproto/tl/objects/set_typing.rb +49 -0
  47. data/lib/mtproto/tl/objects/update_status.rb +24 -0
  48. data/lib/mtproto/tl/objects/updates.rb +117 -0
  49. data/lib/mtproto/tl/objects/updates_difference.rb +45 -69
  50. data/lib/mtproto/tl/objects/upload_profile_photo.rb +65 -0
  51. data/lib/mtproto/tl/reader.rb +188 -0
  52. data/lib/mtproto/transport/abridged_packet_codec.rb +5 -1
  53. data/lib/mtproto/unencrypted_message.rb +39 -0
  54. data/lib/mtproto/version.rb +1 -1
  55. data/lib/mtproto.rb +4 -1
  56. data/scripts/gen_constructor_names.rb +72 -0
  57. data/scripts/tl_to_json.rb +72 -0
  58. data/scripts/verify_ids.rb +33 -0
  59. metadata +38 -2
  60. data/lib/mtproto/message/message.rb +0 -85
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bot_command_scope'
4
+
5
+ module MTProto
6
+ module TL
7
+ # bots.getBotCommands — fetch the bot's commands for a given scope/language.
8
+ # Returns Vector<BotCommand>. Result parsing belongs to a later layer; this
9
+ # object only builds the request.
10
+ class GetBotCommands
11
+ include Binary
12
+
13
+ CONSTRUCTOR = 0xe34c0dd6
14
+
15
+ # scope: BotCommandScope hash (see BotCommandScope); defaults to default scope.
16
+ def initialize(scope: { type: :default }, lang_code: '')
17
+ @scope = scope
18
+ @lang_code = lang_code
19
+ end
20
+
21
+ def serialize
22
+ result = u32_b(CONSTRUCTOR)
23
+ result += BotCommandScope.serialize(@scope)
24
+ result += serialize_tl_string(@lang_code)
25
+ result
26
+ end
27
+
28
+ private
29
+
30
+ def serialize_tl_string(str)
31
+ bytes = str.to_s.encode('UTF-8').bytes
32
+ length = bytes.length
33
+ if length <= 253
34
+ [length] + bytes + padding(length + 1)
35
+ else
36
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
37
+ end
38
+ end
39
+
40
+ def padding(current_length)
41
+ pad_length = (4 - (current_length % 4)) % 4
42
+ [0] * pad_length
43
+ end
44
+ end
45
+ end
46
+ end
@@ -7,10 +7,18 @@ module MTProto
7
7
 
8
8
  CONSTRUCTOR = 0xbe5335be
9
9
  INPUT_PHOTO_FILE_LOCATION = 0x40181ffe
10
+ INPUT_DOCUMENT_FILE_LOCATION = 0xbad07584
11
+
12
+ LOCATION_CONSTRUCTORS = {
13
+ photo: INPUT_PHOTO_FILE_LOCATION,
14
+ document: INPUT_DOCUMENT_FILE_LOCATION
15
+ }.freeze
10
16
 
11
17
  # location: { id:, access_hash:, file_reference: (binary String), thumb_size: (String) }
12
- def initialize(location:, offset: 0, limit: 1024 * 1024)
18
+ # type: :photo for photos, :document for documents/voice/video/audio.
19
+ def initialize(location:, type: :photo, offset: 0, limit: 1024 * 1024)
13
20
  @location = location
21
+ @type = type
14
22
  @offset = offset
15
23
  @limit = limit
16
24
  end
@@ -27,7 +35,7 @@ module MTProto
27
35
  private
28
36
 
29
37
  def serialize_location
30
- u32_b(INPUT_PHOTO_FILE_LOCATION) +
38
+ u32_b(LOCATION_CONSTRUCTORS.fetch(@type)) +
31
39
  u64_b(@location[:id]) +
32
40
  u64_b(@location[:access_hash]) +
33
41
  serialize_tl_bytes(@location[:file_reference]) +
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ImportAuthorization
6
+ include Binary
7
+
8
+ def initialize(id:, bytes:)
9
+ @id = id
10
+ @bytes = bytes
11
+ end
12
+
13
+ def serialize
14
+ u32_b(Constructors::AUTH_IMPORT_AUTHORIZATION) +
15
+ u64_b(@id) +
16
+ serialize_tl_bytes(@bytes)
17
+ end
18
+
19
+ private
20
+
21
+ def serialize_tl_bytes(value)
22
+ bytes = value.to_s.b.bytes
23
+ length = bytes.length
24
+
25
+ if length <= 253
26
+ [length] + bytes + padding(length + 1)
27
+ else
28
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
29
+ end
30
+ end
31
+
32
+ def padding(current_length)
33
+ pad_length = (4 - (current_length % 4)) % 4
34
+ [0] * pad_length
35
+ end
36
+ end
37
+ end
38
+ 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,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class KeyboardButtonCallback
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0xe62bc960
9
+
10
+ # data: raw callback payload bytes echoed back to the bot on press.
11
+ def initialize(text:, data:, requires_password: false)
12
+ @text = text
13
+ @data = data
14
+ @requires_password = requires_password
15
+ end
16
+
17
+ def serialize
18
+ flags = 0
19
+ flags |= (1 << 0) if @requires_password
20
+
21
+ result = u32_b(CONSTRUCTOR)
22
+ result += u32_b(flags)
23
+ result += serialize_tl_string(@text)
24
+ result += serialize_tl_bytes(@data)
25
+ result
26
+ end
27
+
28
+ private
29
+
30
+ def serialize_tl_string(str)
31
+ serialize_tl_bytes(str.to_s.encode('UTF-8').b)
32
+ end
33
+
34
+ def serialize_tl_bytes(bytes_str)
35
+ bytes = bytes_str.to_s.b.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
@@ -1,37 +1,113 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../reader'
4
+
3
5
  module MTProto
4
6
  module TL
7
+ # The high-level chat-message TL structure `message#7600b9d3` (text/media/
8
+ # fwd_from/reply/sender/...). Owns the field walk for that one constructor; the
9
+ # low-level TL reads it needs (peer/string/bytes/media/document, plus the
10
+ # shared schema) live in Reader and are reused here.
11
+ #
12
+ # Reading is decoupled from alignment: `.deserialize` only READS the fields we
13
+ # surface and returns a Message (or nil for a non-`message` constructor — e.g.
14
+ # messageService/messageEmpty). Advancing past the whole object is the caller's
15
+ # job via `Schema#skip`, so any unmodelled tail never drops the message. This is
16
+ # the single parser for all three inbound paths (live `Updates` push,
17
+ # `updates.getDifference`, and `messages.getHistory`).
5
18
  class Message
6
- attr_reader :auth_key_id, :msg_id, :body
19
+ MESSAGE = Constructors::MESSAGE
20
+ MESSAGE_FWD_HEADER = 0x4e4df4bb
7
21
 
8
- def initialize(auth_key_id:, msg_id:, body:)
9
- @auth_key_id = auth_key_id
10
- @msg_id = msg_id
11
- @body = body
12
- end
22
+ FIELDS = %i[id date message peer_type peer_id from_type from_id out media
23
+ document photo is_reply reply_to_msg_id fwd_from_type fwd_from_id].freeze
24
+
25
+ attr_reader(*FIELDS)
26
+
27
+ # `text` is the message body; a get_history-friendly alias for `message`.
28
+ alias text message
13
29
 
14
- def serialize
15
- auth_key_id_bytes = [@auth_key_id].pack('Q<')
16
- msg_id_bytes = [@msg_id].pack('Q<')
17
- body_length = [@body.bytesize].pack('L<')
30
+ def initialize(attrs)
31
+ FIELDS.each { |f| instance_variable_set("@#{f}", attrs[f]) }
32
+ end
18
33
 
19
- auth_key_id_bytes + msg_id_bytes + body_length + @body
34
+ # The legacy hash shape the Updates / UpdatesDifference consumers expect.
35
+ def to_h
36
+ FIELDS.each_with_object({}) { |f, h| h[f] = public_send(f) }
20
37
  end
21
38
 
22
- def self.deserialize(data)
23
- if data.bytesize < 20
24
- raise(ArgumentError,
25
- 'Invalid MTProto message: expected at least 20 bytes, ' \
26
- "got #{data.bytesize} bytes (hex: #{data.unpack1('H*')})")
39
+ class << self
40
+ def deserialize(data, offset, constructor)
41
+ return unless constructor == MESSAGE
42
+
43
+ offset += 4 # constructor
44
+ flags = data[offset, 4].unpack1('L<')
45
+ offset += 4
46
+ flags2 = data[offset, 4].unpack1('L<')
47
+ offset += 4
48
+ id = data[offset, 4].unpack1('l<')
49
+ offset += 4
50
+
51
+ from_type = from_id = nil
52
+ from_type, from_id, offset = x.parse_peer(data, offset) if flags.anybits?(1 << 8) # from_id (Peer)
53
+ offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
54
+ _, offset = x.read_tl_string(data, offset) if flags2.anybits?(1 << 12) # from_rank (layer 227)
55
+ peer_type, peer_id, offset = x.parse_peer(data, offset)
56
+ offset = x.schema.skip(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
57
+ fwd_from_type = fwd_from_id = nil
58
+ if flags.anybits?(1 << 2) # fwd_from
59
+ fwd_from_type, fwd_from_id = parse_fwd_origin(data, offset)
60
+ offset = x.schema.skip(data, offset)
61
+ end
62
+ offset += 8 if flags.anybits?(1 << 11) # via_bot_id
63
+ offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
64
+ offset = x.schema.skip(data, offset) if flags2.anybits?(1 << 19) # guestchat_via_from (layer 227, Peer)
65
+
66
+ is_reply = false
67
+ reply_to_msg_id = nil
68
+ if flags.anybits?(1 << 3) # reply_to
69
+ reply_to_msg_id, is_reply = x.parse_reply_to(data, offset)
70
+ offset = x.schema.skip(data, offset)
71
+ end
72
+
73
+ date = data[offset, 4].unpack1('L<')
74
+ offset += 4
75
+ message_text, offset = x.read_tl_string(data, offset)
76
+ has_media = flags.anybits?(1 << 9)
77
+ media = has_media ? x.parse_media(data, offset) : nil
78
+ document = has_media ? x.parse_document(data, offset) : nil
79
+ photo = has_media ? x.parse_photo(data, offset) : nil
80
+
81
+ new(id: id, date: date, message: message_text, peer_type: peer_type, peer_id: peer_id,
82
+ from_type: from_type, from_id: from_id, out: flags.anybits?(1 << 1),
83
+ media: media, document: document, photo: photo, is_reply: is_reply,
84
+ reply_to_msg_id: reply_to_msg_id, fwd_from_type: fwd_from_type, fwd_from_id: fwd_from_id)
27
85
  end
28
86
 
29
- auth_key_id = data[0, 8].unpack1('Q<')
30
- msg_id = data[8, 8].unpack1('Q<')
31
- body_length = data[16, 4].unpack1('L<')
32
- body = data[20, body_length]
87
+ private
88
+
89
+ # The low-level TL primitives, shared from Reader.
90
+ def x
91
+ Reader
92
+ end
33
93
 
34
- new(auth_key_id: auth_key_id, msg_id: msg_id, body: body)
94
+ # messageFwdHeader#4e4df4bb flags:# imported:flags.7?true from_id:flags.0?Peer
95
+ # ... — from_id is the forward origin (a peerChannel for a forwarded channel
96
+ # post). imported (flags.7) is a flag-only bool, so from_id sits right after
97
+ # the flags. => [type, id] or [nil, nil].
98
+ def parse_fwd_origin(data, offset)
99
+ return [nil, nil] unless data[offset, 4].unpack1('L<') == MESSAGE_FWD_HEADER
100
+
101
+ io = offset + 4
102
+ flags = data[io, 4].unpack1('L<')
103
+ io += 4
104
+ return [nil, nil] unless flags.anybits?(1 << 0) # from_id present
105
+
106
+ type, id, = x.parse_peer(data, io)
107
+ [type, id]
108
+ rescue StandardError
109
+ [nil, nil]
110
+ end
35
111
  end
36
112
  end
37
113
  end
@@ -1,20 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../schema'
4
+ require_relative 'message'
4
5
 
5
6
  module MTProto
6
7
  module TL
8
+ # A messages.messages / messagesSlice / channelMessages container (e.g. the
9
+ # messages.getHistory reply). Its messages are TL::Message instances.
7
10
  class Messages
8
- Message = Struct.new(:id, :from_id, :date, :text, keyword_init: true)
9
-
10
- MESSAGES_MESSAGES = 0x8c718e87
11
- MESSAGES_SLICE = 0x762b263d
11
+ MESSAGES_MESSAGES = 0x1d73e7ea
12
+ MESSAGES_SLICE = 0x5f206716
12
13
  MESSAGES_CHANNEL = 0xc776ba4e
13
14
 
14
- MESSAGE = Constructors::MESSAGE
15
- MESSAGE_SERVICE = 0x7a800e0a
16
- MESSAGE_EMPTY = 0x90a6ca84
17
-
18
15
  VALID_CONSTRUCTORS = [MESSAGES_MESSAGES, MESSAGES_SLICE, MESSAGES_CHANNEL].freeze
19
16
 
20
17
  attr_reader :messages, :count
@@ -86,76 +83,12 @@ module MTProto
86
83
  messages = []
87
84
  count.times do
88
85
  constructor = data[offset, 4].unpack1('L<')
89
-
90
- if constructor == MESSAGE
91
- msg, = parse_message(data, offset)
92
- messages << msg if msg
93
- end
94
-
86
+ msg = Message.deserialize(data, offset, constructor)
87
+ messages << msg if msg
95
88
  offset = schema.skip(data, offset)
96
89
  end
97
90
  [messages, offset]
98
91
  end
99
-
100
- # message#9815cec8 flags:# flags2:# id:int
101
- # from_id:flags.8?Peer ... date:int message:string ...
102
- def parse_message(data, offset)
103
- offset += 4 # constructor
104
- flags = data[offset, 4].unpack1('L<')
105
- offset += 4
106
- flags2 = data[offset, 4].unpack1('L<')
107
- offset += 4
108
- id = data[offset, 4].unpack1('l<')
109
- offset += 4
110
-
111
- from_id = nil
112
- _, from_id, offset = parse_peer(data, offset) if flags.anybits?(1 << 8)
113
- offset += 4 if flags.anybits?(1 << 29) # from_boosts_applied
114
- _, _, offset = parse_peer(data, offset) # peer_id (skip)
115
- _, _, offset = parse_peer(data, offset) if flags.anybits?(1 << 28) # saved_peer_id
116
- offset = schema.skip(data, offset) if flags.anybits?(1 << 2) # fwd_from
117
- offset += 8 if flags.anybits?(1 << 11) # via_bot_id
118
- offset += 8 if flags2.anybits?(1 << 0) # via_business_bot_id
119
- offset = schema.skip(data, offset) if flags.anybits?(1 << 3) # reply_to
120
-
121
- date = data[offset, 4].unpack1('L<')
122
- offset += 4
123
-
124
- text, offset = read_tl_string(data, offset)
125
-
126
- msg = Message.new(id: id, from_id: from_id, date: date, text: text)
127
- [msg, offset]
128
- end
129
-
130
- def parse_peer(data, offset)
131
- constructor = data[offset, 4].unpack1('L<')
132
- offset += 4
133
- case constructor
134
- when Constructors::PEER_USER
135
- [:user, data[offset, 8].unpack1('Q<'), offset + 8]
136
- when Constructors::PEER_CHAT
137
- [:chat, data[offset, 8].unpack1('Q<'), offset + 8]
138
- when Constructors::PEER_CHANNEL
139
- [:channel, data[offset, 8].unpack1('Q<'), offset + 8]
140
- else
141
- raise "Unknown peer constructor: 0x#{constructor.to_s(16)}"
142
- end
143
- end
144
-
145
- def read_tl_string(data, offset)
146
- first_byte = data.getbyte(offset)
147
- if first_byte == 254
148
- len = data.getbyte(offset + 1) |
149
- (data.getbyte(offset + 2) << 8) |
150
- (data.getbyte(offset + 3) << 16)
151
- total = 4 + len
152
- else
153
- len = first_byte
154
- total = 1 + len
155
- end
156
- padding = (4 - (total % 4)) % 4
157
- [data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
158
- end
159
92
  end
160
93
  end
161
94
  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,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ReplyInlineMarkup
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0x48a30254
9
+ KEYBOARD_BUTTON_ROW = 0x77608b83
10
+
11
+ # rows: array of rows; each row is an array of button objects (each responds to #serialize).
12
+ def initialize(rows:)
13
+ @rows = rows
14
+ end
15
+
16
+ def serialize
17
+ result = u32_b(CONSTRUCTOR)
18
+ result += u32_b(Constructors::VECTOR)
19
+ result += u32_b(@rows.size)
20
+ @rows.each do |row|
21
+ result += u32_b(KEYBOARD_BUTTON_ROW)
22
+ result += u32_b(Constructors::VECTOR)
23
+ result += u32_b(row.size)
24
+ row.each { |button| result += button.serialize }
25
+ end
26
+ result
27
+ end
28
+ end
29
+ end
30
+ 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,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'bot_command_scope'
4
+
5
+ module MTProto
6
+ module TL
7
+ # bots.resetBotCommands — clear the bot's commands for a given scope/language.
8
+ # Returns Bool.
9
+ class ResetBotCommands
10
+ include Binary
11
+
12
+ CONSTRUCTOR = 0x3d8de0f9
13
+
14
+ # scope: BotCommandScope hash (see BotCommandScope); defaults to default scope.
15
+ def initialize(scope: { type: :default }, lang_code: '')
16
+ @scope = scope
17
+ @lang_code = lang_code
18
+ end
19
+
20
+ def serialize
21
+ result = u32_b(CONSTRUCTOR)
22
+ result += BotCommandScope.serialize(@scope)
23
+ result += serialize_tl_string(@lang_code)
24
+ result
25
+ end
26
+
27
+ private
28
+
29
+ def serialize_tl_string(str)
30
+ bytes = str.to_s.encode('UTF-8').bytes
31
+ length = bytes.length
32
+ if length <= 253
33
+ [length] + bytes + padding(length + 1)
34
+ else
35
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
36
+ end
37
+ end
38
+
39
+ def padding(current_length)
40
+ pad_length = (4 - (current_length % 4)) % 4
41
+ [0] * pad_length
42
+ end
43
+ end
44
+ end
45
+ end