📜 ⬆️ ⬇️

More information about the protocol Mail.Ru Agent

On Habré already wrote about how Mail.Ru Agent is arranged. At the moment there is no official documentation for the protocol in the public domain, so you have to investigate the device empirically. In this article, I will look at sending formatted text messages and creating and sending messages to the conference.

A few words about the protocol


Messages are transmitted in packets of a specific format. The first 44 bytes are the header, which looks like this:

struct.pack( '<7L16s', magic, # DEAD BEEF,  ,  ,    MMP  proto, #   seq, #    msg, #  : /   /... dlen, #   from_, #  ,      fromport, #  ,       reserved #  16  ) 

The numbers here are transmitted in UL format, which looks like 4 bytes, written from right to left. Thus, the number 10 will look like 00 00 00 0A. So we will pack in UL:

 class MRIMType(object): def __init__(self, value): self.value = value class UL(MRIMType): def pack(self): if isinstance(self.value, int): return struct.pack("<L", self.value) else: return struct.pack("<L", len(self.value)) 

Text is transmitted in LPS format — strings with a given length (the length is specified as UL). We will pack it in this way:
')
 class LPSZ(MRIMType): def pack(self): if isinstance(self.value, (list, tuple)): #      data = "" data += UL(self.value).pack() for element in self.value: data += element.pack() return data elif isinstance(self.value, MRIMType): #     data = self.value.pack() return struct.pack("<L", len(data)) + data else: data = self.value return struct.pack("<L", len(data)) + data 

We also need to pack the lines in LPS in other encodings:

 class LPSX(MRIMType): def __init__(self, value, encoding): super(LPSX, self).__init__(value) self.encoding = encoding def pack(self): encoded_data = self.value.encode(self.encoding) return struct.pack("<L", len(encoded_data)) + encoded_data class LPSW(LPSX): def __init__(self, value): super(LPSW, self).__init__(value, encoding="UTF-16LE") class LPSA(LPSX): def __init__(self, value): super(LPSA, self).__init__(value, encoding="cp1251") 

Formatted text messages


Let's see what the messages look like. The msg field in the header must be filled with the constant 0x1008, otherwise the message packet is as follows:

  HEADER UL(0x80), LPSA(recipient), # email   LPSW(body), # ,   LPSZ(rtf_part) 

The last component of the package is the part of the message related to the formatting of the text. If we do not need formatting, rtf_part must consist of a space. In this case, the Mail.Ru Agent that receives this message will use the default fonts installed in the recipient agent.

If we want to send a formatted message, then the last part of the packet should be LPSZ (rtf_part), where:

 rtf_part = pack_rtf(rtf) def pack_rtf(rtf): msg = UL(2).pack() + LPSZ(rtf).pack() + LPSZ("\xFF\xFF\xFF\x00").pack() message = base64.b64encode(msg.encode('zlib')) return message 

The last item is the background color; when a message is received, the chat window will change its color entirely.
rtf for writing “qwerty” looks like this:

 r"{{\rtf1\ansi\ansicpg1252\deff0\deflang1033" \ #  r"{{\fonttbl{{\f0\fnil\fcharset204 Tahoma;}}{{\f1\fnil\fcharset0 Tahoma;}}}}\ # ,   .    r"{{\colortbl ;\red0\green0\blue0;}} " \ #   r"\viewkind4\uc1\pard\cf1\f0\fs32 q\f1 werty\f0\par }}" # ,  

You can see that the first letter is written in one font, and the rest in another. I cannot explain this behavior, but the rtf generated by the Mail.Ru Agent that I managed to get looked like this. rtfs that do not have this property remain valid. The remaining parameters (language, font table, Russian) affect the validity of rtf.

It remains to be noted that if the rtf-part of the message is not empty, it will come in the message. If the text part of the message is indicated (body), then we will see this text in the Mail.Ru Agent pop-up window.

Conferences


If in order to start a chat with another contact, you just need to send a message, then in order to start chatting in a conference, you need to do a few squats.

Creating a conference


Each conference has its own unique name, which looks like 5895986@chat.agent, which we receive from the server in response to this message:

 HEADER #    0x1019 UL(0x80), LPSW(""), LPSW(""), LPSW(chat_name), # ,       LPSW(""), LPSW(""), LPSW(""), LPSZ(LPSZ(LPSZ(packed_recipients))) #     

In response to this message, a message is received from the server with the same number in the header and the IT manager. After receiving a response from the server, you can send messages to the conference.

Sending messages to the conference


To send a message to the conference, you need to send two packets. The first package has no meaning, it is preparatory:

 HEADER #   --  (0x1008) UL(0x200400), LPSA(chat_alias), # ,      LPSZ(" "), LPSZ("") 

And now, directly, the message:

 HEADER UL(0x80), LPSA(chat_alias), LPSW(body), LPSZ(rtf_part) 

It looks like a regular message with a conference receiver.

Exit from the conference


I needed these studies to implement autotests. I quickly encountered a problem - the account may be in the final number of conferences. Therefore, at the end of the test you need to leave the conference.

 HEADER #  ,    0x101B UL(absolute_chat_number), #  ,    . UL(0x0081), UL(0xF4244), LPSZ(chat_alias), #   LPSW(chat_name), #   LPSZ(""), 

It was not possible to figure out how to get the absolute conference number, but it was experimentally found that the chat is not identified by it. Therefore, you can specify any reasonable number, for example, 42.

My research is far from being complete, so I will be glad to any corrections and additions.

Source: https://habr.com/ru/post/253303/


All Articles