-module(ephraim_roster). -include_lib("exmpp/include/exmpp.hrl"). -behaviour(gen_server). -export([start_link/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]). -export([presence/2, roster_iq/2, vcard_iq/3, get_alias/2]). -record(roster_state, { event_manager :: pid(), connection :: pid(), 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() }). start_link(EventManager, Connection) -> gen_server:start_link(?MODULE, {EventManager, Connection}, []). %-spec init() -> ok. init({EventManager, Connection}) -> process_flag(trap_exit, true), ephraim_roster_handler:start(EventManager, self()), io:format("Getting roster...~n"), ephraim_conn:send_packet(Connection, exmpp_client_roster:get_roster("foo")), {ok, #roster_state{event_manager=EventManager,connection=Connection,entries=dict:new()}}. -spec updateResource(#roster_state{}, binary(), integer(), atom(), atom(), binary()) -> #roster_state{}. 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_state.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_state.entries), viewUpdate(Roster, BareJID, NewEntry), Roster#roster_state{entries=Entries}. -spec updateRosterEntry(#roster_state{}, binary(), binary(), atom(), set()) -> #roster_state{}. updateRosterEntry(Roster, JID, Name, Subscription, Groups) -> OldEntry = case dict:find(JID, Roster#roster_state.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_state.entries), viewUpdate(Roster, JID, NewEntry), Roster#roster_state{entries=Entries}. -spec updateVCard(#roster_state{}, binary(), dict()) -> #roster_state{}. updateVCard(Roster, JID, VCard) -> OldEntry = case dict:find(JID, Roster#roster_state.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_state.entries), viewUpdate(Roster, JID, NewEntry), Roster#roster_state{entries=Entries}. -spec viewUpdate(#roster_state{}, binary(), #roster_entry{}) -> ok. viewUpdate(State, JID, RosterEntry) -> if RosterEntry#roster_entry.subscription =:= undefined -> ok; true -> gen_event:notify(State#roster_state.event_manager, {view_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_state{}, #xmlel{}) -> #roster_state{}. 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_conn:send_packet(Roster#roster_state.connection, 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_state{}, [#xmlel{}]) -> #roster_state{}. 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(pid(), binary()) -> binary(). get_alias(Roster, JID) -> gen_server:call(Roster, {get_alias,JID}). presence(Roster, Packet) -> gen_server:cast(Roster, {presence, Packet}). roster_iq(Roster, Payload) -> gen_server:cast(Roster, {roster_iq, Payload}). vcard_iq(Roster, From, Payload) -> gen_server:cast(Roster, {vcard_iq, From, Payload}). handle_call({get_alias,JID}, _From, State) -> {Node, Domain, _Resource} = exmpp_jid:to_lower(exmpp_jid:parse(JID)), BareJID = list_to_binary([Node, <<"@">>, Domain]), Result = case dict:find(BareJID, State#roster_state.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, {reply, Result, State}. handle_cast({presence, Packet}, State) -> %io:format("ephraim_roster: Presence: ~p~n", [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), State2 = updateResource(State, From, Priority, Type, Show, Status), {noreply, State2}; handle_cast({roster_iq, Payload}, State) -> State2 = handleRosterIQs(State, Payload#xmlel.children), {noreply, State2}; handle_cast({vcard_iq, From, Payload}, State) -> Dict = handleVCardIQs(dict:new(), Payload#xmlel.children), State2 = updateVCard(State, From, Dict), {noreply, State2}. handle_info(_Msg, State) -> {noreply, State}. terminate(_Reason, State) -> ephraim_roster_handler:stop(State#roster_state.event_manager), ok. code_change(_OldVersion, State, _Extra) -> {ok, State}.