-
-
Save bibby/53cecfbf3f6722c2fa2509da6c60755d to your computer and use it in GitHub Desktop.
ejabberd module
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% domain names have been changed for forum posting | |
%% | |
%% Ejabberd server component that relays messages sent | |
%% to a configured subdomain to an outside service. | |
%% The reponse is then relayed back to the user on another channel. | |
%% @author bibby <bibby@redacted.tld> | |
%% | |
%% compile with ejabberd's include/ path included. | |
%% erlc -pa /usr/lib/ejabberd/ebin -I /usr/lib/ejabberd/include -o /usr/lib/ejabberd/ebin mod_chatrelay.erl | |
%% | |
%% setup: modules section in ejabberd.cfg | |
%% { modules, [ | |
%% .. other modules | |
%% {mod_chatrelay,[{host, "relay.localhost"}]} | |
%% ]} | |
%% | |
-module(mod_chatrelay). | |
-behaviour(gen_server). | |
-behaviour(gen_mod). | |
-include("ejabberd.hrl"). | |
-include("jlib.hrl"). | |
-include("logger.hrl"). | |
-include("ejabberd_http.hrl"). | |
%% gen_mod api | |
-export([ | |
start/2, | |
stop/1 | |
]). | |
%% gen_server api | |
-export([ | |
code_change/3, | |
handle_call/3, | |
handle_cast/2, | |
handle_info/2, | |
init/1, | |
start_link/2, | |
terminate/2] | |
). | |
-export([ | |
route/3, | |
respond/3, | |
fetchResponse/3, | |
strip_bom/1 | |
]). | |
% captured messages are sent to this end point | |
-define( SERVICE_URL, "http://game.redacted.tld:3000/"). | |
-define( SERVICE_METHOD, "rpc"). | |
% registers as a virtual host | |
% messages to this channel are captured by this module | |
-define( ACCEPT_CHANNEL, "relay.redacted.tld" ). | |
% server responses are sent to this channel: ( game-{id}@muc.localhost ) | |
-define( RETURN_CHANNEL, "global"). | |
-define( RETURN_DOMAIN, "muc."). | |
%% gen_server API | |
start_link(Host, Opts) -> | |
gen_server:start_link( | |
{local, ?MODULE}, ?MODULE, [Host, Opts], []). | |
%% gen_mod API | |
start(Host, Opts) -> | |
inets:start(), | |
Proc = gen_mod:get_module_proc(Host, ?MODULE), | |
ChildSpec = {Proc, | |
{?MODULE, start_link, [Host, Opts]}, | |
transient, | |
1000, | |
worker, | |
[?MODULE]}, | |
supervisor:start_child(ejabberd_sup, ChildSpec). | |
stop(Host) -> | |
Proc = gen_mod:get_module_proc(Host, ?MODULE), | |
gen_server:call(Proc, stop), | |
supervisor:terminate_child(ejabberd_sup, Proc), | |
supervisor:delete_child(ejabberd_sup, Proc). | |
init([Host, Opts]) -> | |
% add a new virtual host / subdomain "echo".example.com | |
VirtHost = gen_mod:get_opt_host(Host, Opts, ?ACCEPT_CHANNEL ), | |
ejabberd_router:register_route(VirtHost,{apply, ?MODULE, route}), | |
{ok, Host}. | |
handle_call(stop, _From, Host) -> | |
{stop, normal, ok, Host}. | |
handle_cast(_Msg, Host) -> {noreply, Host}. | |
handle_info(_Msg, Host) -> {noreply, Host}. | |
terminate(_Reason, Host) -> | |
ejabberd_router:unregister_route(Host), ok. | |
code_change(_OldVsn, Host, _Extra) -> {ok, Host}. | |
%% Helper | |
strip_bom([239, 187, 191 | C]) -> | |
C; | |
strip_bom(C) -> | |
C. | |
%% Custom Message Routing | |
route(From, To, {xmlelement, "message", _, _} = Packet) -> | |
?INFO_MSG("Custom routing",[]), | |
case | |
xml:get_subtag_cdata(Packet, "body") | |
of | |
"" -> | |
?INFO_MSG("Empty body",[]), | |
ok; | |
Body -> | |
case | |
xml:get_tag_attr_s("type", Packet) | |
of | |
"error" -> | |
?INFO_MSG("body with type=error",[]), | |
?ERROR_MSG( | |
"Received error message~n~p -> ~p~n~p", | |
[From, To, Packet] | |
); | |
_ -> | |
?INFO_MSG("making API call",[]), | |
{ok, Response} = fetchResponse(From, To, strip_bom(Body)), | |
?INFO_MSG("got '~p'",[Response]), | |
respond( From, To, Response) | |
end | |
end, | |
ok. | |
fetchResponse(From, To, BodyStr) -> | |
{jid,Room,_,_,_,_,_} = To, | |
{ok, {{_Version, 200, _ReasonPhrase}, _Headers, Body}} = | |
httpc:request( | |
post, | |
{ | |
string:concat( ?SERVICE_URL, ?SERVICE_METHOD ), | |
[], | |
"application/x-www-form-urlencoded", | |
urlenc:encode([ | |
{"from", jlib:jid_to_string(From) }, | |
{"to", jlib:jid_to_string(To) }, | |
{"body", BodyStr } | |
]) | |
}, | |
[], | |
[] | |
), | |
?INFO_MSG("response to ~p: ~p~n", [Room,Body]), | |
{ok, Body}. | |
getId( Jid ) -> | |
PosA = string:str( Jid, "/" ), | |
PosB = string:str( Jid, "@" ), | |
substr( Jid, PosA, PosB - PosA ). | |
substr( _Jid, 0, _PosB) -> | |
""; | |
substr( Str, PosA, PosB) -> | |
string:substr( Str, PosA, PosB). | |
getHost( Jid ) -> | |
PosA = string:str( Jid, "@" ), | |
PosB = string:str( Jid, "/" ), | |
substr( Jid, PosA + 1, PosB ). | |
chatRoom( _Room, "") -> | |
?RETURN_CHANNEL; | |
chatRoom( Room, ChatRoom ) -> | |
string:concat( Room, getId(ChatRoom) ). | |
respondPublically( _From , _To, []) -> | |
empty; | |
respondPublically( From , To, Body) -> | |
{jid,Room,_,_,_,_,_} = To, | |
Json = ejson:encode(Body), | |
ChatRoom = jlib:jid_to_string( To ), | |
ToJid = jlib:make_jid( | |
chatRoom( Room, getId(ChatRoom) ), | |
string:concat(?RETURN_DOMAIN, getHost(ChatRoom)), | |
"" | |
), | |
XmlBody = { | |
xmlelement, | |
"message", | |
[ | |
{"type", "groupchat"}, | |
{"xmlns", "jabber:client"} | |
], | |
[ | |
{ xmlelement, | |
"body", | |
[], | |
[ | |
{xmlcdata, Json} | |
] | |
} | |
] | |
}, | |
ejabberd_router:route( From, ToJid, XmlBody). | |
respondPrivately( _FromPlayer, _ToServer, []) -> | |
empty; | |
respondPrivately( FromPlayer, _ToServer, Body) -> | |
ToPlayer = FromPlayer, | |
Json = ejson:encode(Body), | |
FromServer = jlib:make_jid( | |
?RETURN_CHANNEL, | |
string:concat(?RETURN_DOMAIN,getHost(FromPlayer)), | |
"" | |
), | |
XmlBody = { | |
xmlelement, | |
"message", | |
[ | |
{"type", "chat"}, | |
{"xmlns", "jabber:client"} | |
], | |
[ | |
{ xmlelement, | |
"body", | |
[], | |
[ | |
{xmlcdata, Json} | |
] | |
} | |
] | |
}, | |
ejabberd_router:route( FromServer, ToPlayer, XmlBody). | |
respond( From, To, Body) -> | |
{jid,_,_,_,_,_,_} = To, | |
{[{<<"public">>,Pub},{<<"private">>,Priv}]} = ejson:decode(Body), | |
respondPrivately( From, To, Priv ), | |
respondPublically( From, To, Pub ). |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment