Search code examples
phpxmppejabberdmongoose-im

MongooseIM module not getting variables from Packet


I wrote the following module for MongooseIM but nothing is posted to the PHP file.

start(_Host, _Opt) -> 
    inets:start(),
    ejabberd_hooks:add(user_send_packet, _Host, ?MODULE, fetchPacketData, 50).
stop (_Host) -> 
    ejabberd_hooks:delete(user_send_packet, _Host, ?MODULE, fetchPacketData, 50).
fetchPacketData(_From, _To, Packet) ->
    To = xml:get_tag_attr_s(<<"to">>, Packet),
    httpc:request(post, {"http://example.com/receiver.php",[],
            "application/x-www-form-urlencoded",
            lists:concat(["To=",To,"&Type=1","&Body=ABC"])}, [], []).

I was able to successfully implement the module after following erszcz's suggestion(Please see below). Below is the code i used. Hope it helps someone else too :)

start(Host, _Opts)->
  inets:start(),
  ejabberd_hooks:add(user_send_packet, Host, ?MODULE, sendMessage, 50),
  ok.
stop(Host)->
  ejabberd_hooks:delete(user_send_packet, Host, ?MODULE, sendMessage, 50),
  ok.
sendMessage(_From, _To, Packet) ->
  case xml:get_tag_attr_s(<<"type">>, Packet) of
    <<"chat">> ->
      To = lists:flatten(io_lib:format("~s", [xml:get_tag_attr_s(<<"to">>, Packet)])),
      ** post variables to PHP file using httpc:request **
      ok;
    _ ->
      ok
    end.

Solution

  • offline_message_hook is only called when the server is routing a message to a user who is not online. user_send_packet is run every time the server receives a stanza from a client. This might explain why the handler is not run, though it depends on how you test. There's an article with one section describing some hooks in MongooseIM available on the official wiki.

    As for problems with retrieving packet attributes, either logging the incoming packet for inspection or using dbg in the server Erlang shell to trace the actual calls done by your module might be the way to tell what's happening.


    An example session with dbg debugging the problem might look like this:

    (mongooseim@localhost)1> dbg:tracer().
    {ok,<0.570.0>}
    (mongooseim@localhost)2> dbg:p(all, call).
    {ok,[{matched,mongooseim@localhost,279}]}
    (mongooseim@localhost)3> dbg:tpl(mod_test, x).
    {ok,[{matched,mongooseim@localhost,5},{saved,x}]}
    (mongooseim@localhost)4> (<0.576.0>) call mod_test:fetchPacketData({jid,<<"alice">>,<<"localhost">>,<<"escalus-default-resource">>,<<"alice">>,
         <<"localhost">>,<<"escalus-default-resource">>},{jid,<<"alice">>,<<"localhost">>,<<>>,<<"alice">>,<<"localhost">>,<<>>},{xmlel,<<"presence">>,[{<<"xml:lang">>,<<"en">>}],[]})
    (<0.576.0>) exception_from {mod_test,fetchPacketData,3} {error,function_clause}
    2015-03-15 11:46:03.028 [error] <0.576.0>@ejabberd_hooks:run1:240 {function_clause,[{lists,thing_to_list,[<<>>],[{file,"lists.erl"},{line,601}]},{lists,flatmap,2,[{file,"lists.erl"},{line,1248}]},{lists,flatmap,2,[{file,"lists.erl"},{line,1248}]},{mod_test,fetchPacketData,3,[{file,"src/mod_test.erl"},{line,15}]},{safely,apply,3,[{file,"src/safely.erl"},{line,19}]},{ejabberd_hooks,run1,3,[{file,"src/ejabberd_hooks.erl"},{line,236}]},{ejabberd_c2s,session_established2,2,[{file,"src/ejabberd_c2s.erl"},{line,1063}]},{p1_fsm_old,handle_msg,10,[{file,"src/p1_fsm_old.erl"},{line,542}]}]}
        Running hook: {user_send_packet,[{jid,<<"alice">>,<<"localhost">>,<<"escalus-default-resource">>,<<"alice">>,<<"localhost">>,<<"escalus-default-resource">>},{jid,<<"alice">>,<<"localhost">>,<<>>,<<"alice">>,<<"localhost">>,<<>>},{xmlel,<<"presence">>,[{<<"xml:lang">>,<<"en">>}],[]}]} 
        Callback: mod_test:fetchPacketData
    

    We see that the handler errors out with function_clause when calling lists:thing_to_list(<<>>). The empty binary is a result of xml:get_tag_attr_s/2 when the attribute asked for is not found. lists:thing_to_list/1 is called to convert each parameter of lists:concat/1 to a list, but it's not possible to convert an empty binary <<>> to a list, hence the crash.

    Match on the result of xml:get_tag_attr_s/2 and craft your logic appropriately to each case: when the attribute is found and when it's not there.


    I don't know how to start a module in dbg. I tried what you had shared above and I think you missed the 4th command which could be an example of how to initiate a module.

    This is a raw dump of my console without any edits - I did not miss any part. You don't "start a module in dbg." You simply start a module the usual way and then use dbg from the server shell.

    What I did was I took your example code, put it into apps/ejabberd/src/mod_test.erl file and built a release. After that you can enable the module either in ejabberd.cfg of the release (look for the modules section and do it similarly as examples there show) or you can start the server in live mode with mongooseimctl live and start the module manually with gen_mod:start_module(<<"localhost">>, mod_test, []) (where <<"localhost">> is just an example XMPP domain - substitute your own suitable domain there).

    When the module is running (can be checked with gen_mod:is_loaded(<<"your-xmpp-domain">>, mod_name_goes_here)) you have to enable dbg. This is shown in the listing I added previously. I won't delve into describing how to use dbg as a very good introduction is already available on StackOverflow.


    an example of how to test if an attribute exists or not

    case xml:get_tag_attr_s(<<"some-attribute">>, Packet) of
        <<>> ->
            %% attribute does not exist, as get_tag_attr_s returned the default value
            ok;
        <<"some-value">> ->
            %% do something sensible with <<"some-value">>
            ok
    end
    

    Alternatively, you can use exml which is also part of MongooseIM (but not original ejabberd) and is more explicit about not finding the attribute you ask for:

    case exml_query:attr(Packet, <<"some-attribute">>) of
        undefined ->
            %% not found
            ok;
        <<"some-value">> ->
            %% do something
            ...
    end