Class: MTProto::Type::RPC::Updates::Difference

Inherits:
Object
  • Object
show all
Defined in:
lib/mtproto/type/rpc/updates/difference.rb

Constant Summary collapse

CONSTRUCTOR_DIFFERENCE =
0x00f49ca0
CONSTRUCTOR_DIFFERENCE_EMPTY =
0x5d75a138
CONSTRUCTOR_DIFFERENCE_SLICE =
0xa8fb1981
CONSTRUCTOR_DIFFERENCE_TOO_LONG =
0x4afe8f6d
CONSTRUCTOR_UPDATE_SHORT =

Updates constructors (can be returned instead of Difference)

0x78d4dec1
CONSTRUCTOR_UPDATES =
0x74ae4240
CONSTRUCTOR_UPDATE_SHORT_MESSAGE =
0x313bc7f8
CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE =
0x402d5dbb
CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE =
0x11f1331c
VECTOR_CONSTRUCTOR =
0x1cb5c415
MESSAGE_CONSTRUCTOR =
0x452c0e65
MESSAGE_EMPTY_CONSTRUCTOR =
0x83e5de54

Class Method Summary collapse

Class Method Details

.parse(response) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/mtproto/type/rpc/updates/difference.rb', line 27

def self.parse(response)
  offset = 0

  constructor = response[offset, 4].unpack1('L<')
  offset += 4

  if constructor == Type::GzipPacked::CONSTRUCTOR
    response = Type::GzipPacked.unpack(response)
    constructor = response[0, 4].unpack1('L<')
    offset = 4
  end

  if constructor == Type::RpcError::CONSTRUCTOR
    error = Type::RpcError.deserialize(response)
    raise MTProto::RpcError.new(error.error_code, error.error_message)
  end

  case constructor
  when CONSTRUCTOR_DIFFERENCE_EMPTY
    # updates.differenceEmpty#5d75a138 date:int seq:int = updates.Difference;
    date = response[offset, 4].unpack1('L<')
    offset += 4

    seq = response[offset, 4].unpack1('L<')

    {
      type: :empty,
      date: date,
      seq: seq,
      new_messages: [],
      state: nil
    }

  when CONSTRUCTOR_DIFFERENCE, CONSTRUCTOR_DIFFERENCE_SLICE
    # updates.difference#f49ca0 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> state:updates.State = updates.Difference;
    # updates.differenceSlice#a8fb1981 new_messages:Vector<Message> new_encrypted_messages:Vector<EncryptedMessage> other_updates:Vector<Update> chats:Vector<Chat> users:Vector<User> intermediate_state:updates.State = updates.Difference;

    # Parse new_messages vector
    messages, offset = parse_messages_vector(response, offset)

    # Skip new_encrypted_messages vector
    offset = skip_vector(response, offset)

    # Skip other_updates vector
    offset = skip_vector(response, offset)

    # Skip chats vector
    offset = skip_vector(response, offset)

    # Parse users vector
    users, offset = parse_users_vector(response, offset)

    {
      type: constructor == CONSTRUCTOR_DIFFERENCE ? :difference : :slice,
      new_messages: messages,
      users: users,
      state: nil # Would need to parse state
    }

  when CONSTRUCTOR_DIFFERENCE_TOO_LONG
    # updates.differenceTooLong#4afe8f6d pts:int = updates.Difference;
    pts = response[offset, 4].unpack1('L<')

    {
      type: :too_long,
      pts: pts,
      new_messages: [],
      state: nil
    }

  when CONSTRUCTOR_UPDATE_SHORT_MESSAGE
    # updateShortMessage#313bc7f8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector<MessageEntity> ttl_period:flags.25?int = Updates;

    flags = response[offset, 4].unpack1('L<')
    offset += 4

    id = response[offset, 4].unpack1('L<')
    offset += 4

    user_id = response[offset, 8].unpack1('Q<')
    offset += 8

    # Parse message string
    msg_len = response[offset].unpack1('C')
    offset += 1
    message_text = response[offset, msg_len].force_encoding('UTF-8')
    offset += msg_len
    padding = (4 - ((1 + msg_len) % 4)) % 4
    offset += padding

    pts = response[offset, 4].unpack1('L<')
    offset += 4

    pts_count = response[offset, 4].unpack1('L<')
    offset += 4

    date = response[offset, 4].unpack1('L<')
    offset += 4

    # Return with the extracted message
    {
      type: :short_message,
      new_messages: [{
        id: id,
        user_id: user_id,
        date: date,
        message: message_text,
        flags: flags
      }],
      state: { pts: pts, pts_count: pts_count }
    }

  when CONSTRUCTOR_UPDATE_SHORT, CONSTRUCTOR_UPDATES,
       CONSTRUCTOR_UPDATE_SHORT_CHAT_MESSAGE,
       CONSTRUCTOR_UPDATE_SHORT_SENT_MESSAGE
    # Sometimes getDifference returns Updates instead of Difference
    # This means there are no new differences, treat as empty
    {
      type: :empty,
      date: Time.now.to_i,
      seq: 0,
      new_messages: [],
      state: nil
    }

  else
    # Unknown constructor - likely an Updates variant we don't handle yet
    # Treat as empty to avoid breaking the polling loop
    warn "Warning: Unknown Difference/Updates constructor: 0x#{constructor.to_s(16)}, treating as empty"
    {
      type: :empty,
      date: Time.now.to_i,
      seq: 0,
      new_messages: [],
      state: nil
    }
  end
end

.parse_message(response, offset) ⇒ Object



185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
# File 'lib/mtproto/type/rpc/updates/difference.rb', line 185

def self.parse_message(response, offset)
  msg_constructor = response[offset, 4].unpack1('L<')
  offset += 4

  return [nil, offset] if msg_constructor == MESSAGE_EMPTY_CONSTRUCTOR

  return [nil, offset] unless msg_constructor == MESSAGE_CONSTRUCTOR

  # message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true invert_media:flags.27?true offline:flags.30?true id:int from_id:flags.8?Peer from_boosts_applied:flags.29?int peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector<MessageEntity> views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector<RestrictionReason> ttl_period:flags.25?int quick_reply_shortcut_id:flags.30?int effect:flags.31?long = Message;

  flags = response[offset, 4].unpack1('L<')
  offset += 4

  # For simplicity, we'll just extract id, date, and message text
  # A full implementation would parse all fields based on flags

  id = response[offset, 4].unpack1('L<')
  offset += 4

  # Skip from_id if present (flags.8)
  if (flags & (1 << 8)) != 0
    # Skip Peer object (constructor + id, could be user/chat/channel)
    offset += 4 # peer constructor
    offset += 8 # peer id (long for user, int for others, but let's assume worst case)
  end

  # Skip from_boosts_applied if present (flags.29)
  offset += 4 if (flags & (1 << 29)) != 0

  # peer_id:Peer (always present) - skip it
  offset += 4 # peer constructor
  offset += 8 # peer id

  # Skip fwd_from if present (flags.2)
  # This is complex, so we'll just skip ahead assuming it's not there for now

  # Skip via_bot_id if present (flags.11)
  offset += 8 if (flags & (1 << 11)) != 0

  # Skip reply_to if present (flags.3)
  # Complex object, skip for now

  # date:int (always present)
  date = response[offset, 4].unpack1('L<')
  offset += 4

  # message:string (always present)
  msg_len = response[offset].unpack1('C')
  offset += 1
  message_text = response[offset, msg_len].force_encoding('UTF-8')
  offset += msg_len
  padding = (4 - ((1 + msg_len) % 4)) % 4
  offset += padding

  # Return simplified message
  [{
    id: id,
    date: date,
    message: message_text,
    flags: flags
  }, offset]
rescue StandardError => e
  # If parsing fails, skip this message
  [nil, offset]
end

.parse_messages_vector(response, offset) ⇒ Object



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/mtproto/type/rpc/updates/difference.rb', line 166

def self.parse_messages_vector(response, offset)
  # Check for Vector constructor
  vector_constructor = response[offset, 4].unpack1('L<')
  offset += 4

  return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR

  count = response[offset, 4].unpack1('L<')
  offset += 4

  messages = []
  count.times do
    message, offset = parse_message(response, offset)
    messages << message if message
  end

  [messages, offset]
end

.parse_user(response, offset) ⇒ Object



296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/mtproto/type/rpc/updates/difference.rb', line 296

def self.parse_user(response, offset)
  user_constructor = response[offset, 4].unpack1('L<')
  offset += 4

  # user#20b1422 flags:# flags2:# id:long access_hash:flags.0?long ...
  return [nil, offset] unless user_constructor == 0x20b1422

  flags = response[offset, 4].unpack1('L<')
  offset += 4

  flags2 = response[offset, 4].unpack1('L<')
  offset += 4

  # id:long (always present)
  user_id = response[offset, 8].unpack1('Q<')
  offset += 8

  # access_hash:flags.0?long (conditional)
  access_hash = nil
  if (flags & (1 << 0)) != 0
    access_hash = response[offset, 8].unpack1('Q<')
    offset += 8
  end

  # We have what we need, skip the rest
  [{
    id: user_id,
    access_hash: access_hash
  }, offset]
rescue StandardError => e
  [nil, offset]
end

.parse_users_vector(response, offset) ⇒ Object



277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/mtproto/type/rpc/updates/difference.rb', line 277

def self.parse_users_vector(response, offset)
  # Check for Vector constructor
  vector_constructor = response[offset, 4].unpack1('L<')
  offset += 4

  return [[], offset] unless vector_constructor == VECTOR_CONSTRUCTOR

  count = response[offset, 4].unpack1('L<')
  offset += 4

  users = []
  count.times do
    user, offset = parse_user(response, offset)
    users << user if user
  end

  [users, offset]
end

.skip_vector(response, offset) ⇒ Object



251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
# File 'lib/mtproto/type/rpc/updates/difference.rb', line 251

def self.skip_vector(response, offset)
  # Read vector constructor
  vector_constructor = response[offset, 4].unpack1('L<')
  offset += 4

  return offset unless vector_constructor == VECTOR_CONSTRUCTOR

  # Read count
  count = response[offset, 4].unpack1('L<')
  offset += 4

  # Skip each element - we can't know the size, so return offset as-is
  # This is a limitation - proper skipping would require parsing each element
  # For now, just read the constructor of each element and skip based on known patterns
  count.times do
    constructor = response[offset, 4].unpack1('L<')
    offset += 4

    # This is a simplified skip - real implementation would need to parse each type
    # For now, skip a reasonable amount (most TL objects are small)
    # This is fragile but works for common cases
  end

  offset
end