Rich Text Interface

The Messages interface is the preferred interface for text messaging, because it supports the full range of messaging features exposed by the protocols.

Some older connection managers may not support this interface. If this is the case, or if you really only want to send simple messages (e.g. for a status reporting system), you can use the simple messaging interface documented below.

Telepathy provides support for rich-text messaging via the Messages interface. Rich-text messaging can include features like formatted (rich text) messages, alternatives (similar to MIME's multipart/alternative) and attachments. Messages are formatted in XHTML-IM.

8.1.1. Message Structure

Messages are sent and received as an array of Message_Part key-value mappings, the first of which contains the message headers. Example 8-1 shows an example message.

Example 8-1Example Message

An example message consisting of four Message_Parts: the headers, two alternatives and an attachment.

Key Value
message-token "9de9546a-3400-4419-a505-3ea270cb834c"
message-sender 42
message-sent 1210067943
message-received 1210067947
message-type Channel_Text_Message_Type_Normal
pending-message-id 437
alternative "main"
content-type "text/html"
content """Here is a photo of my cat:<br /><img src="cid:catphoto" alt="lol!" /><br />Isn't it cute?"""
alternative "main"
content-type "text/plain"
content """Here is a photo of my cat:\n[IMG: lol!]\nIsn't it cute?"""
identifier "catphoto"
content-type "image/jpeg"
size 101000
needs-retrieval True

The known headers for a message are:

Key Type Description
message-token String An opaque, globally-unique identifier for the entire message. Which may be treated the same as a MIME message-id for the mid: and cid: URI schemes. Not always present.
message-sent, message-received Unix_Timestamp64 The time the message was sent and received respectively. May not be present if a time cannot be determined.
message-sender Contact_Handle The handle id of the contact who sent the message. May be 0 or ommitted, if the sender cannot be determined.
message-type Channel_Text_Message_Type The type of message. Defaults to Channel_Text_Message_Type_Normal if ommitted.
pending-message-id Message_ID The incoming message ID, only valid for as long as the message is unacknowledged.

This header is important for acknowledging the received message.

interface DBus_Interface This message is specific to the given interface, which is neither Text nor Messages. This key can also appear in subsequent parts of the message.
scrollback Boolean If true, the incoming message was part of a replay of message history. Defaults to false.
rescued Boolean If true, the incoming message has been seen in a previous channel during the lifetime of the Connection, but had not been acknowledged when that channel closed. Defaults to false.

Alternatives with the same name should be ordered from highest fidelity to lowest fidelity (i.e. rich text before plain text). Clients should display the first alternative they understand.

8.1.2. Receiving Messages

When a remote user initiates a new text chat, Telepathy will automatically create a new channel for the chat (see Section 6.2 ― Incoming Channels). Filter on the channels given for channels of type Channel_Type_Text. Example 8-2 shows how to discover this channel using the NewChannels signal.

Example 8-2Discovering Incoming Channels with NewChannels
def get_interfaces_cb (self, interfaces):
    conn = self.conn

    print "Connection Interfaces:"
    for interface in interfaces:
        print " - %s" % interface

    if CONNECTION_INTERFACE_REQUESTS in interfaces:
        conn[CONNECTION_INTERFACE_REQUESTS].connect_to_signal('NewChannels',
            self.new_channels_cb)

def new_channels_cb (self, channels):
    for channel, props in channels:
        if props[CHANNEL + '.ChannelType'] == CHANNEL_TYPE_TEXT:
            print 'New chat from %s' % props[CHANNEL + '.TargetID']
            # let's hook up to this channel
            TextChannel(self, channel)

Complete Source Code

Set up a D-Bus proxy for the channel, like you would for any other channel (see Section 6.1 ― Requesting Channels). Check to ensure that the Messages interface is available.

Incoming messages are announced with the MessageReceived signal, which places them on the pending message queue. To remove messages from the pending message queue, they must be acknowledged with Text.AcknowledgePendingMessages. There will already be messages pending, the ones that caused the creation of the channel. These can be accessed via the PendingMessages property. See Example 8-3.

The message id to acknowledge the message is contained in the headers of the message as the key pending-message-id (see Section 8.1.1 ― Message Structure).

Always Acknowledge Messages

You must always acknowledge a received message, but you should only do so after you have attempted to parse it.

Failure to acknowledge a message will result in a new channel being created with the pending messages when the current channel is closed.

It's posssible that a bug in your application's message parser could cause an application crash. You should attempt to parse the message before acknowledging it. Thus, if your application has a crash, the message will still be pending when the client reloads.

Example 8-3Setting Up and Receiving Messages
def interfaces_cb (self, interfaces):
    channel = self

    print "Channel Interfaces:"
    for interface in interfaces:
        print " - %s" % interface

    if CHANNEL_INTERFACE_MESSAGES in interfaces:
        channel[CHANNEL_INTERFACE_MESSAGES].connect_to_signal(
            'MessageReceived', self.message_received_cb)
        channel[CHANNEL_INTERFACE_MESSAGES].connect_to_signal(
            'PendingMessagesRemoved', self.pending_messages_removed_cb)

        # find out if we have any pending messages
        channel[DBUS_PROPERTIES].Get(CHANNEL_INTERFACE_MESSAGES,
            'PendingMessages',
            reply_handler = self.get_pending_messages,
            error_handler = self.parent.error_cb)

def get_pending_messages (self, messages):
    for message in messages:
        self.message_received_cb (message)

def message_received_cb (self, message):
    channel = self

    # we need to acknowledge the message
    msg_id = message[0]['pending-message-id']
    channel[CHANNEL_TYPE_TEXT].AcknowledgePendingMessages([msg_id],
        reply_handler = self.parent.generic_reply,
        error_handler = self.parent.error_cb)

Complete Source Code