Retrieving messages from a MAM message archive

This is a more complex example which described how we can retrieve messages from a MAM archive and return them asynchronous. Message Archive Manangement (MAM) is decribed here in XEP-0313.

In some other samples before we were always sending a query and expecting a response. In a regular request response pattern we always have 1 request and are awaiting a single response.

When requesting messages from a MAM archive the flow is the following:

===> Client sends the request

<=== Server replies with message 1 matching the query
<=== Server replies with message 2 matching the query
<=== Server replies with message 3 matching the query
...

<=== Server sends response to the iq

Now we want to build a method which does the following:

  • Send an iq to request messages teh last 10 messages we exchanged with a given user jid
  • The result we are interested in should contains certain parameters of the respone
  • The result should also contain a collection of all the messages which the server returned

To achieve this we are going to write a function that sends adn awaits the MAM Iq query. In addition we setup a subscription that is looking for archive messages that match our QueryId and should be returned by the server before response to the Iq.

First we create a class for our result. For requesting next and previous pages from the archive we should store some information. Also we want to store if we retrieved all messages or can more pages.

MAM result class

public class MamResult
{
    // was this request a Success or failure
    public bool IsSuccess { get; set; }
    // is the result complete, or can we request more pages
    public bool Complete { get; set; }
    // last is in the results for paging
    public string Last { get; set; }
    // first is in teh results for paging
    public string First { get; set; }

    // collection of messages retrieved
    public ReadOnlyCollection<Message> Messages { get; set; }

    public MamResult WithMessages(List<Message> messages)
    {
        Messages = new ReadOnlyCollection<Message>(messages);
        return this;
    }

    public static MamResult FromIq(Iq iq)
    {
        if (iq.Type == IqType.Result)
        {
            // success
            if (iq.Query is Final final)
            {
                return new MamResult()
                {
                    IsSuccess = true,
                    Complete = final.Complete,
                    Last = final.ResultSet.Last,
                    First = final.ResultSet.First.Value
                };
            }
        }
    
        return new MamResult()
        {
            IsSuccess = false,
        };
    }
}

Task to retrieve messages for a given Jid from archive

/// <summary>
/// Task to retrieve messages for a given Jid from archive
/// </summary>
/// <param name="xmppClient">XmppClient instance</param>
/// <param name="jid">The jid we request messages fore</param>
/// <param name="maxResults">Max number results (paging)</param>
/// <returns>MamResult object</returns>
public async Task<MamResult> RequestLastChatMessagesFromArchive(
    XmppClient xmppClient,
    string jid, 
    int maxResults
    )
{    
    // build the MAM query
    var mamQuery = new IqQuery<MessageArchive>
    {
        Type = IqType.Set,
        Query =
        {
            QueryId = Guid.NewGuid().ToString(),
            XData = new Data
            {
                Type = FormType.Submit,
                Fields = new[]
                {
                    new Field
                    {
                        Var = "FORM_TYPE",
                        Type = FieldType.Hidden,
                        Values = new[] {Namespaces.MessageArchiveManagement}
                    },
                    new Field
                    {
                        Var = "with",
                        Values = new[] {jid}
                    }
                }
            },
            ResultSet = new Set
            {
                Max = maxResults                
            }            
        }
    };

    var messages = new List<Message>();

    // set up message subscription
    // we look for messages that:
    // * match our query id
    // * are a MAM result messages
    // * are sent from us or the Jid we query for
    var messageSubscription = xmppClient
        .XmppXElementReceived
        .Where(el => el is Message { IsMamResult: true } msg                        
                && msg.MamResult.QueryId == mamQuery.Query.QueryId
                && (msg.From.EqualsBare(jid) || msg.From.EqualsBare(xmppClient.Jid))
        )
        .Select(el => el.Cast<Message>().MamResult)
        .Subscribe(result =>
        {           
            messages.Add(result.Forwarded.Message);           
        });


    var resIq = await xmppClient.SendIqAsync(mamQuery);

    // dispose the subscription
    messageSubscription.Dispose();

    // return iq result and messages in the MamResult object
    return MamResult
        .FromIq(resIq)
        .WithMessages(messages);
}