processone / ejabberd-contrib

Growing and curated ejabberd contributions repository - PR or ask to join !
http://ejabberd.im
248 stars 137 forks source link

Ejabberd custom module compiling errors - Unknown option auth_token. No available options. #291

Closed geniussy88 closed 4 years ago

geniussy88 commented 4 years ago

EDIT: I did some stuffs and now it seems to be working in the sense that options are now recognized. But Ejabberd says "Bad module" when I uninstall it. I posted the new code here, in EDIT 1 section, https://stackoverflow.com/questions/61918443/ejabberd-custom-module-compiling-errors-unknown-option-auth-token. I'm investigating in it. I'll be right back.

EDIT 2: The module is called :-D But it crashes, which is a very interesting case. The error is posted in EDIT 2 section, https://stackoverflow.com/questions/61918443/ejabberd-custom-module-compiling-errors-unknown-option-auth-token. I'll be right back.

Original: I followed this guide https://github.com/processone/ejabberd-contrib but I got errors that prevent Ejabberd to start. The options used in ejabberd.yml are not recognized - Unknown option; there are no available options. Indentations are checked and everything seems alright over there. Ejabberd starts when I do mod_offline_http_post: {}. The module is also successfully installed without errors. Only 2 warnings are displayed and that's just some args we didn't use in the body of the related functions. I will put an underscore in front later.

Everything seems to be at the right place since folders like ebin are created. Even a new folder with the same name is created inside /.ejabberd-modules. The Beam file is present inside that created folder and inside /source/mod_offline_http_post/ebin.

Also, on a later note, I tried to put all the options inside the erl source code so that I would get rid of the conf file even though I would not continue with that route because I know there is something wrong and I'm in production mode. But, just to test, you know. Ejabberd started successfully of course but I realized the module is not used. No log is shown. Offline messages are not forwarded.

I am using Ejabberd 20.04 on AWS EC2 running CENTOS 7.

I posted that issue over here, https://stackoverflow.com/questions/61918443/ejabberd-custom-module-compiling-errors-unknown-option-auth-token.

Sorry in advance for posting code like this. That's my first post here and I don't know how to post code properly here. Please, could you shed some lights on what's going on? The module is intended to forward messages sent to an offline user to a specific server that will, in turn, send an FCM notification to the offline user.

Last thing: dev@codepond.org is the great author and the code is updated by the great info@ph-f.nl :-D I just added the required functions mod_options/1 and mod_depends/2. The guy or the woman didn't put those because the module was intended to Ejabberd 19.02. See more here: https://github.com/PH-F/mod_offline_http_post.

Thank you very much for reading :-)

Module config:modules:

mod_offline_http_post: auth_token: "secret" post_url: "http://SERVER_IP_ADDRESS/end_of_url" confidential: false

My custom module code:

%% name of module must match file name %% Update: info@ph-f.nl -module(mod_offline_http_post). -author("dev@codepond.org").

-behaviour(gen_mod).

-export([start/2, stop/1, depends/2, mod_options/1, create_message/1, create_message/3]).

%% Required by ?INFO_MSG macros -include("logger.hrl").

-include("scram.hrl"). -include("xmpp.hrl").

start(_Host, _Opt) -> ?INFO_MSG("mod_offline_http_post loading", []), inets:start(), ?INFO_MSG("HTTP client started", []), ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 1).

stop (_Host) -> ?INFO_MSG("stopping mod_offline_http_post", []), ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 1).

depends(_Host, _Opts) -> [].

mod_options(_Host) -> [].

createmessage({Action, Packet} = Acc) when (Packet#message.type == chat) and (Packet#message.body /= []) -> [{text, , Body}] = Packet#message.body, post_offline_message(Packet#message.from, Packet#message.to, Body, Packet#message.id), Acc;

create_message(Acc) -> Acc.

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.

post_offline_message(From, To, Body, MessageId) -> ?INFO_MSG("Posting From ~p To ~p Body ~p ID ~p~n",[From, To, Body, MessageId]), Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token, fun(S) -> iolist_to_binary(S) end, list_to_binary("")), PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url, fun(S) -> iolist_to_binary(S) end, list_to_binary("")), ToUser = To#jid.luser, FromUser = From#jid.luser, Vhost = To#jid.lserver, case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential, false) of true -> Data = string:join(["to=", binary_to_list(ToUser), "&from=", binary_to_list(FromUser), "&vhost=", binary_to_list(Vhost), "&messageId=", binary_to_list(MessageId)], ""); false -> Data = string:join(["to=", binary_to_list(ToUser), "&from=", binary_to_list(FromUser), "&vhost=", binary_to_list(Vhost), "&body=", binary_to_list(Body), "&messageId=", binary_to_list(MessageId)], "") end, Request = {binary_to_list(PostUrl), [{"Authorization", binary_to_list(Token)}], "application/x-www-form-urlencoded", Data}, httpc:request(post, Request,[],[]), ?INFO_MSG("post request sent", []).

badlop commented 4 years ago

I copy your file to ejabberd/src then recompile and configure like this in ejabberd.yml

modules:
  mod_offline_http_post: {}

Then I start ejabberd and the log file shows:

...
2020-05-22 18:07:21.745559+02:00 [info] mod_offline_http_post loading
2020-05-22 18:07:21.749162+02:00 [info] HTTP client started

But you should update some function calls, the last argument isn't necessary:

  post_offline_message(From, To, Body, MessageId) ->
   ?DEBUG("Posting From ~p To ~p Body ~p ID ~p~n",[From, To, Body, MessageId]),
-  Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token, fun(S) -> iolist_to_binary(S) end, list_to_binary("")),
-  PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url, fun(S) -> iolist_to_binary(S) end, list_to_binary("")),
+  Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token),
+  PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url),
   ToUser = To#jid.luser,
   FromUser = From#jid.luser,
   Vhost = To#jid.lserver,
-  case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential, false) of
+  case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential) of
geniussy88 commented 4 years ago

I copy your file to ejabberd/src then recompile and configure like this in ejabberd.yml

modules:
  mod_offline_http_post: {}

Then I start ejabberd and the log file shows:

...
2020-05-22 18:07:21.745559+02:00 [info] mod_offline_http_post loading
2020-05-22 18:07:21.749162+02:00 [info] HTTP client started

But you should update some function calls, the last argument isn't necessary:

  post_offline_message(From, To, Body, MessageId) ->
   ?DEBUG("Posting From ~p To ~p Body ~p ID ~p~n",[From, To, Body, MessageId]),
-  Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token, fun(S) -> iolist_to_binary(S) end, list_to_binary("")),
-  PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url, fun(S) -> iolist_to_binary(S) end, list_to_binary("")),
+  Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token),
+  PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url),
   ToUser = To#jid.luser,
   FromUser = From#jid.luser,
   Vhost = To#jid.lserver,
-  case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential, false) of
+  case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential) of

Thank's Badlop. I did refactor the signature of these functions as I saw in the source code of Ejabberd that the validation and the default value are not part of them anymore.

But by ejabberd/src in your setup, do you mean whatever/.ejabberd-modules/sources in mine? Or should I create that src directory to put the erl file there because I don't have none in my setup?

Did you use ejabberdctl module_install modulename to recompile or did something else?

geniussy88 commented 4 years ago

This is my setup:

The module is inside /opt/ejabberd/.ejabberd-modules/sources. sources │ │───ebin │ └───modulename.beam │───conf │ └───modulename.yml │───src │ └───modulename.erl │───README.txt │───COPYING │───modulename.spec

badlop commented 4 years ago

Well, in my case I have all the ejabberd source code, so I copy new modules to ejabberd/src/ and compile. In your case yes, whatever place the module source code is.

geniussy88 commented 4 years ago

Well, in my case I have all the ejabberd source code, so I copy new modules to ejabberd/src/ and compile. In your case yes, whatever place the module source code is.

Gotcha. Can you give me the commands you used to compile so that I do the same?

badlop commented 4 years ago

I think you don't need to mess with compiling all ejabberd just for this small problem, but anyway: https://docs.ejabberd.im/admin/installation/#install-from-source-code

geniussy88 commented 4 years ago

I think you don't need to mess with compiling all ejabberd just for this small problem, but anyway: https://docs.ejabberd.im/admin/installation/#install-from-source-code

Do you have another solution for the compiling then? I was using ejabberdctl module_install but the logs don't show anything. I installed Ejabberd by their RPM package following this link: https://www.osradar.com/install-ejabberd-on-centos-7/

That's a small module but we need it to send FCM notifications to offline users when messages are sent to them.

badlop commented 4 years ago

https://www.process-one.net/en/ejabberd/downloads/

geniussy88 commented 4 years ago

Thank's.

geniussy88 commented 4 years ago

Some progress

`2020-05-22 21:35:37.128 [debug] <0.357.0>@mod_offline_http_post:start:20 mod_offline_http_post loading

2020-05-22 21:35:37.128 [debug] <0.357.0>@mod_offline_http_post:start:22 HTTP client started`

geniussy88 commented 4 years ago

Amazing. It's now working. Notifications are sent to offline users. Well, I mean that messages to offline users are forwarded to the backend that, in turn, sends FCM notification to the users.

Since I spent 3 days on this and having little to almost no infos about current version 20.04 about this problem, I will go in details about the solution for whoever meets that problem.

First and foremost, in addition of start/2 and stop/1, export these 3 functions if your custom module supports options: depends/2 (probably a function that indicates dependencies i.e you list required modules there if I'm not wrong), mod_opt_type/1 (each option should be validated) and mod_options/2 (where you list the options and their default value).

Second, many custom modules from the internet are made for previous versions where these were not required. Then, if they use gen_mod:get_module_opt, they add the validation and the default value. You end up having gen_mod:get_module_opt/4 or gen_mod:get_module_opt/5. Now, since you already got the validation and the default value for each option in mod_options/2 and mod_opt_type/1, mod:get_module_opt becomes mod:get_module_opt/3. You should keep only the 3 first args.

If you installed Ejabberd via the RPM package like in my case, you might have the server compiled without LAGER enabled. Well, or an explaination like that and this, at least for custom modules. I mean, you see the logs for modules embedded in the package but not yours from your custom module. To enable LAGER or your infos (please, correct these statements if I didn't use the correct terms), do this right after export:

-ifndef(LAGER).
-define(LAGER, 1).
-endif.

Even though this piece of code seems to be related only to debugging processes, the custom module was not called at all when I didn't have it in the code. Interestingly strange.

I saw some people having problems with that module "mod_offline_http_post" with the error like ebin directory not found. The thing is, once you make a git clone in the .ejabberd-modules/sources directory, the module will be there but Ejabberd might lack permissions over this. Just do this after that: chown -R ejabberd:ejabberd your_pathto.ejabberd-modules/sources After that, hit ejabberdctl install mod_offline_http_post. If you see warning like ArgumentX not used, ignore this. It just means what it said. To prevent displaying this, just put an underscore in front of the arg that is not used in your erl file i.e _ArgumentX.

Some other people don't even have the .ejabberd-modules directory. Hey guys, just install a random contrib module like mod_cron and this directory will be created. Go where ejabberdctl is, mine is in /opt/ejabberd-20.04/bin and hit: ejabberdctl module_install mod_cron Once done, don't remove it but go to /opt/ejabberd/.ejabberd-modules/sources and git clone the custom module there. After that, uninstall mod_cron by doing ejabberdctl module_uninstall mod_cron.

Your directory .ejabberd-modules might be somewhere else though. After these commands, you don't need to restart Ejabberd. As a matter of fact, module_install compiles, installs and starts the module.

Well, since you need to put the module inside ejabberd.yml, you have to restart ejabberd but once done, if some things don't work and you end up uninstalling the custom module, once reinstalled, you don't need to restart Ejabberd.

I will post the original code that is working for Ejabberd 19.02 and the edited code that works with 20.04

For 19.02

%% name of module must match file name
%% Update: info@ph-f.nl
-module(mod_offline_http_post).
-author("dev@codepond.org").

-behaviour(gen_mod).

-export([start/2, stop/1, create_message/1, create_message/3]).

-include("scram.hrl").
-include("xmpp.hrl").
-include("logger.hrl").

start(_Host, _Opt) ->
  ?INFO_MSG("mod_offline_http_post loading", []),
  inets:start(),
  ?INFO_MSG("HTTP client started", []),
  ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 1).

stop (_Host) ->
  ?INFO_MSG("stopping mod_offline_http_post", []),
  ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 1).

create_message({Action, Packet} = Acc) when (Packet#message.type == chat) and (Packet#message.body /= []) ->
    [{text, _, Body}] = Packet#message.body,
    post_offline_message(Packet#message.from, Packet#message.to, Body, Packet#message.id),
  Acc;

create_message(Acc) ->
  Acc.

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.

post_offline_message(From, To, Body, MessageId) ->
  ?INFO_MSG("Posting From ~p To ~p Body ~p ID ~p~n",[From, To, Body, MessageId]),
  Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token, fun(S) -> iolist_to_binary(S) end, list_to_binary("")),
  PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url, fun(S) -> iolist_to_binary(S) end, list_to_binary("")),
  ToUser = To#jid.luser,
  FromUser = From#jid.luser,
  Vhost = To#jid.lserver,
  case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential, false) of
    true -> Data = string:join(["to=", binary_to_list(ToUser), "&from=", binary_to_list(FromUser), "&vhost=", binary_to_list(Vhost), "&messageId=", binary_to_list(MessageId)], "");
    false -> Data = string:join(["to=", binary_to_list(ToUser), "&from=", binary_to_list(FromUser), "&vhost=", binary_to_list(Vhost), "&body=", binary_to_list(Body), "&messageId=", binary_to_list(MessageId)], "")
  end,
  Request = {binary_to_list(PostUrl), [{"Authorization", binary_to_list(Token)}], "application/x-www-form-urlencoded", Data},
  httpc:request(post, Request,[],[]),
  ?INFO_MSG("post request sent", []).

For 20.04

%% name of module must match file name
%% Update: pape.diack@live.fr
-module(mod_offline_http_post).
-author("dev@codepond.org").

-behaviour(gen_mod).

-export([start/2,
        stop/1,
        depends/2,
        mod_options/1,
        mod_opt_type/1,
        create_message/1,
        create_message/3]).

-ifndef(LAGER).
-define(LAGER, 1).
-endif.

-include("logger.hrl").
-include("xmpp.hrl").

start(_Host, _Opt) ->
  ?INFO_MSG("mod_offline_http_post loading", []),
  inets:start(),
  ?INFO_MSG("HTTP client started", []),
  ejabberd_hooks:add(offline_message_hook, _Host, ?MODULE, create_message, 50).

stop (_Host) ->
  ?INFO_MSG("stopping mod_offline_http_post", []),
  ejabberd_hooks:delete(offline_message_hook, _Host, ?MODULE, create_message, 50).

depends(_Host, _Opts) ->
  [].

mod_options(_Host) ->
  [{auth_token, <<"secret">>},
  {post_url, <<"http://example.com/test">>},
  {confidential, false}].

mod_opt_type(auth_token) ->
  fun iolist_to_binary/1;
mod_opt_type(post_url) ->
  fun iolist_to_binary/1;
mod_opt_type(confidential) ->
  fun (B) when is_boolean(B) -> B end.

create_message({Action, Packet} = Acc) when (Packet#message.type == chat) and (Packet#message.body /= []) ->
    [{text, _, Body}] = Packet#message.body,
    post_offline_message(Packet#message.from, Packet#message.to, Body, Packet#message.id),
  Acc;

create_message(Acc) ->
  Acc.

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.

post_offline_message(From, To, Body, MessageId) ->
  ?INFO_MSG("Posting From ~p To ~p Body ~p ID ~p~n",[From, To, Body, MessageId]),
  Token = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, auth_token),
  PostUrl = gen_mod:get_module_opt(To#jid.lserver, ?MODULE, post_url),
  ToUser = To#jid.luser,
  FromUser = From#jid.luser,
  Vhost = To#jid.lserver,
  case gen_mod:get_module_opt(To#jid.lserver, ?MODULE, confidential) of
    true -> Data = string:join(["to=", binary_to_list(ToUser), "&from=", binary_to_list(FromUser), "&vhost=", binary_to_list(Vhost), "&messageId=", binary_to_list(MessageId), "");
    false -> Data = string:join(["to=", binary_to_list(ToUser), "&from=", binary_to_list(FromUser), "&vhost=", binary_to_list(Vhost), "&body=", binary_to_list(Body), "&messageId=", binary_to_list(MessageId)], "")
  end,
  Request = {binary_to_list(PostUrl), [{"Authorization", binary_to_list(Token)}, {"Logged-Out", "logged-out"}], "application/x-www-form-urlencoded", Data},
  httpc:request(post, Request,[],[]),
  ?INFO_MSG("post request sent", []).

Have an easier life.