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]:
|
2022-10-15 20:56:30 +02:00
|
|
|
groove = song.data.get_dict("groove")
|
2019-12-08 22:43:49 +01:00
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"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"),
|
2019-12-08 22:43:49 +01:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
def __format_iidx_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"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)),
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2022-10-15 20:56:30 +02:00
|
|
|
9: VersionConstants.JUBEAT_FESTO,
|
2022-08-17 06:58:31 +02:00
|
|
|
}.get(int(song.id / 10000000), VersionConstants.JUBEAT)
|
|
|
|
# Map the category to the version numbers defined on BEMAPI.
|
|
|
|
categorymapping = {
|
2022-10-15 20:56:30 +02:00
|
|
|
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",
|
2022-08-17 06:58:31 +02:00
|
|
|
}
|
2019-12-08 22:43:49 +01:00
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"difficulty": song.data.get_int("difficulty"),
|
|
|
|
"category": categorymapping.get(
|
|
|
|
song.data.get_int("version", defaultcategory), "1"
|
|
|
|
),
|
|
|
|
"bpm_min": song.data.get_int("bpm_min"),
|
|
|
|
"bpm_max": song.data.get_int("bpm_max"),
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __format_museca_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"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"),
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __format_popn_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"difficulty": song.data.get_int("difficulty"),
|
|
|
|
"category": song.data.get_str("category"),
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __format_reflec_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"difficulty": song.data.get_int("difficulty"),
|
|
|
|
"category": str(song.data.get_int("folder")),
|
|
|
|
"musicid": song.data.get_str("chart_id"),
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __format_sdvx_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
return {
|
2022-10-15 20:56:30 +02:00
|
|
|
"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"),
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
def __format_song(self, song: Song) -> Dict[str, Any]:
|
|
|
|
base = {
|
2022-10-15 20:56:30 +02:00
|
|
|
"song": str(song.id),
|
|
|
|
"chart": str(song.chart),
|
|
|
|
"title": song.name or "",
|
|
|
|
"artist": song.artist or "",
|
|
|
|
"genre": song.genre or "",
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2022-10-15 20:56:30 +02:00
|
|
|
if item.type in ["qp_body", "qp_face", "qp_hair", "qp_hand", "qp_head"]
|
2021-05-17 23:51:45 +02:00
|
|
|
],
|
|
|
|
}
|
|
|
|
|
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:
|
2022-10-25 15:59:13 +02:00
|
|
|
if self.game in {
|
|
|
|
GameConstants.IIDX,
|
|
|
|
GameConstants.MUSECA,
|
|
|
|
GameConstants.JUBEAT,
|
2023-03-02 10:10:49 +01:00
|
|
|
GameConstants.POPN_MUSIC,
|
2022-10-25 15:59:13 +02:00
|
|
|
}:
|
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
|
|
|
|
|
2022-10-15 20:56:30 +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(
|
2022-10-15 20:56:30 +02:00
|
|
|
"Unsupported ID for lookup!",
|
2019-12-08 22:43:49 +01:00
|
|
|
405,
|
|
|
|
)
|
|
|
|
|
|
|
|
# Fetch the songs
|
|
|
|
songs = self.data.local.music.get_all_songs(self.game, self.music_version)
|
2022-10-15 20:56:30 +02:00
|
|
|
if (
|
|
|
|
self.game == GameConstants.JUBEAT
|
|
|
|
and self.version == VersionConstants.JUBEAT_CLAN
|
|
|
|
):
|
2019-12-08 22:43:49 +01:00
|
|
|
# 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 = {
|
2022-10-15 20:56:30 +02:00
|
|
|
"songs": [self.__format_song(song) for song in songs],
|
2019-12-08 22:43:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# Fetch any optional extras per-game, return
|
|
|
|
retval.update(self.__format_extras())
|
|
|
|
return retval
|