mtproto 0.0.13 → 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.
Files changed (93) hide show
  1. checksums.yaml +4 -4
  2. data/FUTURE.md +9 -0
  3. data/docs/test_architecture_level1.md +237 -0
  4. data/lib/mtproto/auth_key_generator.rb +5 -5
  5. data/lib/mtproto/client/api/get_contacts.rb +14 -0
  6. data/lib/mtproto/client/api/send_message.rb +15 -0
  7. data/lib/mtproto/client/api.rb +2 -0
  8. data/lib/mtproto/client/rpc/response.rb +1 -1
  9. data/lib/mtproto/client/rpc.rb +35 -2
  10. data/lib/mtproto/client.rb +81 -18
  11. data/lib/mtproto/tl/constructor_names.rb +10 -0
  12. data/lib/mtproto/tl/constructors.rb +26 -0
  13. data/lib/mtproto/tl/object.rb +1 -1
  14. data/lib/mtproto/tl/objects/account_password.rb +1 -1
  15. data/lib/mtproto/tl/objects/authorization.rb +1 -1
  16. data/lib/mtproto/tl/objects/channels_create_channel.rb +48 -0
  17. data/lib/mtproto/tl/objects/channels_delete_messages.rb +32 -0
  18. data/lib/mtproto/tl/objects/channels_delete_participant_history.rb +34 -0
  19. data/lib/mtproto/tl/objects/channels_edit_banned.rb +54 -0
  20. data/lib/mtproto/tl/objects/channels_get_messages.rb +33 -0
  21. data/lib/mtproto/tl/objects/channels_get_participant.rb +36 -0
  22. data/lib/mtproto/tl/objects/channels_join_channel.rb +22 -0
  23. data/lib/mtproto/tl/objects/channels_leave_channel.rb +22 -0
  24. data/lib/mtproto/tl/objects/channels_report_spam.rb +37 -0
  25. data/lib/mtproto/tl/objects/check_password.rb +1 -1
  26. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +1 -1
  27. data/lib/mtproto/tl/objects/contacts.rb +156 -0
  28. data/lib/mtproto/tl/objects/create_bot.rb +54 -0
  29. data/lib/mtproto/tl/objects/delete_messages.rb +25 -0
  30. data/lib/mtproto/tl/objects/dh_gen_response.rb +1 -1
  31. data/lib/mtproto/tl/objects/dialogs.rb +18 -4
  32. data/lib/mtproto/tl/objects/edit_access_settings.rb +46 -0
  33. data/lib/mtproto/tl/objects/edit_message.rb +58 -0
  34. data/lib/mtproto/tl/objects/export_bot_token.rb +32 -0
  35. data/lib/mtproto/tl/objects/export_login_token.rb +1 -1
  36. data/lib/mtproto/tl/objects/exported_bot_token.rb +27 -0
  37. data/lib/mtproto/tl/objects/forward_messages.rb +55 -0
  38. data/lib/mtproto/tl/objects/get_access_settings.rb +28 -0
  39. data/lib/mtproto/tl/objects/get_channel_difference.rb +41 -0
  40. data/lib/mtproto/tl/objects/get_config.rb +1 -1
  41. data/lib/mtproto/tl/objects/get_contacts.rb +17 -0
  42. data/lib/mtproto/tl/objects/get_dialogs.rb +1 -1
  43. data/lib/mtproto/tl/objects/get_difference.rb +1 -1
  44. data/lib/mtproto/tl/objects/get_file.rb +54 -0
  45. data/lib/mtproto/tl/objects/get_full_channel.rb +29 -0
  46. data/lib/mtproto/tl/objects/get_full_user.rb +28 -0
  47. data/lib/mtproto/tl/objects/get_history.rb +1 -1
  48. data/lib/mtproto/tl/objects/get_messages_reactions.rb +39 -0
  49. data/lib/mtproto/tl/objects/get_password.rb +1 -1
  50. data/lib/mtproto/tl/objects/get_state.rb +1 -1
  51. data/lib/mtproto/tl/objects/get_users.rb +1 -1
  52. data/lib/mtproto/tl/objects/help_config.rb +1 -1
  53. data/lib/mtproto/tl/objects/import_bot_authorization.rb +44 -0
  54. data/lib/mtproto/tl/objects/import_login_token.rb +1 -1
  55. data/lib/mtproto/tl/objects/init_connection.rb +2 -2
  56. data/lib/mtproto/tl/objects/input_keyboard_button_request_peer.rb +54 -0
  57. data/lib/mtproto/tl/objects/invite_to_channel.rb +35 -0
  58. data/lib/mtproto/tl/objects/invoke_with_layer.rb +2 -2
  59. data/lib/mtproto/tl/objects/login_token.rb +2 -2
  60. data/lib/mtproto/tl/objects/messages.rb +1 -1
  61. data/lib/mtproto/tl/objects/messages_get_messages.rb +26 -0
  62. data/lib/mtproto/tl/objects/pq_inner_data.rb +1 -1
  63. data/lib/mtproto/tl/objects/raw_response.rb +29 -0
  64. data/lib/mtproto/tl/objects/reply_keyboard_markup.rb +35 -0
  65. data/lib/mtproto/tl/objects/req_dh_params.rb +1 -1
  66. data/lib/mtproto/tl/objects/req_pq_multi.rb +1 -1
  67. data/lib/mtproto/tl/objects/request_peer_type_create_bot.rb +47 -0
  68. data/lib/mtproto/tl/objects/res_pq.rb +1 -1
  69. data/lib/mtproto/tl/objects/resolve_username.rb +40 -0
  70. data/lib/mtproto/tl/objects/save_file_part.rb +40 -0
  71. data/lib/mtproto/tl/objects/send_bot_requested_peer.rb +51 -0
  72. data/lib/mtproto/tl/objects/send_code.rb +1 -1
  73. data/lib/mtproto/tl/objects/send_media.rb +96 -0
  74. data/lib/mtproto/tl/objects/send_message.rb +73 -0
  75. data/lib/mtproto/tl/objects/send_reaction.rb +68 -0
  76. data/lib/mtproto/tl/objects/sent_code.rb +1 -1
  77. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +1 -1
  78. data/lib/mtproto/tl/objects/server_dh_params.rb +1 -1
  79. data/lib/mtproto/tl/objects/set_bot_commands.rb +50 -0
  80. data/lib/mtproto/tl/objects/set_bot_info.rb +64 -0
  81. data/lib/mtproto/tl/objects/set_client_dh_params.rb +1 -1
  82. data/lib/mtproto/tl/objects/sign_in.rb +1 -1
  83. data/lib/mtproto/tl/objects/update.rb +1 -1
  84. data/lib/mtproto/tl/objects/update_short.rb +2 -2
  85. data/lib/mtproto/tl/objects/update_short_message.rb +1 -1
  86. data/lib/mtproto/tl/objects/update_short_sent_message.rb +36 -0
  87. data/lib/mtproto/tl/objects/update_username.rb +39 -0
  88. data/lib/mtproto/tl/objects/updates_difference.rb +93 -14
  89. data/lib/mtproto/tl/objects/updates_state.rb +1 -1
  90. data/lib/mtproto/tl/objects/upload_profile_photo.rb +65 -0
  91. data/lib/mtproto/tl/objects/users.rb +1 -1
  92. data/lib/mtproto/version.rb +1 -1
  93. metadata +49 -1
@@ -16,7 +16,7 @@ module MTProto
16
16
  !@sign_up_required && @user_id
17
17
  end
18
18
 
19
- def self.parse(data)
19
+ def self.deserialize(data)
20
20
  constructor = data[0, 4].unpack1('L<')
21
21
 
22
22
  return new(sign_up_required: true) if constructor == Constructors::AUTH_AUTHORIZATION_SIGN_UP_REQUIRED
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ # channels.createChannel — create a broadcast channel or, with the megagroup
6
+ # flag, a supergroup. Returns Updates carrying the new channel.
7
+ class ChannelsCreateChannel
8
+ include Binary
9
+
10
+ CONSTRUCTOR = 0x91006707
11
+
12
+ def initialize(title:, about: '', megagroup: true, broadcast: false)
13
+ @title = title
14
+ @about = about
15
+ @megagroup = megagroup
16
+ @broadcast = broadcast
17
+ end
18
+
19
+ def serialize
20
+ flags = 0
21
+ flags |= (1 << 0) if @broadcast
22
+ flags |= (1 << 1) if @megagroup
23
+
24
+ result = u32_b(CONSTRUCTOR)
25
+ result += u32_b(flags)
26
+ result += serialize_tl_string(@title)
27
+ result += serialize_tl_string(@about)
28
+ result
29
+ end
30
+
31
+ private
32
+
33
+ def serialize_tl_string(str)
34
+ bytes = str.to_s.b.bytes
35
+ length = bytes.length
36
+ if length <= 253
37
+ [length] + bytes + padding(length + 1)
38
+ else
39
+ [254] + u32_b(length)[0, 3] + bytes + padding(length + 4)
40
+ end
41
+ end
42
+
43
+ def padding(current_length)
44
+ [0] * ((4 - (current_length % 4)) % 4)
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsDeleteMessages
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0x84c1fd4e
9
+
10
+ def initialize(channel:, ids:)
11
+ @channel = channel
12
+ @ids = ids
13
+ end
14
+
15
+ def serialize
16
+ result = u32_b(CONSTRUCTOR)
17
+ result += serialize_input_channel
18
+ result += u32_b(Constructors::VECTOR) + u32_b(@ids.length)
19
+ @ids.each { |id| result += u32_b(id) }
20
+ result
21
+ end
22
+
23
+ private
24
+
25
+ def serialize_input_channel
26
+ u32_b(Constructors::INPUT_CHANNEL) +
27
+ u64_b(@channel[:id]) +
28
+ u64_b(@channel[:access_hash])
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsDeleteParticipantHistory
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0x367544db
9
+
10
+ def initialize(channel:, participant:)
11
+ @channel = channel
12
+ @participant = participant
13
+ end
14
+
15
+ def serialize
16
+ result = u32_b(CONSTRUCTOR)
17
+ result += u32_b(Constructors::INPUT_CHANNEL) + u64_b(@channel[:id]) + u64_b(@channel[:access_hash])
18
+ result += serialize_input_peer(@participant)
19
+ result
20
+ end
21
+
22
+ private
23
+
24
+ def serialize_input_peer(peer)
25
+ case peer[:type]
26
+ when :user
27
+ u32_b(Constructors::INPUT_PEER_USER) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
28
+ else
29
+ raise "Unsupported participant peer type: #{peer[:type]}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsEditBanned
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0x96e6cd81
9
+ CHAT_BANNED_RIGHTS = 0x9f120418
10
+
11
+ def initialize(channel:, participant:, view_messages: false, send_messages: false, until_date: 0)
12
+ @channel = channel
13
+ @participant = participant
14
+ @view_messages = view_messages
15
+ @send_messages = send_messages
16
+ @until_date = until_date
17
+ end
18
+
19
+ def serialize
20
+ result = u32_b(CONSTRUCTOR)
21
+ result += serialize_input_channel
22
+ result += serialize_input_peer(@participant)
23
+ result += serialize_banned_rights
24
+ result
25
+ end
26
+
27
+ private
28
+
29
+ def serialize_banned_rights
30
+ flags = 0
31
+ flags |= (1 << 0) if @view_messages
32
+ flags |= (1 << 1) if @send_messages
33
+ u32_b(CHAT_BANNED_RIGHTS) + u32_b(flags) + u32_b(@until_date)
34
+ end
35
+
36
+ def serialize_input_channel
37
+ u32_b(Constructors::INPUT_CHANNEL) +
38
+ u64_b(@channel[:id]) +
39
+ u64_b(@channel[:access_hash])
40
+ end
41
+
42
+ def serialize_input_peer(peer)
43
+ case peer[:type]
44
+ when :user
45
+ u32_b(Constructors::INPUT_PEER_USER) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
46
+ when :channel
47
+ u32_b(Constructors::INPUT_PEER_CHANNEL) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
48
+ else
49
+ raise "Unsupported participant peer type: #{peer[:type]}"
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsGetMessages
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0xad8c9a23
9
+ INPUT_MESSAGE_ID = 0xa676a322
10
+
11
+ def initialize(channel:, ids:)
12
+ @channel = channel
13
+ @ids = ids
14
+ end
15
+
16
+ def serialize
17
+ result = u32_b(CONSTRUCTOR)
18
+ result += serialize_input_channel
19
+ result += u32_b(Constructors::VECTOR) + u32_b(@ids.length)
20
+ @ids.each { |id| result += u32_b(INPUT_MESSAGE_ID) + u32_b(id) }
21
+ result
22
+ end
23
+
24
+ private
25
+
26
+ def serialize_input_channel
27
+ u32_b(Constructors::INPUT_CHANNEL) +
28
+ u64_b(@channel[:id]) +
29
+ u64_b(@channel[:access_hash])
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsGetParticipant
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0xa0ab6cc6
9
+
10
+ def initialize(channel:, participant:)
11
+ @channel = channel
12
+ @participant = participant
13
+ end
14
+
15
+ def serialize
16
+ result = u32_b(CONSTRUCTOR)
17
+ result += u32_b(Constructors::INPUT_CHANNEL) + u64_b(@channel[:id]) + u64_b(@channel[:access_hash])
18
+ result += serialize_input_peer(@participant)
19
+ result
20
+ end
21
+
22
+ private
23
+
24
+ def serialize_input_peer(peer)
25
+ case peer[:type]
26
+ when :user
27
+ u32_b(Constructors::INPUT_PEER_USER) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
28
+ when :self
29
+ u32_b(Constructors::INPUT_PEER_SELF)
30
+ else
31
+ raise "Unsupported participant peer type: #{peer[:type]}"
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsJoinChannel
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0x24b524c5
9
+
10
+ def initialize(channel:)
11
+ @channel = channel
12
+ end
13
+
14
+ def serialize
15
+ u32_b(CONSTRUCTOR) +
16
+ u32_b(Constructors::INPUT_CHANNEL) +
17
+ u64_b(@channel[:id]) +
18
+ u64_b(@channel[:access_hash])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsLeaveChannel
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0xf836aa95
9
+
10
+ def initialize(channel:)
11
+ @channel = channel
12
+ end
13
+
14
+ def serialize
15
+ u32_b(CONSTRUCTOR) +
16
+ u32_b(Constructors::INPUT_CHANNEL) +
17
+ u64_b(@channel[:id]) +
18
+ u64_b(@channel[:access_hash])
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class ChannelsReportSpam
6
+ include Binary
7
+
8
+ CONSTRUCTOR = 0xf44a8315
9
+
10
+ def initialize(channel:, participant:, ids:)
11
+ @channel = channel
12
+ @participant = participant
13
+ @ids = ids
14
+ end
15
+
16
+ def serialize
17
+ result = u32_b(CONSTRUCTOR)
18
+ result += u32_b(Constructors::INPUT_CHANNEL) + u64_b(@channel[:id]) + u64_b(@channel[:access_hash])
19
+ result += serialize_input_peer(@participant)
20
+ result += u32_b(Constructors::VECTOR) + u32_b(@ids.length)
21
+ @ids.each { |id| result += u32_b(id) }
22
+ result
23
+ end
24
+
25
+ private
26
+
27
+ def serialize_input_peer(peer)
28
+ case peer[:type]
29
+ when :user
30
+ u32_b(Constructors::INPUT_PEER_USER) + u64_b(peer[:id]) + u64_b(peer[:access_hash] || 0)
31
+ else
32
+ raise "Unsupported participant peer type: #{peer[:type]}"
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -13,7 +13,7 @@ module MTProto
13
13
  @m1 = m1
14
14
  end
15
15
 
16
- def body
16
+ def serialize
17
17
  u32_b(Constructors::AUTH_CHECK_PASSWORD) +
18
18
  u32_b(Constructors::INPUT_CHECK_PASSWORD_SRP) +
19
19
  u64_b(@srp_id) +
@@ -14,7 +14,7 @@ module MTProto
14
14
  @g_b = g_b
15
15
  end
16
16
 
17
- def body
17
+ def serialize
18
18
  result = u32_b(Constructors::CLIENT_DH_INNER_DATA)
19
19
  result += @nonce.bytes
20
20
  result += @server_nonce.bytes
@@ -0,0 +1,156 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../schema'
4
+
5
+ module MTProto
6
+ module TL
7
+ class Contacts
8
+ Contact = Struct.new(:user_id, :mutual, keyword_init: true)
9
+ User = Struct.new(:id, :first_name, :last_name, :access_hash, keyword_init: true)
10
+
11
+ SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
12
+
13
+ attr_reader :contacts, :saved_count, :users, :not_modified
14
+
15
+ def initialize(contacts: [], saved_count: 0, users: [], not_modified: false)
16
+ @contacts = contacts
17
+ @saved_count = saved_count
18
+ @users = users
19
+ @not_modified = not_modified
20
+ end
21
+
22
+ def not_modified?
23
+ @not_modified
24
+ end
25
+
26
+ def self.schema
27
+ @schema ||= Schema.new(SCHEMA_PATH)
28
+ end
29
+
30
+ def self.deserialize(data)
31
+ constructor = data[0, 4].unpack1('L<')
32
+ return new(not_modified: true) if constructor == Constructors::CONTACTS_CONTACTS_NOT_MODIFIED
33
+ raise UnexpectedConstructorError, constructor unless constructor == Constructors::CONTACTS_CONTACTS
34
+
35
+ offset = 4
36
+ contacts, offset = parse_contacts_vector(data, offset)
37
+ saved_count = data[offset, 4].unpack1('l<')
38
+ offset += 4
39
+ users, = parse_users_vector(data, offset)
40
+
41
+ new(contacts: contacts, saved_count: saved_count, users: users)
42
+ end
43
+
44
+ USER_EMPTY = 0xd3bc4b7a
45
+ private_constant :USER_EMPTY
46
+
47
+ class << self
48
+ private
49
+
50
+ def parse_contacts_vector(data, offset)
51
+ offset += 4 # vector tag
52
+ count = data[offset, 4].unpack1('L<')
53
+ offset += 4
54
+
55
+ contacts = []
56
+ count.times do
57
+ constructor = data[offset, 4].unpack1('L<')
58
+ offset += 4
59
+ raise "Unknown Contact constructor: 0x#{constructor.to_s(16)}" unless constructor == Constructors::CONTACT
60
+
61
+ user_id = data[offset, 8].unpack1('Q<')
62
+ offset += 8
63
+ mutual_tag = data[offset, 4].unpack1('L<')
64
+ offset += 4
65
+ mutual = mutual_tag == Constructors::BOOL_TRUE
66
+ contacts << Contact.new(user_id: user_id, mutual: mutual)
67
+ end
68
+ [contacts, offset]
69
+ end
70
+
71
+ # User parsing mirrors TL::Dialogs.parse_user (user#020b1422). Kept private here
72
+ # so this class can be used without instantiating Dialogs.
73
+ def parse_users_vector(data, offset)
74
+ offset += 4 # vector tag
75
+ count = data[offset, 4].unpack1('L<')
76
+ offset += 4
77
+
78
+ users = []
79
+ count.times do
80
+ user, offset = parse_user(data, offset)
81
+ users << user if user
82
+ end
83
+ [users, offset]
84
+ end
85
+
86
+ def parse_user(data, offset)
87
+ constructor = data[offset, 4].unpack1('L<')
88
+ offset += 4
89
+
90
+ if constructor == USER_EMPTY
91
+ offset += 8
92
+ return [nil, offset]
93
+ end
94
+
95
+ raise "Unknown User constructor: 0x#{constructor.to_s(16)}" unless constructor == Constructors::USER
96
+
97
+ flags = data[offset, 4].unpack1('L<')
98
+ offset += 4
99
+ flags2 = data[offset, 4].unpack1('L<')
100
+ offset += 4
101
+ id = data[offset, 8].unpack1('Q<')
102
+ offset += 8
103
+ access_hash = nil
104
+ if flags.anybits?(1 << 0)
105
+ access_hash = data[offset, 8].unpack1('Q<')
106
+ offset += 8
107
+ end
108
+
109
+ first_name = nil
110
+ first_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 1)
111
+ last_name = nil
112
+ last_name, offset = read_tl_string(data, offset) if flags.anybits?(1 << 2)
113
+
114
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 3) # username
115
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 4) # phone
116
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 5) # photo
117
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 6) # status
118
+ offset += 4 if flags.anybits?(1 << 14) # bot_info_version
119
+ offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if flags.anybits?(1 << 18)
120
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 19) # bot_inline_placeholder
121
+ offset = skip_tl_string(data, offset) if flags.anybits?(1 << 22) # lang_code
122
+ offset = schema.skip(data, offset) if flags.anybits?(1 << 30) # emoji_status
123
+ offset = schema.skip_vector(data, offset) { |d, o| schema.skip(d, o) } if flags2.anybits?(1 << 0)
124
+ offset += 4 if flags2.anybits?(1 << 5) # stories_max_id
125
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 8) # color
126
+ offset = schema.skip(data, offset) if flags2.anybits?(1 << 9) # profile_color
127
+ offset += 4 if flags2.anybits?(1 << 12) # bot_active_users
128
+ offset += 8 if flags2.anybits?(1 << 14) # bot_verification_icon
129
+ offset += 8 if flags2.anybits?(1 << 15) # send_paid_messages_stars
130
+
131
+ [User.new(id: id, first_name: first_name, last_name: last_name, access_hash: access_hash), offset]
132
+ end
133
+
134
+ def read_tl_string(data, offset)
135
+ first_byte = data.getbyte(offset)
136
+ if first_byte == 254
137
+ len = data.getbyte(offset + 1) |
138
+ (data.getbyte(offset + 2) << 8) |
139
+ (data.getbyte(offset + 3) << 16)
140
+ total = 4 + len
141
+ else
142
+ len = first_byte
143
+ total = 1 + len
144
+ end
145
+ padding = (4 - (total % 4)) % 4
146
+ [data[offset + (total - len), len].force_encoding('UTF-8'), offset + total + padding]
147
+ end
148
+
149
+ def skip_tl_string(data, offset)
150
+ _, new_offset = read_tl_string(data, offset)
151
+ new_offset
152
+ end
153
+ end
154
+ end
155
+ end
156
+ end
@@ -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
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MTProto
4
+ module TL
5
+ class DeleteMessages
6
+ include Binary
7
+
8
+ def initialize(ids:, revoke: false)
9
+ @ids = ids
10
+ @revoke = revoke
11
+ end
12
+
13
+ def serialize
14
+ flags = 0
15
+ flags |= (1 << 0) if @revoke
16
+
17
+ result = u32_b(Constructors::MESSAGES_DELETE_MESSAGES)
18
+ result += u32_b(flags)
19
+ result += u32_b(Constructors::VECTOR) + u32_b(@ids.length)
20
+ @ids.each { |id| result += u32_b(id) }
21
+ result
22
+ end
23
+ end
24
+ end
25
+ end
@@ -7,7 +7,7 @@ module MTProto
7
7
 
8
8
  attr_reader :status, :nonce, :server_nonce, :new_nonce_hash
9
9
 
10
- def self.parse(message)
10
+ def self.deserialize(message)
11
11
  data = message.body
12
12
  constructor = b_u32(data[0, 4])
13
13
 
@@ -5,7 +5,7 @@ require_relative '../schema'
5
5
  module MTProto
6
6
  module TL
7
7
  class Dialogs
8
- Dialog = Struct.new(:peer_type, :peer_id, :top_message, :unread_count, keyword_init: true)
8
+ Dialog = Struct.new(:peer_type, :peer_id, :top_message, :unread_count, :pts, keyword_init: true)
9
9
  Chat = Struct.new(:id, :title, :type, :access_hash, keyword_init: true)
10
10
 
11
11
  SCHEMA_PATH = File.expand_path('../../../../data/tl-schema.json', __dir__)
@@ -28,7 +28,7 @@ module MTProto
28
28
  @schema ||= Schema.new(SCHEMA_PATH)
29
29
  end
30
30
 
31
- def self.parse(data)
31
+ def self.deserialize(data)
32
32
  constructor = data[0, 4].unpack1('L<')
33
33
 
34
34
  unless [Constructors::MESSAGES_DIALOGS, Constructors::MESSAGES_DIALOGS_SLICE].include?(constructor)
@@ -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
 
@@ -97,13 +107,17 @@ module MTProto
97
107
 
98
108
  offset = schema.skip(data, offset) # notify_settings (PeerNotifySettings)
99
109
 
100
- offset += 4 if flags.anybits?(1 << 0) # pts
110
+ pts = nil
111
+ if flags.anybits?(1 << 0)
112
+ pts = data[offset, 4].unpack1('L<')
113
+ offset += 4
114
+ end
101
115
  offset = schema.skip(data, offset) if flags.anybits?(1 << 1) # draft (DraftMessage)
102
116
  offset += 4 if flags.anybits?(1 << 4) # folder_id
103
117
  offset += 4 if flags.anybits?(1 << 5) # ttl_period
104
118
 
105
119
  [Dialog.new(peer_type: peer_type, peer_id: peer_id,
106
- top_message: top_message, unread_count: unread_count), offset]
120
+ top_message: top_message, unread_count: unread_count, pts: pts), offset]
107
121
  end
108
122
 
109
123
  def parse_peer(data, offset)