2019-12-08 22:43:49 +01:00
|
|
|
from typing import Any, Dict, List
|
|
|
|
|
|
|
|
from bemani.api.exceptions import APIException
|
|
|
|
from bemani.api.objects.base import BaseObject
|
|
|
|
from bemani.common import GameConstants, APIConstants, DBConstants, VersionConstants
|
|
|
|
from bemani.data import Song
|
|
|
|
|
|
|
|
|
|
|
|
class CatalogObject(BaseObject):
|
|
|
|
|
|
|
|
def __format_ddr_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
groove = song.data.get_dict('groove')
|
|
|
|
return {
|
|
|
|
'editid': str(song.data.get_int('edit_id')),
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
|
|
|
'bpm_min': song.data.get_int('bpm_min'),
|
|
|
|
'bpm_max': song.data.get_int('bpm_max'),
|
|
|
|
'category': str(song.data.get_int('category')),
|
|
|
|
'groove': {
|
|
|
|
'air': groove.get_int('air'),
|
|
|
|
'chaos': groove.get_int('chaos'),
|
|
|
|
'freeze': groove.get_int('freeze'),
|
|
|
|
'stream': groove.get_int('stream'),
|
|
|
|
'voltage': groove.get_int('voltage'),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_iidx_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
|
|
|
'bpm_min': song.data.get_int('bpm_min'),
|
|
|
|
'bpm_max': song.data.get_int('bpm_max'),
|
|
|
|
'notecount': song.data.get_int('notecount'),
|
|
|
|
'category': str(int(song.id / 1000)),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_jubeat_song(self, song: Song) -> Dict[str, Any]:
|
2022-08-17 06:58:31 +02:00
|
|
|
# Map a default if the user hasn't imported the version DB. This is a nasty
|
|
|
|
# hack but we don't want to break existing installations.
|
|
|
|
defaultcategory = {
|
|
|
|
1: VersionConstants.JUBEAT,
|
|
|
|
2: VersionConstants.JUBEAT_RIPPLES,
|
|
|
|
3: VersionConstants.JUBEAT_KNIT,
|
|
|
|
4: VersionConstants.JUBEAT_COPIOUS,
|
|
|
|
5: VersionConstants.JUBEAT_SAUCER,
|
|
|
|
6: VersionConstants.JUBEAT_PROP,
|
|
|
|
7: VersionConstants.JUBEAT_QUBELL,
|
|
|
|
8: VersionConstants.JUBEAT_CLAN,
|
|
|
|
9: VersionConstants.JUBEAT_FESTO
|
|
|
|
}.get(int(song.id / 10000000), VersionConstants.JUBEAT)
|
|
|
|
# Map the category to the version numbers defined on BEMAPI.
|
|
|
|
categorymapping = {
|
|
|
|
VersionConstants.JUBEAT: '1',
|
|
|
|
VersionConstants.JUBEAT_RIPPLES: '2',
|
|
|
|
VersionConstants.JUBEAT_RIPPLES_APPEND: '2a',
|
|
|
|
VersionConstants.JUBEAT_KNIT: '3',
|
|
|
|
VersionConstants.JUBEAT_KNIT_APPEND: '3a',
|
|
|
|
VersionConstants.JUBEAT_COPIOUS: '4',
|
|
|
|
VersionConstants.JUBEAT_COPIOUS_APPEND: '4a',
|
|
|
|
VersionConstants.JUBEAT_SAUCER: '5',
|
|
|
|
VersionConstants.JUBEAT_SAUCER_FULFILL: '5a',
|
|
|
|
VersionConstants.JUBEAT_PROP: '6',
|
|
|
|
VersionConstants.JUBEAT_QUBELL: '7',
|
|
|
|
VersionConstants.JUBEAT_CLAN: '8',
|
|
|
|
VersionConstants.JUBEAT_FESTO: '9',
|
|
|
|
}
|
2019-12-08 22:43:49 +01:00
|
|
|
return {
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
2022-08-17 06:58:31 +02:00
|
|
|
'category': categorymapping.get(song.data.get_int('version', defaultcategory), '1'),
|
2019-12-08 22:43:49 +01:00
|
|
|
'bpm_min': song.data.get_int('bpm_min'),
|
|
|
|
'bpm_max': song.data.get_int('bpm_max'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_museca_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
|
|
|
'bpm_min': song.data.get_int('bpm_min'),
|
|
|
|
'bpm_max': song.data.get_int('bpm_max'),
|
|
|
|
'limited': song.data.get_int('limited'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_popn_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
|
|
|
'category': song.data.get_str('category'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_reflec_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
|
|
|
'category': str(song.data.get_int('folder')),
|
|
|
|
'musicid': song.data.get_str('chart_id'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_sdvx_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
|
|
|
'difficulty': song.data.get_int('difficulty'),
|
|
|
|
'bpm_min': song.data.get_int('bpm_min'),
|
|
|
|
'bpm_max': song.data.get_int('bpm_max'),
|
|
|
|
'limited': song.data.get_int('limited'),
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
base = {
|
|
|
|
'song': str(song.id),
|
|
|
|
'chart': str(song.chart),
|
|
|
|
'title': song.name or "",
|
|
|
|
'artist': song.artist or "",
|
|
|
|
'genre': song.genre or "",
|
|
|
|
}
|
|
|
|
|
|
|
|
if self.game == GameConstants.DDR:
|
|
|
|
base.update(self.__format_ddr_song(song))
|
|
|
|
if self.game == GameConstants.IIDX:
|
|
|
|
base.update(self.__format_iidx_song(song))
|
|
|
|
if self.game == GameConstants.JUBEAT:
|
|
|
|
base.update(self.__format_jubeat_song(song))
|
|
|
|
if self.game == GameConstants.MUSECA:
|
|
|
|
base.update(self.__format_museca_song(song))
|
|
|
|
if self.game == GameConstants.POPN_MUSIC:
|
|
|
|
base.update(self.__format_popn_song(song))
|
|
|
|
if self.game == GameConstants.REFLEC_BEAT:
|
|
|
|
base.update(self.__format_reflec_song(song))
|
|
|
|
if self.game == GameConstants.SDVX:
|
|
|
|
base.update(self.__format_sdvx_song(song))
|
|
|
|
|
|
|
|
return base
|
|
|
|
|
|
|
|
def __format_sdvx_extras(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
|
|
# Gotta look up the unlock catalog
|
|
|
|
items = self.data.local.game.get_items(self.game, self.version)
|
|
|
|
|
|
|
|
# Format it depending on the version
|
|
|
|
if self.version == 1:
|
|
|
|
return {
|
|
|
|
"purchases": [
|
|
|
|
{
|
|
|
|
"catalogid": str(item.id),
|
|
|
|
"song": str(item.data.get_int("musicid")),
|
|
|
|
"chart": str(item.data.get_int("chart")),
|
|
|
|
"price": item.data.get_int("blocks"),
|
|
|
|
}
|
|
|
|
for item in items
|
|
|
|
if item.type == "song_unlock"
|
|
|
|
],
|
|
|
|
"appealcards": [],
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
return {
|
|
|
|
"purchases": [],
|
|
|
|
"appealcards": [
|
|
|
|
{
|
|
|
|
"appealid": str(item.id),
|
|
|
|
"description": item.data.get_str("description"),
|
|
|
|
}
|
|
|
|
for item in items
|
|
|
|
if item.type == "appealcard"
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
2020-04-24 21:20:27 +02:00
|
|
|
def __format_jubeat_extras(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
|
|
# Gotta look up the unlock catalog
|
|
|
|
items = self.data.local.game.get_items(self.game, self.version)
|
|
|
|
|
|
|
|
# Format it depending on the version
|
|
|
|
if self.version in {
|
|
|
|
VersionConstants.JUBEAT_PROP,
|
|
|
|
VersionConstants.JUBEAT_QUBELL,
|
|
|
|
VersionConstants.JUBEAT_CLAN,
|
|
|
|
}:
|
|
|
|
return {
|
|
|
|
"emblems": [
|
|
|
|
{
|
|
|
|
"index": str(item.id),
|
|
|
|
"song": item.data.get_int("music_id"),
|
|
|
|
"layer": item.data.get_int("layer"),
|
|
|
|
"evolved": item.data.get_int("evolved"),
|
|
|
|
"rarity": item.data.get_int("rarity"),
|
|
|
|
"name": item.data.get_str("name"),
|
|
|
|
}
|
|
|
|
for item in items
|
|
|
|
if item.type == "emblem"
|
|
|
|
],
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
return {"emblems": []}
|
|
|
|
|
2021-05-17 23:51:45 +02:00
|
|
|
def __format_iidx_extras(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
|
|
# Gotta look up the unlock catalog
|
|
|
|
items = self.data.local.game.get_items(self.game, self.version)
|
|
|
|
|
|
|
|
return {
|
|
|
|
"qpros": [
|
|
|
|
{
|
|
|
|
"identifier": item.data.get_str("identifier"),
|
|
|
|
"id": str(item.id),
|
|
|
|
"name": item.data.get_str("name"),
|
|
|
|
"type": item.type[3:],
|
|
|
|
}
|
|
|
|
for item in items
|
|
|
|
if item.type in ['qp_body', 'qp_face', 'qp_hair', 'qp_hand', 'qp_head']
|
|
|
|
],
|
|
|
|
}
|
|
|
|
|
2019-12-08 22:43:49 +01:00
|
|
|
def __format_extras(self) -> Dict[str, List[Dict[str, Any]]]:
|
|
|
|
if self.game == GameConstants.SDVX:
|
|
|
|
return self.__format_sdvx_extras()
|
2020-04-24 21:20:27 +02:00
|
|
|
elif self.game == GameConstants.JUBEAT:
|
|
|
|
return self.__format_jubeat_extras()
|
2021-05-17 23:51:45 +02:00
|
|
|
elif self.game == GameConstants.IIDX:
|
|
|
|
return self.__format_iidx_extras()
|
2019-12-08 22:43:49 +01:00
|
|
|
else:
|
|
|
|
return {}
|
|
|
|
|
|
|
|
@property
|
|
|
|
def music_version(self) -> int:
|
2021-08-19 21:21:04 +02:00
|
|
|
if self.game in {GameConstants.IIDX, GameConstants.MUSECA}:
|
2020-12-15 09:02:56 +01:00
|
|
|
if self.omnimix:
|
|
|
|
return self.version + DBConstants.OMNIMIX_VERSION_BUMP
|
|
|
|
else:
|
|
|
|
return self.version
|
2019-12-08 22:43:49 +01:00
|
|
|
else:
|
|
|
|
return self.version
|
|
|
|
|
2021-08-19 21:21:41 +02:00
|
|
|
def fetch_v1(self, idtype: APIConstants, ids: List[str], params: Dict[str, Any]) -> Dict[str, List[Dict[str, Any]]]:
|
2019-12-08 22:43:49 +01:00
|
|
|
# Verify IDs
|
|
|
|
if idtype != APIConstants.ID_TYPE_SERVER:
|
|
|
|
raise APIException(
|
|
|
|
'Unsupported ID for lookup!',
|
|
|
|
405,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Fetch the songs
|
|
|
|
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
|
|
|
|
if self.game == GameConstants.JUBEAT and self.version == VersionConstants.JUBEAT_CLAN:
|
|
|
|
# There's always a special case. We don't store all music IDs since those in
|
|
|
|
# the range of 80000301-80000347 are actually the same song, but copy-pasted
|
|
|
|
# for different prefectures and slightly different charts. So, we need to copy
|
|
|
|
# that song data so that remote clients can resolve scores for those ID ranges.
|
|
|
|
additions: List[Song] = []
|
|
|
|
for song in songs:
|
|
|
|
if song.id == 80000301:
|
|
|
|
for idrange in range(80000302, 80000348):
|
|
|
|
additions.append(
|
|
|
|
Song(
|
|
|
|
song.game,
|
|
|
|
song.version,
|
|
|
|
idrange,
|
|
|
|
song.chart,
|
|
|
|
song.name,
|
|
|
|
song.artist,
|
|
|
|
song.genre,
|
|
|
|
song.data,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
songs.extend(additions)
|
|
|
|
retval = {
|
|
|
|
'songs': [self.__format_song(song) for song in songs],
|
|
|
|
}
|
|
|
|
|
|
|
|
# Fetch any optional extras per-game, return
|
|
|
|
retval.update(self.__format_extras())
|
|
|
|
return retval
|