forked from Hay1tsme/artemis
develop #2
@ -1,7 +1,12 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
|
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
|
||||||
|
|
||||||
|
## 20240530
|
||||||
|
### DIVA
|
||||||
|
+ Fix reader for when dificulty is not a int
|
||||||
|
|
||||||
## 20240526
|
## 20240526
|
||||||
|
### DIVA
|
||||||
+ Fixed missing awaits causing coroutine error
|
+ Fixed missing awaits causing coroutine error
|
||||||
|
|
||||||
## 20240524
|
## 20240524
|
||||||
|
@ -960,7 +960,7 @@ class DLReport:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
cfg_dir = environ.get("DIANA_CFG_DIR", "config")
|
cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config")
|
||||||
cfg: CoreConfig = CoreConfig()
|
cfg: CoreConfig = CoreConfig()
|
||||||
if path.exists(f"{cfg_dir}/core.yaml"):
|
if path.exists(f"{cfg_dir}/core.yaml"):
|
||||||
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
|
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
"""mai2_add_favs_rivals
|
||||||
|
|
||||||
|
Revision ID: 4a02e623e5e6
|
||||||
|
Revises: 8ad40a6e7be2
|
||||||
|
Create Date: 2024-06-08 19:02:43.856395
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '4a02e623e5e6'
|
||||||
|
down_revision = '8ad40a6e7be2'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.create_table('mai2_item_favorite_music',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('musicId', sa.Integer(), nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('user', 'musicId', name='mai2_item_favorite_music_uk'),
|
||||||
|
mysql_charset='utf8mb4'
|
||||||
|
)
|
||||||
|
op.create_table('mai2_user_rival',
|
||||||
|
sa.Column('id', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('user', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('rival', sa.Integer(), nullable=False),
|
||||||
|
sa.Column('show', sa.Boolean(), server_default='0', nullable=False),
|
||||||
|
sa.ForeignKeyConstraint(['rival'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
|
||||||
|
sa.ForeignKeyConstraint(['user'], ['aime_user.id'], onupdate='cascade', ondelete='cascade'),
|
||||||
|
sa.PrimaryKeyConstraint('id'),
|
||||||
|
sa.UniqueConstraint('user', 'rival', name='mai2_user_rival_uk'),
|
||||||
|
mysql_charset='utf8mb4'
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_table('mai2_user_rival')
|
||||||
|
op.drop_table('mai2_item_favorite_music')
|
||||||
|
# ### end Alembic commands ###
|
@ -1,7 +1,7 @@
|
|||||||
"""mai2_buddies_support
|
"""mai2_buddies_support
|
||||||
|
|
||||||
Revision ID: 81e44dd6047a
|
Revision ID: 81e44dd6047a
|
||||||
Revises: d8950c7ce2fc
|
Revises: 6a7e8277763b
|
||||||
Create Date: 2024-03-12 19:10:37.063907
|
Create Date: 2024-03-12 19:10:37.063907
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
"""ongeki: fix clearStatus
|
||||||
|
|
||||||
|
Revision ID: 8ad40a6e7be2
|
||||||
|
Revises: 7dc13e364e53
|
||||||
|
Create Date: 2024-05-29 19:03:30.062157
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '8ad40a6e7be2'
|
||||||
|
down_revision = '7dc13e364e53'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
op.alter_column('ongeki_score_best', 'clearStatus',
|
||||||
|
existing_type=mysql.TINYINT(display_width=1),
|
||||||
|
type_=sa.Integer(),
|
||||||
|
existing_nullable=False)
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
op.alter_column('ongeki_score_best', 'clearStatus',
|
||||||
|
existing_type=sa.Integer(),
|
||||||
|
type_=mysql.TINYINT(display_width=1),
|
||||||
|
existing_nullable=False)
|
@ -121,7 +121,7 @@ class CardData(BaseData):
|
|||||||
result = await self.execute(sql)
|
result = await self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
self.logger.warn(f"Failed to update last login time for {access_code}")
|
self.logger.warn(f"Failed to update last login time for {access_code}")
|
||||||
|
|
||||||
def to_access_code(self, luid: str) -> str:
|
def to_access_code(self, luid: str) -> str:
|
||||||
"""
|
"""
|
||||||
Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string
|
Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string
|
||||||
|
@ -120,3 +120,7 @@ class UserData(BaseData):
|
|||||||
|
|
||||||
result = await self.execute(sql)
|
result = await self.execute(sql)
|
||||||
return result is not None
|
return result is not None
|
||||||
|
|
||||||
|
async def get_user_by_username(self, username: str) -> Optional[Row]:
|
||||||
|
result = await self.execute(aime_user.select(aime_user.c.username == username))
|
||||||
|
if result: return result.fetchone()
|
||||||
|
@ -44,12 +44,13 @@ class ShopOwner():
|
|||||||
self.permissions = perms
|
self.permissions = perms
|
||||||
|
|
||||||
class UserSession():
|
class UserSession():
|
||||||
def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7, chunithm_ver: int = -1):
|
def __init__(self, usr_id: int = 0, ip: str = "", perms: int = 0, ongeki_ver: int = 7, chunithm_ver: int = -1, maimai_version: int = -1):
|
||||||
self.user_id = usr_id
|
self.user_id = usr_id
|
||||||
self.current_ip = ip
|
self.current_ip = ip
|
||||||
self.permissions = perms
|
self.permissions = perms
|
||||||
self.ongeki_version = ongeki_ver
|
self.ongeki_version = ongeki_ver
|
||||||
self.chunithm_version = chunithm_ver
|
self.chunithm_version = chunithm_ver
|
||||||
|
self.maimai_version = maimai_version
|
||||||
|
|
||||||
class FrontendServlet():
|
class FrontendServlet():
|
||||||
def __init__(self, cfg: CoreConfig, config_dir: str) -> None:
|
def __init__(self, cfg: CoreConfig, config_dir: str) -> None:
|
||||||
@ -192,7 +193,7 @@ class FE_Base():
|
|||||||
), media_type="text/html; charset=utf-8")
|
), media_type="text/html; charset=utf-8")
|
||||||
|
|
||||||
if sesh is None:
|
if sesh is None:
|
||||||
resp.delete_cookie("DIANA_SESH")
|
resp.delete_cookie("ARTEMIS_SESH")
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
def get_routes(self) -> List[Route]:
|
def get_routes(self) -> List[Route]:
|
||||||
@ -216,6 +217,8 @@ class FE_Base():
|
|||||||
sesh.current_ip = tk['current_ip']
|
sesh.current_ip = tk['current_ip']
|
||||||
sesh.permissions = tk['permissions']
|
sesh.permissions = tk['permissions']
|
||||||
sesh.chunithm_version = tk['chunithm_version']
|
sesh.chunithm_version = tk['chunithm_version']
|
||||||
|
sesh.maimai_version = tk['maimai_version']
|
||||||
|
sesh.ongeki_version = tk['ongeki_version']
|
||||||
|
|
||||||
if sesh.user_id <= 0:
|
if sesh.user_id <= 0:
|
||||||
self.logger.error("User session failed to validate due to an invalid ID!")
|
self.logger.error("User session failed to validate due to an invalid ID!")
|
||||||
@ -241,7 +244,7 @@ class FE_Base():
|
|||||||
return UserSession()
|
return UserSession()
|
||||||
|
|
||||||
def validate_session(self, request: Request) -> Optional[UserSession]:
|
def validate_session(self, request: Request) -> Optional[UserSession]:
|
||||||
sesh = request.cookies.get('DIANA_SESH', "")
|
sesh = request.cookies.get('ARTEMIS_SESH', "")
|
||||||
if not sesh:
|
if not sesh:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -260,7 +263,17 @@ class FE_Base():
|
|||||||
|
|
||||||
def encode_session(self, sesh: UserSession, exp_seconds: int = 86400) -> str:
|
def encode_session(self, sesh: UserSession, exp_seconds: int = 86400) -> str:
|
||||||
try:
|
try:
|
||||||
return jwt.encode({ "user_id": sesh.user_id, "current_ip": sesh.current_ip, "permissions": sesh.permissions, "ongeki_version": sesh.ongeki_version, "chunithm_version": sesh.chunithm_version, "exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds }, b64decode(self.core_config.frontend.secret), algorithm="HS256")
|
return jwt.encode({
|
||||||
|
"user_id": sesh.user_id,
|
||||||
|
"current_ip": sesh.current_ip,
|
||||||
|
"permissions": sesh.permissions,
|
||||||
|
"ongeki_version": sesh.ongeki_version,
|
||||||
|
"chunithm_version": sesh.chunithm_version,
|
||||||
|
"maimai_version": sesh.maimai_version,
|
||||||
|
"exp": int(datetime.now(tz=timezone.utc).timestamp()) + exp_seconds },
|
||||||
|
b64decode(self.core_config.frontend.secret),
|
||||||
|
algorithm="HS256"
|
||||||
|
)
|
||||||
except jwt.InvalidKeyError:
|
except jwt.InvalidKeyError:
|
||||||
self.logger.error("Failed to encode User session because the secret is invalid!")
|
self.logger.error("Failed to encode User session because the secret is invalid!")
|
||||||
return ""
|
return ""
|
||||||
@ -292,7 +305,7 @@ class FE_Gate(FE_Base):
|
|||||||
error=err,
|
error=err,
|
||||||
sesh=vars(UserSession()),
|
sesh=vars(UserSession()),
|
||||||
), media_type="text/html; charset=utf-8")
|
), media_type="text/html; charset=utf-8")
|
||||||
resp.delete_cookie("DIANA_SESH")
|
resp.delete_cookie("ARTEMIS_SESH")
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
async def render_login(self, request: Request):
|
async def render_login(self, request: Request):
|
||||||
@ -308,8 +321,12 @@ class FE_Gate(FE_Base):
|
|||||||
|
|
||||||
uid = await self.data.card.get_user_id_from_card(access_code)
|
uid = await self.data.card.get_user_id_from_card(access_code)
|
||||||
if uid is None:
|
if uid is None:
|
||||||
self.logger.debug(f"Failed to find user for card {access_code}")
|
user = await self.data.user.get_user_by_username(access_code) # Lookup as username
|
||||||
return RedirectResponse("/gate/?e=1", 303)
|
if not user:
|
||||||
|
self.logger.debug(f"Failed to find user for card/username {access_code}")
|
||||||
|
return RedirectResponse("/gate/?e=1", 303)
|
||||||
|
|
||||||
|
uid = user['id']
|
||||||
|
|
||||||
user = await self.data.user.get_user(uid)
|
user = await self.data.user.get_user(uid)
|
||||||
if user is None:
|
if user is None:
|
||||||
@ -338,7 +355,7 @@ class FE_Gate(FE_Base):
|
|||||||
usr_sesh = self.encode_session(sesh)
|
usr_sesh = self.encode_session(sesh)
|
||||||
self.logger.debug(f"Created session with JWT {usr_sesh}")
|
self.logger.debug(f"Created session with JWT {usr_sesh}")
|
||||||
resp = RedirectResponse("/user/", 303)
|
resp = RedirectResponse("/user/", 303)
|
||||||
resp.set_cookie("DIANA_SESH", usr_sesh)
|
resp.set_cookie("ARTEMIS_SESH", usr_sesh)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@ -377,7 +394,7 @@ class FE_Gate(FE_Base):
|
|||||||
usr_sesh = self.encode_session(sesh)
|
usr_sesh = self.encode_session(sesh)
|
||||||
self.logger.debug(f"Created session with JWT {usr_sesh}")
|
self.logger.debug(f"Created session with JWT {usr_sesh}")
|
||||||
resp = RedirectResponse("/user/", 303)
|
resp = RedirectResponse("/user/", 303)
|
||||||
resp.set_cookie("DIANA_SESH", usr_sesh)
|
resp.set_cookie("ARTEMIS_SESH", usr_sesh)
|
||||||
|
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
@ -495,7 +512,7 @@ class FE_User(FE_Base):
|
|||||||
|
|
||||||
async def render_logout(self, request: Request):
|
async def render_logout(self, request: Request):
|
||||||
resp = RedirectResponse("/gate/", 303)
|
resp = RedirectResponse("/gate/", 303)
|
||||||
resp.delete_cookie("DIANA_SESH")
|
resp.delete_cookie("ARTEMIS_SESH")
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
async def edit_card(self, request: Request) -> RedirectResponse:
|
async def edit_card(self, request: Request) -> RedirectResponse:
|
||||||
@ -879,7 +896,7 @@ class FE_Machine(FE_Base):
|
|||||||
arcade={}
|
arcade={}
|
||||||
), media_type="text/html; charset=utf-8")
|
), media_type="text/html; charset=utf-8")
|
||||||
|
|
||||||
cfg_dir = environ.get("DIANA_CFG_DIR", "config")
|
cfg_dir = environ.get("ARTEMIS_CFG_DIR", "config")
|
||||||
cfg: CoreConfig = CoreConfig()
|
cfg: CoreConfig = CoreConfig()
|
||||||
if path.exists(f"{cfg_dir}/core.yaml"):
|
if path.exists(f"{cfg_dir}/core.yaml"):
|
||||||
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
|
cfg.update(yaml.safe_load(open(f"{cfg_dir}/core.yaml")))
|
||||||
|
@ -15,18 +15,18 @@
|
|||||||
-moz-appearance: textfield;
|
-moz-appearance: textfield;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<form id="login" style="max-width: 240px; min-width: 10%;" action="/gate/gate.login" method="post">
|
<form id="login" style="max-width: 240px; min-width: 15%;" action="/gate/gate.login" method="post">
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="access_code">Card Access Code</label><br>
|
<label for="access_code">Access Code or Username</label><br>
|
||||||
<input form="login" class="form-control" name="access_code" id="access_code" type="number" placeholder="00000000000000000000" maxlength="20" required>
|
<input form="login" class="form-control" name="access_code" id="access_code" placeholder="00000000000000000000" maxlength="20" required aria-describedby="access_code_help">
|
||||||
|
<div id="access_code_help" class="form-text">20 Digit access code from a card registered to your account, or your account username. (NOT your username from a game!)</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group row">
|
<div class="form-group row">
|
||||||
<label for="passwd">Password</label><br>
|
<label for="passwd">Password</label><br>
|
||||||
<input id="passwd" class="form-control" name="passwd" type="password" placeholder="password">
|
<input id="passwd" class="form-control" name="passwd" type="password" placeholder="password" aria-describedby="passwd_help">
|
||||||
|
<div id="passwd_help" class="form-text">Leave blank if registering for the webui. Your card must have been used on a game connected to this server to register.</div>
|
||||||
</div>
|
</div>
|
||||||
<p></p>
|
<p></p>
|
||||||
<input id="submit" class="btn btn-primary" style="display: block; margin: 0 auto;" form="login" type="submit" value="Login">
|
<input id="submit" class="btn btn-primary" style="display: block; margin: 0 auto;" form="login" type="submit" value="Login">
|
||||||
</form>
|
</form>
|
||||||
<h6>*To register for the webui, type in the access code of your card, as shown in a game, and leave the password field blank.</h6>
|
|
||||||
<h6>*If you have not registered a card with this server, you cannot create a webui account.</h6>
|
|
||||||
{% endblock content %}
|
{% endblock content %}
|
@ -68,7 +68,7 @@ class ChuniFrontend(FE_Base):
|
|||||||
|
|
||||||
if usr_sesh.chunithm_version >= 0:
|
if usr_sesh.chunithm_version >= 0:
|
||||||
encoded_sesh = self.encode_session(usr_sesh)
|
encoded_sesh = self.encode_session(usr_sesh)
|
||||||
resp.set_cookie("DIANA_SESH", encoded_sesh)
|
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
|
||||||
return resp
|
return resp
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@ -240,7 +240,7 @@ class ChuniFrontend(FE_Base):
|
|||||||
encoded_sesh = self.encode_session(usr_sesh)
|
encoded_sesh = self.encode_session(usr_sesh)
|
||||||
self.logger.info(f"Created session with JWT {encoded_sesh}")
|
self.logger.info(f"Created session with JWT {encoded_sesh}")
|
||||||
resp = RedirectResponse("/game/chuni/", 303)
|
resp = RedirectResponse("/game/chuni/", 303)
|
||||||
resp.set_cookie("DIANA_SESH", encoded_sesh)
|
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
|
||||||
return resp
|
return resp
|
||||||
else:
|
else:
|
||||||
return RedirectResponse("/gate/", 303)
|
return RedirectResponse("/gate/", 303)
|
@ -5,7 +5,7 @@
|
|||||||
</style>
|
</style>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{% include 'titles/chuni/templates/chuni_header.jinja' %}
|
{% include 'titles/chuni/templates/chuni_header.jinja' %}
|
||||||
{% if profile is defined and profile is not none and profile.id > 0 %}
|
{% if profile is defined and profile is not none and profile|length > 0 %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-lg-8 m-auto mt-3">
|
<div class="col-lg-8 m-auto mt-3">
|
||||||
<div class="card bg-card rounded">
|
<div class="card bg-card rounded">
|
||||||
|
@ -183,7 +183,11 @@ class DivaReader(BaseReader):
|
|||||||
pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val)
|
pv_list[pv_id] = self.add_branch(pv_list[pv_id], key_args, val)
|
||||||
|
|
||||||
for pv_id, pv_data in pv_list.items():
|
for pv_id, pv_data in pv_list.items():
|
||||||
song_id = int(pv_id.split("_")[1])
|
try:
|
||||||
|
song_id = int(pv_id.split("_")[1])
|
||||||
|
except ValueError:
|
||||||
|
self.logger.error(f"Invalid song ID format: {pv_id}")
|
||||||
|
continue
|
||||||
if "songinfo" not in pv_data:
|
if "songinfo" not in pv_data:
|
||||||
continue
|
continue
|
||||||
if "illustrator" not in pv_data["songinfo"]:
|
if "illustrator" not in pv_data["songinfo"]:
|
||||||
|
@ -2,10 +2,12 @@ from titles.mai2.index import Mai2Servlet
|
|||||||
from titles.mai2.const import Mai2Constants
|
from titles.mai2.const import Mai2Constants
|
||||||
from titles.mai2.database import Mai2Data
|
from titles.mai2.database import Mai2Data
|
||||||
from titles.mai2.read import Mai2Reader
|
from titles.mai2.read import Mai2Reader
|
||||||
|
from .frontend import Mai2Frontend
|
||||||
|
|
||||||
index = Mai2Servlet
|
index = Mai2Servlet
|
||||||
database = Mai2Data
|
database = Mai2Data
|
||||||
reader = Mai2Reader
|
reader = Mai2Reader
|
||||||
|
frontend = Mai2Frontend
|
||||||
game_codes = [
|
game_codes = [
|
||||||
Mai2Constants.GAME_CODE_DX,
|
Mai2Constants.GAME_CODE_DX,
|
||||||
Mai2Constants.GAME_CODE_FINALE,
|
Mai2Constants.GAME_CODE_FINALE,
|
||||||
|
@ -4,6 +4,7 @@ import logging
|
|||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
from os import path, stat, remove
|
from os import path, stat, remove
|
||||||
from PIL import ImageFile
|
from PIL import ImageFile
|
||||||
|
from random import randint
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
@ -886,3 +887,45 @@ class Mai2Base:
|
|||||||
self.logger.error(f"Failed to delete {out_name}.bin, please remove it manually")
|
self.logger.error(f"Failed to delete {out_name}.bin, please remove it manually")
|
||||||
|
|
||||||
return {'returnCode': ret_code, 'apiName': 'UploadUserPhotoApi'}
|
return {'returnCode': ret_code, 'apiName': 'UploadUserPhotoApi'}
|
||||||
|
|
||||||
|
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)
|
||||||
|
max_ct = data.get("maxCount", 100) # always 100
|
||||||
|
is_all = data.get("isAllFavoriteItem", False) # always false
|
||||||
|
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": 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"]})
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": user_id,
|
||||||
|
"kind": kind,
|
||||||
|
"nextIndex": 0,
|
||||||
|
"userFavoriteItemList": id_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
userRecommendRateMusicIdList: list[int]
|
||||||
|
"""
|
||||||
|
return {"userId": data["userId"], "userRecommendRateMusicIdList": []}
|
||||||
|
|
||||||
|
async def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
|
||||||
|
"""
|
||||||
|
userRecommendSelectionMusicIdList: list[int]
|
||||||
|
"""
|
||||||
|
return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []}
|
||||||
|
@ -17,16 +17,3 @@ class Mai2Buddies(Mai2FestivalPlus):
|
|||||||
# hardcode lastDataVersion for CardMaker
|
# hardcode lastDataVersion for CardMaker
|
||||||
user_data["lastDataVersion"] = "1.40.00"
|
user_data["lastDataVersion"] = "1.40.00"
|
||||||
return user_data
|
return user_data
|
||||||
|
|
||||||
async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict:
|
|
||||||
# TODO: Added in 1.41, implement this?
|
|
||||||
user_id = data["userId"]
|
|
||||||
version = data.get("version", 1041000)
|
|
||||||
user_playlog_list = data.get("userPlaylogList", [])
|
|
||||||
|
|
||||||
return {
|
|
||||||
"userId": user_id,
|
|
||||||
"itemKind": -1,
|
|
||||||
"itemId": -1,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -563,33 +563,76 @@ class Mai2DX(Mai2Base):
|
|||||||
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
||||||
|
|
||||||
async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
|
async def handle_get_user_rival_data_api_request(self, data: Dict) -> Dict:
|
||||||
user_id = data["userId"]
|
user_id = data.get("userId", 0)
|
||||||
rival_id = data["rivalId"]
|
rival_id = data.get("rivalId", 0)
|
||||||
|
|
||||||
"""
|
if not user_id or not rival_id: return {}
|
||||||
class UserRivalData:
|
|
||||||
rivalId: int
|
rival_pf = await self.data.profile.get_profile_detail(rival_id, self.version)
|
||||||
rivalName: str
|
if not rival_pf: return {}
|
||||||
"""
|
|
||||||
return {"userId": user_id, "userRivalData": {}}
|
return {
|
||||||
|
"userId": user_id,
|
||||||
|
"userRivalData": {
|
||||||
|
"rivalId": rival_id,
|
||||||
|
"rivalName": rival_pf['userName']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
|
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] = {}
|
||||||
|
|
||||||
|
songs = await self.data.score.get_best_scores(rival_id)
|
||||||
|
if songs 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
|
||||||
|
}
|
||||||
|
|
||||||
|
num_user_songs = len(songs)
|
||||||
|
|
||||||
|
for x in range(next_index, upper_lim):
|
||||||
|
if x >= num_user_songs:
|
||||||
|
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})")
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userId": user_id,
|
||||||
|
"rivalId": rival_id,
|
||||||
|
"nextIndex": next_index,
|
||||||
|
"userRivalMusicList": [{"musicId": x, "userRivalMusicDetailList": y} for x, y in rival_music_list.items()]
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_get_user_new_item_api_request(self, data: Dict) -> Dict:
|
||||||
|
# TODO: Added in 1.41, implement this?
|
||||||
user_id = data["userId"]
|
user_id = data["userId"]
|
||||||
rival_id = data["rivalId"]
|
version = data.get("version", 1041000)
|
||||||
next_idx = data["nextIndex"]
|
user_playlog_list = data.get("userPlaylogList", [])
|
||||||
rival_music_levels = data["userRivalMusicLevelList"]
|
|
||||||
|
return {
|
||||||
"""
|
"userId": user_id,
|
||||||
class UserRivalMusicList:
|
"itemKind": -1,
|
||||||
class UserRivalMusicDetailList:
|
"itemId": -1,
|
||||||
level: int
|
}
|
||||||
achievement: int
|
|
||||||
deluxscoreMax: int
|
|
||||||
|
|
||||||
musicId: int
|
|
||||||
userRivalMusicDetailList: list[UserRivalMusicDetailList]
|
|
||||||
"""
|
|
||||||
return {"userId": user_id, "nextIndex": 0, "userRivalMusicList": []}
|
|
||||||
|
|
||||||
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
async def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||||
user_id = data.get("userId", 0)
|
user_id = data.get("userId", 0)
|
||||||
@ -636,3 +679,208 @@ class Mai2DX(Mai2Base):
|
|||||||
return ret
|
return ret
|
||||||
ret['loginId'] = ret.get('loginCount', 0)
|
ret['loginId'] = ret.get('loginCount', 0)
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
|
# CardMaker support added in Universe
|
||||||
|
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
||||||
|
p = await self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
if p is None:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"userName": p["userName"],
|
||||||
|
"rating": p["playerRating"],
|
||||||
|
# hardcode lastDataVersion for CardMaker
|
||||||
|
"lastDataVersion": "1.20.00", # Future versiohs should replace this with the correct version
|
||||||
|
# checks if the user is still logged in
|
||||||
|
"isLogin": False,
|
||||||
|
"isExistSellingCard": True,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||||
|
# user already exists, because the preview checks that already
|
||||||
|
p = await self.data.profile.get_profile_detail(data["userId"], self.version)
|
||||||
|
|
||||||
|
cards = await self.data.card.get_user_cards(data["userId"])
|
||||||
|
if cards is None or len(cards) == 0:
|
||||||
|
# This should never happen
|
||||||
|
self.logger.error(
|
||||||
|
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
|
||||||
|
)
|
||||||
|
return {}
|
||||||
|
|
||||||
|
# get the dict representation of the row so we can modify values
|
||||||
|
user_data = p._asdict()
|
||||||
|
|
||||||
|
# remove the values the game doesn't want
|
||||||
|
user_data.pop("id")
|
||||||
|
user_data.pop("user")
|
||||||
|
user_data.pop("version")
|
||||||
|
|
||||||
|
return {"userId": data["userId"], "userData": user_data}
|
||||||
|
|
||||||
|
async def handle_cm_login_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
async def handle_cm_logout_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
|
||||||
|
async def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
selling_cards = await self.data.static.get_enabled_cards(self.version)
|
||||||
|
if selling_cards is None:
|
||||||
|
return {"length": 0, "sellingCardList": []}
|
||||||
|
|
||||||
|
selling_card_list = []
|
||||||
|
for card in selling_cards:
|
||||||
|
tmp = card._asdict()
|
||||||
|
tmp.pop("id")
|
||||||
|
tmp.pop("version")
|
||||||
|
tmp.pop("cardName")
|
||||||
|
tmp.pop("enabled")
|
||||||
|
|
||||||
|
tmp["startDate"] = datetime.strftime(
|
||||||
|
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
tmp["endDate"] = datetime.strftime(
|
||||||
|
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
tmp["noticeStartDate"] = datetime.strftime(
|
||||||
|
tmp["noticeStartDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
tmp["noticeEndDate"] = datetime.strftime(
|
||||||
|
tmp["noticeEndDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||||
|
)
|
||||||
|
|
||||||
|
selling_card_list.append(tmp)
|
||||||
|
|
||||||
|
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:
|
||||||
|
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
|
||||||
|
|
||||||
|
if len(user_cards[start_idx:]) > 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]),
|
||||||
|
"nextIndex": next_idx,
|
||||||
|
"userCardList": card_list[start_idx:end_idx],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||||
|
await self.handle_get_user_item_api_request(data)
|
||||||
|
|
||||||
|
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
||||||
|
characters = await self.data.item.get_characters(data["userId"])
|
||||||
|
|
||||||
|
chara_list = []
|
||||||
|
for chara in characters:
|
||||||
|
chara_list.append(
|
||||||
|
{
|
||||||
|
"characterId": chara["characterId"],
|
||||||
|
# no clue why those values are even needed
|
||||||
|
"point": 0,
|
||||||
|
"count": 0,
|
||||||
|
"level": chara["level"],
|
||||||
|
"nextAwake": 0,
|
||||||
|
"nextAwakePercent": 0,
|
||||||
|
"favorite": False,
|
||||||
|
"awakening": chara["awakening"],
|
||||||
|
"useCount": chara["useCount"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"length": len(chara_list),
|
||||||
|
"userCharacterList": chara_list,
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"length": 0, "userPrintDetailList": []}
|
||||||
|
|
||||||
|
async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
||||||
|
user_id = data["userId"]
|
||||||
|
upsert = data["userPrintDetail"]
|
||||||
|
|
||||||
|
# set a random card serial number
|
||||||
|
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
||||||
|
|
||||||
|
# calculate start and end date of the card
|
||||||
|
start_date = datetime.utcnow()
|
||||||
|
end_date = datetime.utcnow() + timedelta(days=15)
|
||||||
|
|
||||||
|
user_card = upsert["userCard"]
|
||||||
|
await self.data.item.put_card(
|
||||||
|
user_id,
|
||||||
|
user_card["cardId"],
|
||||||
|
user_card["cardTypeId"],
|
||||||
|
user_card["charaId"],
|
||||||
|
user_card["mapId"],
|
||||||
|
# add the correct start date and also the end date in 15 days
|
||||||
|
start_date,
|
||||||
|
end_date,
|
||||||
|
)
|
||||||
|
|
||||||
|
# get the profile extend to save the new bought card
|
||||||
|
extend = await self.data.profile.get_profile_extend(user_id, self.version)
|
||||||
|
if extend:
|
||||||
|
extend = extend._asdict()
|
||||||
|
# parse the selectedCardList
|
||||||
|
# 6 = Freedom Pass, 4 = Gold Pass (cardTypeId)
|
||||||
|
selected_cards: List = extend["selectedCardList"]
|
||||||
|
|
||||||
|
# if no pass is already added, add the corresponding pass
|
||||||
|
if not user_card["cardTypeId"] in selected_cards:
|
||||||
|
selected_cards.insert(0, user_card["cardTypeId"])
|
||||||
|
|
||||||
|
extend["selectedCardList"] = selected_cards
|
||||||
|
await self.data.profile.put_profile_extend(user_id, self.version, extend)
|
||||||
|
|
||||||
|
# properly format userPrintDetail for the database
|
||||||
|
upsert.pop("userCard")
|
||||||
|
upsert.pop("serialId")
|
||||||
|
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
||||||
|
|
||||||
|
await self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": serial_id,
|
||||||
|
"startDate": datetime.strftime(start_date, Mai2Constants.DATE_TIME_FORMAT),
|
||||||
|
"endDate": datetime.strftime(end_date, Mai2Constants.DATE_TIME_FORMAT),
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {
|
||||||
|
"returnCode": 1,
|
||||||
|
"orderId": 0,
|
||||||
|
"serialId": data["userPrintlog"]["serialId"],
|
||||||
|
}
|
||||||
|
|
||||||
|
async def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
|
||||||
|
return {"returnCode": 1}
|
||||||
|
@ -20,18 +20,6 @@ class Mai2Festival(Mai2UniversePlus):
|
|||||||
|
|
||||||
async def handle_user_login_api_request(self, data: Dict) -> Dict:
|
async def handle_user_login_api_request(self, data: Dict) -> Dict:
|
||||||
user_login = await super().handle_user_login_api_request(data)
|
user_login = await super().handle_user_login_api_request(data)
|
||||||
# useless?
|
# TODO: Make use of this
|
||||||
user_login["Bearer"] = "ARTEMiSTOKEN"
|
user_login["Bearer"] = "ARTEMiSTOKEN"
|
||||||
return user_login
|
return user_login
|
||||||
|
|
||||||
async def handle_get_user_recommend_rate_music_api_request(self, data: Dict) -> Dict:
|
|
||||||
"""
|
|
||||||
userRecommendRateMusicIdList: list[int]
|
|
||||||
"""
|
|
||||||
return {"userId": data["userId"], "userRecommendRateMusicIdList": []}
|
|
||||||
|
|
||||||
async def handle_get_user_recommend_select_music_api_request(self, data: Dict) -> Dict:
|
|
||||||
"""
|
|
||||||
userRecommendSelectionMusicIdList: list[int]
|
|
||||||
"""
|
|
||||||
return {"userId": data["userId"], "userRecommendSelectionMusicIdList": []}
|
|
||||||
|
@ -17,22 +17,3 @@ class Mai2FestivalPlus(Mai2Festival):
|
|||||||
# hardcode lastDataVersion for CardMaker
|
# hardcode lastDataVersion for CardMaker
|
||||||
user_data["lastDataVersion"] = "1.35.00"
|
user_data["lastDataVersion"] = "1.35.00"
|
||||||
return user_data
|
return user_data
|
||||||
|
|
||||||
async def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
|
|
||||||
user_id = data.get("userId", 0)
|
|
||||||
kind = data.get("kind", 2)
|
|
||||||
next_index = data.get("nextIndex", 0)
|
|
||||||
max_ct = data.get("maxCount", 100)
|
|
||||||
is_all = data.get("isAllFavoriteItem", False)
|
|
||||||
|
|
||||||
"""
|
|
||||||
class userFavoriteItemList:
|
|
||||||
orderId: int
|
|
||||||
id: int
|
|
||||||
"""
|
|
||||||
return {
|
|
||||||
"userId": user_id,
|
|
||||||
"kind": kind,
|
|
||||||
"nextIndex": 0,
|
|
||||||
"userFavoriteItemList": [],
|
|
||||||
}
|
|
||||||
|
190
titles/mai2/frontend.py
Normal file
190
titles/mai2/frontend.py
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
from typing import List
|
||||||
|
from starlette.routing import Route, Mount
|
||||||
|
from starlette.requests import Request
|
||||||
|
from starlette.responses import Response, RedirectResponse
|
||||||
|
from os import path
|
||||||
|
import yaml
|
||||||
|
import jinja2
|
||||||
|
|
||||||
|
from core.frontend import FE_Base, UserSession
|
||||||
|
from core.config import CoreConfig
|
||||||
|
from .database import Mai2Data
|
||||||
|
from .config import Mai2Config
|
||||||
|
from .const import Mai2Constants
|
||||||
|
|
||||||
|
class Mai2Frontend(FE_Base):
|
||||||
|
def __init__(
|
||||||
|
self, cfg: CoreConfig, environment: jinja2.Environment, cfg_dir: str
|
||||||
|
) -> None:
|
||||||
|
super().__init__(cfg, environment)
|
||||||
|
self.data = Mai2Data(cfg)
|
||||||
|
self.game_cfg = Mai2Config()
|
||||||
|
if path.exists(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"):
|
||||||
|
self.game_cfg.update(
|
||||||
|
yaml.safe_load(open(f"{cfg_dir}/{Mai2Constants.CONFIG_NAME}"))
|
||||||
|
)
|
||||||
|
self.nav_name = "maimai"
|
||||||
|
|
||||||
|
def get_routes(self) -> List[Route]:
|
||||||
|
return [
|
||||||
|
Route("/", self.render_GET, methods=['GET']),
|
||||||
|
Mount("/playlog", routes=[
|
||||||
|
Route("/", self.render_GET_playlog, methods=['GET']),
|
||||||
|
Route("/{index}", self.render_GET_playlog, methods=['GET']),
|
||||||
|
]),
|
||||||
|
Route("/update.name", self.update_name, methods=['POST']),
|
||||||
|
Route("/version.change", self.version_change, methods=['POST']),
|
||||||
|
]
|
||||||
|
|
||||||
|
async def render_GET(self, request: Request) -> bytes:
|
||||||
|
template = self.environment.get_template(
|
||||||
|
"titles/mai2/templates/mai2_index.jinja"
|
||||||
|
)
|
||||||
|
usr_sesh = self.validate_session(request)
|
||||||
|
if not usr_sesh:
|
||||||
|
usr_sesh = UserSession()
|
||||||
|
|
||||||
|
if usr_sesh.user_id > 0:
|
||||||
|
versions = await self.data.profile.get_all_profile_versions(usr_sesh.user_id)
|
||||||
|
profile = []
|
||||||
|
if versions:
|
||||||
|
# maimai_version is -1 means it is not initialized yet, select a default version from existing.
|
||||||
|
if usr_sesh.maimai_version < 0:
|
||||||
|
usr_sesh.maimai_version = versions[0]['version']
|
||||||
|
profile = await self.data.profile.get_profile_detail(usr_sesh.user_id, usr_sesh.maimai_version)
|
||||||
|
versions = [x['version'] for x in versions]
|
||||||
|
|
||||||
|
resp = Response(template.render(
|
||||||
|
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||||
|
game_list=self.environment.globals["game_list"],
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
user_id=usr_sesh.user_id,
|
||||||
|
profile=profile,
|
||||||
|
version_list=Mai2Constants.VERSION_STRING,
|
||||||
|
versions=versions,
|
||||||
|
cur_version=usr_sesh.maimai_version
|
||||||
|
), media_type="text/html; charset=utf-8")
|
||||||
|
|
||||||
|
if usr_sesh.maimai_version >= 0:
|
||||||
|
encoded_sesh = self.encode_session(usr_sesh)
|
||||||
|
resp.delete_cookie("ARTEMIS_SESH")
|
||||||
|
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
|
||||||
|
return resp
|
||||||
|
|
||||||
|
else:
|
||||||
|
return RedirectResponse("/gate/", 303)
|
||||||
|
|
||||||
|
async def render_GET_playlog(self, request: Request) -> bytes:
|
||||||
|
template = self.environment.get_template(
|
||||||
|
"titles/mai2/templates/mai2_playlog.jinja"
|
||||||
|
)
|
||||||
|
usr_sesh = self.validate_session(request)
|
||||||
|
if not usr_sesh:
|
||||||
|
print("wtf")
|
||||||
|
usr_sesh = UserSession()
|
||||||
|
|
||||||
|
if usr_sesh.user_id > 0:
|
||||||
|
if usr_sesh.maimai_version < 0:
|
||||||
|
print(usr_sesh.maimai_version)
|
||||||
|
return RedirectResponse("/game/mai2/", 303)
|
||||||
|
path_index = request.path_params.get('index')
|
||||||
|
if not path_index or int(path_index) < 1:
|
||||||
|
index = 0
|
||||||
|
else:
|
||||||
|
index = int(path_index) - 1 # 0 and 1 are 1st page
|
||||||
|
user_id = usr_sesh.user_id
|
||||||
|
playlog_count = await self.data.score.get_user_playlogs_count(user_id)
|
||||||
|
if playlog_count < index * 20 :
|
||||||
|
return Response(template.render(
|
||||||
|
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||||
|
game_list=self.environment.globals["game_list"],
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
playlog_count=0
|
||||||
|
), media_type="text/html; charset=utf-8")
|
||||||
|
playlog = await self.data.score.get_playlogs(user_id, index, 20)
|
||||||
|
playlog_with_title = []
|
||||||
|
for record in playlog:
|
||||||
|
music_chart = await self.data.static.get_music_chart(usr_sesh.maimai_version, record.musicId, record.level)
|
||||||
|
if music_chart:
|
||||||
|
difficultyNum=music_chart.chartId
|
||||||
|
difficulty=music_chart.difficulty
|
||||||
|
artist=music_chart.artist
|
||||||
|
title=music_chart.title
|
||||||
|
else:
|
||||||
|
difficultyNum=0
|
||||||
|
difficulty=0
|
||||||
|
artist="unknown"
|
||||||
|
title="musicid: " + str(record.musicId)
|
||||||
|
playlog_with_title.append({
|
||||||
|
"raw": record,
|
||||||
|
"title": title,
|
||||||
|
"difficultyNum": difficultyNum,
|
||||||
|
"difficulty": difficulty,
|
||||||
|
"artist": artist,
|
||||||
|
})
|
||||||
|
return Response(template.render(
|
||||||
|
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||||
|
game_list=self.environment.globals["game_list"],
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
user_id=usr_sesh.user_id,
|
||||||
|
playlog=playlog_with_title,
|
||||||
|
playlog_count=playlog_count
|
||||||
|
), media_type="text/html; charset=utf-8")
|
||||||
|
else:
|
||||||
|
return RedirectResponse("/gate/", 303)
|
||||||
|
|
||||||
|
async def update_name(self, request: Request) -> bytes:
|
||||||
|
usr_sesh = self.validate_session(request)
|
||||||
|
if not usr_sesh:
|
||||||
|
return RedirectResponse("/gate/", 303)
|
||||||
|
|
||||||
|
form_data = await request.form()
|
||||||
|
new_name: str = form_data.get("new_name")
|
||||||
|
new_name_full = ""
|
||||||
|
|
||||||
|
if not new_name:
|
||||||
|
return RedirectResponse("/gate/?e=4", 303)
|
||||||
|
|
||||||
|
if len(new_name) > 8:
|
||||||
|
return RedirectResponse("/gate/?e=8", 303)
|
||||||
|
|
||||||
|
for x in new_name: # FIXME: This will let some invalid characters through atm
|
||||||
|
o = ord(x)
|
||||||
|
try:
|
||||||
|
if o == 0x20:
|
||||||
|
new_name_full += chr(0x3000)
|
||||||
|
elif o < 0x7F and o > 0x20:
|
||||||
|
new_name_full += chr(o + 0xFEE0)
|
||||||
|
elif o <= 0x7F:
|
||||||
|
self.logger.warn(f"Invalid ascii character {o:02X}")
|
||||||
|
return RedirectResponse("/gate/?e=4", 303)
|
||||||
|
else:
|
||||||
|
new_name_full += x
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Something went wrong parsing character {o:04X} - {e}")
|
||||||
|
return RedirectResponse("/gate/?e=4", 303)
|
||||||
|
|
||||||
|
if not await self.data.profile.update_name(usr_sesh.user_id, new_name_full):
|
||||||
|
return RedirectResponse("/gate/?e=999", 303)
|
||||||
|
|
||||||
|
return RedirectResponse("/game/mai2/?s=1", 303)
|
||||||
|
|
||||||
|
async def version_change(self, request: Request):
|
||||||
|
usr_sesh = self.validate_session(request)
|
||||||
|
if not usr_sesh:
|
||||||
|
usr_sesh = UserSession()
|
||||||
|
|
||||||
|
if usr_sesh.user_id > 0:
|
||||||
|
form_data = await request.form()
|
||||||
|
maimai_version = form_data.get("version")
|
||||||
|
self.logger.info(f"version change to: {maimai_version}")
|
||||||
|
if(maimai_version.isdigit()):
|
||||||
|
usr_sesh.maimai_version=int(maimai_version)
|
||||||
|
encoded_sesh = self.encode_session(usr_sesh)
|
||||||
|
self.logger.info(f"Created session with JWT {encoded_sesh}")
|
||||||
|
resp = RedirectResponse("/game/mai2/", 303)
|
||||||
|
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
|
||||||
|
return resp
|
||||||
|
else:
|
||||||
|
return RedirectResponse("/gate/", 303)
|
@ -134,6 +134,20 @@ favorite = Table(
|
|||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fav_music = Table(
|
||||||
|
"mai2_item_favorite_music",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("musicId", Integer, nullable=False),
|
||||||
|
UniqueConstraint("user", "musicId", name="mai2_item_favorite_music_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
charge = Table(
|
charge = Table(
|
||||||
"mai2_item_charge",
|
"mai2_item_charge",
|
||||||
metadata,
|
metadata,
|
||||||
@ -451,6 +465,30 @@ class Mai2ItemData(BaseData):
|
|||||||
return None
|
return None
|
||||||
return result.fetchall()
|
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))
|
||||||
|
if result:
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
async def add_fav_music(self, user_id: int, music_id: int) -> Optional[int]:
|
||||||
|
sql = insert(fav_music).values(
|
||||||
|
user = user_id,
|
||||||
|
musicId = music_id
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_do_nothing()
|
||||||
|
|
||||||
|
result = await self.execute(conflict)
|
||||||
|
if result:
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
self.logger.error(f"Failed to add music {music_id} as favorite for user {user_id}!")
|
||||||
|
|
||||||
|
async def remove_fav_music(self, user_id: int, music_id: int) -> None:
|
||||||
|
result = await self.execute(fav_music.delete(and_(fav_music.c.user == user_id, fav_music.c.musicId == music_id)))
|
||||||
|
if not result:
|
||||||
|
self.logger.error(f"Failed to remove music {music_id} as favorite for user {user_id}!")
|
||||||
|
|
||||||
async def put_card(
|
async def put_card(
|
||||||
self,
|
self,
|
||||||
user_id: int,
|
user_id: int,
|
||||||
|
@ -491,8 +491,31 @@ consec_logins = Table(
|
|||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
rival = Table(
|
||||||
|
"mai2_user_rival",
|
||||||
|
metadata,
|
||||||
|
Column("id", Integer, primary_key=True, nullable=False),
|
||||||
|
Column(
|
||||||
|
"user",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
"rival",
|
||||||
|
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
Column("show", Boolean, nullable=False, server_default="0"),
|
||||||
|
UniqueConstraint("user", "rival", name="mai2_user_rival_uk"),
|
||||||
|
mysql_charset="utf8mb4",
|
||||||
|
)
|
||||||
|
|
||||||
class Mai2ProfileData(BaseData):
|
class Mai2ProfileData(BaseData):
|
||||||
|
async def get_all_profile_versions(self, user_id: int) -> Optional[List[Row]]:
|
||||||
|
result = await self.execute(detail.select(detail.c.user == user_id))
|
||||||
|
if result:
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
async def put_profile_detail(
|
async def put_profile_detail(
|
||||||
self, user_id: int, version: int, detail_data: Dict, is_dx: bool = True
|
self, user_id: int, version: int, detail_data: Dict, is_dx: bool = True
|
||||||
) -> Optional[Row]:
|
) -> Optional[Row]:
|
||||||
@ -843,3 +866,52 @@ class Mai2ProfileData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchone()
|
return result.fetchone()
|
||||||
|
|
||||||
|
async def get_rivals(self, user_id: int) -> Optional[List[Row]]:
|
||||||
|
result = await self.execute(rival.select(rival.c.user == user_id))
|
||||||
|
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))
|
||||||
|
if result:
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
async def set_rival_shown(self, user_id: int, rival_id: int, is_shown: bool) -> None:
|
||||||
|
sql = rival.update(and_(rival.c.user == user_id, rival.c.rival == rival_id)).values(
|
||||||
|
show = is_shown
|
||||||
|
)
|
||||||
|
|
||||||
|
result = await self.execute(sql)
|
||||||
|
if not result:
|
||||||
|
self.logger.error(f"Failed to set rival {rival_id} shown status to {is_shown} for user {user_id}")
|
||||||
|
|
||||||
|
async def add_rival(self, user_id: int, rival_id: int) -> Optional[int]:
|
||||||
|
sql = insert(rival).values(
|
||||||
|
user = user_id,
|
||||||
|
rival = rival_id
|
||||||
|
)
|
||||||
|
|
||||||
|
conflict = sql.on_duplicate_key_do_nothing()
|
||||||
|
|
||||||
|
result = await self.execute(conflict)
|
||||||
|
if result:
|
||||||
|
return result.lastrowid
|
||||||
|
|
||||||
|
self.logger.error(f"Failed to add music {rival_id} as favorite for user {user_id}!")
|
||||||
|
|
||||||
|
async def remove_rival(self, user_id: int, rival_id: int) -> None:
|
||||||
|
result = await self.execute(rival.delete(and_(rival.c.user == user_id, rival.c.rival == rival_id)))
|
||||||
|
if not result:
|
||||||
|
self.logger.error(f"Failed to remove rival {rival_id} for user {user_id}!")
|
||||||
|
|
||||||
|
async def update_name(self, user_id: int, new_name: str) -> bool:
|
||||||
|
sql = detail.update(detail.c.user == user_id).values(
|
||||||
|
userName=new_name
|
||||||
|
)
|
||||||
|
result = await self.execute(sql)
|
||||||
|
|
||||||
|
if result is None:
|
||||||
|
self.logger.warning(f"Failed to set user {user_id} name to {new_name}")
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
@ -319,16 +319,16 @@ class Mai2ScoreData(BaseData):
|
|||||||
sql = best_score.select(
|
sql = best_score.select(
|
||||||
and_(
|
and_(
|
||||||
best_score.c.user == user_id,
|
best_score.c.user == user_id,
|
||||||
(best_score.c.song_id == song_id) if song_id is not None else True,
|
(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)
|
||||||
else:
|
else:
|
||||||
sql = best_score_old.select(
|
sql = best_score_old.select(
|
||||||
and_(
|
and_(
|
||||||
best_score_old.c.user == user_id,
|
best_score_old.c.user == user_id,
|
||||||
(best_score_old.c.song_id == song_id) if song_id is not None else True,
|
(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)
|
||||||
|
|
||||||
result = await self.execute(sql)
|
result = await self.execute(sql)
|
||||||
if result is None:
|
if result is None:
|
||||||
@ -398,3 +398,23 @@ class Mai2ScoreData(BaseData):
|
|||||||
if result is None:
|
if result is None:
|
||||||
return None
|
return None
|
||||||
return result.fetchall()
|
return result.fetchall()
|
||||||
|
|
||||||
|
async def get_playlogs(self, user_id: int, idx: int = 0, limit: int = 0) -> Optional[List[Row]]:
|
||||||
|
sql = playlog.select(playlog.c.user == user_id)
|
||||||
|
|
||||||
|
if limit:
|
||||||
|
sql = sql.limit(limit)
|
||||||
|
if idx:
|
||||||
|
sql = sql.offset(idx * limit)
|
||||||
|
|
||||||
|
result = await self.execute(sql)
|
||||||
|
if result:
|
||||||
|
return result.fetchall()
|
||||||
|
|
||||||
|
async def get_user_playlogs_count(self, aime_id: int) -> Optional[Row]:
|
||||||
|
sql = select(func.count()).where(playlog.c.user == aime_id)
|
||||||
|
result = await self.execute(sql)
|
||||||
|
if result is None:
|
||||||
|
self.logger.warning(f"aime_id {aime_id} has no playlog ")
|
||||||
|
return None
|
||||||
|
return result.scalar()
|
||||||
|
195
titles/mai2/templates/css/mai2_style.css
Normal file
195
titles/mai2/templates/css/mai2_style.css
Normal file
@ -0,0 +1,195 @@
|
|||||||
|
.mai2-header {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mai2-navi {
|
||||||
|
list-style-type: none;
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #333;
|
||||||
|
text-align: center;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mai2-navi li {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mai2-navi li a {
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
text-align: center;
|
||||||
|
padding: 14px 16px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mai2-navi li a:hover:not(.active) {
|
||||||
|
background-color: #111;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mai2-navi li a.active {
|
||||||
|
background-color: #4CAF50;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul.mai2-navi li.right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
|
||||||
|
ul.mai2-navi li.right,
|
||||||
|
ul.mai2-navi li {
|
||||||
|
float: none;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
border-collapse: separate;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #555555;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
text-align: left;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-rowdistinct tr:nth-child(even) {
|
||||||
|
background-color: #303030;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-rowdistinct tr:nth-child(odd) {
|
||||||
|
background-color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
text-align: center;
|
||||||
|
color: white;
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-large {
|
||||||
|
margin: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-large th,
|
||||||
|
.table-large td {
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-small {
|
||||||
|
width: 100%;
|
||||||
|
margin: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-small th,
|
||||||
|
.table-small td {
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bg-card {
|
||||||
|
background-color: #555555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover {
|
||||||
|
transition: all 0.2s ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-hover:hover {
|
||||||
|
transform: scale(1.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
.basic {
|
||||||
|
color: #28a745;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hard {
|
||||||
|
color: #ffc107;
|
||||||
|
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expert {
|
||||||
|
color: #dc3545;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.master {
|
||||||
|
color: #dd09e8;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ultimate {
|
||||||
|
color: #000000;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.score {
|
||||||
|
color: #ffffff;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rainbow {
|
||||||
|
background: linear-gradient(to right, red, yellow, lime, aqua, blue, fuchsia) 0 / 5em;
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.platinum {
|
||||||
|
color: #FFFF00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gold {
|
||||||
|
color: #FFFF00;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolling-text {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolling-text p {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolling-text h6 {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolling-text h5 {
|
||||||
|
white-space: nowrap;
|
||||||
|
display: inline-block;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrolling {
|
||||||
|
animation: scroll 10s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scroll {
|
||||||
|
0% {
|
||||||
|
transform: translateX(100%);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: translateX(-100%);
|
||||||
|
}
|
||||||
|
}
|
17
titles/mai2/templates/mai2_header.jinja
Normal file
17
titles/mai2/templates/mai2_header.jinja
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<div class="mai2-header">
|
||||||
|
<h1>maimai</h1>
|
||||||
|
<ul class="mai2-navi">
|
||||||
|
<li><a class="nav-link" href="/game/mai2/">PROFILE</a></li>
|
||||||
|
<li><a class="nav-link" href="/game/mai2/playlog/">RECORD</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
var currentPath = window.location.pathname;
|
||||||
|
if (currentPath === '/game/mai2/') {
|
||||||
|
$('.nav-link[href="/game/mai2/"]').addClass('active');
|
||||||
|
} else if (currentPath.startsWith('/game/mai2/playlog/')) {
|
||||||
|
$('.nav-link[href="/game/mai2/playlog/"]').addClass('active');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
134
titles/mai2/templates/mai2_index.jinja
Normal file
134
titles/mai2/templates/mai2_index.jinja
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
{% extends "core/templates/index.jinja" %}
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
{% include 'titles/mai2/templates/css/mai2_style.css' %}
|
||||||
|
</style>
|
||||||
|
<div class="container">
|
||||||
|
{% include 'titles/mai2/templates/mai2_header.jinja' %}
|
||||||
|
{% if profile is defined and profile is not none and profile|length > 0 %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-lg-8 m-auto mt-3">
|
||||||
|
<div class="card bg-card rounded">
|
||||||
|
<table class="table-large table-rowdistinct">
|
||||||
|
<caption align="top">OVERVIEW</caption>
|
||||||
|
<tr>
|
||||||
|
<th>{{ profile.userName }}</th>
|
||||||
|
<th>
|
||||||
|
<button type="button" class="btn btn-primary btn-sm" data-bs-toggle="modal" data-bs-target="#name_change">Edit</button>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>version:</td>
|
||||||
|
<td>
|
||||||
|
<select name="version" id="version" onChange="changeVersion(this)">
|
||||||
|
{% for ver in versions %}
|
||||||
|
{% if ver == cur_version %}
|
||||||
|
<option value="{{ ver }}" selected>{{ version_list[ver] }}</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="{{ ver }}">{{ version_list[ver] }}</option>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
{% if versions | length > 1 %}
|
||||||
|
<p style="margin-block-end: 0;">You have {{ versions | length }} versions.</p>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Rating:</td>
|
||||||
|
<td>
|
||||||
|
<span class="{% if profile.playerRating >= 15000 %}rainbow{% elif profile.playerRating < 15000 and profile.playerRating >= 14500 %}platinum{% elif profile.playerRating < 14500 and profile.playerRating >=14000 %}platinum{% endif %}">
|
||||||
|
{{ profile.playerRating }}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
(highest: {{ profile.highestRating }})
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Play Counts:</td>
|
||||||
|
<td>{{ profile.playCount }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Last Play Date:</td>
|
||||||
|
<td>{{ profile.lastPlayDate }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-8 m-auto mt-3">
|
||||||
|
<div class="card bg-card rounded">
|
||||||
|
<table class="table-large table-rowdistinct">
|
||||||
|
<caption align="top">SCORE</caption>
|
||||||
|
<tr>
|
||||||
|
<td>Total Delux Score:</td>
|
||||||
|
<td>{{ profile.totalDeluxscore }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Basic Delux Score:</td>
|
||||||
|
<td>{{ profile.totalBasicDeluxscore }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Advanced Delux Score:</td>
|
||||||
|
<td>{{ profile.totalAdvancedDeluxscore }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Expert Delux Score:</td>
|
||||||
|
<td>{{ profile.totalExpertDeluxscore }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total Master Delux Score:</td>
|
||||||
|
<td>{{ profile.totalMasterDeluxscore }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Total ReMaster Delux Score:</td>
|
||||||
|
<td>{{ profile.totalReMasterDeluxscore }}</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% if error is defined %}
|
||||||
|
{% include "core/templates/widgets/err_banner.jinja" %}
|
||||||
|
{% endif %}
|
||||||
|
{% elif sesh is defined and sesh is not none and sesh.user_id > 0 %}
|
||||||
|
No profile information found for this account.
|
||||||
|
{% else %}
|
||||||
|
Login to view profile information.
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="modal fade" id="name_change" tabindex="-1" aria-labelledby="name_change_label" data-bs-theme="dark"
|
||||||
|
aria-hidden="true">
|
||||||
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title">Name change</h5>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="new_name_form" action="/game/mai2/update.name" method="post" style="outline: 0;">
|
||||||
|
<label class="form-label" for="new_name">new name:</label>
|
||||||
|
<input class="form-control" aria-describedby="newNameHelp" form="new_name_form" id="new_name"
|
||||||
|
name="new_name" maxlength="14" type="text" required>
|
||||||
|
<div id="newNameHelp" class="form-text">name must be full-width character string.
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<input type=submit class="btn btn-primary" type="button" form="new_name_form">
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script>
|
||||||
|
function changeVersion(sel) {
|
||||||
|
$.post("/game/mai2/version.change", { version: sel.value })
|
||||||
|
.done(function (data) {
|
||||||
|
location.reload();
|
||||||
|
})
|
||||||
|
.fail(function () {
|
||||||
|
alert("Failed to update version.");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
225
titles/mai2/templates/mai2_playlog.jinja
Normal file
225
titles/mai2/templates/mai2_playlog.jinja
Normal file
@ -0,0 +1,225 @@
|
|||||||
|
{% extends "core/templates/index.jinja" %}
|
||||||
|
{% block content %}
|
||||||
|
<style>
|
||||||
|
{% include 'titles/mai2/templates/css/mai2_style.css' %}
|
||||||
|
</style>
|
||||||
|
<div class="container">
|
||||||
|
{% include 'titles/mai2/templates/mai2_header.jinja' %}
|
||||||
|
{% if playlog is defined and playlog is not none %}
|
||||||
|
<div class="row">
|
||||||
|
<h4 style="text-align: center;">Playlog counts: {{ playlog_count }}</h4>
|
||||||
|
{% set rankName = ['D', 'C', 'B', 'BB', 'BBB', 'A', 'AA', 'AAA', 'S', 'S+', 'SS', 'SS+', 'SSS', 'SSS+'] %}
|
||||||
|
{% set difficultyName = ['basic', 'hard', 'expert', 'master', 'ultimate'] %}
|
||||||
|
{% for record in playlog %}
|
||||||
|
<div class="col-lg-6 mt-3">
|
||||||
|
<div class="card bg-card rounded card-hover">
|
||||||
|
<div class="card-header row">
|
||||||
|
<div class="col-8 scrolling-text">
|
||||||
|
<h5 class="card-text"> {{ record.title }} </h5>
|
||||||
|
<br>
|
||||||
|
<h6 class="card-text"> {{ record.artist }} </h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<h6 class="card-text">{{ record.raw.userPlayDate }}</h6>
|
||||||
|
<h6 class="card-text">TRACK {{ record.raw.trackNo }}</h6>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body row">
|
||||||
|
<div class="col-3" style="text-align: center;">
|
||||||
|
<h4 class="card-text">{{ record.raw.deluxscore }}</h4>
|
||||||
|
<h2>{{ rankName[record.raw.rank] }}</h2>
|
||||||
|
<h6
|
||||||
|
class="{% if record.raw.level == 0 %}basic{% elif record.raw.level == 1 %}advanced{% elif record.raw.level == 2 %}expert{% elif record.raw.level == 3 %}master{% elif record.raw.level == 4 %}remaster{% endif %}">
|
||||||
|
{{ difficultyName[record.raw.level] }}  {{ record.difficulty }}
|
||||||
|
</h6>
|
||||||
|
</div>
|
||||||
|
<div class="col-6" style="text-align: center;">
|
||||||
|
<table class="table-small table-rowdistinc">
|
||||||
|
<tr>
|
||||||
|
<td>CRITICAL PERFECT</td>
|
||||||
|
<td>
|
||||||
|
Tap: {{ record.raw.tapCriticalPerfect }}<br>
|
||||||
|
Hold: {{ record.raw.holdCriticalPerfect }}<br>
|
||||||
|
Slide: {{ record.raw.slideCriticalPerfect }}<br>
|
||||||
|
Touch: {{ record.raw.touchCriticalPerfect }}<br>
|
||||||
|
Break: {{ record.raw.breakCriticalPerfect }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>PERFECT</td>
|
||||||
|
<td>
|
||||||
|
Tap: {{ record.raw.tapPerfect }}<br>
|
||||||
|
Hold: {{ record.raw.holdPerfect }}<br>
|
||||||
|
Slide: {{ record.raw.slidePerfect }}<br>
|
||||||
|
Touch: {{ record.raw.touchPerfect }}<br>
|
||||||
|
Break: {{ record.raw.breakPerfect }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>GREAT</td>
|
||||||
|
<td>
|
||||||
|
Tap: {{ record.raw.tapGreat }}<br>
|
||||||
|
Hold: {{ record.raw.holdGreat }}<br>
|
||||||
|
Slide: {{ record.raw.slideGreat }}<br>
|
||||||
|
Touch: {{ record.raw.touchGreat }}<br>
|
||||||
|
Break: {{ record.raw.breakGreat }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>GOOD</td>
|
||||||
|
<td>
|
||||||
|
Tap: {{ record.raw.tapGood }}<br>
|
||||||
|
Hold: {{ record.raw.holdGood }}<br>
|
||||||
|
Slide: {{ record.raw.slideGood }}<br>
|
||||||
|
Touch: {{ record.raw.touchGood }}<br>
|
||||||
|
Break: {{ record.raw.breakGood }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>MISS</td>
|
||||||
|
<td>
|
||||||
|
Tap: {{ record.raw.tapMiss }}<br>
|
||||||
|
Hold: {{ record.raw.holdMiss }}<br>
|
||||||
|
Slide: {{ record.raw.slideMiss }}<br>
|
||||||
|
Touch: {{ record.raw.touchMiss }}<br>
|
||||||
|
Break: {{ record.raw.breakMiss }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="col-3" style="text-align: center;">
|
||||||
|
{%if record.raw.comboStatus == 1 %}
|
||||||
|
<h6>FULL COMBO</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.comboStatus == 2 %}
|
||||||
|
<h6>FULL COMBO +</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.comboStatus == 3 %}
|
||||||
|
<h6>ALL PERFECT</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.comboStatus == 4 %}
|
||||||
|
<h6>ALL PERFECT +</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.syncStatus == 1 %}
|
||||||
|
<h6>FULL SYNC</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.syncStatus == 2 %}
|
||||||
|
<h6>FULL SYNC +</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.syncStatus == 3 %}
|
||||||
|
<h6>FULL SYNC DX</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.syncStatus == 4 %}
|
||||||
|
<h6>FULL SYNC DX +</h6>
|
||||||
|
{% endif %}
|
||||||
|
{%if record.raw.isAchieveNewRecord == 1 or record.raw.isDeluxscoreNewRecord == 1 %}
|
||||||
|
<h6>NEW RECORD</h6>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
{% set playlog_pages = playlog_count // 20 + 1 %}
|
||||||
|
{% elif sesh is defined and sesh is not none and sesh.user_id > 0 %}
|
||||||
|
No Playlog information found for this account.
|
||||||
|
{% else %}
|
||||||
|
Login to view profile information.
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<footer class="navbar-fixed-bottom">
|
||||||
|
<nav aria-label="Playlog page navigation">
|
||||||
|
<ul class="pagination justify-content-center mt-3">
|
||||||
|
<li class="page-item"><a id="prev_page" class="page-link" href="#">Previous</a></li>
|
||||||
|
<li class="page-item"><a id="first_page" class="page-link" href="/game/mai2/playlog/">1</a></li>
|
||||||
|
<li class="page-item"><a id="prev_3_page" class="page-link" href="">...</a></li>
|
||||||
|
<li class="page-item"><a id="front_page" class="page-link" href="">2</a></li>
|
||||||
|
<li class="page-item"><a id="cur_page" class="page-link active" href="">3</a></li>
|
||||||
|
<li class="page-item"><a id="back_page" class="page-link" href="">4</a></li>
|
||||||
|
<li class="page-item"><a id="next_3_page" class="page-link" href="">...</a></li>
|
||||||
|
<li class="page-item"><a id="last_page" class="page-link" href="/game/mai2/playlog/{{ playlog_pages }}">{{
|
||||||
|
playlog_pages }}</a></li>
|
||||||
|
<li class="page-item"><a id="next_page" class="page-link" href="#">Next</a></li>
|
||||||
|
 
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-5"></div>
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="input-group rounded">
|
||||||
|
<input id="page_input" type="text" class="form-control" placeholder="go to page">
|
||||||
|
<span class="input-group-btn">
|
||||||
|
<button id="go_button" class="btn btn-light" type="button">
|
||||||
|
Go!
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-5"></div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
$(document).ready(function () {
|
||||||
|
$('.scrolling-text p, .scrolling-text h1, .scrolling-text h2, .scrolling-text h3, .scrolling-text h4, .scrolling-text h5, .scrolling-text h6').each(function () {
|
||||||
|
var parentWidth = $(this).parent().width();
|
||||||
|
var elementWidth = $(this).outerWidth();
|
||||||
|
var elementWidthWithPadding = $(this).outerWidth(true);
|
||||||
|
|
||||||
|
if (elementWidthWithPadding > parentWidth) {
|
||||||
|
$(this).addClass('scrolling');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var currentUrl = window.location.pathname;
|
||||||
|
var currentPage = parseInt(currentUrl.split('/').pop());
|
||||||
|
var rootUrl = '/game/mai2/playlog/';
|
||||||
|
var playlogPages = {{ playlog_pages }};
|
||||||
|
if (Number.isNaN(currentPage)) {
|
||||||
|
currentPage = 1;
|
||||||
|
}
|
||||||
|
$('#cur_page').text(currentPage);
|
||||||
|
$('#prev_page').attr('href', rootUrl + (currentPage - 1))
|
||||||
|
$('#next_page').attr('href', rootUrl + (currentPage + 1))
|
||||||
|
$('#front_page').attr('href', rootUrl + (currentPage - 1))
|
||||||
|
$('#front_page').text(currentPage - 1);
|
||||||
|
$('#back_page').attr('href', rootUrl + (currentPage + 1))
|
||||||
|
$('#back_page').text(currentPage + 1);
|
||||||
|
$('#prev_3_page').attr('href', rootUrl + (currentPage - 3))
|
||||||
|
$('#next_3_page').attr('href', rootUrl + (currentPage + 3))
|
||||||
|
if ((currentPage - 1) < 3) {
|
||||||
|
$('#prev_3_page').hide();
|
||||||
|
if ((currentPage - 1) < 2) {
|
||||||
|
$('#front_page').hide();
|
||||||
|
if (currentPage === 1) {
|
||||||
|
$('#first_page').hide();
|
||||||
|
$('#prev_page').addClass('disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((playlogPages - currentPage) < 3) {
|
||||||
|
$('#next_3_page').hide();
|
||||||
|
if ((playlogPages - currentPage) < 2) {
|
||||||
|
$('#back_page').hide();
|
||||||
|
if (currentPage === playlogPages) {
|
||||||
|
$('#last_page').hide();
|
||||||
|
$('#next_page').addClass('disabled');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('#go_button').click(function () {
|
||||||
|
var pageNumber = parseInt($('#page_input').val());
|
||||||
|
|
||||||
|
if (!Number.isNaN(pageNumber) && pageNumber <= playlogPages && pageNumber >= 0) {
|
||||||
|
var url = '/game/mai2/playlog/' + pageNumber;
|
||||||
|
window.location.href = url;
|
||||||
|
} else {
|
||||||
|
$('#page_input').val('');
|
||||||
|
$('#page_input').attr('placeholder', 'invalid input!');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
@ -1,8 +1,6 @@
|
|||||||
from typing import Any, List, Dict
|
from typing import Any, List, Dict
|
||||||
from random import randint
|
from random import randint
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import pytz
|
|
||||||
import json
|
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
from titles.mai2.splashplus import Mai2SplashPlus
|
from titles.mai2.splashplus import Mai2SplashPlus
|
||||||
@ -14,207 +12,3 @@ class Mai2Universe(Mai2SplashPlus):
|
|||||||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||||
super().__init__(cfg, game_cfg)
|
super().__init__(cfg, game_cfg)
|
||||||
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
self.version = Mai2Constants.VER_MAIMAI_DX_UNIVERSE
|
||||||
|
|
||||||
async def handle_cm_get_user_preview_api_request(self, data: Dict) -> Dict:
|
|
||||||
p = await self.data.profile.get_profile_detail(data["userId"], self.version)
|
|
||||||
if p is None:
|
|
||||||
return {}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"userName": p["userName"],
|
|
||||||
"rating": p["playerRating"],
|
|
||||||
# hardcode lastDataVersion for CardMaker
|
|
||||||
"lastDataVersion": "1.20.00",
|
|
||||||
# checks if the user is still logged in
|
|
||||||
"isLogin": False,
|
|
||||||
"isExistSellingCard": True,
|
|
||||||
}
|
|
||||||
|
|
||||||
async def handle_cm_get_user_data_api_request(self, data: Dict) -> Dict:
|
|
||||||
# user already exists, because the preview checks that already
|
|
||||||
p = await self.data.profile.get_profile_detail(data["userId"], self.version)
|
|
||||||
|
|
||||||
cards = await self.data.card.get_user_cards(data["userId"])
|
|
||||||
if cards is None or len(cards) == 0:
|
|
||||||
# This should never happen
|
|
||||||
self.logger.error(
|
|
||||||
f"handle_get_user_data_api_request: Internal error - No cards found for user id {data['userId']}"
|
|
||||||
)
|
|
||||||
return {}
|
|
||||||
|
|
||||||
# get the dict representation of the row so we can modify values
|
|
||||||
user_data = p._asdict()
|
|
||||||
|
|
||||||
# remove the values the game doesn't want
|
|
||||||
user_data.pop("id")
|
|
||||||
user_data.pop("user")
|
|
||||||
user_data.pop("version")
|
|
||||||
|
|
||||||
return {"userId": data["userId"], "userData": user_data}
|
|
||||||
|
|
||||||
async def handle_cm_login_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
async def handle_cm_logout_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
|
||||||
async def handle_cm_get_selling_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
selling_cards = await self.data.static.get_enabled_cards(self.version)
|
|
||||||
if selling_cards is None:
|
|
||||||
return {"length": 0, "sellingCardList": []}
|
|
||||||
|
|
||||||
selling_card_list = []
|
|
||||||
for card in selling_cards:
|
|
||||||
tmp = card._asdict()
|
|
||||||
tmp.pop("id")
|
|
||||||
tmp.pop("version")
|
|
||||||
tmp.pop("cardName")
|
|
||||||
tmp.pop("enabled")
|
|
||||||
|
|
||||||
tmp["startDate"] = datetime.strftime(
|
|
||||||
tmp["startDate"], Mai2Constants.DATE_TIME_FORMAT
|
|
||||||
)
|
|
||||||
tmp["endDate"] = datetime.strftime(
|
|
||||||
tmp["endDate"], Mai2Constants.DATE_TIME_FORMAT
|
|
||||||
)
|
|
||||||
tmp["noticeStartDate"] = datetime.strftime(
|
|
||||||
tmp["noticeStartDate"], Mai2Constants.DATE_TIME_FORMAT
|
|
||||||
)
|
|
||||||
tmp["noticeEndDate"] = datetime.strftime(
|
|
||||||
tmp["noticeEndDate"], Mai2Constants.DATE_TIME_FORMAT
|
|
||||||
)
|
|
||||||
|
|
||||||
selling_card_list.append(tmp)
|
|
||||||
|
|
||||||
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:
|
|
||||||
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
|
|
||||||
|
|
||||||
if len(user_cards[start_idx:]) > 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]),
|
|
||||||
"nextIndex": next_idx,
|
|
||||||
"userCardList": card_list[start_idx:end_idx],
|
|
||||||
}
|
|
||||||
|
|
||||||
async def handle_cm_get_user_item_api_request(self, data: Dict) -> Dict:
|
|
||||||
await super().handle_get_user_item_api_request(data)
|
|
||||||
|
|
||||||
async def handle_cm_get_user_character_api_request(self, data: Dict) -> Dict:
|
|
||||||
characters = await self.data.item.get_characters(data["userId"])
|
|
||||||
|
|
||||||
chara_list = []
|
|
||||||
for chara in characters:
|
|
||||||
chara_list.append(
|
|
||||||
{
|
|
||||||
"characterId": chara["characterId"],
|
|
||||||
# no clue why those values are even needed
|
|
||||||
"point": 0,
|
|
||||||
"count": 0,
|
|
||||||
"level": chara["level"],
|
|
||||||
"nextAwake": 0,
|
|
||||||
"nextAwakePercent": 0,
|
|
||||||
"favorite": False,
|
|
||||||
"awakening": chara["awakening"],
|
|
||||||
"useCount": chara["useCount"],
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"length": len(chara_list),
|
|
||||||
"userCharacterList": chara_list,
|
|
||||||
}
|
|
||||||
|
|
||||||
async def handle_cm_get_user_card_print_error_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"length": 0, "userPrintDetailList": []}
|
|
||||||
|
|
||||||
async def handle_cm_upsert_user_print_api_request(self, data: Dict) -> Dict:
|
|
||||||
user_id = data["userId"]
|
|
||||||
upsert = data["userPrintDetail"]
|
|
||||||
|
|
||||||
# set a random card serial number
|
|
||||||
serial_id = "".join([str(randint(0, 9)) for _ in range(20)])
|
|
||||||
|
|
||||||
# calculate start and end date of the card
|
|
||||||
start_date = datetime.utcnow()
|
|
||||||
end_date = datetime.utcnow() + timedelta(days=15)
|
|
||||||
|
|
||||||
user_card = upsert["userCard"]
|
|
||||||
await self.data.item.put_card(
|
|
||||||
user_id,
|
|
||||||
user_card["cardId"],
|
|
||||||
user_card["cardTypeId"],
|
|
||||||
user_card["charaId"],
|
|
||||||
user_card["mapId"],
|
|
||||||
# add the correct start date and also the end date in 15 days
|
|
||||||
start_date,
|
|
||||||
end_date,
|
|
||||||
)
|
|
||||||
|
|
||||||
# get the profile extend to save the new bought card
|
|
||||||
extend = await self.data.profile.get_profile_extend(user_id, self.version)
|
|
||||||
if extend:
|
|
||||||
extend = extend._asdict()
|
|
||||||
# parse the selectedCardList
|
|
||||||
# 6 = Freedom Pass, 4 = Gold Pass (cardTypeId)
|
|
||||||
selected_cards: List = extend["selectedCardList"]
|
|
||||||
|
|
||||||
# if no pass is already added, add the corresponding pass
|
|
||||||
if not user_card["cardTypeId"] in selected_cards:
|
|
||||||
selected_cards.insert(0, user_card["cardTypeId"])
|
|
||||||
|
|
||||||
extend["selectedCardList"] = selected_cards
|
|
||||||
await self.data.profile.put_profile_extend(user_id, self.version, extend)
|
|
||||||
|
|
||||||
# properly format userPrintDetail for the database
|
|
||||||
upsert.pop("userCard")
|
|
||||||
upsert.pop("serialId")
|
|
||||||
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
|
||||||
|
|
||||||
await self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
|
||||||
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"orderId": 0,
|
|
||||||
"serialId": serial_id,
|
|
||||||
"startDate": datetime.strftime(start_date, Mai2Constants.DATE_TIME_FORMAT),
|
|
||||||
"endDate": datetime.strftime(end_date, Mai2Constants.DATE_TIME_FORMAT),
|
|
||||||
}
|
|
||||||
|
|
||||||
async def handle_cm_upsert_user_printlog_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {
|
|
||||||
"returnCode": 1,
|
|
||||||
"orderId": 0,
|
|
||||||
"serialId": data["userPrintlog"]["serialId"],
|
|
||||||
}
|
|
||||||
|
|
||||||
async def handle_cm_upsert_buy_card_api_request(self, data: Dict) -> Dict:
|
|
||||||
return {"returnCode": 1}
|
|
||||||
|
@ -31,7 +31,8 @@ class OngekiFrontend(FE_Base):
|
|||||||
|
|
||||||
def get_routes(self) -> List[Route]:
|
def get_routes(self) -> List[Route]:
|
||||||
return [
|
return [
|
||||||
Route("/", self.render_GET)
|
Route("/", self.render_GET),
|
||||||
|
Route("/version.change", self.render_POST, methods=['POST'])
|
||||||
]
|
]
|
||||||
|
|
||||||
async def render_GET(self, request: Request) -> bytes:
|
async def render_GET(self, request: Request) -> bytes:
|
||||||
@ -69,29 +70,34 @@ class OngekiFrontend(FE_Base):
|
|||||||
return RedirectResponse("/gate/", 303)
|
return RedirectResponse("/gate/", 303)
|
||||||
|
|
||||||
async def render_POST(self, request: Request):
|
async def render_POST(self, request: Request):
|
||||||
uri = request.uri.decode()
|
uri = request.url.path
|
||||||
|
frm = await request.form()
|
||||||
usr_sesh = self.validate_session(request)
|
usr_sesh = self.validate_session(request)
|
||||||
if not usr_sesh:
|
if not usr_sesh:
|
||||||
usr_sesh = UserSession()
|
usr_sesh = UserSession()
|
||||||
|
|
||||||
if usr_sesh.user_id > 0:
|
if usr_sesh.user_id > 0:
|
||||||
if uri == "/game/ongeki/rival.add":
|
if uri == "/game/ongeki/rival.add":
|
||||||
rival_id = request.args[b"rivalUserId"][0].decode()
|
rival_id = frm.get("rivalUserId")
|
||||||
await self.data.profile.put_rival(usr_sesh.user_id, rival_id)
|
await self.data.profile.put_rival(usr_sesh.user_id, rival_id)
|
||||||
# self.logger.info(f"{usr_sesh.user_id} added a rival")
|
# self.logger.info(f"{usr_sesh.user_id} added a rival")
|
||||||
return RedirectResponse(b"/game/ongeki/", 303)
|
return RedirectResponse(b"/game/ongeki/", 303)
|
||||||
|
|
||||||
elif uri == "/game/ongeki/rival.delete":
|
elif uri == "/game/ongeki/rival.delete":
|
||||||
rival_id = request.args[b"rivalUserId"][0].decode()
|
rival_id = frm.get("rivalUserId")
|
||||||
await self.data.profile.delete_rival(usr_sesh.user_id, rival_id)
|
await self.data.profile.delete_rival(usr_sesh.user_id, rival_id)
|
||||||
# self.logger.info(f"{response}")
|
# self.logger.info(f"{response}")
|
||||||
return RedirectResponse(b"/game/ongeki/", 303)
|
return RedirectResponse(b"/game/ongeki/", 303)
|
||||||
|
|
||||||
elif uri == "/game/ongeki/version.change":
|
elif uri == "/game/ongeki/version.change":
|
||||||
ongeki_version=request.args[b"version"][0].decode()
|
ongeki_version=frm.get("version")
|
||||||
if(ongeki_version.isdigit()):
|
if(ongeki_version.isdigit()):
|
||||||
usr_sesh.ongeki_version=int(ongeki_version)
|
usr_sesh.ongeki_version=int(ongeki_version)
|
||||||
return RedirectResponse("/game/ongeki/", 303)
|
enc = self.encode_session(usr_sesh)
|
||||||
|
resp = RedirectResponse("/game/ongeki/", 303)
|
||||||
|
resp.delete_cookie('ARTEMIS_SESH')
|
||||||
|
resp.set_cookie('ARTEMIS_SESH', enc)
|
||||||
|
return resp
|
||||||
|
|
||||||
else:
|
else:
|
||||||
Response("Something went wrong", status_code=500)
|
Response("Something went wrong", status_code=500)
|
||||||
|
@ -30,7 +30,7 @@ score_best = Table(
|
|||||||
Column("isFullCombo", Boolean, nullable=False),
|
Column("isFullCombo", Boolean, nullable=False),
|
||||||
Column("isAllBreake", Boolean, nullable=False),
|
Column("isAllBreake", Boolean, nullable=False),
|
||||||
Column("isLock", Boolean, nullable=False),
|
Column("isLock", Boolean, nullable=False),
|
||||||
Column("clearStatus", Boolean, nullable=False),
|
Column("clearStatus", Integer, nullable=False),
|
||||||
Column("isStoryWatched", Boolean, nullable=False),
|
Column("isStoryWatched", Boolean, nullable=False),
|
||||||
Column("platinumScoreMax", Integer),
|
Column("platinumScoreMax", Integer),
|
||||||
UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"),
|
UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"),
|
||||||
|
Loading…
Reference in New Issue
Block a user