Allow multiple entry with same nick to MUC rooms (thanks to Magnus Henoch)(EJAB-305)

This commit is contained in:
Badlop 2011-08-23 21:52:08 +02:00
parent 7d3633e91c
commit 8ddbf1c1c5
2 changed files with 177 additions and 67 deletions

View File

@ -451,8 +451,8 @@ normal_state({route, From, ToNick,
ToNick),
From, Err);
_ ->
ToJID = find_jid_by_nick(ToNick, StateData),
case ToJID of
ToJIDs = find_jids_by_nick(ToNick, StateData),
case ToJIDs of
false ->
ErrText = "Recipient is not in the conference room",
Err = exmpp_stanza:reply_with_error(Packet,
@ -465,7 +465,7 @@ normal_state({route, From, ToNick,
From, Err);
_ ->
SrcIsVisitor = is_visitor(From, StateData),
DstIsModerator = is_moderator(ToJID, StateData),
DstIsModerator = is_moderator(hd(ToJIDs), StateData),
PmFromVisitors = (StateData#state.config)#config.allow_private_messages_from_visitors,
if SrcIsVisitor == false;
PmFromVisitors == anyone;
@ -473,11 +473,8 @@ normal_state({route, From, ToNick,
{ok, #user{nick = FromNick}} =
?DICT:find(jlib:jid_tolower(From),
StateData#state.users),
ejabberd_router:route(
jid_replace_resource(
StateData#state.jid,
FromNick),
ToJID, Packet);
FromNickJID = jid_replace_resource(StateData#state.jid, FromNick),
[ejabberd_router:route(FromNickJID, ToJID, Packet) || ToJID <- ToJIDs];
true ->
ErrText = "It is not allowed to send private messages",
Err = exmpp_stanza:reply_with_error(Packet,
@ -953,7 +950,10 @@ process_presence(From, Nick, #xmlel{name = 'presence'} = Packet,
true ->
NewState =
add_user_presence_un(From, Packet, StateData),
send_new_presence(From, NewState),
case ?DICT:find(Nick, StateData#state.nicks) of
{ok, [_, _ | _]} -> ok;
_ -> send_new_presence(From, NewState)
end,
Reason = case exmpp_xml:get_element(Packet, 'status') of
undefined -> <<>>;
Status_el -> exmpp_xml:get_cdata(Status_el)
@ -977,7 +977,7 @@ process_presence(From, Nick, #xmlel{name = 'presence'} = Packet,
true ->
case is_nick_change(From, Nick, StateData) of
true ->
case {is_nick_exists(Nick, StateData),
case {nick_collision(From, Nick, StateData),
mod_muc:can_use_nick(
StateData#state.host, From, Nick),
{(StateData#state.config)#config.allow_visitor_nickchange,
@ -1296,21 +1296,31 @@ set_role(JID, Role, StateData) ->
[]
end
end,
Users = case Role of
none ->
lists:foldl(fun(J, Us) ->
?DICT:erase(J,
Us)
end, StateData#state.users, LJIDs);
_ ->
lists:foldl(fun(J, Us) ->
{ok, User} = ?DICT:find(J, Us),
?DICT:store(J,
User#user{role = Role},
Us)
end, StateData#state.users, LJIDs)
end,
StateData#state{users = Users}.
{Users, Nicks}
= case Role of
none ->
lists:foldl(fun(J, {Us, Ns}) ->
NewNs =
case ?DICT:find(J, Us) of
{ok, #user{nick = Nick}} ->
?DICT:erase(Nick, Ns);
_ ->
Ns
end,
{?DICT:erase(J, Us), NewNs}
end,
{StateData#state.users, StateData#state.nicks},
LJIDs);
_ ->
{lists:foldl(fun(J, Us) ->
{ok, User} = ?DICT:find(J, Us),
?DICT:store(J,
User#user{role = Role},
Us)
end, StateData#state.users, LJIDs),
StateData#state.nicks}
end,
StateData#state{users = Users, nicks = Nicks}.
get_role(JID, StateData) ->
LJID = jlib:short_prepd_jid(JID),
@ -1493,8 +1503,19 @@ add_online_user(JID, Nick, Role, StateData) ->
role = Role},
StateData#state.users),
add_to_log(join, Nick, StateData),
Nicks = ?DICT:update(Nick,
fun(Entry) ->
case lists:member(LJID, Entry) of
true ->
Entry;
false ->
[LJID|Entry]
end
end,
[LJID],
StateData#state.nicks),
tab_add_online_user(JID, StateData),
StateData#state{users = Users}.
StateData#state{users = Users, nicks = Nicks}.
remove_online_user(JID, StateData) ->
remove_online_user(JID, StateData, <<>>).
@ -1506,7 +1527,15 @@ remove_online_user(JID, StateData, Reason) ->
add_to_log(leave, {Nick, Reason}, StateData),
tab_remove_online_user(JID, StateData),
Users = ?DICT:erase(LJID, StateData#state.users),
StateData#state{users = Users}.
Nicks = case ?DICT:find(Nick, StateData#state.nicks) of
{ok, [LJID]} ->
?DICT:erase(Nick, StateData#state.nicks);
{ok, U} ->
?DICT:store(Nick, U -- [LJID], StateData#state.nicks);
false ->
StateData#state.nicks
end,
StateData#state{users = Users, nicks = Nicks}.
filter_presence(#xmlel{name = 'presence'} = Packet) ->
@ -1556,19 +1585,48 @@ add_user_presence_un(JID, Presence, StateData) ->
StateData#state{users = Users}.
is_nick_exists(Nick, StateData) ->
?DICT:fold(fun(_, #user{nick = N}, B) ->
B orelse (N == Nick)
end, false, StateData#state.users).
%% Find and return a list of the full JIDs of the users of Nick.
%% Return jid record.
find_jids_by_nick(Nick, StateData) ->
case ?DICT:find(Nick, StateData#state.nicks) of
{ok, [User]} ->
[exmpp_jid:make(User)];
{ok, Users} ->
[exmpp_jid:make(LJID) || LJID <- Users];
error ->
false
end.
%% @spec(Nick::binary(), StateData) -> JID::jid() | false
%% Find and return the full JID of the user of Nick with
%% highest-priority presence. Return jid record.
find_jid_by_nick(Nick, StateData) ->
?DICT:fold(fun(_, #user{jid = JID, nick = N}, R) ->
case Nick of
N -> JID;
_ -> R
end
end, false, StateData#state.users).
case ?DICT:find(Nick, StateData#state.nicks) of
{ok, [User]} ->
exmpp_jid:make(User);
{ok, [FirstUser|Users]} ->
#user{last_presence = FirstPresence} =
?DICT:fetch(FirstUser, StateData#state.users),
{LJID, _} =
lists:foldl(fun(Compare, {HighestUser, HighestPresence}) ->
#user{last_presence = P1} =
?DICT:fetch(Compare, StateData#state.users),
case higher_presence(P1, HighestPresence) of
true ->
{Compare, P1};
false ->
{HighestUser, HighestPresence}
end
end, {FirstUser, FirstPresence}, Users),
exmpp_jid:make(LJID);
error ->
false
end.
higher_presence(Pres1, Pres2) ->
Pri1 = exmpp_presence:get_priority(Pres1),
Pri2 = exmpp_presence:get_priority(Pres2),
Pri1 > Pri2.
is_nick_change(JID, Nick, StateData) ->
LJID = jlib:short_prepd_jid(JID),
@ -1581,6 +1639,13 @@ is_nick_change(JID, Nick, StateData) ->
Nick /= OldNick
end.
nick_collision(User, Nick, StateData) ->
UserOfNick = find_jid_by_nick(Nick, StateData),
%% if nick is not used, or is used by another resource of the same
%% user, it's ok.
UserOfNick /= false andalso
not exmpp_jid:bare_compare(UserOfNick, User).
add_new_user(From, Nick, Packet, StateData) ->
Lang = exmpp_stanza:get_lang(Packet),
MaxUsers = get_max_users(StateData),
@ -1593,13 +1658,14 @@ add_new_user(From, Nick, Packet, StateData) ->
MaxConferences = gen_mod:get_module_opt(
StateData#state.server_host,
mod_muc, max_user_conferences, 10),
Collision = nick_collision(From, Nick, StateData),
case {(ServiceAffiliation == owner orelse
MaxUsers < 0 orelse
((Affiliation == admin orelse Affiliation == owner) andalso
NUsers < MaxAdminUsers) orelse
NUsers < MaxUsers) andalso
NConferences < MaxConferences,
is_nick_exists(Nick, StateData),
Collision,
mod_muc:can_use_nick(StateData#state.host, From, Nick),
get_default_role(Affiliation, StateData)} of
{false, _, _, _} ->
@ -1937,13 +2003,19 @@ send_new_presence(NJID, StateData) ->
%% @spec(NJID::jid(), Reason::binary(), StateData) ->
send_new_presence({U, S, R}, Reason, StateData) ->
send_new_presence(exmpp_jid:make(U, S, R), Reason, StateData);
send_new_presence(NJID, Reason, StateData) ->
send_new_presence(NJID1, Reason, StateData) ->
NJID = {exmpp_jid:node(NJID1), exmpp_jid:domain(NJID1), exmpp_jid:resource(NJID1)},
%% First, find the nick associated with this JID.
#user{nick = Nick} = ?DICT:fetch(NJID, StateData#state.users),
%% Then find the JID using this nick with highest priority.
LJID1 = find_jid_by_nick(Nick, StateData),
LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
%% Then we get the presence data we're supposed to send.
{ok, #user{jid = RealJID,
nick = Nick,
role = Role,
last_presence = Presence}} =
?DICT:find(jlib:short_prepd_jid(NJID), StateData#state.users),
Affiliation = get_affiliation(NJID, StateData),
?DICT:find(LJID, StateData#state.users),
Affiliation = get_affiliation(LJID1, StateData),
SAffiliation = affiliation_to_binary(Affiliation),
SRole = role_to_binary(Role),
lists:foreach(
@ -2003,21 +2075,23 @@ send_new_presence(NJID, Reason, StateData) ->
send_existing_presences(ToJID, StateData) ->
LToJID = jlib:short_prepd_jid(ToJID),
LToJID = {exmpp_jid:node(ToJID), exmpp_jid:domain(ToJID), exmpp_jid:resource(ToJID)},
{ok, #user{jid = RealToJID,
role = Role}} =
?DICT:find(LToJID, StateData#state.users),
lists:foreach(
fun({LJID, #user{jid = FromJID,
nick = FromNick,
role = FromRole,
last_presence = Presence
}}) ->
fun({FromNick, _Users}) ->
LJID1 = find_jid_by_nick(FromNick, StateData),
LJID = {exmpp_jid:node(LJID1), exmpp_jid:domain(LJID1), exmpp_jid:resource(LJID1)},
#user{jid = FromJID,
role = FromRole,
last_presence = Presence
} = ?DICT:fetch(LJID, StateData#state.users),
case RealToJID of
FromJID ->
ok;
_ ->
{N,D,R} = LJID,
{N,D,R} = LJID,
FromAffiliation = get_affiliation(exmpp_jid:make(N,D,R),
StateData),
ItemAttrs =
@ -2044,7 +2118,7 @@ send_existing_presences(ToJID, StateData) ->
RealToJID,
Packet)
end
end, ?DICT:to_list(StateData#state.users)).
end, ?DICT:to_list(StateData#state.nicks)).
@ -2062,12 +2136,37 @@ change_nick(JID, Nick, StateData) ->
fun(#user{} = User) ->
User#user{nick = Nick}
end, StateData#state.users),
NewStateData = StateData#state{users = Users},
send_nick_changing(JID, OldNick, NewStateData),
OldNickUsers = ?DICT:fetch(OldNick, StateData#state.nicks),
NewNickUsers = case ?DICT:find(Nick, StateData#state.nicks) of
{ok, U} -> U;
error -> []
end,
%% Send unavailable presence from the old nick if it's no longer
%% used.
SendOldUnavailable = length(OldNickUsers) == 1,
%% If we send unavailable presence from the old nick, we should
%% probably send presence from the new nick, in order not to
%% confuse clients. Otherwise, do it only if the new nick was
%% unused.
SendNewAvailable = SendOldUnavailable orelse
NewNickUsers == [],
Nicks =
case OldNickUsers of
[LJID] ->
?DICT:store(Nick, [LJID|NewNickUsers],
?DICT:erase(OldNick, StateData#state.nicks));
[_|_] ->
?DICT:store(Nick, [LJID|NewNickUsers],
?DICT:store(OldNick, OldNickUsers -- [LJID],
StateData#state.nicks))
end,
NewStateData = StateData#state{users = Users, nicks = Nicks},
send_nick_changing(JID, OldNick, NewStateData, SendOldUnavailable, SendNewAvailable),
add_to_log(nickchange, {OldNick, Nick}, StateData),
NewStateData.
send_nick_changing(JID, OldNick, StateData) ->
send_nick_changing(JID, OldNick, StateData,
SendOldUnavailable, SendNewAvailable) ->
{ok, #user{jid = RealJID,
nick = Nick,
role = Role,
@ -2120,15 +2219,22 @@ send_nick_changing(JID, OldNick, StateData) ->
children =[#xmlel{ns = ?NS_MUC_USER,
name = 'item',
attrs = ItemAttrs2}]}),
ejabberd_router:route(
jid_replace_resource(StateData#state.jid, OldNick),
Info#user.jid,
Packet1),
ejabberd_router:route(
jid_replace_resource(StateData#state.jid, Nick),
Info#user.jid,
Packet2)
if SendOldUnavailable ->
ejabberd_router:route(
jid_replace_resource(StateData#state.jid, OldNick),
Info#user.jid,
Packet1);
true ->
ok
end,
if SendNewAvailable ->
ejabberd_router:route(
jid_replace_resource(StateData#state.jid, Nick),
Info#user.jid,
Packet2);
true ->
ok
end
end, ?DICT:to_list(StateData#state.users)).
@ -3530,7 +3636,7 @@ process_iq_disco_info(_From, get, Lang, StateData) ->
#xmlcdata{cdata = list_to_binary(Val)}]}]}).
iq_disco_info_extras(Lang, StateData) ->
Len = length(?DICT:to_list(StateData#state.users)),
Len = ?DICT:size(StateData#state.users),
RoomDescription = (StateData#state.config)#config.description,
[#xmlel{ns = ?NS_DATA_FORMS, name = 'x',
attrs = [?XMLATTR(<<"type">>, <<"result">>)],
@ -3795,27 +3901,29 @@ add_to_log(Type, Data, StateData) ->
tab_add_online_user(JID, StateData) ->
LUser = exmpp_jid:prep_node(JID),
LServer = exmpp_jid:prep_domain(JID),
LResource = exmpp_jid:prep_resource(JID),
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
catch ets:insert(
muc_online_users,
#muc_online_users{us = US, room = Room, host = Host}).
#muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
tab_remove_online_user(JID, StateData) when ?IS_JID(JID) ->
LUser = exmpp_jid:prep_node(JID),
LServer = exmpp_jid:prep_domain(JID),
tab_remove_online_user({LUser, LServer, none},StateData);
LResource = exmpp_jid:prep_resource(JID),
tab_remove_online_user({LUser, LServer, LResource},StateData);
tab_remove_online_user({LUser, LServer,_}, StateData) ->
tab_remove_online_user({LUser, LServer, LResource}, StateData) ->
US = {LUser, LServer},
Room = StateData#state.room,
Host = StateData#state.host,
catch ets:delete_object(
muc_online_users,
#muc_online_users{us = US, room = Room, host = Host}).
#muc_online_users{us = US, resource = LResource, room = Room, host = Host}).
tab_count_user(JID) ->
LUser = exmpp_jid:prep_node(JID),

View File

@ -69,6 +69,7 @@
config = #config{},
users = ?DICT:new(),
robots = ?DICT:new(),
nicks = ?DICT:new(),
affiliations = ?DICT:new(),
history,
subject = "",
@ -79,6 +80,7 @@
room_queue = queue:new()}).
-record(muc_online_users, {us,
resource,
room,
host}).