From a28f7232431e9e92ccbd824f933a54556ee4d198 Mon Sep 17 00:00:00 2001 From: Alexey Shchepin Date: Sun, 16 Feb 2003 20:07:21 +0000 Subject: [PATCH] * src/mod_irc/: New IRC transport (not completed yet) SVN Revision: 74 --- ChangeLog | 4 + src/ejabberd.cfg | 1 + src/ejabberd_router.erl | 2 +- src/mod_irc/Makefile | 44 +++ src/mod_irc/iconv.erl | 75 +++++ src/mod_irc/iconv_erl.c | 115 ++++++++ src/mod_irc/mod_irc.erl | 93 ++++++ src/mod_irc/mod_irc_connection.erl | 443 +++++++++++++++++++++++++++++ src/namespaces.hrl | 2 + 9 files changed, 778 insertions(+), 1 deletion(-) create mode 100644 src/mod_irc/Makefile create mode 100644 src/mod_irc/iconv.erl create mode 100644 src/mod_irc/iconv_erl.c create mode 100644 src/mod_irc/mod_irc.erl create mode 100644 src/mod_irc/mod_irc_connection.erl diff --git a/ChangeLog b/ChangeLog index cd995573c..cb82a538a 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2003-02-16 Alexey Shchepin + + * src/mod_irc/: New IRC transport (not completed yet) + 2003-02-14 Alexey Shchepin * src/ejabberd_service.erl: Answer "Bad Request" on unknown tags diff --git a/src/ejabberd.cfg b/src/ejabberd.cfg index c119b6d3a..f910398f2 100644 --- a/src/ejabberd.cfg +++ b/src/ejabberd.cfg @@ -58,6 +58,7 @@ {mod_offline, []}, {mod_echo, [{host, "echo.e.localhost"}]}, {mod_private, []}, + {mod_irc, []}, {mod_time, [{iqdisc, no_queue}]}, {mod_version, []} ]}. diff --git a/src/ejabberd_router.erl b/src/ejabberd_router.erl index 9de7e67f4..956a497cc 100644 --- a/src/ejabberd_router.erl +++ b/src/ejabberd_router.erl @@ -112,7 +112,7 @@ do_route(From, To, Packet) -> Node = R#route.node, case node() of Node -> - Pid = R#local_route.pid, + Pid = R#route.pid, ?DEBUG("routed to process ~p~n", [Pid]), Pid ! {route, From, To, Packet}; _ -> diff --git a/src/mod_irc/Makefile b/src/mod_irc/Makefile new file mode 100644 index 000000000..e5bf89189 --- /dev/null +++ b/src/mod_irc/Makefile @@ -0,0 +1,44 @@ +# $Id$ + +include ../Makefile.inc + +INCLUDES = -I/usr/lib/erlang/usr/include \ + -I$(EI_DIR)/include \ + -I/usr/local/include + +LIBDIRS = -L$(EI_DIR)/lib -L/usr/local/lib + +ERLSHLIBS = ../iconv_erl.so + + + +OUTDIR = .. +EFLAGS = -I .. -pz .. +OBJS = \ + $(OUTDIR)/mod_irc.beam \ + $(OUTDIR)/mod_irc_connection.beam \ + $(OUTDIR)/iconv.beam + +all: $(OBJS) $(ERLSHLIBS) + +$(OUTDIR)/%.beam: %.erl + erlc -W $(EFLAGS) -o $(OUTDIR) $< + + + +#all: $(ERLSHLIBS) +# erl -s make all report "{outdir, \"..\"}" -noinput -s erlang halt + +$(ERLSHLIBS): ../%.so: %.c + gcc -Wall $(INCLUDES) $(LIBDIRS) \ + $(subst ../,,$(subst .so,.c,$@)) \ + -lerl_interface \ + -lei \ + -o $@ -fpic -shared \ + +clean: + rm -f *.beam + +TAGS: + etags *.erl + diff --git a/src/mod_irc/iconv.erl b/src/mod_irc/iconv.erl new file mode 100644 index 000000000..ea42f0e41 --- /dev/null +++ b/src/mod_irc/iconv.erl @@ -0,0 +1,75 @@ +%%%---------------------------------------------------------------------- +%%% File : iconv.erl +%%% Author : Alexey Shchepin +%%% Purpose : Interface to libiconv +%%% Created : 16 Feb 2003 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(iconv). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +-behaviour(gen_server). + +-export([start/0, start_link/0, convert/3]). + +%% Internal exports, call-back functions. +-export([init/1, + handle_call/3, + handle_cast/2, + handle_info/2, + code_change/3, + terminate/2]). + + + +start() -> + gen_server:start({local, ?MODULE}, ?MODULE, [], []). + +start_link() -> + gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). + +init([]) -> + ok = erl_ddll:load_driver(".", iconv_erl), + Port = open_port({spawn, iconv_erl}, []), + ets:new(iconv_table, [set, public, named_table]), + ets:insert(iconv_table, {port, Port}), + {ok, Port}. + + +%%% -------------------------------------------------------- +%%% The call-back functions. +%%% -------------------------------------------------------- + +handle_call(_, _, State) -> + {noreply, State}. + +handle_cast(_, State) -> + {noreply, State}. + +handle_info({'EXIT', Pid, Reason}, Port) -> + {noreply, Port}; + +handle_info({'EXIT', Port, Reason}, Port) -> + {stop, {port_died, Reason}, Port}; +handle_info(_, State) -> + {noreply, State}. + +code_change(OldVsn, State, Extra) -> + {ok, State}. + +terminate(_Reason, Port) -> + Port ! {self, close}, + ok. + + + +convert(From, To, String) -> + [{port, Port} | _] = ets:lookup(iconv_table, port), + Bin = term_to_binary({From, To, String}), + BRes = port_control(Port, 1, Bin), + binary_to_list(BRes). + + + diff --git a/src/mod_irc/iconv_erl.c b/src/mod_irc/iconv_erl.c new file mode 100644 index 000000000..4cca2ece8 --- /dev/null +++ b/src/mod_irc/iconv_erl.c @@ -0,0 +1,115 @@ +/* $Id$ */ + +#include +#include +#include +#include + +typedef struct { + ErlDrvPort port; + iconv_t cd; +} iconv_data; + + +static ErlDrvData iconv_erl_start(ErlDrvPort port, char *buff) +{ + iconv_data* d = (iconv_data*)driver_alloc(sizeof(iconv_data)); + d->port = port; + d->cd = NULL; + + set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); + + return (ErlDrvData)d; +} + +static void iconv_erl_stop(ErlDrvData handle) +{ + driver_free((char*)handle); +} + +static int iconv_erl_control(ErlDrvData drv_data, + unsigned int command, + char *buf, int len, + char **rbuf, int rlen) +{ + int i; + int size; + int index = 0; + int avail; + int inleft, outleft; + ErlDrvBinary *b; + char *from, *to, *string, *stmp, *rstring, *rtmp; + iconv_t cd; + + ei_decode_version(buf, &index, &i); + ei_decode_tuple_header(buf, &index, &i); + ei_get_type(buf, &index, &i, &size); + from = malloc(size + 1); + ei_decode_string(buf, &index, from); + + ei_get_type(buf, &index, &i, &size); + to = malloc(size + 1); + ei_decode_string(buf, &index, to); + + ei_get_type(buf, &index, &i, &size); + stmp = string = malloc(size + 1); + ei_decode_string(buf, &index, string); + + cd = iconv_open(to, from); + // TODO: check result + /* + if(cd == (iconv_t) -1) + { + perror ("iconv_open"); + } + else + { + printf("iconv_open from=%s, to=%s OK\r\n", from, to); + printf("string=%s size=%d\r\n", string, size); + } + */ + + outleft = avail = 4*size; + inleft = size; + rtmp = rstring = malloc(avail); + iconv(cd, &stmp, &inleft, &rtmp, &outleft); + + size = rtmp - rstring; + + //printf("size=%d, res=%s\r\n", size, rstring); + + *rbuf = (char*)(b = driver_alloc_binary(size)); + memcpy(b->orig_bytes, rstring, size); + + free(from); + free(to); + free(string); + free(rstring); + iconv_close(cd); + + return size; +} + + + +ErlDrvEntry iconv_driver_entry = { + NULL, /* F_PTR init, N/A */ + iconv_erl_start, /* L_PTR start, called when port is opened */ + iconv_erl_stop, /* F_PTR stop, called when port is closed */ + NULL, /* F_PTR output, called when erlang has sent */ + NULL, /* F_PTR ready_input, called when input descriptor ready */ + NULL, /* F_PTR ready_output, called when output descriptor ready */ + "iconv_erl", /* char *driver_name, the argument to open_port */ + NULL, /* F_PTR finish, called when unloaded */ + NULL, /* handle */ + iconv_erl_control, /* F_PTR control, port_command callback */ + NULL, /* F_PTR timeout, reserved */ + NULL /* F_PTR outputv, reserved */ +}; + +DRIVER_INIT(iconv_erl) /* must match name in driver_entry */ +{ + return &iconv_driver_entry; +} + + diff --git a/src/mod_irc/mod_irc.erl b/src/mod_irc/mod_irc.erl new file mode 100644 index 000000000..12a540ab2 --- /dev/null +++ b/src/mod_irc/mod_irc.erl @@ -0,0 +1,93 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_irc.erl +%%% Author : Alexey Shchepin +%%% Purpose : IRC transport +%%% Created : 15 Feb 2003 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(mod_irc). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +-behaviour(gen_mod). + +-export([start/1, init/1, stop/0]). + +-include("ejabberd.hrl"). + +-record(irc_connection, {userserver, pid}). + +start(Opts) -> + iconv:start(), + Host = gen_mod:get_opt(host, Opts, "irc." ++ ?MYNAME), + register(ejabberd_mod_irc, spawn(?MODULE, init, [Host])). + +init(Host) -> + catch ets:new(irc_connection, [named_table, + public, + {keypos, #irc_connection.userserver}]), + ejabberd_router:register_route(Host), + loop(Host). + +loop(Host) -> + receive + {route, From, To, Packet} -> + case catch do_route(Host, From, To, Packet) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p", [Reason]); + _ -> + ok + end, + loop(Host); + stop -> + ejabberd_router:unregister_global_route(Host), + ok; + _ -> + loop(Host) + end. + + +do_route(Host, From, To, Packet) -> + {ChanServ, _, Resource} = To, + case ChanServ of + "" -> + % TODO + Err = jlib:make_error_reply(Packet, "406", "Not Acceptable"), + ejabberd_router:route(To, From, Err); + _ -> + case string:tokens(ChanServ, "%") of + [[_ | _] = Channel, [_ | _] = Server] -> + case ets:lookup(irc_connection, {From, Server}) of + [] -> + io:format("open new connection~n"), + {ok, Pid} = mod_irc_connection:start( + From, Host, Server), + ets:insert( + irc_connection, + #irc_connection{userserver = {From, Server}, + pid = Pid}), + mod_irc_connection:route( + Pid, Channel, Resource, Packet), + ok; + [R] -> + Pid = R#irc_connection.pid, + io:format("send to process ~p~n", + [Pid]), + mod_irc_connection:route( + Pid, Channel, Resource, Packet), + ok + end; + _ -> + Err = jlib:make_error_reply( + Packet, "406", "Not Acceptable"), + ejabberd_router:route(To, From, Err) + end + end. + + + + +stop() -> + ejabberd_mod_irc ! stop, + ok. diff --git a/src/mod_irc/mod_irc_connection.erl b/src/mod_irc/mod_irc_connection.erl new file mode 100644 index 000000000..268572df2 --- /dev/null +++ b/src/mod_irc/mod_irc_connection.erl @@ -0,0 +1,443 @@ +%%%---------------------------------------------------------------------- +%%% File : mod_irc_connection.erl +%%% Author : Alexey Shchepin +%%% Purpose : +%%% Created : 15 Feb 2003 by Alexey Shchepin +%%% Id : $Id$ +%%%---------------------------------------------------------------------- + +-module(mod_irc_connection). +-author('alexey@sevcom.net'). +-vsn('$Revision$ '). + +-behaviour(gen_fsm). + +%% External exports +-export([start/3, receiver/2, route/4]). + +%% gen_fsm callbacks +-export([init/1, + open_socket/2, + %wait_for_registration/2, + stream_established/2, + handle_event/3, + handle_sync_event/4, + handle_info/3, + terminate/3, + code_change/4]). + +-include("ejabberd.hrl"). +-include("namespaces.hrl"). + +-record(state, {socket, receiver, queue, + user, myname, server, nick, + inbuf = "", outbuf = ""}). + +-define(IRC_ENCODING, "koi8-r"). + +-define(DBGFSM, true). + +-ifdef(DBGFSM). +-define(FSMOPTS, [{debug, [trace]}]). +-else. +-define(FSMOPTS, []). +-endif. + +%%%---------------------------------------------------------------------- +%%% API +%%%---------------------------------------------------------------------- +start(From, Host, Server) -> + gen_fsm:start(?MODULE, [From, Host, Server], ?FSMOPTS). + +%%%---------------------------------------------------------------------- +%%% Callback functions from gen_fsm +%%%---------------------------------------------------------------------- + +%%---------------------------------------------------------------------- +%% Func: init/1 +%% Returns: {ok, StateName, StateData} | +%% {ok, StateName, StateData, Timeout} | +%% ignore | +%% {stop, StopReason} +%%---------------------------------------------------------------------- +init([From, Host, Server]) -> + gen_fsm:send_event(self(), init), + {Nick, _, _} = From, + {ok, open_socket, #state{queue = queue:new(), + user = From, + nick = Nick, + myname = Host, + server = Server}}. + +%%---------------------------------------------------------------------- +%% Func: StateName/2 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} +%%---------------------------------------------------------------------- +open_socket(init, StateData) -> + Addr = StateData#state.server, + Port = 6667, + ?DEBUG("connecting to ~s:~p~n", [Addr, Port]), + case gen_tcp:connect(Addr, Port, [binary, {packet, 0}]) of + {ok, Socket} -> + % TODO: send nick, etc... + %send_text(Socket, io_lib:format(?STREAM_HEADER, + % [StateData#state.server])), + %send_queue(StateData#state.socket, StateData#state.queue), + send_text(Socket, io_lib:format("NICK ~s\r\n", + [StateData#state.nick])), + send_text(Socket, + io_lib:format( + "USER ~s ~s ~s :~s\r\n", + [StateData#state.nick, + StateData#state.nick, + StateData#state.myname, + StateData#state.nick])), + {next_state, wait_for_registration, + StateData#state{socket = Socket}}; + {error, Reason} -> + ?DEBUG("connect return ~p~n", [Reason]), + Text = case Reason of + timeout -> "Server Connect Timeout"; + _ -> "Server Connect Failed" + end, + bounce_messages(Text), + {stop, normal, StateData} + end. + + +stream_established({xmlstreamend, Name}, StateData) -> + {stop, normal, StateData}; + +stream_established(timeout, StateData) -> + {stop, normal, StateData}; + +stream_established(closed, StateData) -> + {stop, normal, StateData}. + + + +%%---------------------------------------------------------------------- +%% Func: StateName/3 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {reply, Reply, NextStateName, NextStateData} | +%% {reply, Reply, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} | +%% {stop, Reason, Reply, NewStateData} +%%---------------------------------------------------------------------- +%state_name(Event, From, StateData) -> +% Reply = ok, +% {reply, Reply, state_name, StateData}. + +%%---------------------------------------------------------------------- +%% Func: handle_event/3 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} +%%---------------------------------------------------------------------- +handle_event(Event, StateName, StateData) -> + {next_state, StateName, StateData}. + +%%---------------------------------------------------------------------- +%% Func: handle_sync_event/4 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {reply, Reply, NextStateName, NextStateData} | +%% {reply, Reply, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} | +%% {stop, Reason, Reply, NewStateData} +%%---------------------------------------------------------------------- +handle_sync_event(Event, From, StateName, StateData) -> + Reply = ok, + {reply, Reply, StateName, StateData}. + +code_change(OldVsn, StateName, StateData, Extra) -> + {ok, StateName, StateData}. + +-define(SEND(S), + if + StateName == stream_established -> + send_text(StateData#state.socket, S), + StateData; + true -> + StateData#state{outbuf = StateData#state.outbuf ++ S} + end). + +%%---------------------------------------------------------------------- +%% Func: handle_info/3 +%% Returns: {next_state, NextStateName, NextStateData} | +%% {next_state, NextStateName, NextStateData, Timeout} | +%% {stop, Reason, NewStateData} +%%---------------------------------------------------------------------- +handle_info({route, Channel, Resource, {xmlelement, "presence", Attrs, Els}}, + StateName, StateData) -> + NewStateData = + case xml:get_attr_s("type", Attrs) of + "unavailable" -> + ?SEND(io_lib:format("PART #~s\r\n", [Channel])); + "subscribe" -> StateData; + "subscribed" -> StateData; + "unsubscribe" -> StateData; + "unsubscribed" -> StateData; + _ -> + ?SEND(io_lib:format("JOIN #~s\r\n", [Channel])) + end, + {next_state, StateName, NewStateData}; + +handle_info({route, Channel, Resource, + {xmlelement, "message", Attrs, Els} = El}, + StateName, StateData) -> + NewStateData = + case xml:get_attr_s("type", Attrs) of + "groupchat" -> + ejabberd_router:route( + {lists:concat([Channel, "%", StateData#state.server]), + StateData#state.myname, StateData#state.nick}, + StateData#state.user, El), + % TODO: remove newlines from body + Body = xml:get_path_s(El, [{elem, "body"}, cdata]), + ?SEND(io_lib:format("PRIVMSG #~s :~s\r\n", [Channel, Body])); + _ -> StateData + end, + {next_state, StateName, NewStateData}; + +handle_info({route, Channel, Resource, Packet}, StateName, StateData) -> + {next_state, StateName, StateData}; + + +handle_info({ircstring, [$P, $I, $N, $G, $ | ID]}, StateName, StateData) -> + send_text(StateData#state.socket, "PONG " ++ ID ++ "\r\n"), + {next_state, StateName, StateData}; + +handle_info({ircstring, [$: | String]}, StateName, StateData) -> + Words = string:tokens(String, " "), + case Words of + [_, "353" | Items] -> + process_channel_list(StateData, Items); + [From, "PRIVMSG", [$# | Chan] | _] -> + process_privmsg(StateData, Chan, From, String); + [From, "PART", [$# | Chan] | _] -> + process_part(StateData, Chan, From, String); + [From, "JOIN", Chan | _] -> + process_join(StateData, Chan, From, String); + [From, "MODE", [$# | Chan], "+o", Nick | _] -> + process_mode_o(StateData, Chan, From, Nick, + "admin", "moderator"); + [From, "MODE", [$# | Chan], "-o", Nick | _] -> + process_mode_o(StateData, Chan, From, Nick, + "member", "participant"); + _ -> + io:format("unknown irc command '~s'~n", [String]) + end, + NewStateData = + case StateData#state.outbuf of + "" -> + StateData; + Data -> + send_text(StateData#state.socket, Data), + StateData#state{outbuf = ""} + end, + {next_state, stream_established, NewStateData}; + + +handle_info({ircstring, String}, StateName, StateData) -> + io:format("unknown irc command '~s'~n", [String]), + {next_state, StateName, StateData}; + + +handle_info({send_text, Text}, StateName, StateData) -> + send_text(StateData#state.socket, Text), + {next_state, StateName, StateData}; +handle_info({send_element, El}, StateName, StateData) -> + case StateName of + stream_established -> + send_element(StateData#state.socket, El), + {next_state, StateName, StateData}; + _ -> + Q = queue:in(El, StateData#state.queue), + {next_state, StateName, StateData#state{queue = Q}} + end; +handle_info({tcp, Socket, Data}, StateName, StateData) -> + Buf = StateData#state.inbuf ++ binary_to_list(Data), + {ok, Strings} = regexp:split(Buf, "\r\n"), + io:format("strings=~p~n", [Strings]), + NewBuf = process_lines(Strings), + {next_state, StateName, StateData#state{inbuf = NewBuf}}; +handle_info({tcp_closed, Socket}, StateName, StateData) -> + gen_fsm:send_event(self(), closed), + {next_state, StateName, StateData}; +handle_info({tcp_error, Socket, Reason}, StateName, StateData) -> + gen_fsm:send_event(self(), closed), + {next_state, StateName, StateData}. + +%%---------------------------------------------------------------------- +%% Func: terminate/3 +%% Purpose: Shutdown the fsm +%% Returns: any +%%---------------------------------------------------------------------- +terminate(Reason, StateName, StateData) -> + ejabberd_mod_irc ! {closed_conection, {StateData#state.user, + StateData#state.server}}, + case StateData#state.socket of + undefined -> + ok; + Socket -> + gen_tcp:close(Socket) + end, + ok. + +%%%---------------------------------------------------------------------- +%%% Internal functions +%%%---------------------------------------------------------------------- + +receiver(Socket, C2SPid) -> + XMLStreamPid = xml_stream:start(C2SPid), + receiver(Socket, C2SPid, XMLStreamPid). + +receiver(Socket, C2SPid, XMLStreamPid) -> + case gen_tcp:recv(Socket, 0) of + {ok, Text} -> + xml_stream:send_text(XMLStreamPid, Text), + receiver(Socket, C2SPid, XMLStreamPid); + {error, Reason} -> + exit(XMLStreamPid, closed), + gen_fsm:send_event(C2SPid, closed), + ok + end. + +send_text(Socket, Text) -> + CText = iconv:convert("utf-8", ?IRC_ENCODING, lists:flatten(Text)), + %io:format("IRC OUTu: ~s~nIRC OUTk: ~s~n", [Text, CText]), + gen_tcp:send(Socket, CText). + +send_element(Socket, El) -> + send_text(Socket, xml:element_to_string(El)). + +send_queue(Socket, Q) -> + case queue:out(Q) of + {{value, El}, Q1} -> + send_element(Socket, El), + send_queue(Socket, Q1); + {empty, Q1} -> + ok + end. + +bounce_messages(Reason) -> + receive + {send_element, El} -> + {xmlelement, Name, Attrs, SubTags} = El, + case xml:get_attr_s("type", Attrs) of + "error" -> + ok; + _ -> + Err = jlib:make_error_reply(El, + "502", Reason), + From = jlib:string_to_jid(xml:get_attr_s("from", Attrs)), + To = jlib:string_to_jid(xml:get_attr_s("to", Attrs)), + ejabberd_router ! {route, To, From, Err} + end, + bounce_messages(Reason) + after 0 -> + ok + end. + + +route(Pid, Channel, Resource, Packet) -> + Pid ! {route, Channel, Resource, Packet}. + + +process_lines([S]) -> + S; +process_lines([S | Ss]) -> + self() ! {ircstring, iconv:convert(?IRC_ENCODING, "utf-8", S)}, + process_lines(Ss). + +process_channel_list(StateData, Items) -> + process_channel_list_find_chan(StateData, Items). + +process_channel_list_find_chan(StateData, []) -> + ok; +process_channel_list_find_chan(StateData, [[$# | Chan] | Items]) -> + process_channel_list_users(StateData, Chan, Items); +process_channel_list_find_chan(StateData, [_ | Items]) -> + process_channel_list_find_chan(StateData, Items). + +process_channel_list_users(StateData, Chan, []) -> + ok; +process_channel_list_users(StateData, Chan, [User | Items]) -> + process_channel_list_user(StateData, Chan, User), + process_channel_list_users(StateData, Chan, Items). + +process_channel_list_user(StateData, Chan, User) -> + User1 = case User of + [$: | U1] -> U1; + _ -> User + end, + {User2, Affiliation, Role} = + case User1 of + [$@ | U2] -> {U2, "admin", "moderator"}; + _ -> {User1, "member", "participant"} + end, + ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]), + StateData#state.myname, User2}, + StateData#state.user, + {xmlelement, "presence", [], + [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], + [{xmlelement, "item", + [{"affiliation", Affiliation}, + {"role", Role}], + []}]}]}). + + +process_privmsg(StateData, Chan, From, String) -> + [FromUser | _] = string:tokens(From, "!"), + Msg = lists:last(string:tokens(String, ":")), + ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]), + StateData#state.myname, FromUser}, + StateData#state.user, + {xmlelement, "message", [{"type", "groupchat"}], + [{xmlelement, "body", [], [{xmlcdata, Msg}]}]}). + +process_part(StateData, Chan, From, String) -> + [FromUser | _] = string:tokens(From, "!"), + %Msg = lists:last(string:tokens(String, ":")), + ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]), + StateData#state.myname, FromUser}, + StateData#state.user, + {xmlelement, "presence", [{"type", "unavailable"}], + [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], + [{xmlelement, "item", + [{"affiliation", "member"}, + {"role", "none"}], + []}]}]}). + + +process_join(StateData, Channel, From, String) -> + [FromUser | _] = string:tokens(From, "!"), + Chan = lists:subtract(Channel, ":#"), + ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]), + StateData#state.myname, FromUser}, + StateData#state.user, + {xmlelement, "presence", [], + [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], + [{xmlelement, "item", + [{"affiliation", "member"}, + {"role", "participant"}], + []}]}]}). + + +process_mode_o(StateData, Chan, From, Nick, Affiliation, Role) -> + %Msg = lists:last(string:tokens(String, ":")), + ejabberd_router:route({lists:concat([Chan, "%", StateData#state.server]), + StateData#state.myname, Nick}, + StateData#state.user, + {xmlelement, "presence", [], + [{xmlelement, "x", [{"xmlns", ?NS_MUC_USER}], + [{xmlelement, "item", + [{"affiliation", Affiliation}, + {"role", Role}], + []}]}]}). + + diff --git a/src/namespaces.hrl b/src/namespaces.hrl index 712348a91..e26b9750e 100644 --- a/src/namespaces.hrl +++ b/src/namespaces.hrl @@ -19,5 +19,7 @@ -define(NS_DELAY, "jabber:x:delay"). -define(NS_EVENT, "jabber:x:event"). -define(NS_STATS, "http://jabber.org/protocol/stats"). +-define(NS_MUC, "http://jabber.org/protocol/muc"). +-define(NS_MUC_USER, "http://jabber.org/protocol/muc#user").