from typing import Any, Dict, List, Set, Tuple from bemani.api.exceptions import APIException from bemani.api.objects.base import BaseObject from bemani.common import Profile, ValidatedDict, GameConstants, APIConstants from bemani.data import UserID class ProfileObject(BaseObject): def __format_ddr_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return { 'area': profile.get_int('area', -1) if exact else -1, } def __format_iidx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: qpro = profile.get_dict('qpro') return { 'area': profile.get_int('pid', -1), 'qpro': { 'head': qpro.get_int('head', -1) if exact else -1, 'hair': qpro.get_int('hair', -1) if exact else -1, 'face': qpro.get_int('face', -1) if exact else -1, 'body': qpro.get_int('body', -1) if exact else -1, 'hand': qpro.get_int('hand', -1) if exact else -1, } } def __format_jubeat_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return {} def __format_museca_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return {} def __format_popn_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return { 'character': profile.get_int('chara', -1) if exact else -1, } def __format_reflec_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return { 'icon': profile.get_dict('config').get_int('icon_id', -1) if exact else -1, } def __format_sdvx_profile(self, profile: Profile, exact: bool) -> Dict[str, Any]: return {} def __format_profile(self, cardids: List[str], profile: Profile, settings: ValidatedDict, exact: bool) -> Dict[str, Any]: base = { 'name': profile.get_str('name'), 'cards': cardids, 'registered': settings.get_int('first_play_timestamp', -1), 'updated': settings.get_int('last_play_timestamp', -1), 'plays': settings.get_int('total_plays', -1), 'match': 'exact' if exact else 'partial', } if self.game == GameConstants.DDR: base.update(self.__format_ddr_profile(profile, exact)) if self.game == GameConstants.IIDX: base.update(self.__format_iidx_profile(profile, exact)) if self.game == GameConstants.JUBEAT: base.update(self.__format_jubeat_profile(profile, exact)) if self.game == GameConstants.MUSECA: base.update(self.__format_museca_profile(profile, exact)) if self.game == GameConstants.POPN_MUSIC: base.update(self.__format_popn_profile(profile, exact)) if self.game == GameConstants.REFLEC_BEAT: base.update(self.__format_reflec_profile(profile, exact)) if self.game == GameConstants.SDVX: base.update(self.__format_sdvx_profile(profile, exact)) return base def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> List[Dict[str, Any]]: # Fetch the profiles profiles: List[Tuple[UserID, Profile]] = [] if idtype == APIConstants.ID_TYPE_SERVER: profiles.extend(self.data.local.user.get_all_profiles(self.game, self.version)) elif idtype == APIConstants.ID_TYPE_SONG: raise APIException( 'Unsupported ID for lookup!', 405, ) elif idtype == APIConstants.ID_TYPE_INSTANCE: raise APIException( 'Unsupported ID for lookup!', 405, ) elif idtype == APIConstants.ID_TYPE_CARD: users: Set[UserID] = set() for cardid in ids: userid = self.data.local.user.from_cardid(cardid) if userid is not None: # Don't duplicate loads for users with multiple card IDs if multiples # of those IDs are requested. if userid in users: continue users.add(userid) # We can possibly find another profile for this user. This is important # in the case that we returned scores for a user that doesn't have a # profile on a particular version. We allow that on this network, so in # order to not break remote networks, try our best to return any profile. profile = self.data.local.user.get_any_profile(self.game, self.version, userid) if profile is not None: profiles.append((userid, profile)) else: raise APIException('Invalid ID type!') # Now, fetch the users, and filter out profiles belonging to orphaned users retval: List[Dict[str, Any]] = [] id_to_cards: Dict[UserID, List[str]] = {} for (userid, profile) in profiles: if userid not in id_to_cards: cards = self.data.local.user.get_cards(userid) if len(cards) == 0: # Can't add this user, skip the profile continue id_to_cards[userid] = cards # Format the profile and add it settings = self.data.local.game.get_settings(self.game, userid) if settings is None: settings = ValidatedDict({}) retval.append(self.__format_profile(id_to_cards[userid], profile, settings, profile.version == self.version)) return retval