277 lines
11 KiB
Python
277 lines
11 KiB
Python
from typing import List, Tuple, Optional
|
|
|
|
from bemani.common import APIConstants, GameConstants, Profile, Parallel
|
|
from bemani.data.interfaces import APIProviderInterface
|
|
from bemani.data.api.base import BaseGlobalData
|
|
from bemani.data.mysql.user import UserData
|
|
from bemani.data.remoteuser import RemoteUser
|
|
from bemani.data.types import UserID
|
|
|
|
|
|
class GlobalUserData(BaseGlobalData):
|
|
def __init__(self, api: APIProviderInterface, user: UserData) -> None:
|
|
super().__init__(api)
|
|
self.user = user
|
|
|
|
def __format_ddr_profile(self, updates: Profile, profile: Profile) -> None:
|
|
area = profile.get_int("area", -1)
|
|
if area != -1:
|
|
updates["area"] = area
|
|
|
|
def __format_iidx_profile(self, updates: Profile, profile: Profile) -> None:
|
|
area = profile.get_int("area", -1)
|
|
if area != -1:
|
|
updates["pid"] = area
|
|
|
|
qpro = profile.get_dict("qpro")
|
|
updates["qpro"] = {}
|
|
|
|
head = qpro.get_int("head", -1)
|
|
if head != -1:
|
|
updates["qpro"]["head"] = head
|
|
hair = qpro.get_int("hair", -1)
|
|
if hair != -1:
|
|
updates["qpro"]["hair"] = hair
|
|
face = qpro.get_int("face", -1)
|
|
if face != -1:
|
|
updates["qpro"]["face"] = face
|
|
body = qpro.get_int("body", -1)
|
|
if body != -1:
|
|
updates["qpro"]["body"] = body
|
|
hand = qpro.get_int("hand", -1)
|
|
if hand != -1:
|
|
updates["qpro"]["hand"] = hand
|
|
|
|
def __format_jubeat_profile(self, updates: Profile, profile: Profile) -> None:
|
|
pass
|
|
|
|
def __format_museca_profile(self, updates: Profile, profile: Profile) -> None:
|
|
pass
|
|
|
|
def __format_popn_profile(self, updates: Profile, profile: Profile) -> None:
|
|
chara = profile.get_int("character", -1)
|
|
if chara != -1:
|
|
updates["chara"] = chara
|
|
|
|
def __format_reflec_profile(self, updates: Profile, profile: Profile) -> None:
|
|
icon = profile.get_int("icon", -1)
|
|
if icon != -1:
|
|
updates["config"] = {"icon_id": icon}
|
|
|
|
def __format_sdvx_profile(self, updates: Profile, profile: Profile) -> None:
|
|
pass
|
|
|
|
def __format_profile(self, profile: Profile) -> Profile:
|
|
new = Profile(
|
|
profile.game,
|
|
profile.version,
|
|
profile.refid,
|
|
profile.extid,
|
|
{
|
|
"name": profile.get("name", ""),
|
|
},
|
|
)
|
|
|
|
if profile.game == GameConstants.DDR:
|
|
self.__format_ddr_profile(new, profile)
|
|
if profile.game == GameConstants.IIDX:
|
|
self.__format_iidx_profile(new, profile)
|
|
if profile.game == GameConstants.JUBEAT:
|
|
self.__format_jubeat_profile(new, profile)
|
|
if profile.game == GameConstants.MUSECA:
|
|
self.__format_museca_profile(new, profile)
|
|
if profile.game == GameConstants.POPN_MUSIC:
|
|
self.__format_popn_profile(new, profile)
|
|
if profile.game == GameConstants.REFLEC_BEAT:
|
|
self.__format_reflec_profile(new, profile)
|
|
if profile.game == GameConstants.SDVX:
|
|
self.__format_sdvx_profile(new, profile)
|
|
|
|
return new
|
|
|
|
def __profile_request(self, game: GameConstants, version: int, userid: UserID, exact: bool) -> Optional[Profile]:
|
|
# First, get or create the extid/refid for this virtual user
|
|
cardid = RemoteUser.userid_to_card(userid)
|
|
refid = self.user.get_refid(game, version, userid)
|
|
extid = self.user.get_extid(game, version, userid)
|
|
|
|
profiles = Parallel.flatten(
|
|
Parallel.call(
|
|
[client.get_profiles for client in self.clients],
|
|
game,
|
|
version,
|
|
APIConstants.ID_TYPE_CARD,
|
|
[cardid],
|
|
)
|
|
)
|
|
for profile in profiles:
|
|
cards = [card.upper() for card in profile.get("cards", [])]
|
|
if cardid in cards:
|
|
# Don't take non-exact matches.
|
|
exact_match = profile.get("match", "partial") == "exact"
|
|
if exact and (not exact_match):
|
|
# This is a partial match, not for this game/version
|
|
continue
|
|
|
|
# Add in our defaults we always provide, convert it to a local format.
|
|
return self.__format_profile(
|
|
Profile(
|
|
game,
|
|
version,
|
|
refid,
|
|
extid,
|
|
profile,
|
|
)
|
|
)
|
|
|
|
return None
|
|
|
|
def from_cardid(self, cardid: str) -> Optional[UserID]:
|
|
userid = self.user.from_cardid(cardid)
|
|
if userid is None:
|
|
userid = RemoteUser.card_to_userid(cardid)
|
|
return userid
|
|
|
|
def from_refid(self, game: GameConstants, version: int, refid: str) -> Optional[UserID]:
|
|
return self.user.from_refid(game, version, refid)
|
|
|
|
def from_extid(self, game: GameConstants, version: int, extid: int) -> Optional[UserID]:
|
|
return self.user.from_extid(game, version, extid)
|
|
|
|
def get_profile(self, game: GameConstants, version: int, userid: UserID) -> Optional[Profile]:
|
|
if RemoteUser.is_remote(userid):
|
|
return self.__profile_request(game, version, userid, exact=True)
|
|
else:
|
|
return self.user.get_profile(game, version, userid)
|
|
|
|
def get_any_profile(self, game: GameConstants, version: int, userid: UserID) -> Optional[Profile]:
|
|
if RemoteUser.is_remote(userid):
|
|
return self.__profile_request(game, version, userid, exact=False)
|
|
else:
|
|
return self.user.get_any_profile(game, version, userid)
|
|
|
|
def get_any_profiles(
|
|
self, game: GameConstants, version: int, userids: List[UserID]
|
|
) -> List[Tuple[UserID, Optional[Profile]]]:
|
|
if len(userids) == 0:
|
|
return []
|
|
|
|
remote_ids = [userid for userid in userids if RemoteUser.is_remote(userid)]
|
|
local_ids = [userid for userid in userids if not RemoteUser.is_remote(userid)]
|
|
|
|
if len(remote_ids) == 0:
|
|
# We only have local profiles here, just pass on to the underlying layer
|
|
return self.user.get_any_profiles(game, version, local_ids)
|
|
else:
|
|
# We have to fetch some local profiles and some remote profiles, and then
|
|
# merge them together
|
|
card_to_userid = {RemoteUser.userid_to_card(userid): userid for userid in remote_ids}
|
|
|
|
local_profiles, remote_profiles = Parallel.execute(
|
|
[
|
|
lambda: self.user.get_any_profiles(game, version, local_ids),
|
|
lambda: Parallel.flatten(
|
|
Parallel.call(
|
|
[client.get_profiles for client in self.clients],
|
|
game,
|
|
version,
|
|
APIConstants.ID_TYPE_CARD,
|
|
[RemoteUser.userid_to_card(userid) for userid in remote_ids],
|
|
)
|
|
),
|
|
]
|
|
)
|
|
|
|
for profile in remote_profiles:
|
|
cards = [card.upper() for card in profile.get("cards", [])]
|
|
for card in cards:
|
|
# Map it back to the requested user
|
|
userid = card_to_userid.get(card)
|
|
if userid is None:
|
|
continue
|
|
|
|
# Sanitize the returned data
|
|
exact_match = profile.get("match", "partial") == "exact"
|
|
refid = self.user.get_refid(game, version, userid)
|
|
extid = self.user.get_extid(game, version, userid)
|
|
|
|
# Add in our defaults we always provide
|
|
local_profiles.append(
|
|
(
|
|
userid,
|
|
self.__format_profile(
|
|
Profile(
|
|
game,
|
|
version if exact_match else 0,
|
|
refid,
|
|
extid,
|
|
profile,
|
|
),
|
|
),
|
|
),
|
|
)
|
|
|
|
# Mark that we saw this card/user
|
|
del card_to_userid[card]
|
|
|
|
# Finally, mark all missing remote profiles as None
|
|
for card in card_to_userid:
|
|
local_profiles.append((card_to_userid[card], None))
|
|
|
|
return local_profiles
|
|
|
|
def get_all_profiles(self, game: GameConstants, version: int) -> List[Tuple[UserID, Profile]]:
|
|
# Fetch local and remote profiles, and then merge by adding remote profiles to local
|
|
# profiles when we don't have a profile for that user ID yet.
|
|
local_cards, local_profiles, remote_profiles = Parallel.execute(
|
|
[
|
|
self.user.get_all_cards,
|
|
lambda: self.user.get_all_profiles(game, version),
|
|
lambda: Parallel.flatten(
|
|
Parallel.call(
|
|
[client.get_profiles for client in self.clients],
|
|
game,
|
|
version,
|
|
APIConstants.ID_TYPE_SERVER,
|
|
[],
|
|
)
|
|
),
|
|
]
|
|
)
|
|
|
|
card_to_id = {cardid: userid for (cardid, userid) in local_cards}
|
|
id_to_profile = {userid: profile for (userid, profile) in local_profiles}
|
|
|
|
for profile in remote_profiles:
|
|
cardids = sorted([card.upper() for card in profile.get("cards", [])])
|
|
if len(cardids) == 0:
|
|
# We don't care about anonymous profiles
|
|
continue
|
|
|
|
local_cards = [cardid for cardid in cardids if cardid in card_to_id]
|
|
if len(local_cards) > 0:
|
|
# We have a local version of this profile!
|
|
continue
|
|
|
|
exact_match = profile.get("match", "partial") == "exact"
|
|
if not exact_match:
|
|
continue
|
|
|
|
# Create a fake user with this profile
|
|
userid = RemoteUser.card_to_userid(cardids[0])
|
|
refid = self.user.get_refid(game, version, userid)
|
|
extid = self.user.get_extid(game, version, userid)
|
|
|
|
# Add in our defaults we always provide
|
|
id_to_profile[userid] = self.__format_profile(
|
|
Profile(
|
|
game,
|
|
version,
|
|
refid,
|
|
extid,
|
|
profile,
|
|
),
|
|
)
|
|
|
|
return [(userid, id_to_profile[userid]) for userid in id_to_profile]
|