-module(ephraim_roster). -compile([debug_info, export_all]). -include_lib("exmpp/include/exmpp.hrl"). -record(roster, { entries :: dict() }). -record(avatar, { data :: binary() }). -record(roster_entry, { name :: binary() | undefined, subscription :: atom() | undefined, groups = sets:new() :: set(), resources = dict:new() :: dict(), vcard = dict:new() :: dict(), avatar :: #avatar{} | undefined }). -record(resource_entry, { priority :: integer(), show :: atom(), status :: binary() }). -spec init() -> ok. init() -> loop(#roster{entries=dict:new()}). -spec updateResource(#roster{}, binary(), integer(), atom(), atom(), binary()) -> #roster{}. updateResource(Roster, JID, Priority, Type, Show, Status) -> {Node, Domain, Resource} = exmpp_jid:to_lower(exmpp_jid:parse(JID)), BareJID = list_to_binary([Node, <<"@">>, Domain]), RosterEntry = case dict:find(BareJID, Roster#roster.entries) of {ok, Entry} -> Entry; error -> #roster_entry{} end, Resources = case Type of available -> ResourceEntry = #resource_entry{priority=Priority,show=Show,status=Status}, dict:store(Resource, ResourceEntry, RosterEntry#roster_entry.resources); unavailable -> dict:erase(Resource, RosterEntry#roster_entry.resources); _ -> RosterEntry#roster_entry.resources end, NewEntry = RosterEntry#roster_entry{resources=Resources}, Entries = dict:store(BareJID, NewEntry, Roster#roster.entries), uiUpdate(BareJID, NewEntry), Roster#roster{entries=Entries}. -spec updateRosterEntry(#roster{}, binary(), binary(), atom(), set()) -> #roster{}. updateRosterEntry(Roster, JID, Name, Subscription, Groups) -> OldEntry = case dict:find(JID, Roster#roster.entries) of {ok, Entry} -> Entry; error -> #roster_entry{} end, NewEntry = OldEntry#roster_entry{subscription=Subscription,name=Name,groups=Groups}, Entries = dict:store(JID, NewEntry, Roster#roster.entries), uiUpdate(JID, NewEntry), Roster#roster{entries=Entries}. -spec updateVCard(#roster{}, binary(), dict()) -> #roster{}. updateVCard(Roster, JID, VCard) -> OldEntry = case dict:find(JID, Roster#roster.entries) of {ok, Entry} -> Entry; error -> #roster_entry{} end, Avatar = case dict:find('PHOTO', VCard) of {ok, AvatarData} -> #avatar{data=AvatarData}; error -> OldEntry#roster_entry.avatar end, NewEntry = OldEntry#roster_entry{vcard=VCard,avatar=Avatar}, Entries = dict:store(JID, NewEntry, Roster#roster.entries), uiUpdate(JID, NewEntry), Roster#roster{entries=Entries}. -spec uiUpdate(binary(), #roster_entry{}) -> ok. uiUpdate(JID, RosterEntry) -> if RosterEntry#roster_entry.subscription =:= undefined -> ok; true -> ephraim ! {ui_update, {roster_update, JID, RosterEntry#roster_entry.name, RosterEntry#roster_entry.subscription, sets:to_list(RosterEntry#roster_entry.groups), dict:to_list(RosterEntry#roster_entry.resources), RosterEntry#roster_entry.avatar}}, ok end. -spec getGroups([#xmlel{}]) -> set(). getGroups(Els) -> getGroups(sets:new(), Els). -spec getGroups(set(), [#xmlel{}]) -> set(). getGroups(Groups, []) -> Groups; getGroups(Groups, [#xmlel{ns='jabber:iq:roster',name=group,children=[#xmlcdata{cdata=Group}]}|Rest]) -> getGroups(sets:add_element(Group, Groups), Rest); getGroups(Groups, [_|Rest]) -> getGroups(Groups, Rest). -spec handleRosterIQ(#roster{}, #xmlel{}) -> #roster{}. handleRosterIQ(Roster, Item) -> JID = exmpp_xml:get_attribute(Item, jid, undefined), Name = exmpp_xml:get_attribute(Item, name, undefined), Groups = getGroups(Item#xmlel.children), Subscription = binary_to_atom(exmpp_xml:get_attribute(Item, subscription, undefined), utf8), ephraim ! {send_packet, exmpp_stanza:set_recipient(exmpp_iq:get('jabber:client', #xmlel{ns='vcard-temp',name='vCard'}), JID)}, updateRosterEntry(Roster, JID, Name, Subscription, Groups). -spec handleRosterIQs(#roster{}, [#xmlel{}]) -> #roster{}. handleRosterIQs(Roster, []) -> Roster; handleRosterIQs(Roster, [Item = #xmlel{ns='jabber:iq:roster',name=item}|Rest]) -> Roster2 = handleRosterIQ(Roster, Item), handleRosterIQs(Roster2, Rest); handleRosterIQs(Roster, [_|Rest]) -> handleRosterIQs(Roster, Rest). -spec handleVCardIQ(dict(), #xmlel{}|#xmlcdata{}) -> dict(). handleVCardIQ(Dict, Item=#xmlel{name='PHOTO'}) -> Element = exmpp_xml:get_element(Item, 'BINVAL'), Data = base64:decode(exmpp_xml:get_cdata_from_list(Element#xmlel.children)), dict:store('PHOTO', Data, Dict); handleVCardIQ(Dict, Item=#xmlel{name=Key}) -> Value = exmpp_xml:get_cdata(Item), dict:store(Key, Value, Dict); handleVCardIQ(Dict, #xmlcdata{}) -> Dict. -spec handleVCardIQs(dict(), [#xmlel{}]) -> dict(). handleVCardIQs(VCard, []) -> VCard; handleVCardIQs(VCard, [Item|Rest]) -> VCard2 = handleVCardIQ(VCard, Item), handleVCardIQs(VCard2, Rest). -spec get_alias(#roster{}, binary()) -> binary(). get_alias(Roster, JID) -> {Node, Domain, _Resource} = exmpp_jid:to_lower(exmpp_jid:parse(JID)), BareJID = list_to_binary([Node, <<"@">>, Domain]), case dict:find(BareJID, Roster#roster.entries) of {ok, Entry} -> case Entry#roster_entry.name of undefined -> case dict:find('NICKNAME', Entry#roster_entry.vcard) of {ok, Value} -> Value; error -> Node end; Name -> Name end; error -> Node end. -spec loop(#roster{}) -> ok. loop(Roster) -> receive stop -> ok; {presence, Packet} -> From = exmpp_xml:get_attribute(Packet, from, <<"unknown">>), Priority = exmpp_presence:get_priority(Packet), Type = exmpp_presence:get_type(Packet), Show = exmpp_presence:get_show(Packet), Status = exmpp_presence:get_status(Packet), Roster2 = updateResource(Roster, From, Priority, Type, Show, Status), loop(Roster2); {roster_iq, Payload} -> Roster2 = handleRosterIQs(Roster, Payload#xmlel.children), loop(Roster2); {vcard_iq, From, Payload} -> Dict = handleVCardIQs(dict:new(), Payload#xmlel.children), Roster2 = updateVCard(Roster, From, Dict), loop(Roster2); {Pid, get_alias, JID} -> Pid ! {alias,JID,get_alias(Roster, JID)}, loop(Roster); Msg -> %io:format("ephraim_roster: ~p~n", [Msg]), loop(Roster) end.