forked from Dniel97/artemis
Merge branch 'develop' into idac
This commit is contained in:
commit
0f12288005
11
changelog.md
11
changelog.md
@ -1,6 +1,17 @@
|
|||||||
# 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.
|
||||||
|
|
||||||
|
## 20240526
|
||||||
|
+ Fixed missing awaits causing coroutine error
|
||||||
|
|
||||||
|
## 20240524
|
||||||
|
### DIVA
|
||||||
|
+ Fixed new profile start request causing coroutine error
|
||||||
|
|
||||||
|
## 20240523
|
||||||
|
### DIVA
|
||||||
|
+ Fixed binary handler & render_POST errors
|
||||||
|
|
||||||
## 20240408
|
## 20240408
|
||||||
### System
|
### System
|
||||||
+ Modified the game specific documentation
|
+ Modified the game specific documentation
|
||||||
|
107
core/allnet.py
107
core/allnet.py
@ -171,7 +171,7 @@ class AllnetServlet:
|
|||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
|
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
|
||||||
await self.data.base.log_event(
|
await self.data.base.log_event(
|
||||||
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg
|
"allnet", "ALLNET_AUTH_UNKNOWN_SERIAL", logging.WARN, msg, {"serial": req.serial}, None, None, None, request_ip, req.game_id, req.ver
|
||||||
)
|
)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
@ -183,9 +183,9 @@ class AllnetServlet:
|
|||||||
arcade = await self.data.arcade.get_arcade(machine["arcade"])
|
arcade = await self.data.arcade.get_arcade(machine["arcade"])
|
||||||
if self.config.server.check_arcade_ip:
|
if self.config.server.check_arcade_ip:
|
||||||
if arcade["ip"] and arcade["ip"] is not None and arcade["ip"] != req.ip:
|
if arcade["ip"] and arcade["ip"] is not None and arcade["ip"] != req.ip:
|
||||||
msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})."
|
msg = f"{req.serial} attempted allnet auth from bad IP {req.ip} (expected {arcade['ip']})."
|
||||||
await self.data.base.log_event(
|
await self.data.base.log_event(
|
||||||
"allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg
|
"allnet", "ALLNET_AUTH_BAD_IP", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver
|
||||||
)
|
)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
@ -194,9 +194,9 @@ class AllnetServlet:
|
|||||||
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
||||||
|
|
||||||
elif (not arcade["ip"] or arcade["ip"] is None) and self.config.server.strict_ip_checking:
|
elif (not arcade["ip"] or arcade["ip"] is None) and self.config.server.strict_ip_checking:
|
||||||
msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)."
|
msg = f"{req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)."
|
||||||
await self.data.base.log_event(
|
await self.data.base.log_event(
|
||||||
"allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg
|
"allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver
|
||||||
)
|
)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
@ -204,6 +204,16 @@ class AllnetServlet:
|
|||||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
||||||
|
|
||||||
|
if machine['game'] and machine['game'] != req.game_id:
|
||||||
|
msg = f"{req.serial} attempted allnet auth with bad game ID {req.game_id} (expected {machine['game']})."
|
||||||
|
await self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_BAD_GAME", logging.ERROR, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver
|
||||||
|
)
|
||||||
|
self.logger.warning(msg)
|
||||||
|
|
||||||
|
resp.stat = ALLNET_STAT.bad_game.value
|
||||||
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
|
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
||||||
|
|
||||||
country = (
|
country = (
|
||||||
arcade["country"] if machine["country"] is None else machine["country"]
|
arcade["country"] if machine["country"] is None else machine["country"]
|
||||||
@ -236,11 +246,14 @@ class AllnetServlet:
|
|||||||
arcade["timezone"] if arcade["timezone"] is not None else "+0900" if req.format_ver == 3 else "+09:00"
|
arcade["timezone"] if arcade["timezone"] is not None else "+0900" if req.format_ver == 3 else "+09:00"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
else:
|
||||||
|
arcade = None
|
||||||
|
|
||||||
if req.game_id not in TitleServlet.title_registry:
|
if req.game_id not in TitleServlet.title_registry:
|
||||||
if not self.config.server.is_develop:
|
if not self.config.server.is_develop:
|
||||||
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
||||||
await self.data.base.log_event(
|
await self.data.base.log_event(
|
||||||
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg
|
"allnet", "ALLNET_AUTH_UNKNOWN_GAME", logging.WARN, msg, {}, None, arcade['id'] if arcade else None, machine['id'] if machine else None, request_ip, req.game_id, req.ver
|
||||||
)
|
)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
@ -271,8 +284,17 @@ class AllnetServlet:
|
|||||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n")
|
||||||
|
|
||||||
|
if machine and arcade:
|
||||||
msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}"
|
msg = f"{req.serial} authenticated from {request_ip}: {req.game_id} v{req.ver}"
|
||||||
await self.data.base.log_event("allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg)
|
await self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_SUCCESS", logging.INFO, msg, {}, None, arcade['id'], machine['id'], request_ip, req.game_id, req.ver
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
msg = f"Allow unregistered serial {req.serial} to authenticate from {request_ip}: {req.game_id} v{req.ver}"
|
||||||
|
await self.data.base.log_event(
|
||||||
|
"allnet", "ALLNET_AUTH_SUCCESS_UNREG", logging.INFO, msg, {"serial": req.serial}, None, None, None, request_ip, req.game_id, req.ver
|
||||||
|
)
|
||||||
|
|
||||||
self.logger.info(msg)
|
self.logger.info(msg)
|
||||||
|
|
||||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||||
@ -329,7 +351,11 @@ class AllnetServlet:
|
|||||||
):
|
):
|
||||||
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n")
|
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n")
|
||||||
|
|
||||||
else: # TODO: Keychip check
|
else:
|
||||||
|
machine = await self.data.arcade.get_machine(req.serial)
|
||||||
|
if not machine or not machine['ota_enable'] or not machine['is_cab'] or machine['is_blacklisted']:
|
||||||
|
return PlainTextResponse(urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n")
|
||||||
|
|
||||||
if path.exists(
|
if path.exists(
|
||||||
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
|
f"{self.config.allnet.update_cfg_folder}/{req.game_id}-{req.ver.replace('.', '')}-app.ini"
|
||||||
):
|
):
|
||||||
@ -340,8 +366,13 @@ class AllnetServlet:
|
|||||||
):
|
):
|
||||||
resp.uri += f"|http://{self.config.server.hostname}:{self.config.server.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-opt.ini"
|
resp.uri += f"|http://{self.config.server.hostname}:{self.config.server.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-opt.ini"
|
||||||
|
|
||||||
self.logger.debug(f"Sending download uri {resp.uri}")
|
if resp.uri:
|
||||||
await self.data.base.log_event("allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"{Utils.get_ip_addr(request)} requested DL Order for {req.serial} {req.game_id} v{req.ver}")
|
self.logger.info(f"Sending download uri {resp.uri}")
|
||||||
|
await self.data.base.log_event(
|
||||||
|
"allnet", "DLORDER_REQ_SUCCESS", logging.INFO, f"Send download URI to {req.serial} for {req.game_id} v{req.ver} from {Utils.get_ip_addr(request)}", {"uri": resp.uri}, None,
|
||||||
|
machine['arcade'], machine['id'], request_ip, req.game_id, req.ver
|
||||||
|
)
|
||||||
|
# Maybe add a log event for checkin but no url sent?
|
||||||
|
|
||||||
res_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n"
|
res_str = urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n"
|
||||||
|
|
||||||
@ -357,13 +388,16 @@ class AllnetServlet:
|
|||||||
|
|
||||||
async def handle_dlorder_ini(self, request: Request) -> bytes:
|
async def handle_dlorder_ini(self, request: Request) -> bytes:
|
||||||
req_file = request.path_params.get("file", "").replace("%0A", "").replace("\n", "")
|
req_file = request.path_params.get("file", "").replace("%0A", "").replace("\n", "")
|
||||||
|
request_ip = Utils.get_ip_addr(request)
|
||||||
|
|
||||||
if not req_file:
|
if not req_file:
|
||||||
return PlainTextResponse(status_code=404)
|
return PlainTextResponse(status_code=404)
|
||||||
|
|
||||||
if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"):
|
if path.exists(f"{self.config.allnet.update_cfg_folder}/{req_file}"):
|
||||||
self.logger.info(f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful")
|
self.logger.info(f"Request for DL INI file {req_file} from {request_ip} successful")
|
||||||
await self.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}")
|
await self.data.base.log_event(
|
||||||
|
"allnet", "DLORDER_INI_SENT", logging.INFO, f"{request_ip} successfully recieved {req_file}", {"file": req_file}, ip=request_ip
|
||||||
|
)
|
||||||
|
|
||||||
return PlainTextResponse(open(
|
return PlainTextResponse(open(
|
||||||
f"{self.config.allnet.update_cfg_folder}/{req_file}", "r", encoding="utf-8"
|
f"{self.config.allnet.update_cfg_folder}/{req_file}", "r", encoding="utf-8"
|
||||||
@ -401,7 +435,13 @@ class AllnetServlet:
|
|||||||
msg = f"{rep.serial} @ {client_ip} reported {rep.rep_type.name} download state {rep.rf_state.name} for {rep.gd} v{rep.dav}:"\
|
msg = f"{rep.serial} @ {client_ip} reported {rep.rep_type.name} download state {rep.rf_state.name} for {rep.gd} v{rep.dav}:"\
|
||||||
f" {rep.tdsc}/{rep.tsc} segments downloaded for working files {rep.wfl} with {rep.dfl if rep.dfl else 'none'} complete."
|
f" {rep.tdsc}/{rep.tsc} segments downloaded for working files {rep.wfl} with {rep.dfl if rep.dfl else 'none'} complete."
|
||||||
|
|
||||||
await self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data)
|
machine = await self.data.arcade.get_machine(rep.serial)
|
||||||
|
if machine:
|
||||||
|
await self.data.base.log_event("allnet", "DL_REPORT", logging.INFO, msg, dl_data, None, machine['arcade'], machine['id'], client_ip, rep.gd, rep.dav)
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = "Unknown serial " + msg
|
||||||
|
await self.data.base.log_event("allnet", "DL_REPORT_UNREG", logging.INFO, msg, dl_data, None, None, None, client_ip, rep.gd, rep.dav)
|
||||||
self.logger.info(msg)
|
self.logger.info(msg)
|
||||||
|
|
||||||
return PlainTextResponse("OK")
|
return PlainTextResponse("OK")
|
||||||
@ -421,14 +461,24 @@ class AllnetServlet:
|
|||||||
if serial is None or num_files_dld is None or num_files_to_dl is None or dl_state is None:
|
if serial is None or num_files_dld is None or num_files_to_dl is None or dl_state is None:
|
||||||
return PlainTextResponse("NG")
|
return PlainTextResponse("NG")
|
||||||
|
|
||||||
self.logger.info(f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})")
|
msg = f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})"
|
||||||
|
machine = await self.data.arcade.get_machine(serial)
|
||||||
|
if machine:
|
||||||
|
await self.data.base.log_event("allnet", "LSR_REPORT", logging.INFO, msg, req_dict, None, machine['arcade'], machine['id'], ip)
|
||||||
|
|
||||||
|
else:
|
||||||
|
msg = "Unregistered " + msg
|
||||||
|
await self.data.base.log_event("allnet", "LSR_REPORT_UNREG", logging.INFO, msg, req_dict, None, None, None, ip)
|
||||||
|
|
||||||
|
self.logger.info(msg)
|
||||||
return PlainTextResponse("OK")
|
return PlainTextResponse("OK")
|
||||||
|
|
||||||
async def handle_alive(self, request: Request) -> bytes:
|
async def handle_alive(self, request: Request) -> bytes:
|
||||||
return PlainTextResponse("OK")
|
return PlainTextResponse("OK")
|
||||||
|
|
||||||
async def handle_naomitest(self, request: Request) -> bytes:
|
async def handle_naomitest(self, request: Request) -> bytes:
|
||||||
self.logger.info(f"Ping from {Utils.get_ip_addr(request)}")
|
# This could be spam-able, removing
|
||||||
|
#self.logger.info(f"Ping from {Utils.get_ip_addr(request)}")
|
||||||
return PlainTextResponse("naomi ok")
|
return PlainTextResponse("naomi ok")
|
||||||
|
|
||||||
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]:
|
def allnet_req_to_dict(self, data: str) -> Optional[List[Dict[str, Any]]]:
|
||||||
@ -558,18 +608,35 @@ class BillingServlet:
|
|||||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||||
msg = f"Unrecognised serial {req.keychipid} attempted billing checkin from {request_ip} for {req.gameid} v{req.gamever}."
|
msg = f"Unrecognised serial {req.keychipid} attempted billing checkin from {request_ip} for {req.gameid} v{req.gamever}."
|
||||||
await self.data.base.log_event(
|
await self.data.base.log_event(
|
||||||
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg
|
"allnet", "BILLING_CHECKIN_NG_SERIAL", logging.WARN, msg, ip=request_ip, game=req.gameid, version=req.gamever
|
||||||
)
|
)
|
||||||
self.logger.warning(msg)
|
self.logger.warning(msg)
|
||||||
|
|
||||||
return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n")
|
return PlainTextResponse(f"result=1&requestno={req.requestno}&message=Keychip Serial bad\r\n")
|
||||||
|
|
||||||
msg = (
|
log_details = {
|
||||||
f"Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount "
|
"playcount": req.playcnt,
|
||||||
|
"billing_type": req.billingtype.name,
|
||||||
|
"nearfull": req.nearfull,
|
||||||
|
"playlimit": req.playlimit,
|
||||||
|
}
|
||||||
|
|
||||||
|
if machine is not None:
|
||||||
|
await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, "", log_details, None, machine['arcade'], machine['id'], request_ip, req.gameid, req.gamever)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"Unregistered Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount "
|
||||||
f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}"
|
f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}"
|
||||||
)
|
)
|
||||||
self.logger.info(msg)
|
else:
|
||||||
await self.data.base.log_event("billing", "BILLING_CHECKIN_OK", logging.INFO, msg)
|
log_details['serial'] = req.keychipid
|
||||||
|
await self.data.base.log_event("billing", "BILLING_CHECKIN_OK_UNREG", logging.INFO, "", log_details, None, None, None, request_ip, req.gameid, req.gamever)
|
||||||
|
|
||||||
|
self.logger.info(
|
||||||
|
f"Unregistered Billing checkin from {request_ip}: game {req.gameid} ver {req.gamever} keychip {req.keychipid} playcount "
|
||||||
|
f"{req.playcnt} billing_type {req.billingtype.name} nearfull {req.nearfull} playlimit {req.playlimit}"
|
||||||
|
)
|
||||||
|
|
||||||
if req.traceleft > 0:
|
if req.traceleft > 0:
|
||||||
self.logger.warn(f"{req.traceleft} unsent tracelogs")
|
self.logger.warn(f"{req.traceleft} unsent tracelogs")
|
||||||
kc_playlimit = req.playlimit
|
kc_playlimit = req.playlimit
|
||||||
|
@ -0,0 +1,48 @@
|
|||||||
|
"""add_event_log_info
|
||||||
|
|
||||||
|
Revision ID: 2bf9f38d9444
|
||||||
|
Revises: 81e44dd6047a
|
||||||
|
Create Date: 2024-05-21 23:00:17.468407
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '2bf9f38d9444'
|
||||||
|
down_revision = '81e44dd6047a'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('event_log', sa.Column('user', sa.INTEGER(), nullable=True))
|
||||||
|
op.add_column('event_log', sa.Column('arcade', sa.INTEGER(), nullable=True))
|
||||||
|
op.add_column('event_log', sa.Column('machine', sa.INTEGER(), nullable=True))
|
||||||
|
op.add_column('event_log', sa.Column('ip', sa.TEXT(length=39), nullable=True))
|
||||||
|
op.alter_column('event_log', 'when_logged',
|
||||||
|
existing_type=mysql.TIMESTAMP(),
|
||||||
|
server_default=sa.text('now()'),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.create_foreign_key(None, 'event_log', 'machine', ['machine'], ['id'], onupdate='cascade', ondelete='cascade')
|
||||||
|
op.create_foreign_key(None, 'event_log', 'arcade', ['arcade'], ['id'], onupdate='cascade', ondelete='cascade')
|
||||||
|
op.create_foreign_key(None, 'event_log', 'aime_user', ['user'], ['id'], onupdate='cascade', ondelete='cascade')
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_constraint(None, 'event_log', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'event_log', type_='foreignkey')
|
||||||
|
op.drop_constraint(None, 'event_log', type_='foreignkey')
|
||||||
|
op.alter_column('event_log', 'when_logged',
|
||||||
|
existing_type=mysql.TIMESTAMP(),
|
||||||
|
server_default=sa.text('current_timestamp()'),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.drop_column('event_log', 'ip')
|
||||||
|
op.drop_column('event_log', 'machine')
|
||||||
|
op.drop_column('event_log', 'arcade')
|
||||||
|
op.drop_column('event_log', 'user')
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,46 @@
|
|||||||
|
"""add_event_log_game_version
|
||||||
|
|
||||||
|
Revision ID: 2d024cf145a1
|
||||||
|
Revises: 2bf9f38d9444
|
||||||
|
Create Date: 2024-05-21 23:41:31.445331
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '2d024cf145a1'
|
||||||
|
down_revision = '2bf9f38d9444'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('event_log', sa.Column('game', sa.TEXT(length=4), nullable=True))
|
||||||
|
op.add_column('event_log', sa.Column('version', sa.TEXT(length=24), nullable=True))
|
||||||
|
op.alter_column('event_log', 'ip',
|
||||||
|
existing_type=mysql.TINYTEXT(),
|
||||||
|
type_=sa.TEXT(length=39),
|
||||||
|
existing_nullable=True)
|
||||||
|
op.alter_column('event_log', 'when_logged',
|
||||||
|
existing_type=mysql.TIMESTAMP(),
|
||||||
|
server_default=sa.text('now()'),
|
||||||
|
existing_nullable=False)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.alter_column('event_log', 'when_logged',
|
||||||
|
existing_type=mysql.TIMESTAMP(),
|
||||||
|
server_default=sa.text('current_timestamp()'),
|
||||||
|
existing_nullable=False)
|
||||||
|
op.alter_column('event_log', 'ip',
|
||||||
|
existing_type=sa.TEXT(length=39),
|
||||||
|
type_=mysql.TINYTEXT(),
|
||||||
|
existing_nullable=True)
|
||||||
|
op.drop_column('event_log', 'version')
|
||||||
|
op.drop_column('event_log', 'game')
|
||||||
|
# ### end Alembic commands ###
|
@ -0,0 +1,28 @@
|
|||||||
|
"""cxb_add_playlog_grade
|
||||||
|
|
||||||
|
Revision ID: 7dc13e364e53
|
||||||
|
Revises: 2d024cf145a1
|
||||||
|
Create Date: 2024-05-28 22:31:22.264926
|
||||||
|
|
||||||
|
"""
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
from sqlalchemy.dialects import mysql
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision = '7dc13e364e53'
|
||||||
|
down_revision = '2d024cf145a1'
|
||||||
|
branch_labels = None
|
||||||
|
depends_on = None
|
||||||
|
|
||||||
|
|
||||||
|
def upgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.add_column('cxb_playlog', sa.Column('grade', sa.Integer(), nullable=True))
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade():
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_column('cxb_playlog', 'grade')
|
||||||
|
# ### end Alembic commands ###
|
@ -8,7 +8,8 @@ from sqlalchemy.engine.base import Connection
|
|||||||
from sqlalchemy.sql import text, func, select
|
from sqlalchemy.sql import text, func, select
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
from sqlalchemy import MetaData, Table, Column
|
from sqlalchemy import MetaData, Table, Column
|
||||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON
|
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, INTEGER, TEXT
|
||||||
|
from sqlalchemy.schema import ForeignKey
|
||||||
from sqlalchemy.dialects.mysql import insert
|
from sqlalchemy.dialects.mysql import insert
|
||||||
|
|
||||||
from core.config import CoreConfig
|
from core.config import CoreConfig
|
||||||
@ -22,6 +23,12 @@ event_log = Table(
|
|||||||
Column("system", String(255), nullable=False),
|
Column("system", String(255), nullable=False),
|
||||||
Column("type", String(255), nullable=False),
|
Column("type", String(255), nullable=False),
|
||||||
Column("severity", Integer, nullable=False),
|
Column("severity", Integer, nullable=False),
|
||||||
|
Column("user", INTEGER, ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
|
||||||
|
Column("arcade", INTEGER, ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade")),
|
||||||
|
Column("machine", INTEGER, ForeignKey("machine.id", ondelete="cascade", onupdate="cascade")),
|
||||||
|
Column("ip", TEXT(39)),
|
||||||
|
Column("game", TEXT(4)),
|
||||||
|
Column("version", TEXT(24)),
|
||||||
Column("message", String(1000), nullable=False),
|
Column("message", String(1000), nullable=False),
|
||||||
Column("details", JSON, nullable=False),
|
Column("details", JSON, nullable=False),
|
||||||
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
|
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||||
@ -75,12 +82,19 @@ class BaseData:
|
|||||||
return randrange(10000, 9999999)
|
return randrange(10000, 9999999)
|
||||||
|
|
||||||
async def log_event(
|
async def log_event(
|
||||||
self, system: str, type: str, severity: int, message: str, details: Dict = {}
|
self, system: str, type: str, severity: int, message: str, details: Dict = {}, user: int = None,
|
||||||
|
arcade: int = None, machine: int = None, ip: str = None, game: str = None, version: str = None
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
sql = event_log.insert().values(
|
sql = event_log.insert().values(
|
||||||
system=system,
|
system=system,
|
||||||
type=type,
|
type=type,
|
||||||
severity=severity,
|
severity=severity,
|
||||||
|
user=user,
|
||||||
|
arcade=arcade,
|
||||||
|
machine=machine,
|
||||||
|
ip=ip,
|
||||||
|
game=game,
|
||||||
|
version=version,
|
||||||
message=message,
|
message=message,
|
||||||
details=json.dumps(details),
|
details=json.dumps(details),
|
||||||
)
|
)
|
||||||
@ -94,8 +108,8 @@ class BaseData:
|
|||||||
|
|
||||||
return result.lastrowid
|
return result.lastrowid
|
||||||
|
|
||||||
async def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
|
async def get_event_log(self, entries: int = 100) -> Optional[List[Row]]:
|
||||||
sql = event_log.select().limit(entries).all()
|
sql = event_log.select().limit(entries)
|
||||||
result = await self.execute(sql)
|
result = await self.execute(sql)
|
||||||
|
|
||||||
if result is None:
|
if result is None:
|
||||||
|
@ -133,6 +133,7 @@ class FrontendServlet():
|
|||||||
]),
|
]),
|
||||||
Mount("/sys", routes=[
|
Mount("/sys", routes=[
|
||||||
Route("/", self.system.render_GET, methods=['GET']),
|
Route("/", self.system.render_GET, methods=['GET']),
|
||||||
|
Route("/logs", self.system.render_logs, methods=['GET']),
|
||||||
Route("/lookup.user", self.system.lookup_user, methods=['GET']),
|
Route("/lookup.user", self.system.lookup_user, methods=['GET']),
|
||||||
Route("/lookup.shop", self.system.lookup_shop, methods=['GET']),
|
Route("/lookup.shop", self.system.lookup_shop, methods=['GET']),
|
||||||
Route("/add.user", self.system.add_user, methods=['POST']),
|
Route("/add.user", self.system.add_user, methods=['POST']),
|
||||||
@ -783,6 +784,35 @@ class FE_System(FE_Base):
|
|||||||
cabadd={"id": cab_id, "serial": serial},
|
cabadd={"id": cab_id, "serial": serial},
|
||||||
), media_type="text/html; charset=utf-8")
|
), media_type="text/html; charset=utf-8")
|
||||||
|
|
||||||
|
async def render_logs(self, request: Request):
|
||||||
|
template = self.environment.get_template("core/templates/sys/logs.jinja")
|
||||||
|
events = []
|
||||||
|
|
||||||
|
usr_sesh = self.validate_session(request)
|
||||||
|
if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.SYSADMIN):
|
||||||
|
return RedirectResponse("/sys/?e=11", 303)
|
||||||
|
|
||||||
|
logs = await self.data.base.get_event_log()
|
||||||
|
if not logs:
|
||||||
|
logs = []
|
||||||
|
|
||||||
|
for log in logs:
|
||||||
|
evt = log._asdict()
|
||||||
|
if not evt['user']: evt["user"] = "NONE"
|
||||||
|
if not evt['arcade']: evt["arcade"] = "NONE"
|
||||||
|
if not evt['machine']: evt["machine"] = "NONE"
|
||||||
|
if not evt['ip']: evt["ip"] = "NONE"
|
||||||
|
if not evt['game']: evt["game"] = "NONE"
|
||||||
|
if not evt['version']: evt["version"] = "NONE"
|
||||||
|
evt['when_logged'] = evt['when_logged'].strftime("%x %X")
|
||||||
|
events.append(evt)
|
||||||
|
|
||||||
|
return Response(template.render(
|
||||||
|
title=f"{self.core_config.server.name} | Event Logs",
|
||||||
|
sesh=vars(usr_sesh),
|
||||||
|
events=events
|
||||||
|
), media_type="text/html; charset=utf-8")
|
||||||
|
|
||||||
class FE_Arcade(FE_Base):
|
class FE_Arcade(FE_Base):
|
||||||
async def render_GET(self, request: Request):
|
async def render_GET(self, request: Request):
|
||||||
template = self.environment.get_template("core/templates/arcade/index.jinja")
|
template = self.environment.get_template("core/templates/arcade/index.jinja")
|
||||||
|
@ -51,6 +51,9 @@
|
|||||||
<button type="submit" class="btn btn-primary">Search</button>
|
<button type="submit" class="btn btn-primary">Search</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col-sm-6" style="max-width: 25%;">
|
||||||
|
<a href="/sys/logs"><button class="btn btn-primary">Event Logs</button></a>
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="row" id="rowResult" style="margin: 10px;">
|
<div class="row" id="rowResult" style="margin: 10px;">
|
||||||
|
182
core/templates/sys/logs.jinja
Normal file
182
core/templates/sys/logs.jinja
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
{% extends "core/templates/index.jinja" %}
|
||||||
|
{% block content %}
|
||||||
|
<h1>Event Logs</h1>
|
||||||
|
<table class="table table-dark table-striped-columns" id="tbl_events">
|
||||||
|
<caption>Viewing last 100 logs</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Severity</th>
|
||||||
|
<th>System</th>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>User</th>
|
||||||
|
<th>Arcade</th>
|
||||||
|
<th>Machine</th>
|
||||||
|
<th>Game</th>
|
||||||
|
<th>Version</th>
|
||||||
|
<th>Message</th>
|
||||||
|
<th>Params</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
{% if events is not defined or events|length == 0 %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="10" style="text-align:center"><i>No Events</i></td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
</table>
|
||||||
|
<div id="div_tbl_ctrl">
|
||||||
|
<select id="sel_per_page" onchange="update_tbl()">
|
||||||
|
<option value="10" selected>10</option>
|
||||||
|
<option value="25">25</option>
|
||||||
|
<option value="50">50</option>
|
||||||
|
<option value="100">100</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<button class="btn btn-primary" id="btn_prev" disabled onclick="chg_page(-1)"><<</button>
|
||||||
|
<button class="btn btn-primary" id="btn_next" onclick="chg_page(1)">>></button>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript">
|
||||||
|
{% if events is defined %}
|
||||||
|
const TBL_DATA = {{events}};
|
||||||
|
{% else %}
|
||||||
|
const TBL_DATA = [];
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
var per_page = 0;
|
||||||
|
var page = 0;
|
||||||
|
|
||||||
|
function update_tbl() {
|
||||||
|
if (TBL_DATA.length == 0) { return; }
|
||||||
|
var tbl = document.getElementById("tbl_events");
|
||||||
|
|
||||||
|
for (var i = 0; i < per_page; i++) {
|
||||||
|
try{
|
||||||
|
tbl.deleteRow(1);
|
||||||
|
} catch {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
per_page = document.getElementById("sel_per_page").value;
|
||||||
|
|
||||||
|
if (per_page >= TBL_DATA.length) {
|
||||||
|
page = 0;
|
||||||
|
document.getElementById("btn_next").disabled = true;
|
||||||
|
document.getElementById("btn_prev").disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < per_page; i++) {
|
||||||
|
let off = (page * per_page) + i;
|
||||||
|
if (off >= TBL_DATA.length ) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = TBL_DATA[off];
|
||||||
|
var row = tbl.insertRow(i + 1);
|
||||||
|
var cell_severity = row.insertCell(0);
|
||||||
|
switch (data.severity) {
|
||||||
|
case 10:
|
||||||
|
cell_severity.innerHTML = "DEBUG";
|
||||||
|
row.classList.add("table-success");
|
||||||
|
break;
|
||||||
|
case 20:
|
||||||
|
cell_severity.innerHTML = "INFO";
|
||||||
|
row.classList.add("table-info");
|
||||||
|
break;
|
||||||
|
case 30:
|
||||||
|
cell_severity.innerHTML = "WARN";
|
||||||
|
row.classList.add("table-warning");
|
||||||
|
break;
|
||||||
|
case 40:
|
||||||
|
cell_severity.innerHTML = "ERROR";
|
||||||
|
row.classList.add("table-danger");
|
||||||
|
break;
|
||||||
|
case 50:
|
||||||
|
cell_severity.innerHTML = "CRITICAL";
|
||||||
|
row.classList.add("table-danger");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
cell_severity.innerHTML = "---";
|
||||||
|
row.classList.add("table-primary");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_mod = row.insertCell(1);
|
||||||
|
cell_mod.innerHTML = data.system;
|
||||||
|
|
||||||
|
var cell_name = row.insertCell(2);
|
||||||
|
cell_name.innerHTML = data.type;
|
||||||
|
|
||||||
|
var cell_usr = row.insertCell(3);
|
||||||
|
if (data.user == 'NONE') {
|
||||||
|
cell_usr.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_usr.innerHTML = "<a href=\"/user/" + data.user + "\">" + data.user + "</a>";
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_arcade = row.insertCell(4);
|
||||||
|
if (data.arcade == 'NONE') {
|
||||||
|
cell_arcade.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_arcade.innerHTML = "<a href=\"/shop/" + data.arcade + "\">" + data.arcade + "</a>";
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_machine = row.insertCell(5);
|
||||||
|
if (data.arcade == 'NONE') {
|
||||||
|
cell_machine.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_machine.innerHTML = "<a href=\"/cab/" + data.machine + "\">" + data.machine + "</a>";
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_game = row.insertCell(6);
|
||||||
|
if (data.game == 'NONE') {
|
||||||
|
cell_game.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_game.innerHTML = data.game;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_version = row.insertCell(7);
|
||||||
|
if (data.version == 'NONE') {
|
||||||
|
cell_version.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_version.innerHTML = data.version;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_msg = row.insertCell(8);
|
||||||
|
if (data.message == '') {
|
||||||
|
cell_msg.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_msg.innerHTML = data.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cell_deets = row.insertCell(9);
|
||||||
|
if (data.details == '{}') {
|
||||||
|
cell_deets.innerHTML = "---";
|
||||||
|
} else {
|
||||||
|
cell_deets.innerHTML = data.details;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function chg_page(num) {
|
||||||
|
var max_page = TBL_DATA.length / per_page;
|
||||||
|
page = page + num;
|
||||||
|
|
||||||
|
if (page > max_page) {
|
||||||
|
page = max_page;
|
||||||
|
document.getElementById("btn_next").disabled = true;
|
||||||
|
document.getElementById("btn_prev").disabled = false;
|
||||||
|
return;
|
||||||
|
} else if (page < 0) {
|
||||||
|
page = 0;
|
||||||
|
document.getElementById("btn_next").disabled = false;
|
||||||
|
document.getElementById("btn_prev").disabled = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
update_tbl();
|
||||||
|
}
|
||||||
|
|
||||||
|
update_tbl();
|
||||||
|
</script>
|
||||||
|
{% endblock content %}
|
@ -21,6 +21,8 @@ New Nickname too long
|
|||||||
You must be logged in to preform this action
|
You must be logged in to preform this action
|
||||||
{% elif error == 10 %}
|
{% elif error == 10 %}
|
||||||
Invalid serial number
|
Invalid serial number
|
||||||
|
{% elif error == 11 %}
|
||||||
|
Access Denied
|
||||||
{% else %}
|
{% else %}
|
||||||
An unknown error occoured
|
An unknown error occoured
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -40,6 +40,7 @@ class CxbRev(CxbBase):
|
|||||||
score_data["slow2"],
|
score_data["slow2"],
|
||||||
score_data["fail"],
|
score_data["fail"],
|
||||||
score_data["combo"],
|
score_data["combo"],
|
||||||
|
score_data["grade"],
|
||||||
)
|
)
|
||||||
return {"data": True}
|
return {"data": True}
|
||||||
return {"data": True}
|
return {"data": True}
|
||||||
|
@ -39,6 +39,7 @@ playlog = Table(
|
|||||||
Column("slow2", Integer),
|
Column("slow2", Integer),
|
||||||
Column("fail", Integer),
|
Column("fail", Integer),
|
||||||
Column("combo", Integer),
|
Column("combo", Integer),
|
||||||
|
Column("grade", Integer),
|
||||||
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
||||||
mysql_charset="utf8mb4",
|
mysql_charset="utf8mb4",
|
||||||
)
|
)
|
||||||
@ -104,6 +105,7 @@ class CxbScoreData(BaseData):
|
|||||||
this_slow2: int,
|
this_slow2: int,
|
||||||
fail: int,
|
fail: int,
|
||||||
combo: int,
|
combo: int,
|
||||||
|
grade: int,
|
||||||
) -> Optional[int]:
|
) -> Optional[int]:
|
||||||
"""
|
"""
|
||||||
Add an entry to the user's play log
|
Add an entry to the user's play log
|
||||||
@ -123,6 +125,7 @@ class CxbScoreData(BaseData):
|
|||||||
slow2=this_slow2,
|
slow2=this_slow2,
|
||||||
fail=fail,
|
fail=fail,
|
||||||
combo=combo,
|
combo=combo,
|
||||||
|
grade=grade,
|
||||||
)
|
)
|
||||||
|
|
||||||
result = await self.execute(sql)
|
result = await self.execute(sql)
|
||||||
|
@ -431,7 +431,7 @@ class DivaBase:
|
|||||||
profile = await self.data.profile.get_profile(data["pd_id"], self.version)
|
profile = await self.data.profile.get_profile(data["pd_id"], self.version)
|
||||||
profile_shop = await self.data.item.get_shop(data["pd_id"], self.version)
|
profile_shop = await self.data.item.get_shop(data["pd_id"], self.version)
|
||||||
if profile is None:
|
if profile is None:
|
||||||
return
|
return {}
|
||||||
|
|
||||||
mdl_have = "F" * 250
|
mdl_have = "F" * 250
|
||||||
# generate the mdl_have string if "unlock_all_modules" is disabled
|
# generate the mdl_have string if "unlock_all_modules" is disabled
|
||||||
|
@ -79,7 +79,7 @@ class DivaServlet(BaseServlet):
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
async def render_POST(self, request: Request, game_code: str, matchers: Dict) -> bytes:
|
async def render_POST(self, request: Request) -> bytes:
|
||||||
req_raw = await request.body()
|
req_raw = await request.body()
|
||||||
url_header = request.headers
|
url_header = request.headers
|
||||||
|
|
||||||
@ -98,9 +98,18 @@ class DivaServlet(BaseServlet):
|
|||||||
self.logger.info(f"Binary {bin_req_data['cmd']} Request")
|
self.logger.info(f"Binary {bin_req_data['cmd']} Request")
|
||||||
self.logger.debug(bin_req_data)
|
self.logger.debug(bin_req_data)
|
||||||
|
|
||||||
|
try:
|
||||||
handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request")
|
handler = getattr(self.base, f"handle_{bin_req_data['cmd']}_request")
|
||||||
resp = handler(bin_req_data)
|
resp = handler(bin_req_data)
|
||||||
|
|
||||||
|
except AttributeError as e:
|
||||||
|
self.logger.warning(f"Unhandled {bin_req_data['cmd']} request {e}")
|
||||||
|
return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Error handling method {e}")
|
||||||
|
return PlainTextResponse(f"cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok")
|
||||||
|
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}"
|
f"Response cmd={bin_req_data['cmd']}&req_id={bin_req_data['req_id']}&stat=ok{resp}"
|
||||||
)
|
)
|
||||||
|
@ -54,7 +54,7 @@ class DivaCustomizeItemData(BaseData):
|
|||||||
Given a game version and an aime id, return the cstmz_itm_have hex string
|
Given a game version and an aime id, return the cstmz_itm_have hex string
|
||||||
required for diva directly
|
required for diva directly
|
||||||
"""
|
"""
|
||||||
items_list = self.get_customize_items(aime_id, version)
|
items_list = await self.get_customize_items(aime_id, version)
|
||||||
if items_list is None:
|
if items_list is None:
|
||||||
items_list = []
|
items_list = []
|
||||||
item_have = 0
|
item_have = 0
|
||||||
|
@ -50,7 +50,7 @@ class DivaModuleData(BaseData):
|
|||||||
Given a game version and an aime id, return the mdl_have hex string
|
Given a game version and an aime id, return the mdl_have hex string
|
||||||
required for diva directly
|
required for diva directly
|
||||||
"""
|
"""
|
||||||
module_list = self.get_modules(aime_id, version)
|
module_list = await self.get_modules(aime_id, version)
|
||||||
if module_list is None:
|
if module_list is None:
|
||||||
module_list = []
|
module_list = []
|
||||||
module_have = 0
|
module_have = 0
|
||||||
|
Loading…
Reference in New Issue
Block a user