Message structure

In order for another plug to process your incoming messages, the Message class provides a shared data structure containing many common instant messaging properties.

Of course, a given plug is unlikely to support everything, so it may ignore certain fields as required. It is expected that plugs provide a sensible plain text description of each message, so if a given plug doesn’t support e.g. joins, it can still show some text that says John has joined the channel.

The User class provides a similar amount of flexibility for message senders. The Message.user field holds a standard representation of a network user, where different properties can be used to give messages a native feel across each plug.

Abstract and concrete messages

The Message class represents an abstract messages; that is, the content of a message, regardless of the network it’s designed for or retrieved from.

When a real message arrives, often you’ll want to be able to reference it, or know the source channel. This is where the Receipt class comes in, with metadata about a concrete message.

Edits and deletes

If the plug’s network supports editing messages, this can be represented by a new message object with the same but a new Receipt.revision. If the source message is deleted, treat it as a new revision with the Receipt.deleted flag set.

Replies and forwarding

For networks with threading, Message.reply_to allows linking a message to its parent. If a message needs to forward or otherwise reference another message, it can be included in Message.attachments. These may take the form of a SentMessage, which acts as a pointer to another message, or a plain Message to render any message regardless of its presence in the target channel. If a plug receives a message containing references, it will attempt a native use of the linked message, e.g. to reply or forward it.

Rich text

Message formatting is represented by the RichText class, which is itself just a list of Segment objects. Each segment takes a substring of the message text, along with the formatting that applies to it.

For example, the text “Some bold and emphasis” might be represented as follows:

RichText([Segment('Some '),
          Segment('bold', bold=True),
          Segment(' and '),
          Segment('emphasis', italic=True)])

For plugs that don’t support formatting, calling str() on a RichText object produces a string with formatting removed.

Raw format

If you need to persist formatting in plain-text storage (such as in the Database), rich text can be (de)serialised via RichText.raw() and RichText.unraw(). The raw format syntax is loosely based on HTML, except that one tag holds a comma-separated list of modifiers.

The example above in raw format, with added underline:

Some <b>bold</> and <i,u>emphasis</>

Valid tags match the RichText attributes, and may be shortened to their first character. Links may be either be qualified, or use the segment text as their link target:

Qualified link: <l=>Example site</>
Implied link: <l></>

Mentions can be constructed using the plug name, user identifier and user name:

Mentioning <m=demo/123456/John Smith>@John</>

Note that tags do not nest, and always represent the break between message segments. For example:

Plain <b>Bold <b,i>Both<i> Italic</> Plain


Currently only image file attachments are supported. A plug may provide at least one of two methods to fetch a given file: a public URL, or opaque content retrieval.

In most cases, a plug will need its own implementation of File.get_content(), which takes no arguments and returns a file-like object of the attachment. This should handle any authentication required by the network.

If the plug’s network provides publicly-accessible URLs for files, you may instead just set File.source, and the default implementation of File.get_content() will download it.