I am trying to create a custom module for my Ejabberd server. I am using relatively newer version so ejabberd.hrl is not available. The module is supposed to do two things:
The module kind of works but not really. I found that the acknowledgment message is generated only if B is offline but I need it to be generated no matter what. The first part works just fine. I think the issue is with the message hook. Here is the code:
-module(mod_http_offline).
-author("Allen Turing").
-behaviour(gen_mod).
-export([start/2, stop/1, create_message/1, depends/2, mod_doc/0, mod_options/1]).
%% Record definitions
-record(jid, {user = <<"">>, server = <<"">>, resource = <<"">>,
luser = <<"">>, lserver = <<"">>, lresource = <<>>}).
-record(xmlel, {name, attrs = [], children = []}).
-record(xmlcdata, {content}).
%% Start function
start(Host, _Opts) ->
inets:start(),
ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, create_message, 50),
ok.
%% Stop function
stop(Host) ->
ejabberd_hooks:delete(offline_message_hook, Host, ?MODULE, create_message, 50),
ok.
%% Create message function
create_message({archived, {message, StanzaId, Type, _, FromJID, ToJID, _, _, _, _, _}}) ->
handle_message(Type, FromJID, ToJID, StanzaId);
create_message({_, {message, StanzaId, Type, _, FromJID, ToJID, _, _, _, _, _}}) ->
handle_message(Type, FromJID, ToJID, StanzaId);
create_message(_Msg) ->
ok.
%% Handle message function
handle_message(Type, FromJID, ToJID, StanzaId) ->
case Type of
chat ->
ToLUser = ToJID#jid.luser,
ToLServer = ToJID#jid.lserver,
%% send HTTP notification
post_offline_message(ToLUser, ToLServer),
%% send acknowledgment
send_acknowledgment(FromJID, ToJID, StanzaId),
ok;
_ ->
ok
end.
%% Function to send acknowledgment
send_acknowledgment(FromJID, ToJID, StanzaId) ->
%% Build the 'arrived' extension element
ArrivedExtension = #xmlel{
name = <<"arrived">>,
attrs = [{<<"xmlns">>, <<"urn:xmpp:ack">>}],
children = []
},
%% Build the message packet
AckPacket = #xmlel{
name = <<"message">>,
attrs = [{<<"to">>, jid_to_binary(FromJID)},
{<<"from">>, jid_to_binary(ToJID)},
{<<"type">>, <<"chat">>}],
children = [
%% Include the stanza ID in the message body
#xmlel{
name = <<"body">>,
attrs = [],
children = [#xmlcdata{content = StanzaId}]
},
%% Add the 'arrived' extension
ArrivedExtension
]
},
%% Send acknowledgment
ejabberd_router:route(ToJID, FromJID, AckPacket),
ok.
%% Helper function to convert JID to binary
jid_to_binary(#jid{user = User, server = Server, resource = Resource}) ->
case Resource of
<<>> -> <<User/binary, "@", Server/binary>>;
_ -> <<User/binary, "@", Server/binary, "/", Resource/binary>>
end.
%% function to post offline message via HTTP
post_offline_message(ToLUser, ToLServer) ->
JID = <<ToLUser/binary, "@", ToLServer/binary>>,
Data = lists:flatten(io_lib:format("jid=~s", [JID])),
Headers = [
{"API-Key", generate_api_key()}
],
case httpc:request(post, {"https://www.myappserver.com/notify", Headers, "application/x-www-form-urlencoded", Data}, [], []) of
{ok, {{_Version, StatusCode, _ReasonPhrase}, _Headers, _Body}} ->
ok;
{error, Reason} ->
ok
end.
%% API key generation
generate_api_key() ->
%% Return a fixed, placeholder API key
<<"DUMMY_API_KEY_1234567890">>.
%% Optional callbacks for gen_mod behaviour
depends(_Host, _Opts) -> [].
mod_doc() ->
"mod_http_offline: This module sends HTTP requests for offline messages and sends acknowledgments.".
mod_options(_Host) -> [].
I was thinking about maybe breaking the task into two separate modules one that checks if B is offline and then communicates with the appserver and another to send an acknowledgment message back to A but I would rather keep it all in one module.
offline_message_hook, as the name suggests, is only called when a message is received by an offline user. You need a different hook to handle all incoming messages. Candidates are:
You can find examples of the hooks in the ejabberd repository (https://github.com/processone/ejabberd) or the modules contrib repository (https://github.com/processone/ejabberd-contrib)
A bit unrelated to the question, but I also suggest that you don't work directly with xmlel records but rather use the xmpp records, such as #message{}. This also applies to the arguments of your hook handler. If you need to specify custom XML elements outside the XMPP protocol, you can still include them in the sub_els property of #message{}