erlangejabberderlejabberd-moduleejabberd-hooks

Parsing ejabberd packet with erlang


I am using ejabberd18.09 for IM application. the application has few features that needed to add extensions to the ejabberd(xmpp) messages.

I created a custom module on the offline_message_hook to capture the offline messages and send them to my own url for further processing .

the messages that are being sent to ejabberd have different cases depending on the type of message as following

if I am sending a photo the message will be as following

<message xmlns="jabber:client" xml:lang="en" to="someuserjid2" from="{someuserjid}" type="chat" id="mP8tO-8">
   <mtype xmlns="urn:xmpp:mtype" value="media" />
   <url xmlns="urn:xmpp:url" id="myId" mediaType="photo" link="myphotourl.com" />
   <body>thumbnail string</body>
</message>

when sending a text

<message xmlns="jabber:client" xml:lang="en" to="someuserjid2" from="{someuserjid}" type="chat" id="mP8tO-8">
   <mtype xmlns="urn:xmpp:mtype" value="text" />
   <body>Hi John</body>
</message>

when sending a location

<message xmlns="jabber:client" xml:lang="en" to="someuserjid2" from="{someuserjid}" type="chat" id="mP8tO-8">
   <mtype xmlns="urn:xmpp:mtype" value="location" />
   <location xmlns="urn:xmpp:geo" lat="1.2" lng="2.2 " />
   <body>location thumbnailstring</body>
</message>

I used a .erl code to read the body and the message ID as following

create_message(_From, _To, Packet) when (Packet#message.type == chat) and (Packet#message.body /= []) ->
  Body = fxml:get_path_s(Packet, [{elem, list_to_binary("body")}, cdata]),
  MessageId = fxml:get_tag_attr_s(list_to_binary("id"), Packet),
  post_offline_message(_From, _To, Body, MessageId),
  ok.

what I want is how (in erlang) can I read the value attribute of the mtype tag the create a switch statement on ( media, location , test ) values so that I can process each message separately ?


Solution

  • You can pass attr in the list of arguments to fxml:get_path_s to pick out the value of an attribute of a certain element:

    case fxml:get_path_s(Packet, [{elem, <<"mtype">>}, {attr, <<"value">>}]) of
        <<"media">> ->
            %% handle media...
        <<"location">> ->
            %% handle location...
        <<"">> ->
            %% no mtype element, or missing value attribute!
            %% let's handle that case as well
    end
    

    Another thought: do you actually need the <mtype> element? It looks like you could just check for the presence of a <location> or <url> element. You could do it like this:

    case fxml:get_subtag(Packet, <<"location">>) of
        false ->
            %% No location. Try url
            case fxml:get_subtag(Packet, <<"url">>) of
                false ->
                    %% Neither location nor url
                    %% Handle this case somehow...
                Url ->
                    %% Do something with Url...
            end
        Location ->
            %% Do something with Location...
    end
    

    The code gets a bit messy, since we need to nest the case expressions, unlike the previous version where we just checked the value of a single expression. One thing you could do is writing a helper function that tries each element in turn:

    find_one_of(Packet, []) ->
        not_found;
    find_one_of(Packet, [ElementName | Rest]) ->
        case fxml:get_subtag(Packet, ElementName) of
            false ->
                find_one_of(Packet, Rest);
            Element ->
                {ElementName, Element}
        end.
    

    And then call it like:

    case find_one_of(Packet, [<<"location">>, <<"url">>]) of
        {<<"location">>, Location} ->
            %% Do something with Location...
        {<<"url">>, Url} ->
            %% Do something with Url...
        not_found ->
            %% Neither location nor url
    end