mtproto 0.0.13 → 0.0.14

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 (79) 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 +2 -2
  10. data/lib/mtproto/client.rb +28 -18
  11. data/lib/mtproto/tl/constructors.rb +26 -0
  12. data/lib/mtproto/tl/object.rb +1 -1
  13. data/lib/mtproto/tl/objects/account_password.rb +1 -1
  14. data/lib/mtproto/tl/objects/authorization.rb +1 -1
  15. data/lib/mtproto/tl/objects/channels_create_channel.rb +48 -0
  16. data/lib/mtproto/tl/objects/channels_delete_messages.rb +32 -0
  17. data/lib/mtproto/tl/objects/channels_delete_participant_history.rb +34 -0
  18. data/lib/mtproto/tl/objects/channels_edit_banned.rb +54 -0
  19. data/lib/mtproto/tl/objects/channels_get_messages.rb +33 -0
  20. data/lib/mtproto/tl/objects/channels_get_participant.rb +36 -0
  21. data/lib/mtproto/tl/objects/channels_join_channel.rb +22 -0
  22. data/lib/mtproto/tl/objects/channels_leave_channel.rb +22 -0
  23. data/lib/mtproto/tl/objects/channels_report_spam.rb +37 -0
  24. data/lib/mtproto/tl/objects/check_password.rb +1 -1
  25. data/lib/mtproto/tl/objects/client_dh_inner_data.rb +1 -1
  26. data/lib/mtproto/tl/objects/contacts.rb +156 -0
  27. data/lib/mtproto/tl/objects/delete_messages.rb +25 -0
  28. data/lib/mtproto/tl/objects/dh_gen_response.rb +1 -1
  29. data/lib/mtproto/tl/objects/dialogs.rb +8 -4
  30. data/lib/mtproto/tl/objects/edit_message.rb +58 -0
  31. data/lib/mtproto/tl/objects/export_login_token.rb +1 -1
  32. data/lib/mtproto/tl/objects/forward_messages.rb +49 -0
  33. data/lib/mtproto/tl/objects/get_channel_difference.rb +41 -0
  34. data/lib/mtproto/tl/objects/get_config.rb +1 -1
  35. data/lib/mtproto/tl/objects/get_contacts.rb +17 -0
  36. data/lib/mtproto/tl/objects/get_dialogs.rb +1 -1
  37. data/lib/mtproto/tl/objects/get_difference.rb +1 -1
  38. data/lib/mtproto/tl/objects/get_file.rb +54 -0
  39. data/lib/mtproto/tl/objects/get_full_channel.rb +29 -0
  40. data/lib/mtproto/tl/objects/get_full_user.rb +28 -0
  41. data/lib/mtproto/tl/objects/get_history.rb +1 -1
  42. data/lib/mtproto/tl/objects/get_messages_reactions.rb +39 -0
  43. data/lib/mtproto/tl/objects/get_password.rb +1 -1
  44. data/lib/mtproto/tl/objects/get_state.rb +1 -1
  45. data/lib/mtproto/tl/objects/get_users.rb +1 -1
  46. data/lib/mtproto/tl/objects/help_config.rb +1 -1
  47. data/lib/mtproto/tl/objects/import_bot_authorization.rb +44 -0
  48. data/lib/mtproto/tl/objects/import_login_token.rb +1 -1
  49. data/lib/mtproto/tl/objects/init_connection.rb +2 -2
  50. data/lib/mtproto/tl/objects/invite_to_channel.rb +35 -0
  51. data/lib/mtproto/tl/objects/invoke_with_layer.rb +2 -2
  52. data/lib/mtproto/tl/objects/login_token.rb +2 -2
  53. data/lib/mtproto/tl/objects/messages.rb +1 -1
  54. data/lib/mtproto/tl/objects/pq_inner_data.rb +1 -1
  55. data/lib/mtproto/tl/objects/raw_response.rb +29 -0
  56. data/lib/mtproto/tl/objects/req_dh_params.rb +1 -1
  57. data/lib/mtproto/tl/objects/req_pq_multi.rb +1 -1
  58. data/lib/mtproto/tl/objects/res_pq.rb +1 -1
  59. data/lib/mtproto/tl/objects/resolve_username.rb +40 -0
  60. data/lib/mtproto/tl/objects/save_file_part.rb +40 -0
  61. data/lib/mtproto/tl/objects/send_code.rb +1 -1
  62. data/lib/mtproto/tl/objects/send_media.rb +85 -0
  63. data/lib/mtproto/tl/objects/send_message.rb +70 -0
  64. data/lib/mtproto/tl/objects/send_reaction.rb +68 -0
  65. data/lib/mtproto/tl/objects/sent_code.rb +1 -1
  66. data/lib/mtproto/tl/objects/server_dh_inner_data.rb +1 -1
  67. data/lib/mtproto/tl/objects/server_dh_params.rb +1 -1
  68. data/lib/mtproto/tl/objects/set_client_dh_params.rb +1 -1
  69. data/lib/mtproto/tl/objects/sign_in.rb +1 -1
  70. data/lib/mtproto/tl/objects/update.rb +1 -1
  71. data/lib/mtproto/tl/objects/update_short.rb +2 -2
  72. data/lib/mtproto/tl/objects/update_short_message.rb +1 -1
  73. data/lib/mtproto/tl/objects/update_short_sent_message.rb +36 -0
  74. data/lib/mtproto/tl/objects/update_username.rb +39 -0
  75. data/lib/mtproto/tl/objects/updates_difference.rb +2 -2
  76. data/lib/mtproto/tl/objects/updates_state.rb +1 -1
  77. data/lib/mtproto/tl/objects/users.rb +1 -1
  78. data/lib/mtproto/version.rb +1 -1
  79. metadata +36 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ef5c11c5bf0b9bfe4f3b80e905270264b5658bc55a2932e92c945df8ba0dc723
4
- data.tar.gz: 58fec06d053d66b52c382049e7fb917dbf0c4af2395c7f81f5dcb66d70778228
3
+ metadata.gz: e4d643a3e49ecb3e3d2744952aa42edb40237c2307a0d7d316c1ed350443bab7
4
+ data.tar.gz: f3f21cb5f49140c69d1d8dd8d34b7b12f2cf0aaaae48efedc9bce6b954cdf1ae
5
5
  SHA512:
6
- metadata.gz: b108736ec52172e501b8609b6c8b5596052b7fa80d72bfaa6cadb393baddbe89ec009148da487f3b8c16b9a6000e5bdea4aa5b15c77fd9ddd19ce43ae8db6763
7
- data.tar.gz: 3268f1b6943ea7c470ee274a56cbc3e4d3c5a2aebff7e8b5b4522805e8a922756932cb396909650f520cb3153d75d8e34a61e5952916602421ed01df2b9e5c7e
6
+ metadata.gz: ba8f0adc18f691fa85b9722dc994ac1784a30d9465f972f9992da6a95027722655467952c94b31877531c08fb5fa45effef9fc77558ff6b7e2a8cccc69d9310e
7
+ data.tar.gz: a95133f8932df90e61b39de4cd6342b6d5f5b790bd803eacb3cde964b6d2fbd1e6983fc6f2bfb0543c25568287225e094f10e95bdc52a68fbbc5ff9ebbdc8484
data/FUTURE.md ADDED
@@ -0,0 +1,9 @@
1
+ # Future Tasks
2
+
3
+ ## Implement msgs_ack
4
+
5
+ Client should acknowledge received messages by sending `msgs_ack` with a list of received
6
+ msg_ids. Without this, the server may redeliver messages it considers unacknowledged.
7
+
8
+ Also add handling for `bad_msg_notification` responses from the server (constructor exists
9
+ in constructors.rb but no handler in Client#process_message).
@@ -0,0 +1,237 @@
1
+ # Level 1 Test Architecture: Constructor Capture and Test Cases
2
+
3
+ ## Purpose
4
+
5
+ Collect real data from Telegram in specific user-facing scenarios and use it as the
6
+ source for two things:
7
+
8
+ 1. **A library of TL constructor variants**: every concrete form of every constructor
9
+ we ever observe (across all the optional fields and flag-driven shapes) — stored
10
+ under `spec/fixtures/level1/constructors/`. This is the format catalog Level 2
11
+ (deserialization) and Level 3 (API logic) tests work against.
12
+ 2. **Test cases**: one user-facing scenario per executable script under `spec/level1/`,
13
+ producing a folder of fixtures under `spec/fixtures/level1/test_cases/<scenario>/`.
14
+ A test case captures whatever Telegram actually sends end-to-end during that
15
+ scenario.
16
+
17
+ The set of test cases is chosen so that, taken together, they cover every variant
18
+ of every constructor we need to characterize — not so that they reproduce every
19
+ possible user-facing scenario. The same constructor can show up in many test cases
20
+ and inside different parent constructors; we only need to see each variant at least
21
+ once.
22
+
23
+ ## How It Works
24
+
25
+ These are semi-manual tests. Each test case:
26
+
27
+ 1. Connects to Telegram with a pre-authenticated session
28
+ 2. Tells the user what to do (e.g., "Send a text message to this account")
29
+ 3. Waits until the corresponding data arrives from Telegram
30
+ 4. Saves the raw decrypted message bodies as `.bin` files and a `captured.json`
31
+ listing the TL constructors received
32
+
33
+ On subsequent runs, the freshly captured constructor list is compared to the
34
+ previously stored `captured.json` for the same test case. If they differ, the
35
+ script prints a `CHANGED` warning — usually a signal that Telegram's wire-level
36
+ behavior shifted and the fixtures need to be re-examined.
37
+
38
+ ## Example Scenarios
39
+
40
+ The list below illustrates the kinds of situations these tests cover.
41
+ It is not exhaustive — new scenarios are added as needed.
42
+
43
+ - Text message in private chat
44
+ - Text message in a group
45
+ - Text message in a channel
46
+ - Reply to a message
47
+ - Forwarded message
48
+ - Message with photo
49
+ - Message with document
50
+ - Message in a forum topic
51
+ - User joined a group
52
+ - User left a group
53
+ - Message edited
54
+ - Message deleted
55
+
56
+ ## Fixture Layout
57
+
58
+ ```
59
+ spec/fixtures/level1/
60
+ test_cases/
61
+ receive_private_text_message/
62
+ 001.bin
63
+ 002.bin
64
+ 003.bin
65
+ 004.bin
66
+ captured.json
67
+ receive_group_text_message/
68
+ 001.bin
69
+ 002.bin
70
+ captured.json
71
+ ...
72
+ constructors/
73
+ updateShortMessage/
74
+ plain_in.bin
75
+ with_fwd_from.bin
76
+ with_reply_to.bin
77
+ with_entities_bold.bin
78
+ ...
79
+ messageFwdHeader/
80
+ from_user.bin
81
+ from_channel.bin
82
+ ...
83
+ messageEntity/
84
+ bold.bin
85
+ italic.bin
86
+ url.bin
87
+ ...
88
+ ...
89
+ ```
90
+
91
+ Each test case produces:
92
+
93
+ - One or more `.bin` files with raw decrypted message bodies, numbered sequentially
94
+ in arrival order (one user action may trigger multiple messages from Telegram).
95
+ - A `captured.json` listing each captured message as an object with two fields —
96
+ the TL `constructor` name and the `file` containing its raw body bytes.
97
+
98
+ Shape of `captured.json`:
99
+
100
+ ```json
101
+ [
102
+ { "constructor": "msgs_ack", "file": "001.bin" },
103
+ { "constructor": "updateShort", "file": "002.bin" },
104
+ { "constructor": "updateShortMessage", "file": "003.bin" }
105
+ ]
106
+ ```
107
+
108
+ The `constructors/` tree is a separate, deduplicated library populated from
109
+ test-case captures: one `.bin` per distinct variant of each constructor, with
110
+ the filename describing the variant (e.g. `plain_in.bin`, `with_fwd_from.bin`).
111
+ No metadata file — the folder + filename are the catalogue.
112
+
113
+ ## File Structure
114
+
115
+ Each scenario is an executable script (not an RSpec spec). The flow is
116
+ interactive — the script prints instructions, waits on STDIN, then captures
117
+ the resulting Telegram traffic. RSpec's declarative `describe`/`it` shape
118
+ doesn't fit that, so these scripts live under `spec/level1/` alongside the
119
+ rspec tree but are invoked directly, not through `rake spec`.
120
+
121
+ ```
122
+ spec/
123
+ level1/
124
+ support/ # Shared test infrastructure
125
+ receive_private_text_message # Executable script (one per test case)
126
+ receive_group_text_message
127
+ ...
128
+ fixtures/
129
+ level1/
130
+ test_cases/ # Per-test-case captures (folder per scenario)
131
+ constructors/ # Deduplicated constructor variant library
132
+ ```
133
+
134
+ ## Running with xp
135
+
136
+ These scripts are interactive — they print a prompt, block on STDIN, run
137
+ until the captured traffic arrives, then exit. When the operator is a
138
+ Claude Code bot driving the repo from short-lived Bash invocations, a
139
+ plain shell can't keep the script alive across calls: each tool call is
140
+ a fresh process, so the script would exit (or be killed) the moment the
141
+ shell returns.
142
+
143
+ The way around it is [xp](https://github.com/alev-pro/xp) — a small
144
+ process stdin/stdout multiplexer. A long-lived xp daemon owns the
145
+ script under a named topic; separate client invocations send to its
146
+ stdin and read from its stdout. The script keeps running between
147
+ calls.
148
+
149
+ The recipe below assumes xp is built at
150
+ `~/devel/alev-pro/xp/target/release/xp`. Adjust the path if it lives
151
+ elsewhere.
152
+
153
+ ### 1. Start the xp daemon
154
+
155
+ ```sh
156
+ nohup ~/devel/alev-pro/xp/target/release/xp --run-daemon \
157
+ >/tmp/xp-daemon.log 2>&1 &
158
+ echo $! > /tmp/xp-daemon.pid
159
+ ```
160
+
161
+ The daemon binds a Unix socket at `/tmp/xp-<uid>.sock`. The PID file
162
+ is just a convention so teardown can `kill` by PID — `pkill -f` on
163
+ the daemon command line is dangerous because it can also match the
164
+ caller's own wrapper shell.
165
+
166
+ ### 2. Spawn the Level 1 script under a topic
167
+
168
+ ```sh
169
+ ~/devel/alev-pro/xp/target/release/xp -t l1 -- \
170
+ bash -lc 'cd ~/devel/alev-pro/mtproto-ruby \
171
+ && bundle exec spec/level1/receive_private_text_message'
172
+ ```
173
+
174
+ `l1` is just a chosen topic name. Any string works.
175
+
176
+ ### 3. Read the script's stdout
177
+
178
+ ```sh
179
+ ~/devel/alev-pro/xp/target/release/xp -t l1 --read-stdout --cursor c1
180
+ ```
181
+
182
+ `--cursor c1` keeps a stable read position across separate xp client
183
+ invocations. Without `--cursor`, xp keys the cursor on the caller's
184
+ posix session id (`getsid(0)`), and every fresh Bash tool call has a
185
+ new sid — meaning every read replays the buffer from byte 0.
186
+
187
+ Expected initial output:
188
+
189
+ ```
190
+ Connecting...
191
+ Connected as: <first> <last>
192
+ Send a text message to this account in a private chat.
193
+ Press Enter when done.
194
+ ```
195
+
196
+ ### 4. Trigger the scenario
197
+
198
+ Perform the user-facing action the script asks for (in this case,
199
+ send a private text message to the test-DC account from another
200
+ Telegram client).
201
+
202
+ ### 5. Unblock the script
203
+
204
+ ```sh
205
+ echo "" | ~/devel/alev-pro/xp/target/release/xp -t l1 --put-to-stdin
206
+ ```
207
+
208
+ The script then prints the captured constructor list and writes the
209
+ fixtures + `captured.json` to `spec/fixtures/level1/test_cases/<scenario>/`.
210
+
211
+ ### 6. Tear down
212
+
213
+ ```sh
214
+ kill "$(cat /tmp/xp-daemon.pid)"
215
+ rm -f /tmp/xp-<uid>.sock /tmp/xp-daemon.pid
216
+ ```
217
+
218
+ ### STDOUT buffering caveat
219
+
220
+ Ruby's `STDOUT` defaults to full buffering when stdout is not a tty,
221
+ and xp pipes the child's stdout through a plain pipe rather than a
222
+ pty. Without an explicit flush the script's `puts` calls would stay
223
+ in-process until exit, and step 3 would read nothing. Each Level 1
224
+ script must therefore set `STDOUT.sync = true` near the top, before
225
+ any output.
226
+
227
+ ### `bash -lc` noise
228
+
229
+ When `bash -i` runs under xp without a pty, it complains:
230
+
231
+ ```
232
+ bash: cannot set terminal process group (...): Inappropriate ioctl for device
233
+ bash: no job control in this shell
234
+ ```
235
+
236
+ That noise lands on stderr, which the daemon inherits and writes into
237
+ `/tmp/xp-daemon.log`. The script's own stdout stays clean.
@@ -86,13 +86,13 @@ module MTProto
86
86
  private
87
87
 
88
88
  def send_and_receive(rpc, response_class)
89
- message = Message.new(rpc.body)
89
+ message = Message.new(rpc.serialize)
90
90
  @connection.send(Transport::Packet.new(message.bytes))
91
91
 
92
92
  response_packet = @connection.receive
93
93
  raise 'Receive timeout' if response_packet.nil?
94
94
 
95
- result = response_class.parse(Message.parse(response_packet))
95
+ result = response_class.deserialize(Message.parse(response_packet))
96
96
 
97
97
  raise 'Nonce mismatch!' if rpc.respond_to?(:nonce) && result.nonce != rpc.nonce
98
98
  raise 'Server nonce mismatch!' if rpc.respond_to?(:server_nonce) && result.server_nonce != rpc.server_nonce
@@ -114,7 +114,7 @@ module MTProto
114
114
  new_nonce: new_nonce,
115
115
  dc: dc_value
116
116
  )
117
- encrypted_data = Crypto::RSA_PAD.encrypt(inner_data.body.pack('C*'), server_key)
117
+ encrypted_data = Crypto::RSA_PAD.encrypt(inner_data.serialize.pack('C*'), server_key)
118
118
 
119
119
  rpc = TL::ReqDHParams.new(
120
120
  nonce: res_pq.nonce,
@@ -149,7 +149,7 @@ module MTProto
149
149
  answer = answer[0..-2]
150
150
  end
151
151
 
152
- TL::ServerDHInnerData.parse(answer)
152
+ TL::ServerDHInnerData.deserialize(answer)
153
153
  end
154
154
 
155
155
  def send_client_dh_params(res_pq, _new_nonce, client_dh_params, tmp_aes_key, tmp_aes_iv)
@@ -160,7 +160,7 @@ module MTProto
160
160
  g_b: client_dh_params[:g_b_bytes]
161
161
  )
162
162
 
163
- client_dh_data = client_dh_inner_data.body.pack('C*')
163
+ client_dh_data = client_dh_inner_data.serialize.pack('C*')
164
164
  client_dh_data_with_hash = Digest::SHA1.digest(client_dh_data) + client_dh_data
165
165
 
166
166
  padding_length = (16 - (client_dh_data_with_hash.bytesize % 16)) % 16
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/get_contacts'
4
+ require_relative '../../tl/objects/contacts'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def get_contacts(hash: 0)
10
+ rpc_call(TL::GetContacts.new(hash: hash), TL::Contacts).body
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../tl/objects/send_message'
4
+ require_relative '../../tl/objects/update_short_sent_message'
5
+
6
+ module MTProto
7
+ class Client
8
+ class API
9
+ def send_message(peer:, message:, random_id: nil)
10
+ rpc_call(TL::SendMessage.new(peer: peer, message: message, random_id: random_id),
11
+ TL::UpdateShortSentMessage).body
12
+ end
13
+ end
14
+ end
15
+ end
@@ -10,6 +10,8 @@ require_relative 'api/get_dialogs'
10
10
  require_relative 'api/get_history'
11
11
  require_relative 'api/get_updates_state'
12
12
  require_relative 'api/get_updates_difference'
13
+ require_relative 'api/send_message'
14
+ require_relative 'api/get_contacts'
13
15
 
14
16
  module MTProto
15
17
  class Client
@@ -43,7 +43,7 @@ module MTProto
43
43
  end
44
44
 
45
45
  def signal(raw_body)
46
- @result = @response_class.parse(raw_body)
46
+ @result = @response_class.deserialize(raw_body)
47
47
  @condition.signal
48
48
  end
49
49
 
@@ -69,8 +69,8 @@ module MTProto
69
69
  private
70
70
 
71
71
  def serialize_request(request)
72
- if request.respond_to?(:body)
73
- request.body.pack('C*')
72
+ if request.respond_to?(:serialize)
73
+ request.serialize.pack('C*')
74
74
  else
75
75
  request
76
76
  end
@@ -92,24 +92,7 @@ module MTProto
92
92
 
93
93
  begin
94
94
  Async do
95
- @running = true
96
- @receiver_task = Async do
97
- @connection.receive do |packet, error|
98
- if error
99
- warn "[MTProto] Packet read error: #{error.message}"
100
- next
101
- end
102
-
103
- decrypted = EncryptedMessage.decrypt(
104
- auth_key: @auth_key,
105
- encrypted_message_data: packet.data.pack('C*'),
106
- sender: :server
107
- )
108
-
109
- process_message(decrypted[:body])
110
- end
111
- end
112
-
95
+ start_receiving!
113
96
  yield self
114
97
  ensure
115
98
  disconnect!
@@ -119,6 +102,33 @@ module MTProto
119
102
  end
120
103
  end
121
104
 
105
+ # Start the receiver task without taking over the current Async reactor.
106
+ # Use when orchestrating multiple clients in a shared Async block — call
107
+ # from inside an Async do ... end. The caller is responsible for
108
+ # disconnect! at the end.
109
+ def start_receiving!
110
+ raise 'Auth key not set' unless auth_key?
111
+ raise 'Mainloop already running' if @running
112
+
113
+ @running = true
114
+ @receiver_task = Async do
115
+ @connection.receive do |packet, error|
116
+ if error
117
+ warn "[MTProto] Packet read error: #{error.message}"
118
+ next
119
+ end
120
+
121
+ decrypted = EncryptedMessage.decrypt(
122
+ auth_key: @auth_key,
123
+ encrypted_message_data: packet.data.pack('C*'),
124
+ sender: :server
125
+ )
126
+
127
+ process_message(decrypted[:body])
128
+ end
129
+ end
130
+ end
131
+
122
132
  def disconnect!
123
133
  @running = false
124
134
  @receiver_task&.stop
@@ -19,6 +19,7 @@ module MTProto
19
19
  PEER_CHAT = 0x36c6019a
20
20
  PEER_CHANNEL = 0xa2a5371e
21
21
  INPUT_PEER_EMPTY = 0x7f3b18ea
22
+ INPUT_PEER_SELF = 0x7da07ec9
22
23
  INPUT_PEER_USER = 0xdde8a54c
23
24
  INPUT_PEER_CHAT = 0x35a95cb9
24
25
  INPUT_PEER_CHANNEL = 0x27bcbbfc
@@ -71,9 +72,24 @@ module MTProto
71
72
  USERS_GET_USERS = 0x0d91a548
72
73
  USER = 0x020b1422
73
74
 
75
+ # Contacts
76
+ CONTACTS_GET_CONTACTS = 0x5dd69e12
77
+ CONTACTS_CONTACTS = 0xeae87e42
78
+ CONTACTS_CONTACTS_NOT_MODIFIED = 0xb74ba9d2
79
+ CONTACT = 0x145ade0b
80
+
74
81
  # Messages
75
82
  MESSAGES_GET_DIALOGS = 0xa0f4cb4f
76
83
  MESSAGES_GET_HISTORY = 0x4423e6c5
84
+ MESSAGES_SEND_MESSAGE = 0xfe05dc9a
85
+ MESSAGES_SEND_MEDIA = 0xac55d9c1
86
+ MESSAGES_EDIT_MESSAGE = 0xdfd14005
87
+ MESSAGES_DELETE_MESSAGES = 0xe58e95d2
88
+
89
+ # Upload + media
90
+ UPLOAD_SAVE_FILE_PART = 0xb304a621
91
+ INPUT_FILE = 0xf52ff27f
92
+ INPUT_MEDIA_UPLOADED_PHOTO = 0x1e287d04
77
93
  MESSAGES_DIALOGS = 0x15ba6c40
78
94
  MESSAGES_DIALOGS_SLICE = 0x71e094f3
79
95
  MESSAGE = 0x9815cec8
@@ -87,6 +103,16 @@ module MTProto
87
103
  UPDATE_NEW_MESSAGE = 0x1f2b0afd
88
104
  UPDATE_SHORT = 0x78d4dec1
89
105
  UPDATE_SHORT_MESSAGE = 0x313bc7f8
106
+ UPDATE_SHORT_SENT_MESSAGE = 0x9015e101
107
+
108
+ # Channel updates
109
+ INPUT_CHANNEL = 0xf35aec28
110
+ INPUT_CHANNEL_EMPTY = 0xee8c1e86
111
+ CHANNEL_MESSAGES_FILTER_EMPTY = 0x94d42ee7
112
+ UPDATES_GET_CHANNEL_DIFFERENCE = 0x03173d78
113
+ UPDATES_CHANNEL_DIFFERENCE = 0x2064674e
114
+ UPDATES_CHANNEL_DIFFERENCE_EMPTY = 0x3e11affb
115
+ UPDATES_CHANNEL_DIFFERENCE_TOO_LONG = 0xa4bcc6fe
90
116
  UPDATES_GET_STATE = 0xedd4882a
91
117
  UPDATES_STATE = 0xa56c2a3e
92
118
  UPDATES_GET_DIFFERENCE = 0x19c2f763
@@ -12,7 +12,7 @@ module MTProto
12
12
  @payload = payload
13
13
  end
14
14
 
15
- def self.parse(message)
15
+ def self.deserialize(message)
16
16
  body = message.body
17
17
  constructor_id = b_u32(body[0, 4])
18
18
  constructor = ConstructorNames::NAMES.fetch(constructor_id)
@@ -12,7 +12,7 @@ module MTProto
12
12
  @srp_id = srp_id
13
13
  end
14
14
 
15
- def self.parse(data)
15
+ def self.deserialize(data)
16
16
  constructor = data[0, 4].unpack1('L<')
17
17
  raise UnexpectedConstructorError, constructor unless constructor == Constructors::ACCOUNT_PASSWORD
18
18
 
@@ -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