Ejabberd:处理离线消息的简单模块出错

Ejabberd: error in simple module to handle offline messages

我安装了 Ejabberd 17.01,我需要在收件人离线时推送通知。这似乎是一个常见的任务,使用自定义 Ejabberd 模块的解决方案随处可见。但是,我就是不明白 运行。首先,这是我的脚本:

    -module(mod_offline_push).
    -behaviour(gen_mod).

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

    -include("ejabberd.hrl").
    -include("logger.hrl").
    -include("jlib.hrl").

    start(Host, _Opts) ->
            ?INFO_MSG("mod_offline_push loading", []),
            ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_message, 10),
            ok.

    stop(Host) ->
            ?INFO_MSG("mod_offline_push stopping", []),
            ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_message, 10),
            ok.

    push_message(From, To, Packet) ->
            ?INFO_MSG("mod_offline_push -> push_message", [To]),
            Type = fxml:get_tag_attr_s(<<"type">>, Packet), % Supposedly since 16.04
            %Type = xml:get_tag_attr_s(<<"type">>, Packet), % Supposedly since 13.XX
            %Type = xml:get_tag_attr_s("type", Packet),
            %Type = xml:get_tag_attr_s(list_to_binary("type"), Packet),
            ?INFO_MSG("mod_offline_push -> push_message", []),
            ok.

问题出在方法push_message中的第Type = ...行;如果没有该行,则会记录最后一条信息消息(因此挂钩肯定有效)。在网上浏览时,我可以找到各种从Packet中提取元素的函数调用。据我了解,随着新版本的发布,它会随着时间的推移而发生变化。但这并不好,所有变体都会导致某种错误。当前方式returns:

2017-01-25 20:38:08.701 [error] <0.21678.0>@ejabberd_hooks:run1:332 {function_clause,[{fxml,get_tag_attr_s,[<<"type">>,{message,<<>>,normal,<<>>,{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>},[],[{text,<<>>,<<"sfsdfsdf">>}],undefined,[],#{}}],[{file,"src/fxml.erl"},{line,169}]},{mod_offline_push,push_message,3,[{file,"mod_offline_push.erl"},{line,33}]},{ejabberd_hooks,safe_apply,3,[{file,"src/ejabberd_hooks.erl"},{line,382}]},{ejabberd_hooks,run1,3,[{file,"src/ejabberd_hooks.erl"},{line,329}]},{ejabberd_sm,route,3,[{file,"src/ejabberd_sm.erl"},{line,126}]},{ejabberd_local,route,3,[{file,"src/ejabberd_local.erl"},{line,110}]},{ejabberd_router,route,3,[{file,"src/ejabberd_router.erl"},{line,87}]},{ejabberd_c2s,check_privacy_route,5,[{file,"src/ejabberd_c2s.erl"},{line,1886}]}]}

running hook: {offline_message_hook,[{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>},{message,<<>>,normal,<<>>,{jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},{jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>},[],[{text,<<>>,<<"sfsdfsdf">>}],undefined,[],#{}}]}

我是 Ejabberd 和 Erlang 的新手,所以我无法真正解释错误,但是 {mod_offline_push,push_message,3,[{file,"mod_offline_push.erl"}, {line,33}]} 中提到的第 33 行绝对是调用 get_tag_attr_s.

的行

2017 年 1 月 27 日更新: 因为这让我很头疼——而且我仍然不是很开心——我 post 在这里我的当前的工作模块,希望它可以帮助其他人。我的设置是 Ubuntu 16.04 上的 Ejabberd 17.01 运行。大多数我尝试过但失败的东西似乎都适用于旧版本的 Ejabberd:

-module(mod_fcm_fork).
-behaviour(gen_mod).

%% public methods for this module
-export([start/2, stop/1]).
-export([push_notification/3]).

%% included for writing to ejabberd log file
-include("ejabberd.hrl").
-include("logger.hrl").
-include("xmpp_codec.hrl").

%% Copied this record definition from jlib.hrl
%% Including "xmpp_codec.hrl" and "jlib.hrl" resulted in errors ("XYZ already defined")
-record(jid, {user = <<"">> :: binary(),
              server = <<"">> :: binary(),
              resource = <<"">> :: binary(),
              luser = <<"">> :: binary(),
              lserver = <<"">> :: binary(),
              lresource = <<"">> :: binary()}).


start(Host, _Opts) ->
    ?INFO_MSG("mod_fcm_fork loading", []),
    % Providing the most basic API to the clients and servers that are part of the Inets application
    inets:start(),
    % Add hook to handle message to user who are offline
    ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_notification, 10),
    ok.


stop(Host) ->
    ?INFO_MSG("mod_fcm_fork stopping", []),
    ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, push_notification, 10),
    ok.


push_notification(From, To, Packet) ->
    % Generate JID of sender and receiver
    FromJid = lists:concat([binary_to_list(From#jid.user), "@", binary_to_list(From#jid.server), "/", binary_to_list(From#jid.resource)]),
    ToJid = lists:concat([binary_to_list(To#jid.user), "@", binary_to_list(To#jid.server), "/", binary_to_list(To#jid.resource)]),
    % Get message body
    MessageBody = Packet#message.body,
    % Check of MessageBody is not empty
    case MessageBody/=[] of
        true ->
            % Get first element (no idea when this list can have more elements)
            [First | _ ] = MessageBody,
            % Get message data and convert to string
            MessageBodyText = binary_to_list(First#text.data),
            send_post_request(FromJid, ToJid, MessageBodyText);
        false ->
            ?INFO_MSG("mod_fcm_fork -> push_notification: MessageBody is empty",[])
    end,    
    ok.


send_post_request(FromJid, ToJid, MessageBodyText) ->
    %?INFO_MSG("mod_fcm_fork -> send_post_request -> MessageBodyText = ~p", [Demo]),    
    Method = post,
    PostURL = gen_mod:get_module_opt(global, ?MODULE, post_url,fun(X) -> X end, all),
    % Add data as query string. Not nice, query body would be preferable
    % Problem: message body itself can be in a JSON string, and I couldn't figure out the correct encoding.
    URL = lists:concat([binary_to_list(PostURL), "?", "fromjid=", FromJid,"&tojid=", ToJid,"&body=", edoc_lib:escape_uri(MessageBodyText)]),   
    Header = [],
    ContentType = "application/json",
    Body = [],
    ?INFO_MSG("mod_fcm_fork -> send_post_request -> URL = ~p", [URL]),    
    % ADD SSL CONFIG BELOW!
    %HTTPOptions = [{ssl,[{versions, ['tlsv1.2']}]}],
    HTTPOptions = [], 
    Options = [],
    httpc:request(Method, {URL, Header, ContentType, Body}, HTTPOptions, Options),
    ok.

最新的方法似乎是 xmpp:get_type/1:

Type = xmpp:get_type(Packet),

它 returns 一个原子,在这种情况下 normal

实际上它失败了,第二个参数 Packet 你在 push_message 函数

中传递给 fxml:get_tag_attr_s
{message,<<>>,normal,<<>>,
         {jid,<<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>,
              <<"homer">>,<<"xxx.xxx.xxx.xxx">>,<<"conference">>},
         {jid,<<"carl">>,<<"xxx.xxx.xxx.xxx">>,<<>>,<<"carl">>,
              <<"xxx.xxx.xxx.xxx">>,<<>>},
         [],
         [{text,<<>>,<<"sfsdfsdf">>}],
         undefined,[],#{}}

因为它不是 xmlel

看起来是在 tools/xmpp_codec.hrl 中定义的记录 "message" 使用 <<>> ID 和类型 'normal'

xmpp_codec.hrl
    -record(message, {id :: binary(),
                      type = normal :: 'chat' | 'error' | 'groupchat' | 'headline' | 'normal',
                      lang :: binary(),
                      from :: any(),
                      to :: any(),
                      subject = [] :: [#text{}],
                      body = [] :: [#text{}],
                      thread :: binary(),
                      error :: #error{},
                      sub_els = [] :: [any()]}).

包含此文件并仅使用

Type =  Packet#message.type

或者,如果您期望二进制值

 Type =  erlang:atom_to_binary(Packet#message.type, utf8)