25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-20 16:15:59 +01:00

* src/mod_irc/: Added support for private chats, nicks changes and

error handling

SVN Revision: 78
This commit is contained in:
Alexey Shchepin 2003-02-18 20:33:10 +00:00
parent 17000076ad
commit b704efd553
3 changed files with 253 additions and 60 deletions

View File

@ -1,3 +1,8 @@
2003-02-18 Alexey Shchepin <alexey@sevcom.net>
* src/mod_irc/: Added support for private chats, nicks changes and
error handling
2003-02-17 Alexey Shchepin <alexey@sevcom.net>
* src/mod_irc/: Still not completed...

View File

@ -91,21 +91,39 @@ do_route(Host, From, To, Packet) ->
irc_connection,
#irc_connection{userserver = {From, Server},
pid = Pid}),
mod_irc_connection:route(
mod_irc_connection:route_chan(
Pid, Channel, Resource, Packet),
ok;
[R] ->
Pid = R#irc_connection.pid,
io:format("send to process ~p~n",
[Pid]),
mod_irc_connection:route(
mod_irc_connection:route_chan(
Pid, Channel, Resource, Packet),
ok
end;
_ ->
Err = jlib:make_error_reply(
Packet, "406", "Not Acceptable"),
ejabberd_router:route(To, From, Err)
case string:tokens(ChanServ, "!") of
[[_ | _] = Nick, [_ | _] = Server] ->
case ets:lookup(irc_connection, {From, Server}) of
[] ->
Err = jlib:make_error_reply(
Packet,
"503", "Service Unavailable"),
ejabberd_router:route(To, From, Err);
[R] ->
Pid = R#irc_connection.pid,
io:format("send to process ~p~n",
[Pid]),
mod_irc_connection:route_nick(
Pid, Nick, Packet),
ok
end;
_ ->
Err = jlib:make_error_reply(
Packet, "406", "Not Acceptable"),
ejabberd_router:route(To, From, Err)
end
end
end.

View File

@ -13,7 +13,7 @@
-behaviour(gen_fsm).
%% External exports
-export([start/3, receiver/2, route/4]).
-export([start/3, receiver/2, route_chan/4, route_nick/3]).
%% gen_fsm callbacks
-export([init/1,
@ -33,7 +33,7 @@
-record(state, {socket, receiver, queue,
user, myname, server, nick,
channels = ?SETS:new(),
channels = dict:new(),
inbuf = "", outbuf = ""}).
-define(IRC_ENCODING, "koi8-r").
@ -110,17 +110,6 @@ open_socket(init, StateData) ->
end.
wait_for_registration(closed, StateData) ->
bounce_messages("Server Connect Failed"),
lists:foreach(
fun(Chan) ->
ejabberd_router:route(
{lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick},
StateData#state.user,
{xmlelement, "presence", [{"type", "error"}],
[{xmlelement, "error", [{"code", "502"}],
[{xmlcdata, "Server Connect Failed"}]}]})
end, ?SETS:to_list(StateData#state.channels)),
{stop, normal, StateData}.
stream_established({xmlstreamend, Name}, StateData) ->
@ -187,31 +176,41 @@ code_change(OldVsn, StateName, StateData, Extra) ->
%% {next_state, NextStateName, NextStateData, Timeout} |
%% {stop, Reason, NewStateData}
%%----------------------------------------------------------------------
handle_info({route, Channel, Resource, {xmlelement, "presence", Attrs, Els}},
handle_info({route_chan, Channel, Resource,
{xmlelement, "presence", Attrs, Els}},
StateName, StateData) ->
NewStateData =
case xml:get_attr_s("type", Attrs) of
"unavailable" ->
S1 = ?SEND(io_lib:format("PART #~s\r\n", [Channel])),
S1#state{channels =
remove_element(Channel, S1#state.channels)};
dict:erase(Channel, S1#state.channels)};
"subscribe" -> StateData;
"subscribed" -> StateData;
"unsubscribe" -> StateData;
"unsubscribed" -> StateData;
_ ->
S1 = ?SEND(io_lib:format("JOIN #~s\r\n", [Channel])),
S1#state{channels =
?SETS:add_element(Channel, S1#state.channels)}
S1 = ?SEND(io_lib:format("NICK ~s\r\n"
"JOIN #~s\r\n",
[Resource, Channel])),
case dict:is_key(Channel, S1#state.channels) of
true ->
S1#state{nick = Resource};
_ ->
S1#state{nick = Resource,
channels =
dict:store(Channel, ?SETS:new(),
S1#state.channels)}
end
end,
case ?SETS:is_empty(NewStateData#state.channels) of
true ->
case length(dict:fetch_keys(NewStateData#state.channels)) of
0 ->
{stop, normal, NewStateData};
_ ->
{next_state, StateName, NewStateData}
end;
handle_info({route, Channel, Resource,
handle_info({route_chan, Channel, Resource,
{xmlelement, "message", Attrs, Els} = El},
StateName, StateData) ->
NewStateData =
@ -236,11 +235,58 @@ handle_info({route, Channel, Resource,
[Channel, S])
end, Strings)),
?SEND(Res);
_ -> StateData
"chat" ->
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
Body1 = case Body of
[$/, $m, $e, $ | Rest] ->
"\001ACTION " ++ Rest ++ "\001";
_ ->
Body
end,
Strings = string:tokens(Body1, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
io_lib:format("PRIVMSG ~s :~s\r\n",
[Resource, S])
end, Strings)),
?SEND(Res);
_ ->
StateData
end,
{next_state, StateName, NewStateData};
handle_info({route, Channel, Resource, Packet}, StateName, StateData) ->
handle_info({route_chan, Channel, Resource, Packet}, StateName, StateData) ->
{next_state, StateName, StateData};
handle_info({route_nick, Nick,
{xmlelement, "message", Attrs, Els} = El},
StateName, StateData) ->
NewStateData =
case xml:get_attr_s("type", Attrs) of
"chat" ->
Body = xml:get_path_s(El, [{elem, "body"}, cdata]),
Body1 = case Body of
[$/, $m, $e, $ | Rest] ->
"\001ACTION " ++ Rest ++ "\001";
_ ->
Body
end,
Strings = string:tokens(Body1, "\n"),
Res = lists:concat(
lists:map(
fun(S) ->
io_lib:format("PRIVMSG ~s :~s\r\n",
[Nick, S])
end, Strings)),
?SEND(Res);
_ ->
StateData
end,
{next_state, StateName, NewStateData};
handle_info({route_nick, Nick, Packet}, StateName, StateData) ->
{next_state, StateName, StateData};
@ -253,20 +299,20 @@ handle_info({ircstring, [$: | String]}, StateName, StateData) ->
NewStateData =
case Words of
[_, "353" | Items] ->
process_channel_list(StateData, Items),
StateData;
process_channel_list(StateData, Items);
[From, "PRIVMSG", [$# | Chan] | _] ->
process_chanprivmsg(StateData, Chan, From, String),
StateData;
[From, "PRIVMSG", Nick, ":\001VERSION\001" | _] ->
process_version(StateData, Nick, From),
StateData;
[From, "PRIVMSG", Nick | _] ->
process_privmsg(StateData, Nick, From, String),
StateData;
[From, "PART", [$# | Chan] | _] ->
process_part(StateData, Chan, From, String),
StateData;
process_part(StateData, Chan, From, String);
[From, "JOIN", Chan | _] ->
process_join(StateData, Chan, From, String),
StateData;
process_join(StateData, Chan, From, String);
[From, "MODE", [$# | Chan], "+o", Nick | _] ->
process_mode_o(StateData, Chan, From, Nick,
"admin", "moderator"),
@ -278,6 +324,8 @@ handle_info({ircstring, [$: | String]}, StateName, StateData) ->
[From, "KICK", [$# | Chan], Nick | _] ->
process_kick(StateData, Chan, From, Nick),
StateData;
[From, "NICK", Nick | _] ->
process_nick(StateData, From, Nick);
_ ->
io:format("unknown irc command '~s'~n", [String]),
StateData
@ -292,6 +340,11 @@ handle_info({ircstring, [$: | String]}, StateName, StateData) ->
end,
{next_state, stream_established, NewStateData1};
handle_info({ircstring, [$E, $R, $R, $O, $R | _] = String},
StateName, StateData) ->
process_error(StateData, String),
{next_state, StateName, StateData};
handle_info({ircstring, String}, StateName, StateData) ->
io:format("unknown irc command '~s'~n", [String]),
@ -331,6 +384,17 @@ handle_info({tcp_error, Socket, Reason}, StateName, StateData) ->
terminate(Reason, StateName, StateData) ->
mod_irc:closed_conection(StateData#state.user,
StateData#state.server),
bounce_messages("Server Connect Failed"),
lists:foreach(
fun(Chan) ->
ejabberd_router:route(
{lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick},
StateData#state.user,
{xmlelement, "presence", [{"type", "error"}],
[{xmlelement, "error", [{"code", "502"}],
[{xmlcdata, "Server Connect Failed"}]}]})
end, dict:fetch_keys(StateData#state.channels)),
case StateData#state.socket of
undefined ->
ok;
@ -395,8 +459,11 @@ bounce_messages(Reason) ->
end.
route(Pid, Channel, Resource, Packet) ->
Pid ! {route, Channel, Resource, Packet}.
route_chan(Pid, Channel, Resource, Packet) ->
Pid ! {route_chan, Channel, Resource, Packet}.
route_nick(Pid, Nick, Packet) ->
Pid ! {route_nick, Nick, Packet}.
process_lines([S]) ->
@ -409,17 +476,17 @@ process_channel_list(StateData, Items) ->
process_channel_list_find_chan(StateData, Items).
process_channel_list_find_chan(StateData, []) ->
ok;
StateData;
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;
StateData;
process_channel_list_users(StateData, Chan, [User | Items]) ->
process_channel_list_user(StateData, Chan, User),
process_channel_list_users(StateData, Chan, Items).
NewStateData = process_channel_list_user(StateData, Chan, User),
process_channel_list_users(NewStateData, Chan, Items).
process_channel_list_user(StateData, Chan, User) ->
User1 = case User of
@ -439,13 +506,21 @@ process_channel_list_user(StateData, Chan, User) ->
[{xmlelement, "item",
[{"affiliation", Affiliation},
{"role", Role}],
[]}]}]}).
[]}]}]}),
case catch dict:update(Chan,
fun(Ps) ->
?SETS:add_element(User2, Ps)
end, StateData#state.channels) of
{'EXIT', _} ->
StateData;
NS ->
StateData#state{channels = NS}
end.
process_chanprivmsg(StateData, Chan, From, String) ->
[FromUser | _] = string:tokens(From, "!"),
{ok, Msg, _} = regexp:sub(String, ".*PRIVMSG[^:]*:", ""),
%Msg = lists:last(string:tokens(String, ":")),
Msg1 = case Msg of
[1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
"/me " ++ Rest;
@ -468,23 +543,44 @@ process_chanprivmsg(StateData, Chan, From, String) ->
{xmlelement, "message", [{"type", "groupchat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
process_privmsg(StateData, Nick, From, String) ->
[FromUser | _] = string:tokens(From, "!"),
{ok, Msg, _} = regexp:sub(String, ".*PRIVMSG[^:]*:", ""),
Msg1 = case Msg of
[1, $A, $C, $T, $I, $O, $N, $ | Rest] ->
"/me " ++ Rest;
_ ->
Msg
end,
Msg2 = lists:filter(
fun(C) ->
if (C < 32) and
(C /= 9) and
(C /= 10) and
(C /= 13) ->
false;
true -> true
end
end, Msg1),
ejabberd_router:route(
{lists:concat([FromUser, "!", StateData#state.server]),
StateData#state.myname, ""},
StateData#state.user,
{xmlelement, "message", [{"type", "chat"}],
[{xmlelement, "body", [], [{xmlcdata, Msg2}]}]}).
process_version(StateData, Nick, From) ->
case StateData#state.nick of
Nick ->
[FromUser | _] = string:tokens(From, "!"),
send_text(
StateData#state.socket,
io_lib:format("NOTICE ~s :\001VERSION "
"ejabberd IRC transport ~s (c) Alexey Shchepin"
"\001\r\n",
[FromUser, ?VERSION]) ++
io_lib:format("NOTICE ~s :\001VERSION "
"http://www.jabber.ru/projects/ejabberd/"
"\001\r\n",
[FromUser]));
_ ->
ok
end.
[FromUser | _] = string:tokens(From, "!"),
send_text(
StateData#state.socket,
io_lib:format("NOTICE ~s :\001VERSION "
"ejabberd IRC transport ~s (c) Alexey Shchepin"
"\001\r\n",
[FromUser, ?VERSION]) ++
io_lib:format("NOTICE ~s :\001VERSION "
"http://www.jabber.ru/projects/ejabberd/"
"\001\r\n",
[FromUser])).
process_part(StateData, Chan, From, String) ->
@ -498,7 +594,16 @@ process_part(StateData, Chan, From, String) ->
[{xmlelement, "item",
[{"affiliation", "member"},
{"role", "none"}],
[]}]}]}).
[]}]}]}),
case catch dict:update(Chan,
fun(Ps) ->
remove_element(FromUser, Ps)
end, StateData#state.channels) of
{'EXIT', _} ->
StateData;
NS ->
StateData#state{channels = NS}
end.
process_join(StateData, Channel, From, String) ->
@ -512,7 +617,17 @@ process_join(StateData, Channel, From, String) ->
[{xmlelement, "item",
[{"affiliation", "member"},
{"role", "participant"}],
[]}]}]}).
[]}]}]}),
case catch dict:update(Chan,
fun(Ps) ->
?SETS:add_element(FromUser, Ps)
end, StateData#state.channels) of
{'EXIT', _} ->
StateData;
NS ->
StateData#state{channels = NS}
end.
process_mode_o(StateData, Chan, From, Nick, Affiliation, Role) ->
@ -541,6 +656,61 @@ process_kick(StateData, Chan, From, Nick) ->
{xmlelement, "status", [{"code", "307"}], []}
]}]}).
process_nick(StateData, From, NewNick) ->
[FromUser | _] = string:tokens(From, "!"),
Nick = lists:subtract(NewNick, ":"),
NewChans =
dict:map(
fun(Chan, Ps) ->
case ?SETS:is_member(FromUser, Ps) of
true ->
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", "participant"},
{"nick", Nick}],
[]},
{xmlelement, "status", [{"code", "303"}], []}
]}]}),
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", "member"},
{"role", "participant"}],
[]}
]}]}),
?SETS:add_element(Nick,
remove_element(FromUser, Ps));
_ ->
Ps
end
end, StateData#state.channels),
StateData#state{channels = NewChans}.
process_error(StateData, String) ->
lists:foreach(
fun(Chan) ->
ejabberd_router:route(
{lists:concat([Chan, "%", StateData#state.server]),
StateData#state.myname, StateData#state.nick},
StateData#state.user,
{xmlelement, "presence", [{"type", "error"}],
[{xmlelement, "error", [{"code", "502"}],
[{xmlcdata, String}]}]})
end, dict:fetch_keys(StateData#state.channels)).
remove_element(E, Set) ->
case ?SETS:is_element(E, Set) of