push the same limit/offset changes for maimai and ongeki

This commit is contained in:
2024-11-15 21:36:16 +07:00
parent 37d07e6035
commit 2f59d7b0d6
12 changed files with 952 additions and 540 deletions

View File

@ -282,11 +282,11 @@ class ChuniBase:
max_ct = int(data["maxCount"])
# add one to the limit so we know if there's a next page of items
characters = await self.data.item.get_characters(
rows = await self.data.item.get_characters(
user_id, limit=max_ct + 1, offset=next_idx
)
if characters is None:
if rows is None or len(rows) == 0:
return {
"userId": user_id,
"length": 0,
@ -294,16 +294,16 @@ class ChuniBase:
"userCharacterList": [],
}
character_count = len(characters)
character_list = []
for x in range(min(character_count, max_ct)):
tmp = characters[x]._asdict()
tmp.pop("user")
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("id")
tmp.pop("user")
character_list.append(tmp)
if character_count > max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = -1
@ -357,8 +357,8 @@ class ChuniBase:
course_list = []
for x in range(min(len(rows), max_ct)):
tmp = rows[x]._asdict()
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("user")
tmp.pop("id")
course_list.append(tmp)
@ -438,14 +438,14 @@ class ChuniBase:
rival_levels = [int(x["level"]) for x in data["userRivalMusicLevelList"]]
# Fetch all the rival music entries for the user
music_detail_rows = await self.data.score.get_scores(
rows = await self.data.score.get_scores(
rival_id,
levels=rival_levels,
limit=max_ct + 1,
offset=next_idx,
)
if music_detail_rows is None or len(music_detail_rows) == 0:
if rows is None or len(rows) == 0:
return {
"userId": user_id,
"rivalId": rival_id,
@ -453,7 +453,7 @@ class ChuniBase:
"userRivalMusicList": [],
}
music_details = [x._asdict() for x in music_detail_rows]
music_details = [x._asdict() for x in rows]
returned_music_details_count = 0
music_list = []
@ -473,7 +473,7 @@ class ChuniBase:
# if we returned fewer PBs than we originally asked for from the database, that means
# we queried for the PBs of max_ct + 1 songs.
if returned_music_details_count < len(music_detail_rows):
if returned_music_details_count < len(rows):
next_idx += max_ct
else:
next_idx = -1
@ -497,7 +497,7 @@ class ChuniBase:
# still needs to be implemented on WebUI
# 1: Music, 2: User, 3: Character
fav_list = await self.data.item.get_all_favorites(
rows = await self.data.item.get_all_favorites(
user_id,
self.version,
fav_kind=kind,
@ -505,11 +505,11 @@ class ChuniBase:
offset=next_idx,
)
if fav_list is not None:
for fav in fav_list:
if rows is not None:
for fav in rows[:max_ct]:
user_fav_item_list.append({"id": fav["favId"]})
if fav_list is None or len(fav_list) <= max_ct:
if rows is None or len(rows) <= max_ct:
next_idx = -1
else:
next_idx += max_ct
@ -550,8 +550,8 @@ class ChuniBase:
items: List[Dict[str, Any]] = []
for i in range(min(len(rows), max_ct)):
tmp = rows[i]._asdict()
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("user")
tmp.pop("id")
items.append(tmp)

View File

@ -307,8 +307,8 @@ class ChuniNew(ChuniBase):
print_list = []
for i in range(min(max_ct, len(rows))):
tmp = rows[i]._asdict()
for row in rows[:max_ct]:
tmp = row._asdict()
print_list.append(tmp["cardId"])
if len(rows) > max_ct:
@ -317,7 +317,7 @@ class ChuniNew(ChuniBase):
next_idx = -1
return {
"userId": data["userId"],
"userId": user_id,
"length": len(print_list),
"nextIndex": next_idx,
"userPrintedCardList": print_list,

View File

@ -384,7 +384,7 @@ class ChuniItemData(BaseData):
version: int,
fav_kind: int = 1,
limit: Optional[int] = None,
offset: int = 0,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(favorite).where(
and_(
@ -394,8 +394,12 @@ class ChuniItemData(BaseData):
)
)
if limit is not None or offset is not None:
sql = sql.order_by(favorite.c.id)
if limit is not None:
sql = sql.order_by(favorite.c.id.asc()).limit(limit).offset(offset)
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -497,12 +501,16 @@ class ChuniItemData(BaseData):
return result.fetchone()
async def get_characters(
self, user_id: int, limit: Optional[int] = None, offset: int = 0
self, user_id: int, limit: Optional[int] = None, offset: Optional[int] = None
) -> Optional[List[Row]]:
sql = select(character).where(character.c.user == user_id)
if limit is not None or offset is not None:
sql = sql.order_by(character.c.id)
if limit is not None:
sql = sql.limit(limit).offset(offset).order_by(character.c.id.asc())
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -527,17 +535,21 @@ class ChuniItemData(BaseData):
user_id: int,
kind: Optional[int] = None,
limit: Optional[int] = None,
offset: int = 0
offset: Optional[int] = None,
) -> Optional[List[Row]]:
if kind is None:
sql = select(item).where(item.c.user == user_id)
else:
sql = select(item).where(
and_(item.c.user == user_id, item.c.itemKind == kind)
)
cond = item.c.user == user_id
if kind is not None:
cond &= item.c.itemKind == kind
sql = select(item).where(cond)
if limit is not None or offset is not None:
sql = sql.order_by(item.c.id)
if limit is not None:
sql = sql.order_by(item.c.id.asc()).limit(limit).offset(offset)
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -635,7 +647,7 @@ class ChuniItemData(BaseData):
aime_id: int,
has_completed: bool = False,
limit: Optional[int] = None,
offset: int = 0,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(print_state).where(
and_(
@ -644,8 +656,12 @@ class ChuniItemData(BaseData):
)
)
if limit is not None or offset is not None:
sql = sql.order_by(print_state.c.id)
if limit is not None:
sql = sql.order_by(print_state.c.id.asc()).limit(limit).offset(offset)
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:

View File

@ -1,13 +1,11 @@
from typing import Dict, List, Optional, Union
from typing import Dict, List, Optional
from sqlalchemy import Column, PrimaryKeyConstraint, Table, UniqueConstraint, and_
from sqlalchemy import Column, Table, UniqueConstraint
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.engine.base import Connection
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.sql.expression import exists
from sqlalchemy.types import JSON, TIMESTAMP, BigInteger, Boolean, Integer, String
from sqlalchemy.types import Boolean, Integer, String
from core.data.schema import BaseData, metadata
@ -232,11 +230,20 @@ class ChuniRomVersion():
return -1
class ChuniScoreData(BaseData):
async def get_courses(self, aime_id: int, limit: Optional[int] = None, offset: int = 0) -> Optional[List[Row]]:
async def get_courses(
self,
aime_id: int,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(course).where(course.c.user == aime_id)
if limit is not None or offset is not None:
sql = sql.order_by(course.c.id)
if limit is not None:
sql = sql.order_by(course.c.id.asc()).limit(limit).offset(offset)
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -260,14 +267,14 @@ class ChuniScoreData(BaseData):
aime_id: int,
levels: Optional[list[int]] = None,
limit: Optional[int] = None,
offset: int = 0,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
condition = best_score.c.user == aime_id
if levels is not None:
condition &= best_score.c.level.in_(levels)
if limit is None:
if limit is None and offset is None:
sql = (
select(best_score)
.where(condition)
@ -278,16 +285,21 @@ class ChuniScoreData(BaseData):
select(best_score.c.musicId)
.distinct()
.where(condition)
.order_by(best_score.c.musicId.asc())
.limit(limit)
.offset(offset)
.subquery()
.order_by(best_score.c.musicId)
)
if limit is not None:
subq = subq.limit(limit)
if offset is not None:
subq = subq.offset(offset)
subq = subq.subquery()
sql = (
select(best_score)
.join(subq, best_score.c.musicId == subq.c.musicId)
.where(condition)
.order_by(best_score.c.musicId.asc(), best_score.c.level.asc())
.order_by(best_score.c.musicId, best_score.c.level)
)
result = await self.execute(sql)

View File

@ -1,16 +1,17 @@
from datetime import datetime, timedelta
from typing import Any, Dict, List
import itertools
import logging
from base64 import b64decode
from os import path, stat, remove, mkdir, access, W_OK
from PIL import ImageFile
from random import randint
from datetime import datetime, timedelta
from os import W_OK, access, mkdir, path
from typing import Any, Dict, List
import pytz
from core.config import CoreConfig
from core.utils import Utils
from .const import Mai2Constants
from .config import Mai2Config
from .const import Mai2Constants
from .database import Mai2Data
@ -444,23 +445,22 @@ class Mai2Base:
return {"userId": data["userId"], "userOption": options_dict}
async def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = await self.data.item.get_cards(data["userId"])
if user_cards is None:
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
user_id = int(data["userId"])
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
user_cards = await self.data.item.get_cards(
user_id, limit=max_ct + 1, offset=next_idx
)
if len(user_cards[start_idx:]) > max_ct:
next_idx += max_ct
else:
next_idx = 0
if user_cards is None or len(user_cards) == 0:
return {"userId": user_id, "nextIndex": 0, "userCardList": []}
card_list = []
for card in user_cards:
for card in user_cards[:max_ct]:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["startDate"] = datetime.strftime(
@ -469,12 +469,18 @@ class Mai2Base:
tmp["endDate"] = datetime.strftime(
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(tmp)
if len(user_cards) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx],
"userCardList": card_list,
}
async def handle_get_user_charge_api_request(self, data: Dict) -> Dict:
@ -536,28 +542,35 @@ class Mai2Base:
return { "userId": data.get("userId", 0), "userBossData": boss_lst}
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = int(data["nextIndex"] / 10000000000)
next_idx = int(data["nextIndex"] % 10000000000)
user_item_list = await self.data.item.get_items(data["userId"], kind)
user_id: int = data["userId"]
kind: int = data["nextIndex"] // 10000000000
next_idx: int = data["nextIndex"] % 10000000000
max_ct: int = data["maxCount"]
rows = await self.data.item.get_items(user_id, kind, limit=max_ct, offset=next_idx)
if rows is None or len(rows) == 0:
return {
"userId": user_id,
"nextIndex": 0,
"itemKind": kind,
"userItemList": [],
}
items: List[Dict[str, Any]] = []
for i in range(next_idx, len(user_item_list)):
tmp = user_item_list[i]._asdict()
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("user")
tmp.pop("id")
items.append(tmp)
if len(items) >= int(data["maxCount"]):
break
xout = kind * 10000000000 + next_idx + len(items)
if len(items) < int(data["maxCount"]):
next_idx = 0
if len(rows) > max_ct:
next_idx = kind * 10000000000 + next_idx + max_ct
else:
next_idx = xout
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"itemKind": kind,
"userItemList": items,
@ -675,77 +688,90 @@ class Mai2Base:
return {"length": 0, "userPortraitList": []}
async def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
friend_season_ranking = await self.data.item.get_friend_season_ranking(data["userId"])
if friend_season_ranking is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_friend_season_ranking(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": 0,
"userFriendSeasonRankingList": [],
}
friend_season_ranking_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
for x in range(next_idx, len(friend_season_ranking)):
tmp = friend_season_ranking[x]._asdict()
tmp.pop("user")
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["recordDate"] = datetime.strftime(
tmp["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
)
friend_season_ranking_list.append(tmp)
if len(friend_season_ranking_list) >= max_ct:
break
if len(friend_season_ranking) >= next_idx + max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userFriendSeasonRankingList": friend_season_ranking_list,
}
async def handle_get_user_map_api_request(self, data: Dict) -> Dict:
maps = await self.data.item.get_maps(data["userId"])
if maps is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_maps(
user_id, limit=max_ct + 1, offset=next_idx,
)
if rows is None:
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": 0,
"userMapList": [],
}
map_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
for x in range(next_idx, len(maps)):
tmp = maps[x]._asdict()
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("user")
tmp.pop("id")
map_list.append(tmp)
if len(map_list) >= max_ct:
break
if len(maps) >= next_idx + max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userMapList": map_list,
}
async def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
login_bonuses = await self.data.item.get_login_bonuses(data["userId"])
if login_bonuses is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_login_bonuses(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"nextIndex": 0,
@ -753,25 +779,20 @@ class Mai2Base:
}
login_bonus_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
for x in range(next_idx, len(login_bonuses)):
tmp = login_bonuses[x]._asdict()
for row in rows[:max_ct]:
tmp = row._asdict()
tmp.pop("user")
tmp.pop("id")
login_bonus_list.append(tmp)
if len(login_bonus_list) >= max_ct:
break
if len(login_bonuses) >= next_idx + max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userLoginBonusList": login_bonus_list,
}
@ -805,42 +826,54 @@ class Mai2Base:
return {"userId": data["userId"], "userGradeStatus": grade_stat, "length": 0, "userGradeList": []}
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
next_index = data.get("nextIndex", 0)
max_ct = data.get("maxCount", 50)
upper_lim = next_index + max_ct
music_detail_list = []
user_id: int = data.get("userId", 0)
next_idx: int = data.get("nextIndex", 0)
max_ct: int = data.get("maxCount", 50)
if user_id <= 0:
self.logger.warning("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
return {}
songs = await self.data.score.get_best_scores(user_id, is_dx=False)
if songs is None:
rows = await self.data.score.get_best_scores(
user_id, is_dx=False, limit=max_ct + 1, offset=next_idx
)
if rows is None:
self.logger.debug("handle_get_user_music_api_request: get_best_scores returned None!")
return {
"userId": data["userId"],
"nextIndex": 0,
"userMusicList": [],
}
"userId": user_id,
"nextIndex": 0,
"userMusicList": [],
}
num_user_songs = len(songs)
music_details = [row._asdict() for row in rows]
returned_count = 0
music_list = []
for x in range(next_index, upper_lim):
if num_user_songs <= x:
for _music_id, details_iter in itertools.groupby(music_details, key=lambda d: d["musicId"]):
details: list[dict[Any, Any]] = []
for d in details_iter:
d.pop("id")
d.pop("user")
details.append(d)
music_list.append({"userMusicDetailList": details})
returned_count += len(details)
if len(music_list) >= max_ct:
break
if returned_count < len(rows):
next_idx += max_ct
else:
next_idx = 0
tmp = songs[x]._asdict()
tmp.pop("id")
tmp.pop("user")
music_detail_list.append(tmp)
next_index = 0 if len(music_detail_list) < max_ct or num_user_songs == upper_lim else upper_lim
self.logger.info(f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})")
return {
"userId": data["userId"],
"nextIndex": next_index,
"userMusicList": [{"userMusicDetailList": music_detail_list}],
"userId": user_id,
"nextIndex": next_idx,
"userMusicList": music_list,
}
async def handle_upload_user_portrait_api_request(self, data: Dict) -> Dict:
@ -925,30 +958,52 @@ class Mai2Base:
async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
kind = data.get("kind", 0) # 1 is fav music, 2 is rival user IDs
next_index = data.get("nextIndex", 0)
next_idx = data.get("nextIndex", 0)
max_ct = data.get("maxCount", 100) # always 100
is_all = data.get("isAllFavoriteItem", False) # always false
empty_resp = {
"userId": user_id,
"kind": kind,
"nextIndex": 0,
"userFavoriteItemList": [],
}
if not user_id or kind not in (1, 2):
return empty_resp
id_list: List[Dict] = []
if user_id:
if kind == 1:
fav_music = await self.data.item.get_fav_music(user_id)
if fav_music:
for fav in fav_music:
id_list.append({"orderId": fav["orderId"] or 0, "id": fav["musicId"]})
if len(id_list) >= 100: # Lazy but whatever
break
elif kind == 2:
rivals = await self.data.profile.get_rivals_game(user_id)
if rivals:
for rival in rivals:
id_list.append({"orderId": 0, "id": rival["rival"]})
if kind == 1:
rows = await self.data.item.get_fav_music(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return empty_resp
for row in rows[:max_ct]:
id_list.append({"orderId": row["orderId"] or 0, "id": row["musicId"]})
elif kind == 2:
rows = await self.data.profile.get_rivals_game(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return empty_resp
for row in rows[:max_ct]:
id_list.append({"orderId": 0, "id": row["rival"]})
if rows is None or len(rows) <= max_ct:
next_idx = 0
else:
next_idx += max_ct
return {
"userId": user_id,
"kind": kind,
"nextIndex": 0,
"nextIndex": next_idx,
"userFavoriteItemList": id_list,
}
@ -964,5 +1019,4 @@ class Mai2Base:
"""
return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []}
async def handle_get_user_score_ranking_api_request(self, data: Dict) ->Dict:
return {"userId": data["userId"], "userScoreRanking": []}
return {"userId": data["userId"], "userScoreRanking": []}

View File

@ -1,8 +1,9 @@
from typing import Any, List, Dict
import itertools
from datetime import datetime, timedelta
import pytz
import json
from random import randint
from typing import Any, Dict, List
import pytz
from core.config import CoreConfig
from core.utils import Utils
@ -309,83 +310,112 @@ class Mai2DX(Mai2Base):
return {"userId": data["userId"], "userOption": options_dict}
async def handle_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = await self.data.item.get_cards(data["userId"])
if user_cards is None:
return {"userId": data["userId"], "nextIndex": 0, "userCardList": []}
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_cards(user_id, limit=max_ct + 1, offset=next_idx)
if rows is None:
return {"userId": user_id, "nextIndex": 0, "userCardList": []}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
card_list = []
if len(user_cards[start_idx:]) > max_ct:
for row in rows[:max_ct]:
card = row._asdict()
card.pop("id")
card.pop("user")
card["startDate"] = datetime.strftime(
card["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
card["endDate"] = datetime.strftime(
card["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(card)
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
card_list = []
for card in user_cards:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["endDate"] = datetime.strftime(
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(tmp)
return {
"userId": data["userId"],
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx],
"userCardList": card_list,
}
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = data["nextIndex"] // 10000000000
next_idx = data["nextIndex"] % 10000000000
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
kind = next_idx // 10000000000
next_idx = next_idx % 10000000000
items: List[Dict[str, Any]] = []
if kind == 4: # presents
user_pres_list = await self.data.item.get_presents_by_version_user(self.version, data["userId"])
if user_pres_list:
self.logger.debug(f"Found {len(user_pres_list)} possible presents")
for present in user_pres_list:
if (present['startDate'] and present['startDate'].timestamp() > datetime.now().timestamp()):
self.logger.debug(f"Present {present['id']} distribution hasn't started yet (begins {present['startDate']})")
continue # present period hasn't started yet, move onto the next one
if (present['endDate'] and present['endDate'].timestamp() < datetime.now().timestamp()):
self.logger.warn(f"Present {present['id']} ended on {present['endDate']} and should be removed")
continue # present period ended, move onto the next one
test = await self.data.item.get_item(data["userId"], present['itemKind'], present['itemId'])
if not test: # Don't send presents for items the user already has
pres_id = present['itemKind'] * 1000000
pres_id += present['itemId']
items.append({"itemId": pres_id, "itemKind": 4, "stock": present['stock'], "isValid": True})
self.logger.info(f"Give user {data['userId']} {present['stock']}x item {present['itemId']} (kind {present['itemKind']}) as present")
rows = await self.data.item.get_presents_by_version_user(
version=self.version,
user_id=user_id,
exclude_owned=True,
exclude_not_in_present_period=True,
limit=max_ct + 1,
offset=next_idx,
)
if rows is None:
return {
"userId": user_id,
"nextIndex": 0,
"itemKind": kind,
"userItemList": [],
}
for row in rows[:max_ct]:
self.logger.info(
f"Give user {user_id} {row['stock']}x item {row['itemId']} (kind {row['itemKind']}) as present"
)
items.append(
{
"itemId": row["itemKind"] * 1000000 + row["itemId"],
"itemKind": kind,
"stock": row["stock"],
"isValid": True,
}
)
else:
user_item_list = await self.data.item.get_items(data["userId"], kind)
for i in range(next_idx, len(user_item_list)):
tmp = user_item_list[i]._asdict()
tmp.pop("user")
tmp.pop("id")
items.append(tmp)
if len(items) >= int(data["maxCount"]):
break
rows = await self.data.item.get_items(
user_id=user_id,
item_kind=kind,
limit=max_ct + 1,
offset=next_idx,
)
xout = kind * 10000000000 + next_idx + len(items)
if rows is None:
return {
"userId": user_id,
"nextIndex": 0,
"itemKind": kind,
"userItemList": [],
}
if len(items) < int(data["maxCount"]):
for row in rows[:max_ct]:
item = row._asdict()
item.pop("id")
item.pop("user")
items.append(item)
if len(rows) > max_ct:
next_idx = kind * 10000000000 + next_idx + max_ct
else:
next_idx = 0
else:
next_idx = xout
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"itemKind": kind,
"userItemList": items,
@ -491,103 +521,115 @@ class Mai2DX(Mai2Base):
return {"length": 0, "userPortraitList": []}
async def handle_get_user_friend_season_ranking_api_request(self, data: Dict) -> Dict:
friend_season_ranking = await self.data.item.get_friend_season_ranking(data["userId"])
if friend_season_ranking is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_friend_season_ranking(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": 0,
"userFriendSeasonRankingList": [],
}
friend_season_ranking_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
for x in range(next_idx, len(friend_season_ranking)):
tmp = friend_season_ranking[x]._asdict()
tmp.pop("user")
tmp.pop("id")
tmp["recordDate"] = datetime.strftime(
tmp["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
for row in rows[:max_ct]:
friend_season_ranking = row._asdict()
friend_season_ranking.pop("user")
friend_season_ranking.pop("id")
friend_season_ranking["recordDate"] = datetime.strftime(
friend_season_ranking["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
)
friend_season_ranking_list.append(tmp)
friend_season_ranking_list.append(friend_season_ranking)
if len(friend_season_ranking_list) >= max_ct:
break
if len(friend_season_ranking) >= next_idx + max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userFriendSeasonRankingList": friend_season_ranking_list,
}
async def handle_get_user_map_api_request(self, data: Dict) -> Dict:
maps = await self.data.item.get_maps(data["userId"])
if maps is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_maps(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": 0,
"userMapList": [],
}
map_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
for x in range(next_idx, len(maps)):
tmp = maps[x]._asdict()
tmp.pop("user")
tmp.pop("id")
map_list.append(tmp)
for row in rows[:max_ct]:
map = row._asdict()
map.pop("user")
map.pop("id")
map_list.append(map)
if len(map_list) >= max_ct:
break
if len(maps) >= next_idx + max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userMapList": map_list,
}
async def handle_get_user_login_bonus_api_request(self, data: Dict) -> Dict:
login_bonuses = await self.data.item.get_login_bonuses(data["userId"])
if login_bonuses is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_login_bonuses(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": 0,
"userLoginBonusList": [],
}
login_bonus_list = []
next_idx = int(data["nextIndex"])
max_ct = int(data["maxCount"])
for x in range(next_idx, len(login_bonuses)):
tmp = login_bonuses[x]._asdict()
tmp.pop("user")
tmp.pop("id")
login_bonus_list.append(tmp)
for row in rows[:max_ct]:
login_bonus = row._asdict()
login_bonus.pop("user")
login_bonus.pop("id")
login_bonus_list.append(login_bonus)
if len(login_bonus_list) >= max_ct:
break
if len(login_bonuses) >= next_idx + max_ct:
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"nextIndex": next_idx,
"userLoginBonusList": login_bonus_list,
}
@ -619,46 +661,62 @@ class Mai2DX(Mai2Base):
}
async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
rival_id = data.get("rivalId", 0)
next_index = data.get("nextIndex", 0)
max_ct = 100
upper_lim = next_index + max_ct
rival_music_list: Dict[int, List] = {}
user_id: int = data["userId"]
rival_id: int = data["rivalId"]
next_idx: int = data["nextIndex"]
max_ct: int = 100
levels: list[int] = [x["level"] for x in data["userRivalMusicLevelList"]]
songs = await self.data.score.get_best_scores(rival_id)
if songs is None:
rows = await self.data.score.get_best_scores(
rival_id,
is_dx=True,
limit=max_ct + 1,
offset=next_idx,
levels=levels,
)
if rows is None:
self.logger.debug("handle_get_user_rival_music_api_request: get_best_scores returned None!")
return {
"userId": user_id,
"rivalId": rival_id,
"nextIndex": 0,
"userRivalMusicList": [] # musicId userRivalMusicDetailList -> level achievement deluxscoreMax
}
music_details = [x._asdict() for x in rows]
returned_count = 0
music_list = []
num_user_songs = len(songs)
for music_id, details_iter in itertools.groupby(music_details, key=lambda x: x["musicId"]):
details: list[dict[Any, Any]] = []
for x in range(next_index, upper_lim):
if x >= num_user_songs:
for d in details_iter:
details.append(
{
"level": d["level"],
"achievement": d["achievement"],
"deluxscoreMax": d["deluxscoreMax"],
}
)
music_list.append({"musicId": music_id, "userRivalMusicDetailList": details})
returned_count += len(details)
if len(music_list) >= max_ct:
break
tmp = songs[x]._asdict()
if tmp['musicId'] in rival_music_list:
rival_music_list[tmp['musicId']].append([{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}])
else:
if len(rival_music_list) >= max_ct:
break
rival_music_list[tmp['musicId']] = [{"level": tmp['level'], 'achievement': tmp['achievement'], 'deluxscoreMax': tmp['deluxscoreMax']}]
next_index = 0 if len(rival_music_list) < max_ct or num_user_songs == upper_lim else upper_lim
self.logger.info(f"Send rival {rival_id} songs {next_index}-{upper_lim} ({len(rival_music_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})")
if returned_count < len(rows):
next_idx += max_ct
else:
next_idx = 0
return {
"userId": user_id,
"rivalId": rival_id,
"nextIndex": next_index,
"userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()]
"nextIndex": next_idx,
"userRivalMusicList": music_list,
}
async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict:
@ -674,42 +732,55 @@ class Mai2DX(Mai2Base):
}
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
user_id = data.get("userId", 0)
next_index = data.get("nextIndex", 0)
max_ct = data.get("maxCount", 50)
upper_lim = next_index + max_ct
music_detail_list = []
user_id: int = data.get("userId", 0)
next_idx: int = data.get("nextIndex", 0)
max_ct: int = data.get("maxCount", 50)
if user_id <= 0:
self.logger.warning("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
return {}
songs = await self.data.score.get_best_scores(user_id)
if songs is None:
rows = await self.data.score.get_best_scores(
user_id, is_dx=True, limit=max_ct + 1, offset=next_idx
)
if rows is None:
self.logger.debug("handle_get_user_music_api_request: get_best_scores returned None!")
return {
"userId": data["userId"],
"nextIndex": 0,
"userMusicList": [],
}
"userId": user_id,
"nextIndex": 0,
"userMusicList": [],
}
num_user_songs = len(songs)
music_details = [row._asdict() for row in rows]
returned_count = 0
music_list = []
for x in range(next_index, upper_lim):
if num_user_songs <= x:
for _music_id, details_iter in itertools.groupby(music_details, key=lambda d: d["musicId"]):
details: list[dict[Any, Any]] = []
for d in details_iter:
d.pop("id")
d.pop("user")
details.append(d)
music_list.append({"userMusicDetailList": details})
returned_count += len(details)
if len(music_list) >= max_ct:
break
if returned_count < len(rows):
next_idx += max_ct
else:
next_idx = 0
tmp = songs[x]._asdict()
tmp.pop("id")
tmp.pop("user")
music_detail_list.append(tmp)
next_index = 0 if len(music_detail_list) < max_ct or num_user_songs == upper_lim else upper_lim
self.logger.info(f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})")
return {
"userId": data["userId"],
"nextIndex": next_index,
"userMusicList": [{"userMusicDetailList": music_detail_list}],
"userId": user_id,
"nextIndex": next_idx,
"userMusicList": music_list,
}
async def handle_user_login_api_request(self, data: Dict) -> Dict:
@ -812,39 +883,43 @@ class Mai2DX(Mai2Base):
return {"length": len(selling_card_list), "sellingCardList": selling_card_list}
async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = await self.data.item.get_cards(data["userId"])
if user_cards is None:
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.item.get_cards(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {"returnCode": 1, "length": 0, "nextIndex": 0, "userCardList": []}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
card_list = []
if len(user_cards[start_idx:]) > max_ct:
for row in rows[:max_ct]:
card = row._asdict()
card.pop("id")
card.pop("user")
card["startDate"] = datetime.strftime(
card["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
card["endDate"] = datetime.strftime(
card["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(card)
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = 0
card_list = []
for card in user_cards:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("user")
tmp["startDate"] = datetime.strftime(
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
)
tmp["endDate"] = datetime.strftime(
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
)
card_list.append(tmp)
return {
"returnCode": 1,
"length": len(card_list[start_idx:end_idx]),
"length": len(card_list),
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx],
"userCardList": card_list,
}
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:

View File

@ -1,15 +1,16 @@
from core.data.schema import BaseData, metadata
from datetime import datetime
from typing import Optional, Dict, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, or_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BIGINT, INTEGER
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from typing import Dict, List, Optional
from sqlalchemy import Column, Table, UniqueConstraint, and_, or_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.types import BIGINT, INTEGER, JSON, TIMESTAMP, Boolean, Integer, String
character = Table(
from core.data.schema import BaseData, metadata
character: Table = Table(
"mai2_item_character",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -27,7 +28,7 @@ character = Table(
mysql_charset="utf8mb4",
)
card = Table(
card: Table = Table(
"mai2_item_card",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -46,7 +47,7 @@ card = Table(
mysql_charset="utf8mb4",
)
item = Table(
item: Table = Table(
"mai2_item_item",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -63,7 +64,7 @@ item = Table(
mysql_charset="utf8mb4",
)
map = Table(
map: Table = Table(
"mai2_item_map",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -81,7 +82,7 @@ map = Table(
mysql_charset="utf8mb4",
)
login_bonus = Table(
login_bonus: Table = Table(
"mai2_item_login_bonus",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -98,7 +99,7 @@ login_bonus = Table(
mysql_charset="utf8mb4",
)
friend_season_ranking = Table(
friend_season_ranking: Table = Table(
"mai2_item_friend_season_ranking",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -134,7 +135,7 @@ favorite = Table(
mysql_charset="utf8mb4",
)
fav_music = Table(
fav_music: Table = Table(
"mai2_item_favorite_music",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -199,7 +200,7 @@ print_detail = Table(
mysql_charset="utf8mb4",
)
present = Table(
present: Table = Table(
"mai2_item_present",
metadata,
Column('id', BIGINT, primary_key=True, nullable=False),
@ -239,13 +240,26 @@ class Mai2ItemData(BaseData):
return None
return result.lastrowid
async def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]:
if item_kind is None:
sql = item.select(item.c.user == user_id)
else:
sql = item.select(
and_(item.c.user == user_id, item.c.itemKind == item_kind)
)
async def get_items(
self,
user_id: int,
item_kind: Optional[int] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
cond = item.c.user == user_id
if item_kind is not None:
cond &= item.c.itemKind == item_kind
sql = select(item).where(cond)
if limit is not None or offset is not None:
sql = sql.order_by(item.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -296,8 +310,20 @@ class Mai2ItemData(BaseData):
return None
return result.lastrowid
async def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]:
sql = login_bonus.select(login_bonus.c.user == user_id)
async def get_login_bonuses(
self,
user_id: int,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(login_bonus).where(login_bonus.c.user == user_id)
if limit is not None or offset is not None:
sql = sql.order_by(login_bonus.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -347,8 +373,20 @@ class Mai2ItemData(BaseData):
return None
return result.lastrowid
async def get_maps(self, user_id: int) -> Optional[List[Row]]:
sql = map.select(map.c.user == user_id)
async def get_maps(
self,
user_id: int,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(map).where(map.c.user == user_id)
if limit is not None or offset is not None:
sql = sql.order_by(map.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -424,8 +462,20 @@ class Mai2ItemData(BaseData):
return None
return result.fetchone()
async def get_friend_season_ranking(self, user_id: int) -> Optional[Row]:
sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id)
async def get_friend_season_ranking(
self,
user_id: int,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(friend_season_ranking).where(friend_season_ranking.c.user == user_id)
if limit is not None or offset is not None:
sql = sql.order_by(friend_season_ranking.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -480,8 +530,23 @@ class Mai2ItemData(BaseData):
return None
return result.fetchall()
async def get_fav_music(self, user_id: int) -> Optional[List[Row]]:
result = await self.execute(fav_music.select(fav_music.c.user == user_id))
async def get_fav_music(
self,
user_id: int,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(fav_music).where(fav_music.c.user == user_id)
if limit is not None or offset is not None:
sql = sql.order_by(fav_music.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result:
return result.fetchall()
@ -537,13 +602,24 @@ class Mai2ItemData(BaseData):
return None
return result.lastrowid
async def get_cards(self, user_id: int, kind: int = None) -> Optional[Row]:
if kind is None:
sql = card.select(card.c.user == user_id)
else:
sql = card.select(and_(card.c.user == user_id, card.c.cardKind == kind))
async def get_cards(
self,
user_id: int,
kind: Optional[int] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
condition = card.c.user == user_id
sql = sql.order_by(card.c.startDate.desc())
if kind is not None:
condition &= card.c.cardKind == kind
sql = select(card).where(condition).order_by(card.c.startDate.desc(), card.c.id.asc())
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
@ -634,13 +710,46 @@ class Mai2ItemData(BaseData):
if result:
return result.fetchall()
async def get_presents_by_version_user(self, ver: int = None, user_id: int = None) -> Optional[List[Row]]:
result = await self.execute(present.select(
and_(
or_(present.c.user == user_id, present.c.user == None),
or_(present.c.version == ver, present.c.version == None)
async def get_presents_by_version_user(
self,
version: Optional[int] = None,
user_id: Optional[int] = None,
exclude_owned: bool = False,
exclude_not_in_present_period: bool = False,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(present)
condition = (
((present.c.user == user_id) | present.c.user.is_(None))
& ((present.c.version == version) | present.c.version.is_(None))
)
# Do an anti-join with the mai2_item_item table to exclude any
# items the users have already owned.
if exclude_owned:
sql = sql.join(
item,
(present.c.itemKind == item.c.itemKind)
& (present.c.itemId == item.c.itemId)
)
))
condition &= (item.c.itemKind.is_(None) & item.c.itemId.is_(None))
if exclude_not_in_present_period:
condition &= (present.c.startDate.is_(None) | (present.c.startDate <= func.now()))
condition &= (present.c.endDate.is_(None) | (present.c.endDate >= func.now()))
sql = sql.where(condition)
if limit is not None or offset is not None:
sql = sql.order_by(present.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result:
return result.fetchall()

View File

@ -1,15 +1,26 @@
from core.data.schema import BaseData, metadata
from titles.mai2.const import Mai2Constants
from datetime import datetime
from typing import Dict, List, Optional
from uuid import uuid4
from typing import Optional, Dict, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger, SmallInteger, VARCHAR, INTEGER
from sqlalchemy import Column, Table, UniqueConstraint, and_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row
from sqlalchemy.dialects.mysql import insert
from datetime import datetime
from sqlalchemy.types import (
INTEGER,
JSON,
TIMESTAMP,
VARCHAR,
BigInteger,
Boolean,
Integer,
SmallInteger,
String,
)
from core.data.schema import BaseData, metadata
from titles.mai2.const import Mai2Constants
detail = Table(
"mai2_profile_detail",
@ -495,7 +506,7 @@ consec_logins = Table(
mysql_charset="utf8mb4",
)
rival = Table(
rival: Table = Table(
"mai2_user_rival",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -908,8 +919,23 @@ class Mai2ProfileData(BaseData):
if result:
return result.fetchall()
async def get_rivals_game(self, user_id: int) -> Optional[List[Row]]:
result = await self.execute(rival.select(and_(rival.c.user == user_id, rival.c.show == True)).limit(3))
async def get_rivals_game(
self,
user_id: int,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
sql = select(rival).where((rival.c.user == user_id) & rival.c.show.is_(True))
if limit is not None or offset is not None:
sql = sql.order_by(rival.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result:
return result.fetchall()

View File

@ -1,15 +1,16 @@
from typing import Dict, List, Optional
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
from sqlalchemy import Column, Table, UniqueConstraint, and_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.types import JSON, BigInteger, Boolean, Integer, String
from core.data.schema import BaseData, metadata
from core.data import cached
from core.data.schema import BaseData, metadata
best_score = Table(
best_score: Table = Table(
"mai2_score_best",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -272,7 +273,7 @@ playlog_old = Table(
mysql_charset="utf8mb4",
)
best_score_old = Table(
best_score_old: Table = Table(
"maimai_score_best",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -314,21 +315,55 @@ class Mai2ScoreData(BaseData):
return result.lastrowid
@cached(2)
async def get_best_scores(self, user_id: int, song_id: int = None, is_dx: bool = True) -> Optional[List[Row]]:
async def get_best_scores(
self,
user_id: int,
song_id: Optional[int] = None,
is_dx: bool = True,
limit: Optional[int] = None,
offset: Optional[int] = None,
levels: Optional[list[int]] = None,
) -> Optional[List[Row]]:
if is_dx:
sql = best_score.select(
and_(
best_score.c.user == user_id,
(best_score.c.musicId == song_id) if song_id is not None else True,
)
).order_by(best_score.c.musicId).order_by(best_score.c.level)
table = best_score
else:
sql = best_score_old.select(
and_(
best_score_old.c.user == user_id,
(best_score_old.c.musicId == song_id) if song_id is not None else True,
)
).order_by(best_score.c.musicId).order_by(best_score.c.level)
table = best_score_old
cond = table.c.user == user_id
if song_id is not None:
cond &= table.c.musicId == song_id
if levels is not None:
cond &= table.c.level.in_(levels)
if limit is None and offset is None:
sql = (
select(table)
.where(cond)
.order_by(table.c.musicId, table.c.level)
)
else:
subq = (
select(table.c.musicId)
.distinct()
.where(cond)
.order_by(table.c.musicId)
)
if limit is not None:
subq = subq.limit(limit)
if offset is not None:
subq = subq.offset(offset)
subq = subq.subquery()
sql = (
select(table)
.join(subq, table.c.musicId == subq.c.musicId)
.where(cond)
.order_by(table.c.musicId, table.c.level)
)
result = await self.execute(sql)
if result is None:

View File

@ -1,16 +1,16 @@
from datetime import date, datetime, timedelta
from typing import Any, Dict, List
import itertools
import json
import logging
from datetime import datetime, timedelta
from enum import Enum
from typing import Any, Dict, List
import pytz
from core.config import CoreConfig
from core.data.cache import cached
from titles.ongeki.config import OngekiConfig
from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig
from titles.ongeki.database import OngekiData
from titles.ongeki.config import OngekiConfig
class OngekiBattleGrade(Enum):
@ -500,57 +500,93 @@ class OngekiBase:
}
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
song_list = await self.util_generate_music_list(data["userId"])
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
if len(song_list[start_idx:]) > max_ct:
rows = await self.data.score.get_best_scores(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": user_id,
"length": 0,
"nextIndex": 0,
"userMusicList": [],
}
music_details = [row._asdict() for row in rows]
returned_count = 0
music_list = []
for _music_id, details_iter in itertools.groupby(music_details, key=lambda d: d["musicId"]):
details: list[dict[Any, Any]] = []
for d in details_iter:
d.pop("id")
d.pop("user")
details.append(d)
music_list.append({"length": len(details), "userMusicDetailList": details})
returned_count += len(details)
if len(music_list) >= max_ct:
break
if returned_count < len(rows):
next_idx += max_ct
else:
next_idx = -1
next_idx = 0
return {
"userId": data["userId"],
"length": len(song_list[start_idx:end_idx]),
"userId": user_id,
"length": len(music_list),
"nextIndex": next_idx,
"userMusicList": song_list[start_idx:end_idx],
"userMusicList": music_list,
}
async def handle_get_user_item_api_request(self, data: Dict) -> Dict:
kind = data["nextIndex"] / 10000000000
p = await self.data.item.get_items(data["userId"], kind)
user_id: int = data["userId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
if p is None:
kind = next_idx // 10000000000
next_idx = next_idx % 10000000000
rows = await self.data.item.get_items(
user_id, kind, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"nextIndex": -1,
"userId": user_id,
"nextIndex": 0,
"itemKind": kind,
"length": 0,
"userItemList": [],
}
items: List[Dict[str, Any]] = []
for i in range(data["nextIndex"] % 10000000000, len(p)):
if len(items) > data["maxCount"]:
break
tmp = p[i]._asdict()
tmp.pop("user")
tmp.pop("id")
items.append(tmp)
xout = kind * 10000000000 + (data["nextIndex"] % 10000000000) + len(items)
for row in rows[:max_ct]:
item = row._asdict()
item.pop("id")
item.pop("user")
items.append(item)
if len(items) < data["maxCount"] or data["maxCount"] == 0:
nextIndex = 0
if len(rows) > max_ct:
next_idx = kind * 10000000000 + next_idx + max_ct
else:
nextIndex = xout
next_idx = 0
return {
"userId": data["userId"],
"nextIndex": int(nextIndex),
"itemKind": int(kind),
"userId": user_id,
"nextIndex": next_idx,
"itemKind": kind,
"length": len(items),
"userItemList": items,
}
@ -1143,43 +1179,56 @@ class OngekiBase:
"""
Added in Bright
"""
rival_id = data["rivalUserId"]
next_idx = data["nextIndex"]
max_ct = data["maxCount"]
music = self.handle_get_user_music_api_request(
{"userId": rival_id, "nextIndex": next_idx, "maxCount": max_ct}
user_id: int = data["userId"]
rival_id: int = data["rivalUserId"]
next_idx: int = data["nextIndex"]
max_ct: int = data["maxCount"]
rows = await self.data.score.get_best_scores(
rival_id, limit=max_ct + 1, offset=next_idx
)
for song in music["userMusicList"]:
song["userRivalMusicDetailList"] = song["userMusicDetailList"]
song.pop("userMusicDetailList")
if rows is None:
return {
"userId": user_id,
"rivalUserId": rival_id,
"nextIndex": 0,
"length": 0,
"userRivalMusicList": [],
}
music_details = [row._asdict() for row in rows]
returned_count = 0
music_list = []
for _music_id, details_iter in itertools.groupby(music_details, key=lambda d: d["musicId"]):
details: list[dict[Any, Any]] = []
for d in details_iter:
d.pop("id")
d.pop("user")
d.pop("playCount")
d.pop("isLock")
d.pop("clearStatus")
d.pop("isStoryWatched")
details.append(d)
music_list.append({"length": len(details), "userRivalMusicDetailList": details})
returned_count += len(details)
if len(music_list) >= max_ct:
break
if returned_count < len(rows):
next_idx += max_ct
else:
next_idx = 0
return {
"userId": data["userId"],
"userId": user_id,
"rivalUserId": rival_id,
"length": music["length"],
"nextIndex": music["nextIndex"],
"userRivalMusicList": music["userMusicList"],
"nextIndex": next_idx,
"length": len(music_list),
"userRivalMusicList": music_list,
}
@cached(2)
async def util_generate_music_list(self, user_id: int) -> List:
music_detail = await self.data.score.get_best_scores(user_id)
song_list = []
for md in music_detail:
found = False
tmp = md._asdict()
tmp.pop("user")
tmp.pop("id")
for song in song_list:
if song["userMusicDetailList"][0]["musicId"] == tmp["musicId"]:
found = True
song["userMusicDetailList"].append(tmp)
song["length"] = len(song["userMusicDetailList"])
break
if not found:
song_list.append({"length": 1, "userMusicDetailList": [tmp]})
return song_list

View File

@ -1,13 +1,11 @@
from datetime import date, datetime, timedelta
from typing import Any, Dict
from datetime import datetime
from random import randint
import pytz
import json
from typing import Dict
from core.config import CoreConfig
from titles.ongeki.base import OngekiBase
from titles.ongeki.const import OngekiConstants
from titles.ongeki.config import OngekiConfig
from titles.ongeki.const import OngekiConstants
class OngekiBright(OngekiBase):
@ -62,66 +60,72 @@ class OngekiBright(OngekiBase):
return {"returnCode": 1}
async def handle_cm_get_user_card_api_request(self, data: Dict) -> Dict:
user_cards = await self.data.item.get_cards(data["userId"])
if user_cards is None:
user_id: int = data["userId"]
max_ct: int = data["maxCount"]
next_idx: int = data["nextIndex"]
rows = await self.data.item.get_cards(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {}
card_list = []
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
if len(user_cards[start_idx:]) > max_ct:
for row in rows[:max_ct]:
card = row._asdict()
card.pop("id")
card.pop("user")
card_list.append(card)
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = -1
card_list = []
for card in user_cards:
tmp = card._asdict()
tmp.pop("id")
tmp.pop("user")
card_list.append(tmp)
next_idx = 0
return {
"userId": data["userId"],
"length": len(card_list[start_idx:end_idx]),
"length": len(card_list),
"nextIndex": next_idx,
"userCardList": card_list[start_idx:end_idx],
"userCardList": card_list,
}
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
user_characters = await self.data.item.get_characters(data["userId"])
if user_characters is None:
user_id: int = data["userId"]
max_ct: int = data["maxCount"]
next_idx: int = data["nextIndex"]
rows = await self.data.item.get_characters(
user_id, limit=max_ct + 1, offset=next_idx
)
if rows is None:
return {
"userId": data["userId"],
"userId": user_id,
"length": 0,
"nextIndex": 0,
"userCharacterList": [],
}
max_ct = data["maxCount"]
next_idx = data["nextIndex"]
start_idx = next_idx
end_idx = max_ct + start_idx
character_list = []
if len(user_characters[start_idx:]) > max_ct:
for row in rows[:max_ct]:
character = row._asdict()
character.pop("id")
character.pop("user")
character_list.append(character)
if len(rows) > max_ct:
next_idx += max_ct
else:
next_idx = -1
character_list = []
for character in user_characters:
tmp = character._asdict()
tmp.pop("id")
tmp.pop("user")
character_list.append(tmp)
next_idx = 0
return {
"userId": data["userId"],
"length": len(character_list[start_idx:end_idx]),
"length": len(character_list),
"nextIndex": next_idx,
"userCharacterList": character_list[start_idx:end_idx],
"userCharacterList": character_list,
}
async def handle_get_user_gacha_api_request(self, data: Dict) -> Dict:

View File

@ -1,15 +1,16 @@
from datetime import date, datetime, timedelta
from typing import Dict, Optional, List
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
from sqlalchemy.schema import ForeignKey
from sqlalchemy.engine import Row
from sqlalchemy.sql import func, select
from datetime import datetime
from typing import Dict, List, Optional
from sqlalchemy import Column, Table, UniqueConstraint, and_
from sqlalchemy.dialects.mysql import insert
from sqlalchemy.engine import Row
from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.types import TIMESTAMP, Boolean, Integer, String
from core.data.schema import BaseData, metadata
card = Table(
card: Table = Table(
"ongeki_user_card",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -45,7 +46,7 @@ deck = Table(
mysql_charset="utf8mb4",
)
character = Table(
character: Table = Table(
"ongeki_user_character",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -130,7 +131,7 @@ memorychapter = Table(
mysql_charset="utf8mb4",
)
item = Table(
item: Table = Table(
"ongeki_user_item",
metadata,
Column("id", Integer, primary_key=True, nullable=False),
@ -351,9 +352,18 @@ class OngekiItemData(BaseData):
return None
return result.lastrowid
async def get_cards(self, aime_id: int) -> Optional[List[Dict]]:
async def get_cards(
self, aime_id: int, limit: Optional[int] = None, offset: Optional[int] = None
) -> Optional[List[Row]]:
sql = select(card).where(card.c.user == aime_id)
if limit is not None or offset is not None:
sql = sql.order_by(card.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
return None
@ -371,9 +381,18 @@ class OngekiItemData(BaseData):
return None
return result.lastrowid
async def get_characters(self, aime_id: int) -> Optional[List[Dict]]:
async def get_characters(
self, aime_id: int, limit: Optional[int] = None, offset: Optional[int] = None
) -> Optional[List[Row]]:
sql = select(character).where(character.c.user == aime_id)
if limit is not None or offset is not None:
sql = sql.order_by(character.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None:
return None
@ -479,13 +498,26 @@ class OngekiItemData(BaseData):
return None
return result.fetchone()
async def get_items(self, aime_id: int, item_kind: int = None) -> Optional[List[Dict]]:
if item_kind is None:
sql = select(item).where(item.c.user == aime_id)
else:
sql = select(item).where(
and_(item.c.user == aime_id, item.c.itemKind == item_kind)
)
async def get_items(
self,
aime_id: int,
item_kind: Optional[int] = None,
limit: Optional[int] = None,
offset: Optional[int] = None,
) -> Optional[List[Row]]:
cond = item.c.user == aime_id
if item_kind is not None:
cond &= item.c.itemKind == item_kind
sql = select(item).where(cond)
if limit is not None or offset is not None:
sql = sql.order_by(item.c.id)
if limit is not None:
sql = sql.limit(limit)
if offset is not None:
sql = sql.offset(offset)
result = await self.execute(sql)
if result is None: