forked from Hay1tsme/artemis
db: Migrate to SQLite, improve performance
This commit is contained in:
parent
fc947d36a5
commit
f07d60f8ce
|
@ -160,4 +160,9 @@ config/*
|
|||
deliver/*
|
||||
*.gz
|
||||
|
||||
dbdump-*.json
|
||||
dbdump-*.json
|
||||
|
||||
*.sqlite3
|
||||
*.sqlite3-journal
|
||||
*.sqlite3-shm
|
||||
*.sqlite3-wal
|
||||
|
|
|
@ -80,7 +80,14 @@ class AllnetServlet:
|
|||
req = AllnetPowerOnRequest(req_dict[0])
|
||||
# Validate the request. Currently we only validate the fields we plan on using
|
||||
|
||||
if not req.game_id or not req.ver or not req.serial or not req.ip or not req.firm_ver or not req.boot_ver:
|
||||
if (
|
||||
not req.game_id
|
||||
or not req.ver
|
||||
or not req.serial
|
||||
or not req.ip
|
||||
or not req.firm_ver
|
||||
or not req.boot_ver
|
||||
):
|
||||
raise AllnetRequestException(
|
||||
f"Bad auth request params from {request_ip} - {vars(req)}"
|
||||
)
|
||||
|
@ -97,7 +104,7 @@ class AllnetServlet:
|
|||
else:
|
||||
resp = AllnetPowerOnResponse()
|
||||
|
||||
self.logger.debug(f"Allnet request: {vars(req)}")
|
||||
self.logger.debug(f"Allnet request: {vars(req)}")
|
||||
if req.game_id not in self.uri_registry:
|
||||
if not self.config.server.is_develop:
|
||||
msg = f"Unrecognised game {req.game_id} attempted allnet auth from {request_ip}."
|
||||
|
@ -108,7 +115,9 @@ class AllnetServlet:
|
|||
|
||||
resp.stat = -1
|
||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||
return (
|
||||
urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
else:
|
||||
self.logger.info(
|
||||
|
@ -116,16 +125,16 @@ class AllnetServlet:
|
|||
)
|
||||
resp.uri = f"http://{self.config.title.hostname}:{self.config.title.port}/{req.game_id}/{req.ver.replace('.', '')}/"
|
||||
resp.host = f"{self.config.title.hostname}:{self.config.title.port}"
|
||||
|
||||
|
||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||
resp_str = urllib.parse.unquote(urllib.parse.urlencode(resp_dict))
|
||||
|
||||
|
||||
self.logger.debug(f"Allnet response: {resp_str}")
|
||||
return (resp_str + "\n").encode("utf-8")
|
||||
|
||||
resp.uri, resp.host = self.uri_registry[req.game_id]
|
||||
|
||||
machine = self.data.arcade.get_machine(req.serial)
|
||||
machine = self.data.arcade.get_machine(req.serial)
|
||||
if machine is None and not self.config.server.allow_unregistered_serials:
|
||||
msg = f"Unrecognised serial {req.serial} attempted allnet auth from {request_ip}."
|
||||
self.data.base.log_event(
|
||||
|
@ -135,7 +144,9 @@ class AllnetServlet:
|
|||
|
||||
resp.stat = -2
|
||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||
return (
|
||||
urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n"
|
||||
).encode("utf-8")
|
||||
|
||||
if machine is not None:
|
||||
arcade = self.data.arcade.get_arcade(machine["arcade"])
|
||||
|
@ -180,7 +191,7 @@ class AllnetServlet:
|
|||
|
||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||
resp_str = urllib.parse.unquote(urllib.parse.urlencode(resp_dict))
|
||||
self.logger.debug(f"Allnet response: {resp_dict}")
|
||||
self.logger.debug(f"Allnet response: {resp_dict}")
|
||||
resp_str += "\n"
|
||||
|
||||
return resp_str.encode("utf-8")
|
||||
|
@ -228,7 +239,12 @@ class AllnetServlet:
|
|||
resp.uri += f"|http://{self.config.title.hostname}:{self.config.title.port}/dl/ini/{req.game_id}-{req.ver.replace('.', '')}-opt.ini"
|
||||
|
||||
self.logger.debug(f"Sending download uri {resp.uri}")
|
||||
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.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}",
|
||||
)
|
||||
|
||||
return urllib.parse.unquote(urllib.parse.urlencode(vars(resp))) + "\n"
|
||||
|
||||
|
@ -239,8 +255,15 @@ class AllnetServlet:
|
|||
req_file = match["file"].replace("%0A", "")
|
||||
|
||||
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.data.base.log_event("allnet", "DLORDER_INI_SENT", logging.INFO, f"{Utils.get_ip_addr(request)} successfully recieved {req_file}")
|
||||
self.logger.info(
|
||||
f"Request for DL INI file {req_file} from {Utils.get_ip_addr(request)} successful"
|
||||
)
|
||||
self.data.base.log_event(
|
||||
"allnet",
|
||||
"DLORDER_INI_SENT",
|
||||
logging.INFO,
|
||||
f"{Utils.get_ip_addr(request)} successfully recieved {req_file}",
|
||||
)
|
||||
return open(
|
||||
f"{self.config.allnet.update_cfg_folder}/{req_file}", "rb"
|
||||
).read()
|
||||
|
@ -257,7 +280,7 @@ class AllnetServlet:
|
|||
def handle_loaderstaterecorder(self, request: Request, match: Dict) -> bytes:
|
||||
req_data = request.content.getvalue()
|
||||
sections = req_data.decode("utf-8").split("\r\n")
|
||||
|
||||
|
||||
req_dict = dict(urllib.parse.parse_qsl(sections[0]))
|
||||
|
||||
serial: Union[str, None] = req_dict.get("serial", None)
|
||||
|
@ -266,12 +289,19 @@ class AllnetServlet:
|
|||
dl_state: Union[str, None] = req_dict.get("dld_st", None)
|
||||
ip = Utils.get_ip_addr(request)
|
||||
|
||||
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 "NG".encode()
|
||||
|
||||
self.logger.info(f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})")
|
||||
self.logger.info(
|
||||
f"LoaderStateRecorder Request from {ip} {serial}: {num_files_dld}/{num_files_to_dl} Files download (State: {dl_state})"
|
||||
)
|
||||
return "OK".encode()
|
||||
|
||||
|
||||
def handle_alive(self, request: Request, match: Dict) -> bytes:
|
||||
return "OK".encode()
|
||||
|
||||
|
@ -297,7 +327,7 @@ class AllnetServlet:
|
|||
kc_game: str = req_dict[0]["gameid"]
|
||||
kc_date = strptime(req_dict[0]["date"], "%Y%m%d%H%M%S")
|
||||
kc_serial_bytes = kc_serial.encode()
|
||||
|
||||
|
||||
except KeyError as e:
|
||||
return f"result=5&linelimit=&message={e} field is missing".encode()
|
||||
|
||||
|
@ -431,6 +461,7 @@ class AllnetPowerOnRequest:
|
|||
self.format_ver = float(req.get("format_ver", "1.00"))
|
||||
self.token: str = req.get("token", "0")
|
||||
|
||||
|
||||
class AllnetPowerOnResponse:
|
||||
def __init__(self) -> None:
|
||||
self.stat = 1
|
||||
|
@ -443,7 +474,7 @@ class AllnetPowerOnResponse:
|
|||
self.region_name0 = "W"
|
||||
self.region_name1 = ""
|
||||
self.region_name2 = ""
|
||||
self.region_name3 = ""
|
||||
self.region_name3 = ""
|
||||
self.setting = "1"
|
||||
self.year = datetime.now().year
|
||||
self.month = datetime.now().month
|
||||
|
@ -452,6 +483,7 @@ class AllnetPowerOnResponse:
|
|||
self.minute = datetime.now().minute
|
||||
self.second = datetime.now().second
|
||||
|
||||
|
||||
class AllnetPowerOnResponse3(AllnetPowerOnResponse):
|
||||
def __init__(self, token) -> None:
|
||||
super().__init__()
|
||||
|
|
|
@ -111,7 +111,7 @@ class DatabaseConfig:
|
|||
@property
|
||||
def protocol(self) -> str:
|
||||
return CoreConfig.get_config_field(
|
||||
self.__config, "core", "database", "type", default="mysql"
|
||||
self.__config, "core", "database", "protocol", default="mysql"
|
||||
)
|
||||
|
||||
@property
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
from core.data.database import Data
|
||||
from core.data.cache import cached
|
||||
from core.data.database import Data
|
||||
from core.data.cache import cached
|
||||
|
|
|
@ -1,67 +1,67 @@
|
|||
from typing import Any, Callable
|
||||
from functools import wraps
|
||||
import hashlib
|
||||
import pickle
|
||||
import logging
|
||||
from core.config import CoreConfig
|
||||
|
||||
cfg: CoreConfig = None # type: ignore
|
||||
# Make memcache optional
|
||||
try:
|
||||
import pylibmc # type: ignore
|
||||
|
||||
has_mc = True
|
||||
except ModuleNotFoundError:
|
||||
has_mc = False
|
||||
|
||||
|
||||
def cached(lifetime: int = 10, extra_key: Any = None) -> Callable:
|
||||
def _cached(func: Callable) -> Callable:
|
||||
if has_mc:
|
||||
hostname = "127.0.0.1"
|
||||
if cfg:
|
||||
hostname = cfg.database.memcached_host
|
||||
memcache = pylibmc.Client([hostname], binary=True)
|
||||
memcache.behaviors = {"tcp_nodelay": True, "ketama": True}
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
if lifetime is not None:
|
||||
# Hash function args
|
||||
items = kwargs.items()
|
||||
hashable_args = (args[1:], sorted(list(items)))
|
||||
args_key = hashlib.md5(pickle.dumps(hashable_args)).hexdigest()
|
||||
|
||||
# Generate unique cache key
|
||||
cache_key = f'{func.__module__}-{func.__name__}-{args_key}-{extra_key() if hasattr(extra_key, "__call__") else extra_key}'
|
||||
|
||||
# Return cached version if allowed and available
|
||||
try:
|
||||
result = memcache.get(cache_key)
|
||||
except pylibmc.Error as e:
|
||||
logging.getLogger("database").error(f"Memcache failed: {e}")
|
||||
result = None
|
||||
|
||||
if result is not None:
|
||||
logging.getLogger("database").debug(f"Cache hit: {result}")
|
||||
return result
|
||||
|
||||
# Generate output
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
# Cache output if allowed
|
||||
if lifetime is not None and result is not None:
|
||||
logging.getLogger("database").debug(f"Setting cache: {result}")
|
||||
memcache.set(cache_key, result, lifetime)
|
||||
|
||||
return result
|
||||
|
||||
else:
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return _cached
|
||||
from typing import Any, Callable
|
||||
from functools import wraps
|
||||
import hashlib
|
||||
import pickle
|
||||
import logging
|
||||
from core.config import CoreConfig
|
||||
|
||||
cfg: CoreConfig = None # type: ignore
|
||||
# Make memcache optional
|
||||
try:
|
||||
import pylibmc # type: ignore
|
||||
|
||||
has_mc = True
|
||||
except ModuleNotFoundError:
|
||||
has_mc = False
|
||||
|
||||
|
||||
def cached(lifetime: int = 10, extra_key: Any = None) -> Callable:
|
||||
def _cached(func: Callable) -> Callable:
|
||||
if has_mc:
|
||||
hostname = "127.0.0.1"
|
||||
if cfg:
|
||||
hostname = cfg.database.memcached_host
|
||||
memcache = pylibmc.Client([hostname], binary=True)
|
||||
memcache.behaviors = {"tcp_nodelay": True, "ketama": True}
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
if lifetime is not None:
|
||||
# Hash function args
|
||||
items = kwargs.items()
|
||||
hashable_args = (args[1:], sorted(list(items)))
|
||||
args_key = hashlib.md5(pickle.dumps(hashable_args)).hexdigest()
|
||||
|
||||
# Generate unique cache key
|
||||
cache_key = f'{func.__module__}-{func.__name__}-{args_key}-{extra_key() if hasattr(extra_key, "__call__") else extra_key}'
|
||||
|
||||
# Return cached version if allowed and available
|
||||
try:
|
||||
result = memcache.get(cache_key)
|
||||
except pylibmc.Error as e:
|
||||
logging.getLogger("database").error(f"Memcache failed: {e}")
|
||||
result = None
|
||||
|
||||
if result is not None:
|
||||
logging.getLogger("database").debug(f"Cache hit: {result}")
|
||||
return result
|
||||
|
||||
# Generate output
|
||||
result = func(*args, **kwargs)
|
||||
|
||||
# Cache output if allowed
|
||||
if lifetime is not None and result is not None:
|
||||
logging.getLogger("database").debug(f"Setting cache: {result}")
|
||||
memcache.set(cache_key, result, lifetime)
|
||||
|
||||
return result
|
||||
|
||||
else:
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(*args: Any, **kwargs: Any) -> Any:
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return wrapper
|
||||
|
||||
return _cached
|
||||
|
|
|
@ -1,357 +1,370 @@
|
|||
import logging, coloredlogs
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy import create_engine
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import importlib, os
|
||||
import secrets, string
|
||||
import bcrypt
|
||||
from hashlib import sha256
|
||||
|
||||
from core.config import CoreConfig
|
||||
from core.data.schema import *
|
||||
from core.utils import Utils
|
||||
|
||||
|
||||
class Data:
|
||||
current_schema_version = 4
|
||||
engine = None
|
||||
session = None
|
||||
user = None
|
||||
arcade = None
|
||||
card = None
|
||||
base = None
|
||||
def __init__(self, cfg: CoreConfig) -> None:
|
||||
self.config = cfg
|
||||
|
||||
if self.config.database.sha2_password:
|
||||
passwd = sha256(self.config.database.password.encode()).digest()
|
||||
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
|
||||
else:
|
||||
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
|
||||
|
||||
if Data.engine is None:
|
||||
Data.engine = create_engine(self.__url, pool_recycle=3600)
|
||||
self.__engine = Data.engine
|
||||
|
||||
if Data.session is None:
|
||||
s = sessionmaker(bind=Data.engine, autoflush=True, autocommit=True)
|
||||
Data.session = scoped_session(s)
|
||||
|
||||
if Data.user is None:
|
||||
Data.user = UserData(self.config, self.session)
|
||||
|
||||
if Data.arcade is None:
|
||||
Data.arcade = ArcadeData(self.config, self.session)
|
||||
|
||||
if Data.card is None:
|
||||
Data.card = CardData(self.config, self.session)
|
||||
|
||||
if Data.base is None:
|
||||
Data.base = BaseData(self.config, self.session)
|
||||
|
||||
self.logger = logging.getLogger("database")
|
||||
|
||||
# Prevent the logger from adding handlers multiple times
|
||||
if not getattr(self.logger, "handler_set", None):
|
||||
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
|
||||
log_fmt = logging.Formatter(log_fmt_str)
|
||||
fileHandler = TimedRotatingFileHandler(
|
||||
"{0}/{1}.log".format(self.config.server.log_dir, "db"),
|
||||
encoding="utf-8",
|
||||
when="d",
|
||||
backupCount=10,
|
||||
)
|
||||
fileHandler.setFormatter(log_fmt)
|
||||
|
||||
consoleHandler = logging.StreamHandler()
|
||||
consoleHandler.setFormatter(log_fmt)
|
||||
|
||||
self.logger.addHandler(fileHandler)
|
||||
self.logger.addHandler(consoleHandler)
|
||||
|
||||
self.logger.setLevel(self.config.database.loglevel)
|
||||
coloredlogs.install(
|
||||
cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||
)
|
||||
self.logger.handler_set = True # type: ignore
|
||||
|
||||
def create_database(self):
|
||||
self.logger.info("Creating databases...")
|
||||
try:
|
||||
metadata.create_all(self.__engine.connect())
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"Failed to create databases! {e}")
|
||||
return
|
||||
|
||||
games = Utils.get_all_titles()
|
||||
for game_dir, game_mod in games.items():
|
||||
try:
|
||||
if hasattr(game_mod, "database") and hasattr(
|
||||
game_mod, "current_schema_version"
|
||||
):
|
||||
game_mod.database(self.config)
|
||||
metadata.create_all(self.__engine.connect())
|
||||
|
||||
self.base.touch_schema_ver(
|
||||
game_mod.current_schema_version, game_mod.game_codes[0]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"Could not load database schema from {game_dir} - {e}"
|
||||
)
|
||||
|
||||
self.logger.info(f"Setting base_schema_ver to {self.current_schema_version}")
|
||||
self.base.set_schema_ver(self.current_schema_version)
|
||||
|
||||
self.logger.info(
|
||||
f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}"
|
||||
)
|
||||
self.user.reset_autoincrement(
|
||||
self.config.database.user_table_autoincrement_start
|
||||
)
|
||||
|
||||
def recreate_database(self):
|
||||
self.logger.info("Dropping all databases...")
|
||||
self.base.execute("SET FOREIGN_KEY_CHECKS=0")
|
||||
try:
|
||||
metadata.drop_all(self.__engine.connect())
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"Failed to drop databases! {e}")
|
||||
return
|
||||
|
||||
for root, dirs, files in os.walk("./titles"):
|
||||
for dir in dirs:
|
||||
if not dir.startswith("__"):
|
||||
try:
|
||||
mod = importlib.import_module(f"titles.{dir}")
|
||||
|
||||
try:
|
||||
if hasattr(mod, "database"):
|
||||
mod.database(self.config)
|
||||
metadata.drop_all(self.__engine.connect())
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"Could not load database schema from {dir} - {e}"
|
||||
)
|
||||
|
||||
except ImportError as e:
|
||||
self.logger.warning(
|
||||
f"Failed to load database schema dir {dir} - {e}"
|
||||
)
|
||||
break
|
||||
|
||||
self.base.execute("SET FOREIGN_KEY_CHECKS=1")
|
||||
|
||||
self.create_database()
|
||||
|
||||
def migrate_database(self, game: str, version: Optional[int], action: str) -> None:
|
||||
old_ver = self.base.get_schema_ver(game)
|
||||
sql = ""
|
||||
if version is None:
|
||||
if not game == "CORE":
|
||||
titles = Utils.get_all_titles()
|
||||
|
||||
for folder, mod in titles.items():
|
||||
if not mod.game_codes[0] == game:
|
||||
continue
|
||||
|
||||
if hasattr(mod, "current_schema_version"):
|
||||
version = mod.current_schema_version
|
||||
|
||||
else:
|
||||
self.logger.warn(
|
||||
f"current_schema_version not found for {folder}"
|
||||
)
|
||||
|
||||
else:
|
||||
version = self.current_schema_version
|
||||
|
||||
if version is None:
|
||||
self.logger.warn(
|
||||
f"Could not determine latest version for {game}, please specify --version"
|
||||
)
|
||||
|
||||
if old_ver is None:
|
||||
self.logger.error(
|
||||
f"Schema for game {game} does not exist, did you run the creation script?"
|
||||
)
|
||||
return
|
||||
|
||||
if old_ver == version:
|
||||
self.logger.info(
|
||||
f"Schema for game {game} is already version {old_ver}, nothing to do"
|
||||
)
|
||||
return
|
||||
|
||||
if action == "upgrade":
|
||||
for x in range(old_ver, version):
|
||||
if not os.path.exists(
|
||||
f"core/data/schema/versions/{game.upper()}_{x + 1}_{action}.sql"
|
||||
):
|
||||
self.logger.error(
|
||||
f"Could not find {action} script {game.upper()}_{x + 1}_{action}.sql in core/data/schema/versions folder"
|
||||
)
|
||||
return
|
||||
|
||||
with open(
|
||||
f"core/data/schema/versions/{game.upper()}_{x + 1}_{action}.sql",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
sql = f.read()
|
||||
|
||||
result = self.base.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error("Error execuing sql script!")
|
||||
return None
|
||||
|
||||
else:
|
||||
for x in range(old_ver, version, -1):
|
||||
if not os.path.exists(
|
||||
f"core/data/schema/versions/{game.upper()}_{x - 1}_{action}.sql"
|
||||
):
|
||||
self.logger.error(
|
||||
f"Could not find {action} script {game.upper()}_{x - 1}_{action}.sql in core/data/schema/versions folder"
|
||||
)
|
||||
return
|
||||
|
||||
with open(
|
||||
f"core/data/schema/versions/{game.upper()}_{x - 1}_{action}.sql",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
sql = f.read()
|
||||
|
||||
result = self.base.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error("Error execuing sql script!")
|
||||
return None
|
||||
|
||||
result = self.base.set_schema_ver(version, game)
|
||||
if result is None:
|
||||
self.logger.error("Error setting version in schema_version table!")
|
||||
return None
|
||||
|
||||
self.logger.info(f"Successfully migrated {game} to schema version {version}")
|
||||
|
||||
def create_owner(self, email: Optional[str] = None) -> None:
|
||||
pw = "".join(
|
||||
secrets.choice(string.ascii_letters + string.digits) for i in range(20)
|
||||
)
|
||||
hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
|
||||
|
||||
user_id = self.user.create_user(email=email, permission=255, password=hash)
|
||||
if user_id is None:
|
||||
self.logger.error(f"Failed to create owner with email {email}")
|
||||
return
|
||||
|
||||
card_id = self.card.create_card(user_id, "00000000000000000000")
|
||||
if card_id is None:
|
||||
self.logger.error(f"Failed to create card for owner with id {user_id}")
|
||||
return
|
||||
|
||||
self.logger.warn(
|
||||
f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!"
|
||||
)
|
||||
|
||||
def migrate_card(self, old_ac: str, new_ac: str, should_force: bool) -> None:
|
||||
if old_ac == new_ac:
|
||||
self.logger.error("Both access codes are the same!")
|
||||
return
|
||||
|
||||
new_card = self.card.get_card_by_access_code(new_ac)
|
||||
if new_card is None:
|
||||
self.card.update_access_code(old_ac, new_ac)
|
||||
return
|
||||
|
||||
if not should_force:
|
||||
self.logger.warn(
|
||||
f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."
|
||||
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||
)
|
||||
return
|
||||
|
||||
self.logger.info(
|
||||
f"All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||
)
|
||||
self.card.delete_card(new_card["id"])
|
||||
self.card.update_access_code(old_ac, new_ac)
|
||||
|
||||
hanging_user = self.user.get_user(new_card["user"])
|
||||
if hanging_user["password"] is None:
|
||||
self.logger.info(f"Delete hanging user {hanging_user['id']}")
|
||||
self.user.delete_user(hanging_user["id"])
|
||||
|
||||
def delete_hanging_users(self) -> None:
|
||||
"""
|
||||
Finds and deletes users that have not registered for the webui that have no cards assocated with them.
|
||||
"""
|
||||
unreg_users = self.user.get_unregistered_users()
|
||||
if unreg_users is None:
|
||||
self.logger.error("Error occoured finding unregistered users")
|
||||
|
||||
for user in unreg_users:
|
||||
cards = self.card.get_user_cards(user["id"])
|
||||
if cards is None:
|
||||
self.logger.error(f"Error getting cards for user {user['id']}")
|
||||
continue
|
||||
|
||||
if not cards:
|
||||
self.logger.info(f"Delete hanging user {user['id']}")
|
||||
self.user.delete_user(user["id"])
|
||||
|
||||
def autoupgrade(self) -> None:
|
||||
all_game_versions = self.base.get_all_schema_vers()
|
||||
if all_game_versions is None:
|
||||
self.logger.warn("Failed to get schema versions")
|
||||
return
|
||||
|
||||
all_games = Utils.get_all_titles()
|
||||
all_games_list: Dict[str, int] = {}
|
||||
for _, mod in all_games.items():
|
||||
if hasattr(mod, "current_schema_version"):
|
||||
all_games_list[mod.game_codes[0]] = mod.current_schema_version
|
||||
|
||||
for x in all_game_versions:
|
||||
failed = False
|
||||
game = x["game"].upper()
|
||||
update_ver = int(x["version"])
|
||||
latest_ver = all_games_list.get(game, 1)
|
||||
if game == "CORE":
|
||||
latest_ver = self.current_schema_version
|
||||
|
||||
if update_ver == latest_ver:
|
||||
self.logger.info(f"{game} is already latest version")
|
||||
continue
|
||||
|
||||
for y in range(update_ver + 1, latest_ver + 1):
|
||||
if os.path.exists(f"core/data/schema/versions/{game}_{y}_upgrade.sql"):
|
||||
with open(
|
||||
f"core/data/schema/versions/{game}_{y}_upgrade.sql",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
sql = f.read()
|
||||
|
||||
result = self.base.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Error execuing sql script for game {game} v{y}!"
|
||||
)
|
||||
failed = True
|
||||
break
|
||||
else:
|
||||
self.logger.warning(f"Could not find script {game}_{y}_upgrade.sql")
|
||||
failed = True
|
||||
|
||||
if not failed:
|
||||
self.base.set_schema_ver(latest_ver, game)
|
||||
|
||||
def show_versions(self) -> None:
|
||||
all_game_versions = self.base.get_all_schema_vers()
|
||||
for ver in all_game_versions:
|
||||
self.logger.info(f"{ver['game']} -> v{ver['version']}")
|
||||
import logging, coloredlogs
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy import create_engine
|
||||
from logging.handlers import TimedRotatingFileHandler
|
||||
import importlib, os
|
||||
import secrets, string
|
||||
import bcrypt
|
||||
from hashlib import sha256
|
||||
|
||||
from core.config import CoreConfig
|
||||
from core.data.schema import *
|
||||
from core.utils import Utils
|
||||
|
||||
|
||||
class Data:
|
||||
current_schema_version = 4
|
||||
engine = None
|
||||
session = None
|
||||
user = None
|
||||
arcade = None
|
||||
card = None
|
||||
base = None
|
||||
|
||||
def __init__(self, cfg: CoreConfig) -> None:
|
||||
self.config = cfg
|
||||
|
||||
if self.config.database.protocol == "sqlite":
|
||||
self.__url = (
|
||||
f"{self.config.database.protocol}:///{self.config.database.name}"
|
||||
)
|
||||
else:
|
||||
if self.config.database.sha2_password:
|
||||
passwd = sha256(self.config.database.password.encode()).digest()
|
||||
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{passwd.hex()}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
|
||||
else:
|
||||
self.__url = f"{self.config.database.protocol}://{self.config.database.username}:{self.config.database.password}@{self.config.database.host}:{self.config.database.port}/{self.config.database.name}?charset=utf8mb4"
|
||||
|
||||
if Data.engine is None:
|
||||
Data.engine = create_engine(self.__url, pool_recycle=3600)
|
||||
self.__engine = Data.engine
|
||||
|
||||
if Data.session is None:
|
||||
s = sessionmaker(
|
||||
bind=Data.engine,
|
||||
# There are a billion transactions, autoflushing will take a lot of time
|
||||
# and potentially locks up the database.
|
||||
autoflush=self.config.database.protocol != "sqlite",
|
||||
autocommit=True,
|
||||
)
|
||||
Data.session = scoped_session(s)
|
||||
|
||||
if Data.user is None:
|
||||
Data.user = UserData(self.config, self.session)
|
||||
|
||||
if Data.arcade is None:
|
||||
Data.arcade = ArcadeData(self.config, self.session)
|
||||
|
||||
if Data.card is None:
|
||||
Data.card = CardData(self.config, self.session)
|
||||
|
||||
if Data.base is None:
|
||||
Data.base = BaseData(self.config, self.session)
|
||||
|
||||
self.logger = logging.getLogger("database")
|
||||
|
||||
# Prevent the logger from adding handlers multiple times
|
||||
if not getattr(self.logger, "handler_set", None):
|
||||
log_fmt_str = "[%(asctime)s] %(levelname)s | Database | %(message)s"
|
||||
log_fmt = logging.Formatter(log_fmt_str)
|
||||
fileHandler = TimedRotatingFileHandler(
|
||||
"{0}/{1}.log".format(self.config.server.log_dir, "db"),
|
||||
encoding="utf-8",
|
||||
when="d",
|
||||
backupCount=10,
|
||||
)
|
||||
fileHandler.setFormatter(log_fmt)
|
||||
|
||||
consoleHandler = logging.StreamHandler()
|
||||
consoleHandler.setFormatter(log_fmt)
|
||||
|
||||
self.logger.addHandler(fileHandler)
|
||||
self.logger.addHandler(consoleHandler)
|
||||
|
||||
self.logger.setLevel(self.config.database.loglevel)
|
||||
coloredlogs.install(
|
||||
cfg.database.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||
)
|
||||
self.logger.handler_set = True # type: ignore
|
||||
|
||||
def create_database(self):
|
||||
self.logger.info("Creating databases...")
|
||||
self.base.setup_sqlite()
|
||||
try:
|
||||
metadata.create_all(self.__engine.connect())
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"Failed to create databases! {e}")
|
||||
return
|
||||
|
||||
games = Utils.get_all_titles()
|
||||
for game_dir, game_mod in games.items():
|
||||
try:
|
||||
if hasattr(game_mod, "database") and hasattr(
|
||||
game_mod, "current_schema_version"
|
||||
):
|
||||
game_mod.database(self.config)
|
||||
metadata.create_all(self.__engine.connect())
|
||||
|
||||
self.base.touch_schema_ver(
|
||||
game_mod.current_schema_version, game_mod.game_codes[0]
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"Could not load database schema from {game_dir} - {e}"
|
||||
)
|
||||
|
||||
self.logger.info(f"Setting base_schema_ver to {self.current_schema_version}")
|
||||
self.base.set_schema_ver(self.current_schema_version)
|
||||
|
||||
self.logger.info(
|
||||
f"Setting user auto_incrememnt to {self.config.database.user_table_autoincrement_start}"
|
||||
)
|
||||
self.user.reset_autoincrement(
|
||||
self.config.database.user_table_autoincrement_start
|
||||
)
|
||||
|
||||
def recreate_database(self):
|
||||
self.logger.info("Dropping all databases...")
|
||||
self.base.execute("SET FOREIGN_KEY_CHECKS=0")
|
||||
try:
|
||||
metadata.drop_all(self.__engine.connect())
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"Failed to drop databases! {e}")
|
||||
return
|
||||
|
||||
for root, dirs, files in os.walk("./titles"):
|
||||
for dir in dirs:
|
||||
if not dir.startswith("__"):
|
||||
try:
|
||||
mod = importlib.import_module(f"titles.{dir}")
|
||||
|
||||
try:
|
||||
if hasattr(mod, "database"):
|
||||
mod.database(self.config)
|
||||
metadata.drop_all(self.__engine.connect())
|
||||
|
||||
except Exception as e:
|
||||
self.logger.warning(
|
||||
f"Could not load database schema from {dir} - {e}"
|
||||
)
|
||||
|
||||
except ImportError as e:
|
||||
self.logger.warning(
|
||||
f"Failed to load database schema dir {dir} - {e}"
|
||||
)
|
||||
break
|
||||
|
||||
self.base.execute("SET FOREIGN_KEY_CHECKS=1")
|
||||
|
||||
self.create_database()
|
||||
|
||||
def migrate_database(self, game: str, version: Optional[int], action: str) -> None:
|
||||
old_ver = self.base.get_schema_ver(game)
|
||||
sql = ""
|
||||
if version is None:
|
||||
if not game == "CORE":
|
||||
titles = Utils.get_all_titles()
|
||||
|
||||
for folder, mod in titles.items():
|
||||
if not mod.game_codes[0] == game:
|
||||
continue
|
||||
|
||||
if hasattr(mod, "current_schema_version"):
|
||||
version = mod.current_schema_version
|
||||
|
||||
else:
|
||||
self.logger.warn(
|
||||
f"current_schema_version not found for {folder}"
|
||||
)
|
||||
|
||||
else:
|
||||
version = self.current_schema_version
|
||||
|
||||
if version is None:
|
||||
self.logger.warn(
|
||||
f"Could not determine latest version for {game}, please specify --version"
|
||||
)
|
||||
|
||||
if old_ver is None:
|
||||
self.logger.error(
|
||||
f"Schema for game {game} does not exist, did you run the creation script?"
|
||||
)
|
||||
return
|
||||
|
||||
if old_ver == version:
|
||||
self.logger.info(
|
||||
f"Schema for game {game} is already version {old_ver}, nothing to do"
|
||||
)
|
||||
return
|
||||
|
||||
if action == "upgrade":
|
||||
for x in range(old_ver, version):
|
||||
if not os.path.exists(
|
||||
f"core/data/schema/versions/{game.upper()}_{x + 1}_{action}.sql"
|
||||
):
|
||||
self.logger.error(
|
||||
f"Could not find {action} script {game.upper()}_{x + 1}_{action}.sql in core/data/schema/versions folder"
|
||||
)
|
||||
return
|
||||
|
||||
with open(
|
||||
f"core/data/schema/versions/{game.upper()}_{x + 1}_{action}.sql",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
sql = f.read()
|
||||
|
||||
result = self.base.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error("Error execuing sql script!")
|
||||
return None
|
||||
|
||||
else:
|
||||
for x in range(old_ver, version, -1):
|
||||
if not os.path.exists(
|
||||
f"core/data/schema/versions/{game.upper()}_{x - 1}_{action}.sql"
|
||||
):
|
||||
self.logger.error(
|
||||
f"Could not find {action} script {game.upper()}_{x - 1}_{action}.sql in core/data/schema/versions folder"
|
||||
)
|
||||
return
|
||||
|
||||
with open(
|
||||
f"core/data/schema/versions/{game.upper()}_{x - 1}_{action}.sql",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
sql = f.read()
|
||||
|
||||
result = self.base.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error("Error execuing sql script!")
|
||||
return None
|
||||
|
||||
result = self.base.set_schema_ver(version, game)
|
||||
if result is None:
|
||||
self.logger.error("Error setting version in schema_version table!")
|
||||
return None
|
||||
|
||||
self.logger.info(f"Successfully migrated {game} to schema version {version}")
|
||||
|
||||
def create_owner(self, email: Optional[str] = None) -> None:
|
||||
pw = "".join(
|
||||
secrets.choice(string.ascii_letters + string.digits) for i in range(20)
|
||||
)
|
||||
hash = bcrypt.hashpw(pw.encode(), bcrypt.gensalt())
|
||||
|
||||
user_id = self.user.create_user(email=email, permission=255, password=hash)
|
||||
if user_id is None:
|
||||
self.logger.error(f"Failed to create owner with email {email}")
|
||||
return
|
||||
|
||||
card_id = self.card.create_card(user_id, "00000000000000000000")
|
||||
if card_id is None:
|
||||
self.logger.error(f"Failed to create card for owner with id {user_id}")
|
||||
return
|
||||
|
||||
self.logger.warn(
|
||||
f"Successfully created owner with email {email}, access code 00000000000000000000, and password {pw} Make sure to change this password and assign a real card ASAP!"
|
||||
)
|
||||
|
||||
def migrate_card(self, old_ac: str, new_ac: str, should_force: bool) -> None:
|
||||
if old_ac == new_ac:
|
||||
self.logger.error("Both access codes are the same!")
|
||||
return
|
||||
|
||||
new_card = self.card.get_card_by_access_code(new_ac)
|
||||
if new_card is None:
|
||||
self.card.update_access_code(old_ac, new_ac)
|
||||
return
|
||||
|
||||
if not should_force:
|
||||
self.logger.warn(
|
||||
f"Card already exists for access code {new_ac} (id {new_card['id']}). If you wish to continue, rerun with the '--force' flag."
|
||||
f" All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||
)
|
||||
return
|
||||
|
||||
self.logger.info(
|
||||
f"All exiting data on the target card {new_ac} will be perminently erased and replaced with data from card {old_ac}."
|
||||
)
|
||||
self.card.delete_card(new_card["id"])
|
||||
self.card.update_access_code(old_ac, new_ac)
|
||||
|
||||
hanging_user = self.user.get_user(new_card["user"])
|
||||
if hanging_user["password"] is None:
|
||||
self.logger.info(f"Delete hanging user {hanging_user['id']}")
|
||||
self.user.delete_user(hanging_user["id"])
|
||||
|
||||
def delete_hanging_users(self) -> None:
|
||||
"""
|
||||
Finds and deletes users that have not registered for the webui that have no cards assocated with them.
|
||||
"""
|
||||
unreg_users = self.user.get_unregistered_users()
|
||||
if unreg_users is None:
|
||||
self.logger.error("Error occoured finding unregistered users")
|
||||
|
||||
for user in unreg_users:
|
||||
cards = self.card.get_user_cards(user["id"])
|
||||
if cards is None:
|
||||
self.logger.error(f"Error getting cards for user {user['id']}")
|
||||
continue
|
||||
|
||||
if not cards:
|
||||
self.logger.info(f"Delete hanging user {user['id']}")
|
||||
self.user.delete_user(user["id"])
|
||||
|
||||
def autoupgrade(self) -> None:
|
||||
all_game_versions = self.base.get_all_schema_vers()
|
||||
if all_game_versions is None:
|
||||
self.logger.warn("Failed to get schema versions")
|
||||
return
|
||||
|
||||
all_games = Utils.get_all_titles()
|
||||
all_games_list: Dict[str, int] = {}
|
||||
for _, mod in all_games.items():
|
||||
if hasattr(mod, "current_schema_version"):
|
||||
all_games_list[mod.game_codes[0]] = mod.current_schema_version
|
||||
|
||||
for x in all_game_versions:
|
||||
failed = False
|
||||
game = x["game"].upper()
|
||||
update_ver = int(x["version"])
|
||||
latest_ver = all_games_list.get(game, 1)
|
||||
if game == "CORE":
|
||||
latest_ver = self.current_schema_version
|
||||
|
||||
if update_ver == latest_ver:
|
||||
self.logger.info(f"{game} is already latest version")
|
||||
continue
|
||||
|
||||
for y in range(update_ver + 1, latest_ver + 1):
|
||||
if os.path.exists(f"core/data/schema/versions/{game}_{y}_upgrade.sql"):
|
||||
with open(
|
||||
f"core/data/schema/versions/{game}_{y}_upgrade.sql",
|
||||
"r",
|
||||
encoding="utf-8",
|
||||
) as f:
|
||||
sql = f.read()
|
||||
|
||||
result = self.base.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Error execuing sql script for game {game} v{y}!"
|
||||
)
|
||||
failed = True
|
||||
break
|
||||
else:
|
||||
self.logger.warning(f"Could not find script {game}_{y}_upgrade.sql")
|
||||
failed = True
|
||||
|
||||
if not failed:
|
||||
self.base.set_schema_ver(latest_ver, game)
|
||||
|
||||
def show_versions(self) -> None:
|
||||
all_game_versions = self.base.get_all_schema_vers()
|
||||
for ver in all_game_versions:
|
||||
self.logger.info(f"{ver['game']} -> v{ver['version']}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from core.data.schema.user import UserData
|
||||
from core.data.schema.card import CardData
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
from core.data.schema.arcade import ArcadeData
|
||||
|
||||
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]
|
||||
from core.data.schema.user import UserData
|
||||
from core.data.schema.card import CardData
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
from core.data.schema.arcade import ArcadeData
|
||||
|
||||
__all__ = ["UserData", "CardData", "BaseData", "metadata", "ArcadeData"]
|
||||
|
|
|
@ -1,219 +1,219 @@
|
|||
from typing import Optional, Dict
|
||||
from sqlalchemy import Table, Column
|
||||
from sqlalchemy.sql.schema import ForeignKey, PrimaryKeyConstraint
|
||||
from sqlalchemy.types import Integer, String, Boolean
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
import re
|
||||
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
from core.const import *
|
||||
|
||||
arcade = Table(
|
||||
"arcade",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("name", String(255)),
|
||||
Column("nickname", String(255)),
|
||||
Column("country", String(3)),
|
||||
Column("country_id", Integer),
|
||||
Column("state", String(255)),
|
||||
Column("city", String(255)),
|
||||
Column("region_id", Integer),
|
||||
Column("timezone", String(255)),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
machine = Table(
|
||||
"machine",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"arcade",
|
||||
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("serial", String(15), nullable=False),
|
||||
Column("board", String(15)),
|
||||
Column("game", String(4)),
|
||||
Column("country", String(3)), # overwrites if not null
|
||||
Column("timezone", String(255)),
|
||||
Column("ota_enable", Boolean),
|
||||
Column("is_cab", Boolean),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
arcade_owner = Table(
|
||||
"arcade_owner",
|
||||
metadata,
|
||||
Column(
|
||||
"user",
|
||||
Integer,
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column(
|
||||
"arcade",
|
||||
Integer,
|
||||
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("permissions", Integer, nullable=False),
|
||||
PrimaryKeyConstraint("user", "arcade", name="arcade_owner_pk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class ArcadeData(BaseData):
|
||||
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
|
||||
if serial is not None:
|
||||
serial = serial.replace("-", "")
|
||||
if len(serial) == 11:
|
||||
sql = machine.select(machine.c.serial.like(f"{serial}%"))
|
||||
|
||||
elif len(serial) == 15:
|
||||
sql = machine.select(machine.c.serial == serial)
|
||||
|
||||
else:
|
||||
self.logger.error(f"{__name__ }: Malformed serial {serial}")
|
||||
return None
|
||||
|
||||
elif id is not None:
|
||||
sql = machine.select(machine.c.id == id)
|
||||
|
||||
else:
|
||||
self.logger.error(f"{__name__ }: Need either serial or ID to look up!")
|
||||
return None
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_machine(
|
||||
self,
|
||||
arcade_id: int,
|
||||
serial: str = "",
|
||||
board: str = None,
|
||||
game: str = None,
|
||||
is_cab: bool = False,
|
||||
) -> Optional[int]:
|
||||
if arcade_id:
|
||||
self.logger.error(f"{__name__ }: Need arcade id!")
|
||||
return None
|
||||
|
||||
sql = machine.insert().values(
|
||||
arcade=arcade_id, keychip=serial, board=board, game=game, is_cab=is_cab
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def set_machine_serial(self, machine_id: int, serial: str) -> None:
|
||||
result = self.execute(
|
||||
machine.update(machine.c.id == machine_id).values(keychip=serial)
|
||||
)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update serial for machine {machine_id} -> {serial}"
|
||||
)
|
||||
return result.lastrowid
|
||||
|
||||
def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
|
||||
result = self.execute(
|
||||
machine.update(machine.c.id == machine_id).values(board=boardid)
|
||||
)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update board id for machine {machine_id} -> {boardid}"
|
||||
)
|
||||
|
||||
def get_arcade(self, id: int) -> Optional[Dict]:
|
||||
sql = arcade.select(arcade.c.id == id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_arcade(
|
||||
self,
|
||||
name: str,
|
||||
nickname: str = None,
|
||||
country: str = "JPN",
|
||||
country_id: int = 1,
|
||||
state: str = "",
|
||||
city: str = "",
|
||||
regional_id: int = 1,
|
||||
) -> Optional[int]:
|
||||
if nickname is None:
|
||||
nickname = name
|
||||
|
||||
sql = arcade.insert().values(
|
||||
name=name,
|
||||
nickname=nickname,
|
||||
country=country,
|
||||
country_id=country_id,
|
||||
state=state,
|
||||
city=city,
|
||||
regional_id=regional_id,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
|
||||
sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
|
||||
sql = insert(arcade_owner).values(arcade=arcade_id, user=user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def format_serial(
|
||||
self, platform_code: str, platform_rev: int, serial_num: int, append: int = 4152
|
||||
) -> str:
|
||||
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
|
||||
|
||||
def validate_keychip_format(self, serial: str) -> bool:
|
||||
serial = serial.replace("-", "")
|
||||
if len(serial) != 11 or len(serial) != 15:
|
||||
self.logger.error(
|
||||
f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})"
|
||||
)
|
||||
return False
|
||||
|
||||
platform_code = serial[:4]
|
||||
platform_rev = serial[4:6]
|
||||
const_a = serial[6]
|
||||
num = serial[7:11]
|
||||
append = serial[11:15]
|
||||
|
||||
if re.match("A[7|6]\d[E|X][0|1][0|1|2]A\d{4,8}", serial) is None:
|
||||
self.logger.error(f"Serial validate failed: {serial} failed regex")
|
||||
return False
|
||||
|
||||
if len(append) != 0 or len(append) != 4:
|
||||
self.logger.error(
|
||||
f"Serial validate failed: {serial} had malformed append {append}"
|
||||
)
|
||||
return False
|
||||
|
||||
if len(num) != 4:
|
||||
self.logger.error(
|
||||
f"Serial validate failed: {serial} had malformed number {num}"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
from typing import Optional, Dict
|
||||
from sqlalchemy import Table, Column
|
||||
from sqlalchemy.sql.schema import ForeignKey, PrimaryKeyConstraint
|
||||
from sqlalchemy.types import Integer, String, Boolean
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
import re
|
||||
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
from core.const import *
|
||||
|
||||
arcade = Table(
|
||||
"arcade",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("name", String(255)),
|
||||
Column("nickname", String(255)),
|
||||
Column("country", String(3)),
|
||||
Column("country_id", Integer),
|
||||
Column("state", String(255)),
|
||||
Column("city", String(255)),
|
||||
Column("region_id", Integer),
|
||||
Column("timezone", String(255)),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
machine = Table(
|
||||
"machine",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"arcade",
|
||||
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("serial", String(15), nullable=False),
|
||||
Column("board", String(15)),
|
||||
Column("game", String(4)),
|
||||
Column("country", String(3)), # overwrites if not null
|
||||
Column("timezone", String(255)),
|
||||
Column("ota_enable", Boolean),
|
||||
Column("is_cab", Boolean),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
arcade_owner = Table(
|
||||
"arcade_owner",
|
||||
metadata,
|
||||
Column(
|
||||
"user",
|
||||
Integer,
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column(
|
||||
"arcade",
|
||||
Integer,
|
||||
ForeignKey("arcade.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("permissions", Integer, nullable=False),
|
||||
PrimaryKeyConstraint("user", "arcade", name="arcade_owner_pk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class ArcadeData(BaseData):
|
||||
def get_machine(self, serial: str = None, id: int = None) -> Optional[Dict]:
|
||||
if serial is not None:
|
||||
serial = serial.replace("-", "")
|
||||
if len(serial) == 11:
|
||||
sql = machine.select(machine.c.serial.like(f"{serial}%"))
|
||||
|
||||
elif len(serial) == 15:
|
||||
sql = machine.select(machine.c.serial == serial)
|
||||
|
||||
else:
|
||||
self.logger.error(f"{__name__ }: Malformed serial {serial}")
|
||||
return None
|
||||
|
||||
elif id is not None:
|
||||
sql = machine.select(machine.c.id == id)
|
||||
|
||||
else:
|
||||
self.logger.error(f"{__name__ }: Need either serial or ID to look up!")
|
||||
return None
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_machine(
|
||||
self,
|
||||
arcade_id: int,
|
||||
serial: str = "",
|
||||
board: str = None,
|
||||
game: str = None,
|
||||
is_cab: bool = False,
|
||||
) -> Optional[int]:
|
||||
if arcade_id:
|
||||
self.logger.error(f"{__name__ }: Need arcade id!")
|
||||
return None
|
||||
|
||||
sql = machine.insert().values(
|
||||
arcade=arcade_id, keychip=serial, board=board, game=game, is_cab=is_cab
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def set_machine_serial(self, machine_id: int, serial: str) -> None:
|
||||
result = self.execute(
|
||||
machine.update(machine.c.id == machine_id).values(keychip=serial)
|
||||
)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update serial for machine {machine_id} -> {serial}"
|
||||
)
|
||||
return result.lastrowid
|
||||
|
||||
def set_machine_boardid(self, machine_id: int, boardid: str) -> None:
|
||||
result = self.execute(
|
||||
machine.update(machine.c.id == machine_id).values(board=boardid)
|
||||
)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update board id for machine {machine_id} -> {boardid}"
|
||||
)
|
||||
|
||||
def get_arcade(self, id: int) -> Optional[Dict]:
|
||||
sql = arcade.select(arcade.c.id == id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_arcade(
|
||||
self,
|
||||
name: str,
|
||||
nickname: str = None,
|
||||
country: str = "JPN",
|
||||
country_id: int = 1,
|
||||
state: str = "",
|
||||
city: str = "",
|
||||
regional_id: int = 1,
|
||||
) -> Optional[int]:
|
||||
if nickname is None:
|
||||
nickname = name
|
||||
|
||||
sql = arcade.insert().values(
|
||||
name=name,
|
||||
nickname=nickname,
|
||||
country=country,
|
||||
country_id=country_id,
|
||||
state=state,
|
||||
city=city,
|
||||
regional_id=regional_id,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_arcade_owners(self, arcade_id: int) -> Optional[Dict]:
|
||||
sql = select(arcade_owner).where(arcade_owner.c.arcade == arcade_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def add_arcade_owner(self, arcade_id: int, user_id: int) -> None:
|
||||
sql = insert(arcade_owner).values(arcade=arcade_id, user=user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def format_serial(
|
||||
self, platform_code: str, platform_rev: int, serial_num: int, append: int = 4152
|
||||
) -> str:
|
||||
return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
|
||||
|
||||
def validate_keychip_format(self, serial: str) -> bool:
|
||||
serial = serial.replace("-", "")
|
||||
if len(serial) != 11 or len(serial) != 15:
|
||||
self.logger.error(
|
||||
f"Serial validate failed: Incorrect length for {serial} (len {len(serial)})"
|
||||
)
|
||||
return False
|
||||
|
||||
platform_code = serial[:4]
|
||||
platform_rev = serial[4:6]
|
||||
const_a = serial[6]
|
||||
num = serial[7:11]
|
||||
append = serial[11:15]
|
||||
|
||||
if re.match("A[7|6]\d[E|X][0|1][0|1|2]A\d{4,8}", serial) is None:
|
||||
self.logger.error(f"Serial validate failed: {serial} failed regex")
|
||||
return False
|
||||
|
||||
if len(append) != 0 or len(append) != 4:
|
||||
self.logger.error(
|
||||
f"Serial validate failed: {serial} had malformed append {append}"
|
||||
)
|
||||
return False
|
||||
|
||||
if len(num) != 4:
|
||||
self.logger.error(
|
||||
f"Serial validate failed: {serial} had malformed number {num}"
|
||||
)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
|
@ -1,165 +1,170 @@
|
|||
import json
|
||||
import logging
|
||||
from random import randrange
|
||||
from typing import Any, Optional, Dict, List
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.engine.cursor import CursorResult
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.sql import text, func, select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy import MetaData, Table, Column
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.config import CoreConfig
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
schema_ver = Table(
|
||||
"schema_versions",
|
||||
metadata,
|
||||
Column("game", String(4), primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False, server_default="1"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
event_log = Table(
|
||||
"event_log",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("system", String(255), nullable=False),
|
||||
Column("type", String(255), nullable=False),
|
||||
Column("severity", Integer, nullable=False),
|
||||
Column("message", String(1000), nullable=False),
|
||||
Column("details", JSON, nullable=False),
|
||||
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class BaseData:
|
||||
def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
|
||||
self.config = cfg
|
||||
self.conn = conn
|
||||
self.logger = logging.getLogger("database")
|
||||
|
||||
def execute(self, sql: str, opts: Dict[str, Any] = {}) -> Optional[CursorResult]:
|
||||
res = None
|
||||
|
||||
try:
|
||||
self.logger.info(f"SQL Execute: {''.join(str(sql).splitlines())}")
|
||||
res = self.conn.execute(text(sql), opts)
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"SQLAlchemy error {e}")
|
||||
return None
|
||||
|
||||
except UnicodeEncodeError as e:
|
||||
self.logger.error(f"UnicodeEncodeError error {e}")
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
try:
|
||||
res = self.conn.execute(sql, opts)
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"SQLAlchemy error {e}")
|
||||
return None
|
||||
|
||||
except UnicodeEncodeError as e:
|
||||
self.logger.error(f"UnicodeEncodeError error {e}")
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
self.logger.error(f"Unknown error")
|
||||
raise
|
||||
|
||||
return res
|
||||
|
||||
def generate_id(self) -> int:
|
||||
"""
|
||||
Generate a random 5-7 digit id
|
||||
"""
|
||||
return randrange(10000, 9999999)
|
||||
|
||||
def get_all_schema_vers(self) -> Optional[List[Row]]:
|
||||
sql = select(schema_ver)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_schema_ver(self, game: str) -> Optional[int]:
|
||||
sql = select(schema_ver).where(schema_ver.c.game == game)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
row = result.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
|
||||
return row["version"]
|
||||
|
||||
def touch_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||
sql = insert(schema_ver).values(game=game, version=ver)
|
||||
conflict = sql.on_duplicate_key_update(version=schema_ver.c.version)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update schema version for game {game} (v{ver})"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||
sql = insert(schema_ver).values(game=game, version=ver)
|
||||
conflict = sql.on_duplicate_key_update(version=ver)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update schema version for game {game} (v{ver})"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def log_event(
|
||||
self, system: str, type: str, severity: int, message: str, details: Dict = {}
|
||||
) -> Optional[int]:
|
||||
sql = event_log.insert().values(
|
||||
system=system,
|
||||
type=type,
|
||||
severity=severity,
|
||||
message=message,
|
||||
details=json.dumps(details),
|
||||
)
|
||||
result = self.execute(sql)
|
||||
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
|
||||
sql = event_log.select().limit(entries).all()
|
||||
result = self.execute(sql)
|
||||
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def fix_bools(self, data: Dict) -> Dict:
|
||||
for k, v in data.items():
|
||||
if type(v) == str and v.lower() == "true":
|
||||
data[k] = True
|
||||
elif type(v) == str and v.lower() == "false":
|
||||
data[k] = False
|
||||
|
||||
return data
|
||||
import json
|
||||
import logging
|
||||
from random import randrange
|
||||
from typing import Any, Optional, Dict, List
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.engine.cursor import CursorResult
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.sql import text, func, select
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
from sqlalchemy import MetaData, Table, Column
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.config import CoreConfig
|
||||
|
||||
metadata = MetaData()
|
||||
|
||||
schema_ver = Table(
|
||||
"schema_versions",
|
||||
metadata,
|
||||
Column("game", String(4), primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False, server_default="1"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
event_log = Table(
|
||||
"event_log",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("system", String(255), nullable=False),
|
||||
Column("type", String(255), nullable=False),
|
||||
Column("severity", Integer, nullable=False),
|
||||
Column("message", String(1000), nullable=False),
|
||||
Column("details", JSON, nullable=False),
|
||||
Column("when_logged", TIMESTAMP, nullable=False, server_default=func.now()),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class BaseData:
|
||||
def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
|
||||
self.config = cfg
|
||||
self.conn = conn
|
||||
self.logger = logging.getLogger("database")
|
||||
|
||||
def execute(self, sql: str, opts: Dict[str, Any] = {}) -> Optional[CursorResult]:
|
||||
res = None
|
||||
|
||||
try:
|
||||
self.logger.info(f"SQL Execute: {''.join(str(sql).splitlines())}")
|
||||
res = self.conn.execute(text(sql), opts)
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"SQLAlchemy error {e}")
|
||||
return None
|
||||
|
||||
except UnicodeEncodeError as e:
|
||||
self.logger.error(f"UnicodeEncodeError error {e}")
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
try:
|
||||
res = self.conn.execute(sql, opts)
|
||||
|
||||
except SQLAlchemyError as e:
|
||||
self.logger.error(f"SQLAlchemy error {e}")
|
||||
return None
|
||||
|
||||
except UnicodeEncodeError as e:
|
||||
self.logger.error(f"UnicodeEncodeError error {e}")
|
||||
return None
|
||||
|
||||
except Exception:
|
||||
self.logger.error(f"Unknown error")
|
||||
raise
|
||||
|
||||
return res
|
||||
|
||||
def setup_sqlite(self) -> None:
|
||||
if self.config.database.protocol == "sqlite":
|
||||
self.execute("PRAGMA journal_mode=WAL")
|
||||
self.execute("PRAGMA synchronous=NORMAL")
|
||||
|
||||
def generate_id(self) -> int:
|
||||
"""
|
||||
Generate a random 5-7 digit id
|
||||
"""
|
||||
return randrange(10000, 9999999)
|
||||
|
||||
def get_all_schema_vers(self) -> Optional[List[Row]]:
|
||||
sql = select(schema_ver)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_schema_ver(self, game: str) -> Optional[int]:
|
||||
sql = select(schema_ver).where(schema_ver.c.game == game)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
|
||||
row = result.fetchone()
|
||||
if row is None:
|
||||
return None
|
||||
|
||||
return row["version"]
|
||||
|
||||
def touch_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||
sql = insert(schema_ver).values(game=game, version=ver)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(version=schema_ver.c.version))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update schema version for game {game} (v{ver})"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def set_schema_ver(self, ver: int, game: str = "CORE") -> Optional[int]:
|
||||
sql = insert(schema_ver).values(game=game, version=ver)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(version=ver))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update schema version for game {game} (v{ver})"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def log_event(
|
||||
self, system: str, type: str, severity: int, message: str, details: Dict = {}
|
||||
) -> Optional[int]:
|
||||
sql = event_log.insert().values(
|
||||
system=system,
|
||||
type=type,
|
||||
severity=severity,
|
||||
message=message,
|
||||
details=json.dumps(details),
|
||||
)
|
||||
result = self.execute(sql)
|
||||
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__}: Failed to insert event into event log! system = {system}, type = {type}, severity = {severity}, message = {message}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_event_log(self, entries: int = 100) -> Optional[List[Dict]]:
|
||||
sql = event_log.select().limit(entries).all()
|
||||
result = self.execute(sql)
|
||||
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def fix_bools(self, data: Dict) -> Dict:
|
||||
for k, v in data.items():
|
||||
if type(v) == str and v.lower() == "true":
|
||||
data[k] = True
|
||||
elif type(v) == str and v.lower() == "false":
|
||||
data[k] = False
|
||||
|
||||
return data
|
||||
|
|
|
@ -1,104 +1,104 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint
|
||||
from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP
|
||||
from sqlalchemy.sql.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.engine import Row
|
||||
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
aime_card = Table(
|
||||
"aime_card",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("access_code", String(20)),
|
||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||
Column("is_locked", Boolean, server_default="0"),
|
||||
Column("is_banned", Boolean, server_default="0"),
|
||||
UniqueConstraint("user", "access_code", name="aime_card_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CardData(BaseData):
|
||||
def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
|
||||
sql = aime_card.select(aime_card.c.access_code == access_code)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_card_by_id(self, card_id: int) -> Optional[Row]:
|
||||
sql = aime_card.select(aime_card.c.id == card_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def update_access_code(self, old_ac: str, new_ac: str) -> None:
|
||||
sql = aime_card.update(aime_card.c.access_code == old_ac).values(
|
||||
access_code=new_ac
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to change card access code from {old_ac} to {new_ac}"
|
||||
)
|
||||
|
||||
def get_user_id_from_card(self, access_code: str) -> Optional[int]:
|
||||
"""
|
||||
Given a 20 digit access code as a string, get the user id associated with that card
|
||||
"""
|
||||
card = self.get_card_by_access_code(access_code)
|
||||
if card is None:
|
||||
return None
|
||||
|
||||
return int(card["user"])
|
||||
|
||||
def delete_card(self, card_id: int) -> None:
|
||||
sql = aime_card.delete(aime_card.c.id == card_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to delete card with id {card_id}")
|
||||
|
||||
def get_user_cards(self, aime_id: int) -> Optional[List[Row]]:
|
||||
"""
|
||||
Returns all cards owned by a user
|
||||
"""
|
||||
sql = aime_card.select(aime_card.c.user == aime_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def create_card(self, user_id: int, access_code: str) -> Optional[int]:
|
||||
"""
|
||||
Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful
|
||||
"""
|
||||
sql = aime_card.insert().values(user=user_id, access_code=access_code)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
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
|
||||
"""
|
||||
return f"{int(luid, base=16):0{20}}"
|
||||
|
||||
def to_idm(self, access_code: str) -> str:
|
||||
"""
|
||||
Given a 20 digit access code as a string, return the 16 hex character luid
|
||||
"""
|
||||
return f"{int(access_code):0{16}x}"
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint
|
||||
from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP
|
||||
from sqlalchemy.sql.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.engine import Row
|
||||
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
aime_card = Table(
|
||||
"aime_card",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("access_code", String(20)),
|
||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||
Column("is_locked", Boolean, server_default="0"),
|
||||
Column("is_banned", Boolean, server_default="0"),
|
||||
UniqueConstraint("user", "access_code", name="aime_card_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CardData(BaseData):
|
||||
def get_card_by_access_code(self, access_code: str) -> Optional[Row]:
|
||||
sql = aime_card.select(aime_card.c.access_code == access_code)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_card_by_id(self, card_id: int) -> Optional[Row]:
|
||||
sql = aime_card.select(aime_card.c.id == card_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def update_access_code(self, old_ac: str, new_ac: str) -> None:
|
||||
sql = aime_card.update(aime_card.c.access_code == old_ac).values(
|
||||
access_code=new_ac
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to change card access code from {old_ac} to {new_ac}"
|
||||
)
|
||||
|
||||
def get_user_id_from_card(self, access_code: str) -> Optional[int]:
|
||||
"""
|
||||
Given a 20 digit access code as a string, get the user id associated with that card
|
||||
"""
|
||||
card = self.get_card_by_access_code(access_code)
|
||||
if card is None:
|
||||
return None
|
||||
|
||||
return int(card["user"])
|
||||
|
||||
def delete_card(self, card_id: int) -> None:
|
||||
sql = aime_card.delete(aime_card.c.id == card_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to delete card with id {card_id}")
|
||||
|
||||
def get_user_cards(self, aime_id: int) -> Optional[List[Row]]:
|
||||
"""
|
||||
Returns all cards owned by a user
|
||||
"""
|
||||
sql = aime_card.select(aime_card.c.user == aime_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def create_card(self, user_id: int, access_code: str) -> Optional[int]:
|
||||
"""
|
||||
Given a aime_user id and a 20 digit access code as a string, create a card and return the ID if successful
|
||||
"""
|
||||
sql = aime_card.insert().values(user=user_id, access_code=access_code)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
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
|
||||
"""
|
||||
return f"{int(luid, base=16):0{20}}"
|
||||
|
||||
def to_idm(self, access_code: str) -> str:
|
||||
"""
|
||||
Given a 20 digit access code as a string, return the 16 hex character luid
|
||||
"""
|
||||
return f"{int(access_code):0{16}x}"
|
||||
|
|
|
@ -1,109 +1,116 @@
|
|||
from enum import Enum
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import Table, Column
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
import bcrypt
|
||||
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
aime_user = Table(
|
||||
"aime_user",
|
||||
metadata,
|
||||
Column("id", Integer, nullable=False, primary_key=True, autoincrement=True),
|
||||
Column("username", String(25), unique=True),
|
||||
Column("email", String(255), unique=True),
|
||||
Column("password", String(255)),
|
||||
Column("permissions", Integer),
|
||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||
Column("suspend_expire_time", TIMESTAMP),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PermissionBits(Enum):
|
||||
PermUser = 1
|
||||
PermMod = 2
|
||||
PermSysAdmin = 4
|
||||
|
||||
|
||||
class UserData(BaseData):
|
||||
def create_user(
|
||||
self,
|
||||
id: int = None,
|
||||
username: str = None,
|
||||
email: str = None,
|
||||
password: str = None,
|
||||
permission: int = 1,
|
||||
) -> Optional[int]:
|
||||
if id is None:
|
||||
sql = insert(aime_user).values(
|
||||
username=username,
|
||||
email=email,
|
||||
password=password,
|
||||
permissions=permission,
|
||||
)
|
||||
else:
|
||||
sql = insert(aime_user).values(
|
||||
id=id,
|
||||
username=username,
|
||||
email=email,
|
||||
password=password,
|
||||
permissions=permission,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
username=username, email=email, password=password, permissions=permission
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_user(self, user_id: int) -> Optional[Row]:
|
||||
sql = select(aime_user).where(aime_user.c.id == user_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return False
|
||||
return result.fetchone()
|
||||
|
||||
def check_password(self, user_id: int, passwd: bytes = None) -> bool:
|
||||
usr = self.get_user(user_id)
|
||||
if usr is None:
|
||||
return False
|
||||
|
||||
if usr["password"] is None:
|
||||
return False
|
||||
|
||||
if passwd is None or not passwd:
|
||||
return False
|
||||
|
||||
return bcrypt.checkpw(passwd, usr["password"].encode())
|
||||
|
||||
def reset_autoincrement(self, ai_value: int) -> None:
|
||||
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
|
||||
sql = f"ALTER TABLE aime_user AUTO_INCREMENT={ai_value}"
|
||||
self.execute(sql)
|
||||
|
||||
def delete_user(self, user_id: int) -> None:
|
||||
sql = aime_user.delete(aime_user.c.id == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to delete user with id {user_id}")
|
||||
|
||||
def get_unregistered_users(self) -> List[Row]:
|
||||
"""
|
||||
Returns a list of users who have not registered with the webui. They may or may not have cards.
|
||||
"""
|
||||
sql = select(aime_user).where(aime_user.c.password == None)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
from enum import Enum
|
||||
from typing import Optional, List
|
||||
from sqlalchemy import Table, Column
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
import bcrypt
|
||||
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
aime_user = Table(
|
||||
"aime_user",
|
||||
metadata,
|
||||
Column("id", Integer, nullable=False, primary_key=True, autoincrement=True),
|
||||
Column("username", String(25), unique=True),
|
||||
Column("email", String(255), unique=True),
|
||||
Column("password", String(255)),
|
||||
Column("permissions", Integer),
|
||||
Column("created_date", TIMESTAMP, server_default=func.now()),
|
||||
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
|
||||
Column("suspend_expire_time", TIMESTAMP),
|
||||
mysql_charset="utf8mb4",
|
||||
sqlite_autoincrement=True,
|
||||
)
|
||||
|
||||
|
||||
class PermissionBits(Enum):
|
||||
PermUser = 1
|
||||
PermMod = 2
|
||||
PermSysAdmin = 4
|
||||
|
||||
|
||||
class UserData(BaseData):
|
||||
def create_user(
|
||||
self,
|
||||
id: int = None,
|
||||
username: str = None,
|
||||
email: str = None,
|
||||
password: str = None,
|
||||
permission: int = 1,
|
||||
) -> Optional[int]:
|
||||
if id is None:
|
||||
sql = insert(aime_user).values(
|
||||
username=username,
|
||||
email=email,
|
||||
password=password,
|
||||
permissions=permission,
|
||||
)
|
||||
else:
|
||||
sql = insert(aime_user).values(
|
||||
id=id,
|
||||
username=username,
|
||||
email=email,
|
||||
password=password,
|
||||
permissions=permission,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
username=username,
|
||||
email=email,
|
||||
password=password,
|
||||
permissions=permission,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_user(self, user_id: int) -> Optional[Row]:
|
||||
sql = select(aime_user).where(aime_user.c.id == user_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return False
|
||||
return result.fetchone()
|
||||
|
||||
def check_password(self, user_id: int, passwd: bytes = None) -> bool:
|
||||
usr = self.get_user(user_id)
|
||||
if usr is None:
|
||||
return False
|
||||
|
||||
if usr["password"] is None:
|
||||
return False
|
||||
|
||||
if passwd is None or not passwd:
|
||||
return False
|
||||
|
||||
return bcrypt.checkpw(passwd, usr["password"].encode())
|
||||
|
||||
def reset_autoincrement(self, ai_value: int) -> None:
|
||||
# ALTER TABLE isn't in sqlalchemy so we do this the ugly way
|
||||
# sql = f"ALTER TABLE aime_user AUTO_INCREMENT={ai_value}"
|
||||
sql = f"INSERT OR REPLACE INTO sqlite_sequence VALUES ('aime_user', {ai_value})"
|
||||
self.execute(sql)
|
||||
|
||||
def delete_user(self, user_id: int) -> None:
|
||||
sql = aime_user.delete(aime_user.c.id == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to delete user with id {user_id}")
|
||||
|
||||
def get_unregistered_users(self) -> List[Row]:
|
||||
"""
|
||||
Returns a list of users who have not registered with the webui. They may or may not have cards.
|
||||
"""
|
||||
sql = select(aime_user).where(aime_user.c.password == None)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
|
|
@ -227,22 +227,25 @@ class FE_User(FE_Base):
|
|||
usr_sesh = IUserSession(sesh)
|
||||
if usr_sesh.userId == 0:
|
||||
return redirectTo(b"/gate", request)
|
||||
|
||||
|
||||
cards = self.data.card.get_user_cards(usr_sesh.userId)
|
||||
user = self.data.user.get_user(usr_sesh.userId)
|
||||
card_data = []
|
||||
for c in cards:
|
||||
if c['is_locked']:
|
||||
status = 'Locked'
|
||||
elif c['is_banned']:
|
||||
status = 'Banned'
|
||||
if c["is_locked"]:
|
||||
status = "Locked"
|
||||
elif c["is_banned"]:
|
||||
status = "Banned"
|
||||
else:
|
||||
status = 'Active'
|
||||
|
||||
card_data.append({'access_code': c['access_code'], 'status': status})
|
||||
status = "Active"
|
||||
|
||||
card_data.append({"access_code": c["access_code"], "status": status})
|
||||
|
||||
return template.render(
|
||||
title=f"{self.core_config.server.name} | Account", sesh=vars(usr_sesh), cards=card_data, username=user['username']
|
||||
title=f"{self.core_config.server.name} | Account",
|
||||
sesh=vars(usr_sesh),
|
||||
cards=card_data,
|
||||
username=user["username"],
|
||||
).encode("utf-16")
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "core/frontend/index.jinja" %}
|
||||
{% block content %}
|
||||
<h1>Management for {{ username }}</h1>
|
||||
<h1>Management for {{ username.decode("utf-8") }}</h1>
|
||||
<h2>Cards <button class="btn btn-success" data-bs-toggle="modal" data-bs-target="#card_add">Add</button></h2>
|
||||
<ul>
|
||||
{% for c in cards %}
|
||||
|
@ -18,7 +18,7 @@
|
|||
HOW TO:<br>
|
||||
Scan your card on any networked game and press the "View Access Code" button (varies by game) and enter the 20 digit code below.<br>
|
||||
!!FOR AMUSEIC CARDS: DO NOT ENTER THE CODE SHOWN ON THE BACK OF THE CARD ITSELF OR IT WILL NOT WORK!!
|
||||
<p /><label for="card_add_frm_access_code">Access Code: </label><input id="card_add_frm_access_code" maxlength="20" type="text" required>
|
||||
<p /><label for="card_add_frm_access_code">Access Code: </label><input id="card_add_frm_access_code" maxlength="20" type="text" required>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-primary">Add</button>
|
||||
|
@ -28,4 +28,4 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{% endblock content %}
|
||||
{% endblock content %}
|
||||
|
|
|
@ -35,7 +35,9 @@ class MuchaServlet:
|
|||
self.logger.addHandler(consoleHandler)
|
||||
|
||||
self.logger.setLevel(cfg.mucha.loglevel)
|
||||
coloredlogs.install(level=cfg.mucha.loglevel, logger=self.logger, fmt=log_fmt_str)
|
||||
coloredlogs.install(
|
||||
level=cfg.mucha.loglevel, logger=self.logger, fmt=log_fmt_str
|
||||
)
|
||||
|
||||
all_titles = Utils.get_all_titles()
|
||||
|
||||
|
@ -60,7 +62,7 @@ class MuchaServlet:
|
|||
return b"RESULTS=000"
|
||||
|
||||
req = MuchaAuthRequest(req_dict)
|
||||
self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}")
|
||||
self.logger.info(f"Boardauth request from {client_ip} for {req.gameVer}")
|
||||
self.logger.debug(f"Mucha request {vars(req)}")
|
||||
|
||||
if req.gameCd not in self.mucha_registry:
|
||||
|
@ -95,14 +97,17 @@ class MuchaServlet:
|
|||
return b"RESULTS=000"
|
||||
|
||||
req = MuchaUpdateRequest(req_dict)
|
||||
self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}")
|
||||
self.logger.info(f"Updatecheck request from {client_ip} for {req.gameVer}")
|
||||
self.logger.debug(f"Mucha request {vars(req)}")
|
||||
|
||||
if req.gameCd not in self.mucha_registry:
|
||||
self.logger.warn(f"Unknown gameCd {req.gameCd}")
|
||||
return b"RESULTS=000"
|
||||
|
||||
resp = MuchaUpdateResponse(req.gameVer, f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}")
|
||||
resp = MuchaUpdateResponse(
|
||||
req.gameVer,
|
||||
f"{self.config.mucha.hostname}{':' + str(self.config.allnet.port) if self.config.server.is_develop else ''}",
|
||||
)
|
||||
|
||||
self.logger.debug(f"Mucha response {vars(resp)}")
|
||||
|
||||
|
@ -117,9 +122,11 @@ class MuchaServlet:
|
|||
f"Error processing mucha request {request.content.getvalue()}"
|
||||
)
|
||||
return b""
|
||||
|
||||
|
||||
req = MuchaDownloadStateRequest(req_dict)
|
||||
self.logger.info(f"DownloadState request from {client_ip} for {req.gameCd} -> {req.updateVer}")
|
||||
self.logger.info(
|
||||
f"DownloadState request from {client_ip} for {req.gameCd} -> {req.updateVer}"
|
||||
)
|
||||
self.logger.debug(f"request {vars(req)}")
|
||||
return b"RESULTS=001"
|
||||
|
||||
|
@ -225,7 +232,7 @@ class MuchaUpdateRequest:
|
|||
|
||||
class MuchaUpdateResponse:
|
||||
def __init__(self, game_ver: str, mucha_url: str) -> None:
|
||||
self.RESULTS = "001"
|
||||
self.RESULTS = "001"
|
||||
self.EXE_VER = game_ver
|
||||
|
||||
self.UPDATE_VER_1 = game_ver
|
||||
|
@ -244,6 +251,7 @@ class MuchaUpdateResponse:
|
|||
self.USER_ID = ""
|
||||
self.PASSWORD = ""
|
||||
|
||||
|
||||
"""
|
||||
RESULTS
|
||||
EXE_VER
|
||||
|
@ -264,13 +272,16 @@ LAN_INFO_SIZE_1
|
|||
USER_ID
|
||||
PASSWORD
|
||||
"""
|
||||
|
||||
|
||||
class MuchaUpdateResponseStub:
|
||||
def __init__(self, game_ver: str) -> None:
|
||||
self.RESULTS = "001"
|
||||
self.UPDATE_VER_1 = game_ver
|
||||
|
||||
|
||||
class MuchaDownloadStateRequest:
|
||||
def __init__(self, request: Dict) -> None:
|
||||
def __init__(self, request: Dict) -> None:
|
||||
self.gameCd = request.get("gameCd", "")
|
||||
self.updateVer = request.get("updateVer", "")
|
||||
self.serialNum = request.get("serialNum", "")
|
||||
|
|
|
@ -84,7 +84,9 @@ class TitleServlet:
|
|||
request.setResponseCode(405)
|
||||
return b""
|
||||
|
||||
return index.render_GET(request, int(endpoints["version"]), endpoints["endpoint"])
|
||||
return index.render_GET(
|
||||
request, int(endpoints["version"]), endpoints["endpoint"]
|
||||
)
|
||||
|
||||
def render_POST(self, request: Request, endpoints: dict) -> bytes:
|
||||
code = endpoints["game"]
|
||||
|
|
|
@ -84,7 +84,7 @@ if __name__ == "__main__":
|
|||
|
||||
elif args.action == "cleanup":
|
||||
data.delete_hanging_users()
|
||||
|
||||
|
||||
elif args.action == "version":
|
||||
data.show_versions()
|
||||
|
||||
|
|
|
@ -12,13 +12,18 @@ title:
|
|||
hostname: "localhost"
|
||||
port: 8080
|
||||
|
||||
# Only `name` and `protocol` are applicable
|
||||
# if using SQLite.
|
||||
database:
|
||||
host: "localhost"
|
||||
username: "aime"
|
||||
password: "aime"
|
||||
name: "aime"
|
||||
|
||||
# Path to database file, if SQLite
|
||||
name: "database.sqlite3"
|
||||
|
||||
port: 3306
|
||||
protocol: "mysql"
|
||||
protocol: "sqlite"
|
||||
sha2_password: False
|
||||
loglevel: "warn"
|
||||
user_table_autoincrement_start: 10000
|
||||
|
|
19
index.py
19
index.py
|
@ -13,6 +13,7 @@ from twisted.web.http import Request
|
|||
from routes import Mapper
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class HttpDispatcher(resource.Resource):
|
||||
def __init__(self, cfg: CoreConfig, config_dir: str):
|
||||
super().__init__()
|
||||
|
@ -188,16 +189,22 @@ class HttpDispatcher(resource.Resource):
|
|||
|
||||
if type(ret) == str:
|
||||
return ret.encode()
|
||||
|
||||
elif type(ret) == bytes or type(ret) == tuple: # allow for bytes or tuple (data, response code) responses
|
||||
|
||||
elif (
|
||||
type(ret) == bytes or type(ret) == tuple
|
||||
): # allow for bytes or tuple (data, response code) responses
|
||||
return ret
|
||||
|
||||
|
||||
elif ret is None:
|
||||
self.logger.warn(f"None returned by controller for {request.uri.decode()} endpoint")
|
||||
self.logger.warn(
|
||||
f"None returned by controller for {request.uri.decode()} endpoint"
|
||||
)
|
||||
return b""
|
||||
|
||||
|
||||
else:
|
||||
self.logger.warn(f"Unknown data type returned by controller for {request.uri.decode()} endpoint")
|
||||
self.logger.warn(
|
||||
f"Unknown data type returned by controller for {request.uri.decode()} endpoint"
|
||||
)
|
||||
return b""
|
||||
|
||||
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
# ARTEMiS
|
||||
A network service emulator for games running SEGA'S ALL.NET service, and similar.
|
||||
|
||||
This fork uses SQLite instead of MySQL, intended for local use.
|
||||
|
||||
# Supported games
|
||||
Games listed below have been tested and confirmed working. Only game versions older then the version currently active in arcades, or games versions that have not recieved a major update in over one year, are supported.
|
||||
|
||||
|
@ -36,8 +38,6 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
|||
## Requirements
|
||||
- python 3 (tested working with 3.9 and 3.10, other versions YMMV)
|
||||
- pip
|
||||
- memcached (for non-windows platforms)
|
||||
- mysql/mariadb server
|
||||
|
||||
## Setup guides
|
||||
Follow the platform-specific guides for [windows](docs/INSTALL_WINDOWS.md) and [ubuntu](docs/INSTALL_UBUNTU.md) to setup and run the server.
|
||||
|
|
|
@ -2,9 +2,11 @@ from titles.chuni.index import ChuniServlet
|
|||
from titles.chuni.const import ChuniConstants
|
||||
from titles.chuni.database import ChuniData
|
||||
from titles.chuni.read import ChuniReader
|
||||
from titles.chuni.frontend import ChuniFrontend
|
||||
|
||||
index = ChuniServlet
|
||||
database = ChuniData
|
||||
reader = ChuniReader
|
||||
frontend = ChuniFrontend
|
||||
game_codes = [ChuniConstants.GAME_CODE, ChuniConstants.GAME_CODE_NEW]
|
||||
current_schema_version = 4
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import logging
|
||||
import json
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime, timedelta
|
||||
from time import strftime
|
||||
|
||||
|
@ -40,82 +41,83 @@ class ChuniBase:
|
|||
user_id = data["userId"]
|
||||
login_bonus_presets = self.data.static.get_login_bonus_presets(self.version)
|
||||
|
||||
for preset in login_bonus_presets:
|
||||
# check if a user already has some pogress and if not add the
|
||||
# login bonus entry
|
||||
user_login_bonus = self.data.item.get_login_bonus(
|
||||
user_id, self.version, preset["presetId"]
|
||||
)
|
||||
if user_login_bonus is None:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id, self.version, preset["presetId"]
|
||||
)
|
||||
# yeah i'm lazy
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
for preset in login_bonus_presets:
|
||||
# check if a user already has some pogress and if not add the
|
||||
# login bonus entry
|
||||
user_login_bonus = self.data.item.get_login_bonus(
|
||||
user_id, self.version, preset["presetId"]
|
||||
)
|
||||
|
||||
# skip the login bonus entirely if its already finished
|
||||
if user_login_bonus["isFinished"]:
|
||||
continue
|
||||
|
||||
# make sure the last login is more than 24 hours ago
|
||||
if user_login_bonus["lastUpdateDate"] < datetime.now() - timedelta(
|
||||
hours=24
|
||||
):
|
||||
# increase the login day counter and update the last login date
|
||||
bonus_count = user_login_bonus["bonusCount"] + 1
|
||||
last_update_date = datetime.now()
|
||||
|
||||
all_login_boni = self.data.static.get_login_bonus(
|
||||
self.version, preset["presetId"]
|
||||
)
|
||||
|
||||
# skip the current bonus preset if no boni were found
|
||||
if all_login_boni is None or len(all_login_boni) < 1:
|
||||
self.logger.warn(
|
||||
f"No bonus entries found for bonus preset {preset['presetId']}"
|
||||
if user_login_bonus is None:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id, self.version, preset["presetId"]
|
||||
)
|
||||
# yeah i'm lazy
|
||||
user_login_bonus = self.data.item.get_login_bonus(
|
||||
user_id, self.version, preset["presetId"]
|
||||
)
|
||||
|
||||
# skip the login bonus entirely if its already finished
|
||||
if user_login_bonus["isFinished"]:
|
||||
continue
|
||||
|
||||
max_needed_days = all_login_boni[0]["needLoginDayCount"]
|
||||
# make sure the last login is more than 24 hours ago
|
||||
if user_login_bonus["lastUpdateDate"] < datetime.now() - timedelta(
|
||||
hours=24
|
||||
):
|
||||
# increase the login day counter and update the last login date
|
||||
bonus_count = user_login_bonus["bonusCount"] + 1
|
||||
last_update_date = datetime.now()
|
||||
|
||||
# make sure to not show login boni after all days got redeemed
|
||||
is_finished = False
|
||||
if bonus_count > max_needed_days:
|
||||
# assume that all login preset ids under 3000 needs to be
|
||||
# looped, like 30 and 40 are looped, 40 does not work?
|
||||
if preset["presetId"] < 3000:
|
||||
bonus_count = 1
|
||||
else:
|
||||
is_finished = True
|
||||
|
||||
# grab the item for the corresponding day
|
||||
login_item = self.data.static.get_login_bonus_by_required_days(
|
||||
self.version, preset["presetId"], bonus_count
|
||||
)
|
||||
if login_item is not None:
|
||||
# now add the present to the database so the
|
||||
# handle_get_user_item_api_request can grab them
|
||||
self.data.item.put_item(
|
||||
user_id,
|
||||
{
|
||||
"itemId": login_item["presentId"],
|
||||
"itemKind": 6,
|
||||
"stock": login_item["itemNum"],
|
||||
"isValid": True,
|
||||
},
|
||||
all_login_boni = self.data.static.get_login_bonus(
|
||||
self.version, preset["presetId"]
|
||||
)
|
||||
|
||||
self.data.item.put_login_bonus(
|
||||
user_id,
|
||||
self.version,
|
||||
preset["presetId"],
|
||||
bonusCount=bonus_count,
|
||||
lastUpdateDate=last_update_date,
|
||||
isWatched=False,
|
||||
isFinished=is_finished,
|
||||
)
|
||||
# skip the current bonus preset if no boni were found
|
||||
if all_login_boni is None or len(all_login_boni) < 1:
|
||||
self.logger.warn(
|
||||
f"No bonus entries found for bonus preset {preset['presetId']}"
|
||||
)
|
||||
continue
|
||||
|
||||
max_needed_days = all_login_boni[0]["needLoginDayCount"]
|
||||
|
||||
# make sure to not show login boni after all days got redeemed
|
||||
is_finished = False
|
||||
if bonus_count > max_needed_days:
|
||||
# assume that all login preset ids under 3000 needs to be
|
||||
# looped, like 30 and 40 are looped, 40 does not work?
|
||||
if preset["presetId"] < 3000:
|
||||
bonus_count = 1
|
||||
else:
|
||||
is_finished = True
|
||||
|
||||
# grab the item for the corresponding day
|
||||
login_item = self.data.static.get_login_bonus_by_required_days(
|
||||
self.version, preset["presetId"], bonus_count
|
||||
)
|
||||
if login_item is not None:
|
||||
# now add the present to the database so the
|
||||
# handle_get_user_item_api_request can grab them
|
||||
self.data.item.put_item(
|
||||
user_id,
|
||||
{
|
||||
"itemId": login_item["presentId"],
|
||||
"itemKind": 6,
|
||||
"stock": login_item["itemNum"],
|
||||
"isValid": True,
|
||||
},
|
||||
)
|
||||
|
||||
self.data.item.put_login_bonus(
|
||||
user_id,
|
||||
self.version,
|
||||
preset["presetId"],
|
||||
bonusCount=bonus_count,
|
||||
lastUpdateDate=last_update_date,
|
||||
isWatched=False,
|
||||
isFinished=is_finished,
|
||||
)
|
||||
|
||||
return {"returnCode": 1}
|
||||
|
||||
|
@ -639,97 +641,101 @@ class ChuniBase:
|
|||
upsert = data["upsertUserAll"]
|
||||
user_id = data["userId"]
|
||||
|
||||
if "userData" in upsert:
|
||||
try:
|
||||
upsert["userData"][0]["userName"] = self.read_wtf8(
|
||||
upsert["userData"][0]["userName"]
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
if "userData" in upsert:
|
||||
try:
|
||||
upsert["userData"][0]["userName"] = self.read_wtf8(
|
||||
upsert["userData"][0]["userName"]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
if "userDataEx" in upsert:
|
||||
self.data.profile.put_profile_data_ex(
|
||||
user_id, self.version, upsert["userDataEx"][0]
|
||||
)
|
||||
|
||||
if "userDataEx" in upsert:
|
||||
self.data.profile.put_profile_data_ex(
|
||||
user_id, self.version, upsert["userDataEx"][0]
|
||||
)
|
||||
if "userGameOption" in upsert:
|
||||
self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0])
|
||||
|
||||
if "userGameOption" in upsert:
|
||||
self.data.profile.put_profile_option(user_id, upsert["userGameOption"][0])
|
||||
if "userGameOptionEx" in upsert:
|
||||
self.data.profile.put_profile_option_ex(
|
||||
user_id, upsert["userGameOptionEx"][0]
|
||||
)
|
||||
|
||||
if "userGameOptionEx" in upsert:
|
||||
self.data.profile.put_profile_option_ex(
|
||||
user_id, upsert["userGameOptionEx"][0]
|
||||
)
|
||||
if "userRecentRatingList" in upsert:
|
||||
self.data.profile.put_profile_recent_rating(
|
||||
user_id, upsert["userRecentRatingList"]
|
||||
)
|
||||
if "userRecentRatingList" in upsert:
|
||||
self.data.profile.put_profile_recent_rating(
|
||||
user_id, upsert["userRecentRatingList"]
|
||||
)
|
||||
|
||||
if "userCharacterList" in upsert:
|
||||
for character in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, character)
|
||||
if "userActivityList" in upsert:
|
||||
for activity in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(user_id, activity)
|
||||
|
||||
if "userMapList" in upsert:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(user_id, map)
|
||||
if "userChargeList" in upsert:
|
||||
for charge in upsert["userChargeList"]:
|
||||
self.data.profile.put_profile_charge(user_id, charge)
|
||||
|
||||
if "userCourseList" in upsert:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
if "userOverPowerList" in upsert:
|
||||
for overpower in upsert["userOverPowerList"]:
|
||||
self.data.profile.put_profile_overpower(user_id, overpower)
|
||||
|
||||
if "userDuelList" in upsert:
|
||||
for duel in upsert["userDuelList"]:
|
||||
self.data.item.put_duel(user_id, duel)
|
||||
if "userEmoneyList" in upsert:
|
||||
for emoney in upsert["userEmoneyList"]:
|
||||
self.data.profile.put_profile_emoney(user_id, emoney)
|
||||
|
||||
if "userItemList" in upsert:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, item)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userCharacterList" in upsert:
|
||||
for character in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, character)
|
||||
|
||||
if "userActivityList" in upsert:
|
||||
for activity in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(user_id, activity)
|
||||
if "userMapList" in upsert:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(user_id, map)
|
||||
|
||||
if "userChargeList" in upsert:
|
||||
for charge in upsert["userChargeList"]:
|
||||
self.data.profile.put_profile_charge(user_id, charge)
|
||||
if "userDuelList" in upsert:
|
||||
for duel in upsert["userDuelList"]:
|
||||
self.data.item.put_duel(user_id, duel)
|
||||
|
||||
if "userMusicDetailList" in upsert:
|
||||
for song in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_score(user_id, song)
|
||||
if "userItemList" in upsert:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, item)
|
||||
|
||||
if "userPlaylogList" in upsert:
|
||||
for playlog in upsert["userPlaylogList"]:
|
||||
# convert the player names to utf-8
|
||||
playlog["playedUserName1"] = self.read_wtf8(playlog["playedUserName1"])
|
||||
playlog["playedUserName2"] = self.read_wtf8(playlog["playedUserName2"])
|
||||
playlog["playedUserName3"] = self.read_wtf8(playlog["playedUserName3"])
|
||||
self.data.score.put_playlog(user_id, playlog)
|
||||
if "userMapAreaList" in upsert:
|
||||
for map_area in upsert["userMapAreaList"]:
|
||||
self.data.item.put_map_area(user_id, map_area)
|
||||
|
||||
if "userLoginBonusList" in upsert:
|
||||
for login in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id, self.version, login["presetId"], isWatched=True
|
||||
)
|
||||
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
if "userCourseList" in upsert:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
|
||||
if "userMusicDetailList" in upsert:
|
||||
for song in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_score(user_id, song)
|
||||
|
||||
if "userPlaylogList" in upsert:
|
||||
for playlog in upsert["userPlaylogList"]:
|
||||
# convert the player names to utf-8
|
||||
playlog["playedUserName1"] = self.read_wtf8(playlog["playedUserName1"])
|
||||
playlog["playedUserName2"] = self.read_wtf8(playlog["playedUserName2"])
|
||||
playlog["playedUserName3"] = self.read_wtf8(playlog["playedUserName3"])
|
||||
self.data.score.put_playlog(user_id, playlog)
|
||||
|
||||
if "userTeamPoint" in upsert:
|
||||
# TODO: team stuff
|
||||
pass
|
||||
|
||||
if "userMapAreaList" in upsert:
|
||||
for map_area in upsert["userMapAreaList"]:
|
||||
self.data.item.put_map_area(user_id, map_area)
|
||||
|
||||
if "userOverPowerList" in upsert:
|
||||
for overpower in upsert["userOverPowerList"]:
|
||||
self.data.profile.put_profile_overpower(user_id, overpower)
|
||||
|
||||
if "userEmoneyList" in upsert:
|
||||
for emoney in upsert["userEmoneyList"]:
|
||||
self.data.profile.put_profile_emoney(user_id, emoney)
|
||||
|
||||
if "userLoginBonusList" in upsert:
|
||||
for login in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id, self.version, login["presetId"], isWatched=True
|
||||
)
|
||||
|
||||
return {"returnCode": "1"}
|
||||
|
||||
def handle_upsert_user_chargelog_api_request(self, data: Dict) -> Dict:
|
||||
|
|
|
@ -33,7 +33,7 @@ class ChuniConstants:
|
|||
"CHUNITHM PARADISE",
|
||||
"CHUNITHM NEW!!",
|
||||
"CHUNITHM NEW PLUS!!",
|
||||
"CHUNITHM SUN"
|
||||
"CHUNITHM SUN",
|
||||
]
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -108,7 +108,9 @@ class ChuniServlet:
|
|||
hmac_hash_module=SHA1,
|
||||
)
|
||||
|
||||
hashed_name = hash.hex()[:32] # truncate unused bytes like the game does
|
||||
hashed_name = hash.hex()[
|
||||
:32
|
||||
] # truncate unused bytes like the game does
|
||||
self.hash_table[version][hashed_name] = method_fixed
|
||||
|
||||
self.logger.debug(
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import logging
|
||||
from contextlib import nullcontext
|
||||
from datetime import datetime, timedelta
|
||||
from random import randint
|
||||
from typing import Dict
|
||||
|
@ -375,22 +376,23 @@ class ChuniNew(ChuniBase):
|
|||
user_gacha.pop("gachaId")
|
||||
user_gacha.pop("dailyGachaDate")
|
||||
|
||||
self.data.item.put_user_gacha(user_id, gacha_id, user_gacha)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
self.data.item.put_user_gacha(user_id, gacha_id, user_gacha)
|
||||
|
||||
# save all user items
|
||||
if "userItemList" in upsert:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, item)
|
||||
# save all user items
|
||||
if "userItemList" in upsert:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, item)
|
||||
|
||||
# add every gamegachaCard to database
|
||||
for card in upsert["gameGachaCardList"]:
|
||||
self.data.item.put_user_print_state(
|
||||
user_id,
|
||||
hasCompleted=False,
|
||||
placeId=place_id,
|
||||
cardId=card["cardId"],
|
||||
gachaId=card["gachaId"],
|
||||
)
|
||||
# add every gamegachaCard to database
|
||||
for card in upsert["gameGachaCardList"]:
|
||||
self.data.item.put_user_print_state(
|
||||
user_id,
|
||||
hasCompleted=False,
|
||||
placeId=place_id,
|
||||
cardId=card["cardId"],
|
||||
gachaId=card["gachaId"],
|
||||
)
|
||||
|
||||
# retrieve every game gacha card which has been added in order to get
|
||||
# the orderId for the next request
|
||||
|
@ -451,14 +453,15 @@ class ChuniNew(ChuniBase):
|
|||
place_id = data["placeId"]
|
||||
|
||||
# save all user items
|
||||
if "userItemList" in data:
|
||||
for item in data["userItemList"]:
|
||||
self.data.item.put_item(user_id, item)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userItemList" in data:
|
||||
for item in data["userItemList"]:
|
||||
self.data.item.put_item(user_id, item)
|
||||
|
||||
# set the card print state to success and use the orderId as the key
|
||||
self.data.item.put_user_print_state(
|
||||
user_id, id=upsert["orderId"], hasCompleted=True
|
||||
)
|
||||
# set the card print state to success and use the orderId as the key
|
||||
self.data.item.put_user_print_state(
|
||||
user_id, id=upsert["orderId"], hasCompleted=True
|
||||
)
|
||||
|
||||
return {"returnCode": "1", "apiName": "CMUpsertUserPrintSubtractApi"}
|
||||
|
||||
|
@ -467,8 +470,9 @@ class ChuniNew(ChuniBase):
|
|||
user_id = data["userId"]
|
||||
|
||||
# set the card print state to success and use the orderId as the key
|
||||
for order_id in order_ids:
|
||||
self.data.item.put_user_print_state(user_id, id=order_id, hasCompleted=True)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
for order_id in order_ids:
|
||||
self.data.item.put_user_print_state(user_id, id=order_id, hasCompleted=True)
|
||||
|
||||
return {"returnCode": "1", "apiName": "CMUpsertUserPrintCancelApi"}
|
||||
|
||||
|
@ -481,40 +485,41 @@ class ChuniNew(ChuniBase):
|
|||
# check if there is a free matching room
|
||||
matching_room = self.data.item.get_oldest_free_matching(self.version)
|
||||
|
||||
if matching_room is None:
|
||||
# grab the latest roomId and add 1 for the new room
|
||||
newest_matching = self.data.item.get_newest_matching(self.version)
|
||||
if newest_matching is not None:
|
||||
room_id = newest_matching["roomId"] + 1
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if matching_room is None:
|
||||
# grab the latest roomId and add 1 for the new room
|
||||
newest_matching = self.data.item.get_newest_matching(self.version)
|
||||
if newest_matching is not None:
|
||||
room_id = newest_matching["roomId"] + 1
|
||||
|
||||
# fix userName WTF8
|
||||
new_member = data["matchingMemberInfo"]
|
||||
new_member["userName"] = self.read_wtf8(new_member["userName"])
|
||||
# fix userName WTF8
|
||||
new_member = data["matchingMemberInfo"]
|
||||
new_member["userName"] = self.read_wtf8(new_member["userName"])
|
||||
|
||||
# create the new room with room_id and the current user id (host)
|
||||
# user id is required for the countdown later on
|
||||
self.data.item.put_matching(
|
||||
self.version, room_id, [new_member], user_id=new_member["userId"]
|
||||
)
|
||||
# create the new room with room_id and the current user id (host)
|
||||
# user id is required for the countdown later on
|
||||
self.data.item.put_matching(
|
||||
self.version, room_id, [new_member], user_id=new_member["userId"]
|
||||
)
|
||||
|
||||
# get the newly created matching room
|
||||
matching_room = self.data.item.get_matching(self.version, room_id)
|
||||
else:
|
||||
# a room already exists, so just add the new member to it
|
||||
matching_member_list = matching_room["matchingMemberInfoList"]
|
||||
# fix userName WTF8
|
||||
new_member = data["matchingMemberInfo"]
|
||||
new_member["userName"] = self.read_wtf8(new_member["userName"])
|
||||
matching_member_list.append(new_member)
|
||||
# get the newly created matching room
|
||||
matching_room = self.data.item.get_matching(self.version, room_id)
|
||||
else:
|
||||
# a room already exists, so just add the new member to it
|
||||
matching_member_list = matching_room["matchingMemberInfoList"]
|
||||
# fix userName WTF8
|
||||
new_member = data["matchingMemberInfo"]
|
||||
new_member["userName"] = self.read_wtf8(new_member["userName"])
|
||||
matching_member_list.append(new_member)
|
||||
|
||||
# add the updated room to the database, make sure to set isFull correctly!
|
||||
self.data.item.put_matching(
|
||||
self.version,
|
||||
matching_room["roomId"],
|
||||
matching_member_list,
|
||||
user_id=matching_room["user"],
|
||||
is_full=True if len(matching_member_list) >= 4 else False,
|
||||
)
|
||||
# add the updated room to the database, make sure to set isFull correctly!
|
||||
self.data.item.put_matching(
|
||||
self.version,
|
||||
matching_room["roomId"],
|
||||
matching_member_list,
|
||||
user_id=matching_room["user"],
|
||||
is_full=True if len(matching_member_list) >= 4 else False,
|
||||
)
|
||||
|
||||
matching_wait = {
|
||||
"isFinish": False,
|
||||
|
@ -560,26 +565,27 @@ class ChuniNew(ChuniBase):
|
|||
if matching_rooms is None:
|
||||
return {"returnCode": "1"}
|
||||
|
||||
for room in matching_rooms:
|
||||
old_members = room["matchingMemberInfoList"]
|
||||
new_members = [m for m in old_members if m["userId"] != data["userId"]]
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
for room in matching_rooms:
|
||||
old_members = room["matchingMemberInfoList"]
|
||||
new_members = [m for m in old_members if m["userId"] != data["userId"]]
|
||||
|
||||
# if nothing changed go to the next room
|
||||
if len(old_members) == len(new_members):
|
||||
continue
|
||||
# if nothing changed go to the next room
|
||||
if len(old_members) == len(new_members):
|
||||
continue
|
||||
|
||||
# if the last user got removed, delete the matching room
|
||||
if len(new_members) <= 0:
|
||||
self.data.item.delete_matching(self.version, room["roomId"])
|
||||
else:
|
||||
# remove the user from the room
|
||||
self.data.item.put_matching(
|
||||
self.version,
|
||||
room["roomId"],
|
||||
new_members,
|
||||
user_id=room["user"],
|
||||
rest_sec=room["restMSec"],
|
||||
)
|
||||
# if the last user got removed, delete the matching room
|
||||
if len(new_members) <= 0:
|
||||
self.data.item.delete_matching(self.version, room["roomId"])
|
||||
else:
|
||||
# remove the user from the room
|
||||
self.data.item.put_matching(
|
||||
self.version,
|
||||
room["roomId"],
|
||||
new_members,
|
||||
user_id=room["user"],
|
||||
rest_sec=room["restMSec"],
|
||||
)
|
||||
|
||||
return {"returnCode": "1"}
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from contextlib import nullcontext
|
||||
from typing import Optional
|
||||
from os import walk, path
|
||||
import xml.etree.ElementTree as ET
|
||||
|
@ -38,11 +39,13 @@ class ChuniReader(BaseReader):
|
|||
|
||||
for dir in data_dirs:
|
||||
self.logger.info(f"Read from {dir}")
|
||||
self.read_events(f"{dir}/event")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_charges(f"{dir}/chargeItem")
|
||||
self.read_avatar(f"{dir}/avatarAccessory")
|
||||
self.read_login_bonus(f"{dir}/")
|
||||
|
||||
with self.data.static.conn.begin() or nullcontext():
|
||||
self.read_events(f"{dir}/event")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_charges(f"{dir}/chargeItem")
|
||||
self.read_avatar(f"{dir}/avatarAccessory")
|
||||
self.read_login_bonus(f"{dir}/")
|
||||
|
||||
def read_login_bonus(self, root_dir: str) -> None:
|
||||
for root, dirs, files in walk(f"{root_dir}loginBonusPreset"):
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from titles.chuni.schema.profile import ChuniProfileData
|
||||
from titles.chuni.schema.score import ChuniScoreData
|
||||
from titles.chuni.schema.item import ChuniItemData
|
||||
from titles.chuni.schema.static import ChuniStaticData
|
||||
|
||||
__all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"]
|
||||
from titles.chuni.schema.profile import ChuniProfileData
|
||||
from titles.chuni.schema.score import ChuniScoreData
|
||||
from titles.chuni.schema.item import ChuniItemData
|
||||
from titles.chuni.schema.static import ChuniStaticData
|
||||
|
||||
__all__ = ["ChuniProfileData", "ChuniScoreData", "ChuniItemData", "ChuniStaticData"]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,202 +1,202 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
course = Table(
|
||||
"chuni_score_course",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("courseId", Integer),
|
||||
Column("classId", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("scoreMax", Integer),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("isAllJustice", Boolean),
|
||||
Column("isSuccess", Boolean),
|
||||
Column("scoreRank", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("param1", Integer),
|
||||
Column("param2", Integer),
|
||||
Column("param3", Integer),
|
||||
Column("param4", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("theoryCount", Integer),
|
||||
Column("orderId", Integer),
|
||||
Column("playerRating", Integer),
|
||||
UniqueConstraint("user", "courseId", name="chuni_score_course_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
best_score = Table(
|
||||
"chuni_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("scoreMax", Integer),
|
||||
Column("resRequestCount", Integer),
|
||||
Column("resAcceptCount", Integer),
|
||||
Column("resSuccessCount", Integer),
|
||||
Column("missCount", Integer),
|
||||
Column("maxComboCount", Integer),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("isAllJustice", Boolean),
|
||||
Column("isSuccess", Boolean),
|
||||
Column("fullChain", Integer),
|
||||
Column("maxChain", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("isLock", Boolean),
|
||||
Column("ext1", Integer),
|
||||
Column("theoryCount", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"chuni_score_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("orderId", Integer),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("playDate", String(20)),
|
||||
Column("userPlayDate", String(20)),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("customId", Integer),
|
||||
Column("playedUserId1", Integer),
|
||||
Column("playedUserId2", Integer),
|
||||
Column("playedUserId3", Integer),
|
||||
Column("playedUserName1", String(20)),
|
||||
Column("playedUserName2", String(20)),
|
||||
Column("playedUserName3", String(20)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("playedCustom1", Integer),
|
||||
Column("playedCustom2", Integer),
|
||||
Column("playedCustom3", Integer),
|
||||
Column("track", Integer),
|
||||
Column("score", Integer),
|
||||
Column("rank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("maxChain", Integer),
|
||||
Column("rateTap", Integer),
|
||||
Column("rateHold", Integer),
|
||||
Column("rateSlide", Integer),
|
||||
Column("rateAir", Integer),
|
||||
Column("rateFlick", Integer),
|
||||
Column("judgeGuilty", Integer),
|
||||
Column("judgeAttack", Integer),
|
||||
Column("judgeJustice", Integer),
|
||||
Column("judgeCritical", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("playerRating", Integer),
|
||||
Column("isNewRecord", Boolean),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("fullChainKind", Integer),
|
||||
Column("isAllJustice", Boolean),
|
||||
Column("isContinue", Boolean),
|
||||
Column("isFreeToPlay", Boolean),
|
||||
Column("characterId", Integer),
|
||||
Column("skillId", Integer),
|
||||
Column("playKind", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("skillLevel", Integer),
|
||||
Column("skillEffect", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("isMaimai", Boolean),
|
||||
Column("commonId", Integer),
|
||||
Column("charaIllustId", Integer),
|
||||
Column("romVersion", String(255)),
|
||||
Column("judgeHeaven", Integer),
|
||||
Column("regionId", Integer),
|
||||
Column("machineType", Integer),
|
||||
mysql_charset="utf8mb4"
|
||||
)
|
||||
|
||||
|
||||
class ChuniScoreData(BaseData):
|
||||
def get_courses(self, aime_id: int) -> Optional[Row]:
|
||||
sql = select(course).where(course.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]:
|
||||
course_data["user"] = aime_id
|
||||
course_data = self.fix_bools(course_data)
|
||||
|
||||
sql = insert(course).values(**course_data)
|
||||
conflict = sql.on_duplicate_key_update(**course_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_scores(self, aime_id: int) -> Optional[Row]:
|
||||
sql = select(best_score).where(best_score.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]:
|
||||
score_data["user"] = aime_id
|
||||
score_data = self.fix_bools(score_data)
|
||||
|
||||
sql = insert(best_score).values(**score_data)
|
||||
conflict = sql.on_duplicate_key_update(**score_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_playlogs(self, aime_id: int) -> Optional[Row]:
|
||||
sql = select(playlog).where(playlog.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
|
||||
playlog_data["user"] = aime_id
|
||||
playlog_data = self.fix_bools(playlog_data)
|
||||
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
conflict = sql.on_duplicate_key_update(**playlog_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
course = Table(
|
||||
"chuni_score_course",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("courseId", Integer),
|
||||
Column("classId", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("scoreMax", Integer),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("isAllJustice", Boolean),
|
||||
Column("isSuccess", Boolean),
|
||||
Column("scoreRank", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("param1", Integer),
|
||||
Column("param2", Integer),
|
||||
Column("param3", Integer),
|
||||
Column("param4", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("theoryCount", Integer),
|
||||
Column("orderId", Integer),
|
||||
Column("playerRating", Integer),
|
||||
UniqueConstraint("user", "courseId", name="chuni_score_course_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
best_score = Table(
|
||||
"chuni_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("scoreMax", Integer),
|
||||
Column("resRequestCount", Integer),
|
||||
Column("resAcceptCount", Integer),
|
||||
Column("resSuccessCount", Integer),
|
||||
Column("missCount", Integer),
|
||||
Column("maxComboCount", Integer),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("isAllJustice", Boolean),
|
||||
Column("isSuccess", Boolean),
|
||||
Column("fullChain", Integer),
|
||||
Column("maxChain", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("isLock", Boolean),
|
||||
Column("ext1", Integer),
|
||||
Column("theoryCount", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="chuni_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"chuni_score_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("orderId", Integer),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("playDate", String(20)),
|
||||
Column("userPlayDate", String(20)),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("customId", Integer),
|
||||
Column("playedUserId1", Integer),
|
||||
Column("playedUserId2", Integer),
|
||||
Column("playedUserId3", Integer),
|
||||
Column("playedUserName1", String(20)),
|
||||
Column("playedUserName2", String(20)),
|
||||
Column("playedUserName3", String(20)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("playedCustom1", Integer),
|
||||
Column("playedCustom2", Integer),
|
||||
Column("playedCustom3", Integer),
|
||||
Column("track", Integer),
|
||||
Column("score", Integer),
|
||||
Column("rank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("maxChain", Integer),
|
||||
Column("rateTap", Integer),
|
||||
Column("rateHold", Integer),
|
||||
Column("rateSlide", Integer),
|
||||
Column("rateAir", Integer),
|
||||
Column("rateFlick", Integer),
|
||||
Column("judgeGuilty", Integer),
|
||||
Column("judgeAttack", Integer),
|
||||
Column("judgeJustice", Integer),
|
||||
Column("judgeCritical", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("playerRating", Integer),
|
||||
Column("isNewRecord", Boolean),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("fullChainKind", Integer),
|
||||
Column("isAllJustice", Boolean),
|
||||
Column("isContinue", Boolean),
|
||||
Column("isFreeToPlay", Boolean),
|
||||
Column("characterId", Integer),
|
||||
Column("skillId", Integer),
|
||||
Column("playKind", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("skillLevel", Integer),
|
||||
Column("skillEffect", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("isMaimai", Boolean),
|
||||
Column("commonId", Integer),
|
||||
Column("charaIllustId", Integer),
|
||||
Column("romVersion", String(255)),
|
||||
Column("judgeHeaven", Integer),
|
||||
Column("regionId", Integer),
|
||||
Column("machineType", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class ChuniScoreData(BaseData):
|
||||
def get_courses(self, aime_id: int) -> Optional[Row]:
|
||||
sql = select(course).where(course.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_course(self, aime_id: int, course_data: Dict) -> Optional[int]:
|
||||
course_data["user"] = aime_id
|
||||
course_data = self.fix_bools(course_data)
|
||||
|
||||
sql = insert(course).values(**course_data)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**course_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_scores(self, aime_id: int) -> Optional[Row]:
|
||||
sql = select(best_score).where(best_score.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_score(self, aime_id: int, score_data: Dict) -> Optional[int]:
|
||||
score_data["user"] = aime_id
|
||||
score_data = self.fix_bools(score_data)
|
||||
|
||||
sql = insert(best_score).values(**score_data)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**score_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_playlogs(self, aime_id: int) -> Optional[Row]:
|
||||
sql = select(playlog).where(playlog.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
|
||||
playlog_data["user"] = aime_id
|
||||
playlog_data = self.fix_bools(playlog_data)
|
||||
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**playlog_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -13,8 +13,12 @@ class ChuniSun(ChuniNewPlus):
|
|||
|
||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||
ret = super().handle_get_game_setting_api_request(data)
|
||||
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)["rom"]
|
||||
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)["data"]
|
||||
ret["gameSetting"]["romVersion"] = self.game_cfg.version.version(self.version)[
|
||||
"rom"
|
||||
]
|
||||
ret["gameSetting"]["dataVersion"] = self.game_cfg.version.version(self.version)[
|
||||
"data"
|
||||
]
|
||||
ret["gameSetting"][
|
||||
"matchingUri"
|
||||
] = f"http://{self.core_cfg.title.hostname}:{self.core_cfg.title.port}/SDHD/210/ChuniServlet/"
|
||||
|
|
|
@ -30,7 +30,7 @@ class CardMakerServlet:
|
|||
|
||||
self.versions = [
|
||||
CardMakerBase(core_cfg, self.game_cfg),
|
||||
CardMaker135(core_cfg, self.game_cfg)
|
||||
CardMaker135(core_cfg, self.game_cfg),
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("cardmaker")
|
||||
|
|
|
@ -4,6 +4,7 @@ import os
|
|||
import re
|
||||
import csv
|
||||
import xml.etree.ElementTree as ET
|
||||
from contextlib import nullcontext
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from read import BaseReader
|
||||
|
@ -78,10 +79,13 @@ class CardMakerReader(BaseReader):
|
|||
# ONGEKI (MU3) cnnot easily access the bin data(A000.pac)
|
||||
# so only opt_dir will work for now
|
||||
for dir in data_dirs:
|
||||
self.read_chuni_card(f"{dir}/CHU/card")
|
||||
self.read_chuni_gacha(f"{dir}/CHU/gacha")
|
||||
self.read_mai2_card(f"{dir}/MAI/card")
|
||||
self.read_ongeki_gacha(f"{dir}/MU3/gacha")
|
||||
with self.chuni_data.static.conn.begin() or nullcontext():
|
||||
self.read_chuni_card(f"{dir}/CHU/card")
|
||||
self.read_chuni_gacha(f"{dir}/CHU/gacha")
|
||||
with self.mai2_data.static.conn.begin() or nullcontext():
|
||||
self.read_mai2_card(f"{dir}/MAI/card")
|
||||
with self.ongeki_data.static.conn.begin() or nullcontext():
|
||||
self.read_ongeki_gacha(f"{dir}/MU3/gacha")
|
||||
|
||||
def read_chuni_card(self, base_dir: str) -> None:
|
||||
self.logger.info(f"Reading cards from {base_dir}...")
|
||||
|
|
|
@ -1 +1 @@
|
|||
__all__ = []
|
||||
__all__ = []
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import logging
|
||||
import json
|
||||
from decimal import Decimal
|
||||
from contextlib import nullcontext
|
||||
from base64 import b64encode
|
||||
from typing import Any, Dict, List
|
||||
from hashlib import md5
|
||||
|
@ -13,6 +14,7 @@ from titles.cxb.database import CxbData
|
|||
|
||||
from threading import Thread
|
||||
|
||||
|
||||
class CxbBase:
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: CxbConfig) -> None:
|
||||
self.config = cfg # Config file
|
||||
|
@ -255,8 +257,12 @@ class CxbBase:
|
|||
|
||||
# Async threads to generate the response
|
||||
thread_Coupon = Thread(target=CxbBase.task_generateCoupon(index, data1))
|
||||
thread_ShopListTitle = Thread(target=CxbBase.task_generateShopListTitle(index, data1))
|
||||
thread_ShopListIcon = Thread(target=CxbBase.task_generateShopListIcon(index, data1))
|
||||
thread_ShopListTitle = Thread(
|
||||
target=CxbBase.task_generateShopListTitle(index, data1)
|
||||
)
|
||||
thread_ShopListIcon = Thread(
|
||||
target=CxbBase.task_generateShopListIcon(index, data1)
|
||||
)
|
||||
thread_Stories = Thread(target=CxbBase.task_generateStories(index, data1))
|
||||
|
||||
thread_Coupon.start()
|
||||
|
@ -270,11 +276,15 @@ class CxbBase:
|
|||
thread_Stories.join()
|
||||
|
||||
for song in songs:
|
||||
thread_ScoreData = Thread(target=CxbBase.task_generateScoreData(song, index, data1))
|
||||
thread_ScoreData = Thread(
|
||||
target=CxbBase.task_generateScoreData(song, index, data1)
|
||||
)
|
||||
thread_ScoreData.start()
|
||||
|
||||
for v in index:
|
||||
thread_IndexData = Thread(target=CxbBase.task_generateIndexData(versionindex))
|
||||
thread_IndexData = Thread(
|
||||
target=CxbBase.task_generateIndexData(versionindex)
|
||||
)
|
||||
thread_IndexData.start()
|
||||
|
||||
return {"index": index, "data": data1, "version": versionindex}
|
||||
|
@ -295,50 +305,54 @@ class CxbBase:
|
|||
)
|
||||
# Alright.... time to bring the jank code
|
||||
|
||||
for value in data["saveindex"]["data"]:
|
||||
if "playedUserId" in value[1]:
|
||||
self.data.profile.put_profile(
|
||||
data["saveindex"]["uid"], self.version, value[0], value[1]
|
||||
)
|
||||
if "mcode" not in value[1]:
|
||||
self.data.profile.put_profile(
|
||||
data["saveindex"]["uid"], self.version, value[0], value[1]
|
||||
)
|
||||
if "shopId" in value:
|
||||
continue
|
||||
if "mcode" in value[1] and "musicState" in value[1]:
|
||||
song_json = json.loads(value[1])
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext()
|
||||
):
|
||||
for value in data["saveindex"]["data"]:
|
||||
if "playedUserId" in value[1]:
|
||||
self.data.profile.put_profile(
|
||||
data["saveindex"]["uid"], self.version, value[0], value[1]
|
||||
)
|
||||
if "mcode" not in value[1]:
|
||||
self.data.profile.put_profile(
|
||||
data["saveindex"]["uid"], self.version, value[0], value[1]
|
||||
)
|
||||
if "shopId" in value:
|
||||
continue
|
||||
if "mcode" in value[1] and "musicState" in value[1]:
|
||||
song_json = json.loads(value[1])
|
||||
|
||||
songCode = []
|
||||
songCode.append(
|
||||
{
|
||||
"mcode": song_json["mcode"],
|
||||
"musicState": song_json["musicState"],
|
||||
"playCount": song_json["playCount"],
|
||||
"totalScore": song_json["totalScore"],
|
||||
"highScore": song_json["highScore"],
|
||||
"clearRate": song_json["clearRate"],
|
||||
"rankPoint": song_json["rankPoint"],
|
||||
"combo": song_json["combo"],
|
||||
"coupleUserId": song_json["coupleUserId"],
|
||||
"difficulty": song_json["difficulty"],
|
||||
"isFullCombo": song_json["isFullCombo"],
|
||||
"clearGaugeType": song_json["clearGaugeType"],
|
||||
"fieldType": song_json["fieldType"],
|
||||
"gameType": song_json["gameType"],
|
||||
"grade": song_json["grade"],
|
||||
"unlockState": song_json["unlockState"],
|
||||
"extraState": song_json["extraState"],
|
||||
"index": value[0],
|
||||
}
|
||||
)
|
||||
self.data.score.put_best_score(
|
||||
data["saveindex"]["uid"],
|
||||
song_json["mcode"],
|
||||
self.version,
|
||||
value[0],
|
||||
songCode[0],
|
||||
)
|
||||
songCode = []
|
||||
songCode.append(
|
||||
{
|
||||
"mcode": song_json["mcode"],
|
||||
"musicState": song_json["musicState"],
|
||||
"playCount": song_json["playCount"],
|
||||
"totalScore": song_json["totalScore"],
|
||||
"highScore": song_json["highScore"],
|
||||
"clearRate": song_json["clearRate"],
|
||||
"rankPoint": song_json["rankPoint"],
|
||||
"combo": song_json["combo"],
|
||||
"coupleUserId": song_json["coupleUserId"],
|
||||
"difficulty": song_json["difficulty"],
|
||||
"isFullCombo": song_json["isFullCombo"],
|
||||
"clearGaugeType": song_json["clearGaugeType"],
|
||||
"fieldType": song_json["fieldType"],
|
||||
"gameType": song_json["gameType"],
|
||||
"grade": song_json["grade"],
|
||||
"unlockState": song_json["unlockState"],
|
||||
"extraState": song_json["extraState"],
|
||||
"index": value[0],
|
||||
}
|
||||
)
|
||||
self.data.score.put_best_score(
|
||||
data["saveindex"]["uid"],
|
||||
song_json["mcode"],
|
||||
self.version,
|
||||
value[0],
|
||||
songCode[0],
|
||||
)
|
||||
return {}
|
||||
else:
|
||||
self.logger.info(
|
||||
|
@ -355,41 +369,42 @@ class CxbBase:
|
|||
aimeId = profile["aimeId"]
|
||||
i = 0
|
||||
|
||||
for index, value in enumerate(data["saveindex"]["data"]):
|
||||
if int(data["saveindex"]["index"][index]) == 101:
|
||||
self.data.profile.put_profile(
|
||||
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||
)
|
||||
if (
|
||||
int(data["saveindex"]["index"][index]) >= 700000
|
||||
and int(data["saveindex"]["index"][index]) <= 701000
|
||||
):
|
||||
self.data.profile.put_profile(
|
||||
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||
)
|
||||
if (
|
||||
int(data["saveindex"]["index"][index]) >= 500
|
||||
and int(data["saveindex"]["index"][index]) <= 510
|
||||
):
|
||||
self.data.profile.put_profile(
|
||||
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||
)
|
||||
if "playedUserId" in value:
|
||||
self.data.profile.put_profile(
|
||||
aimeId,
|
||||
self.version,
|
||||
data["saveindex"]["index"][index],
|
||||
json.loads(value),
|
||||
)
|
||||
if "mcode" not in value and "normalCR" not in value:
|
||||
self.data.profile.put_profile(
|
||||
aimeId,
|
||||
self.version,
|
||||
data["saveindex"]["index"][index],
|
||||
json.loads(value),
|
||||
)
|
||||
if "shopId" in value:
|
||||
continue
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
for index, value in enumerate(data["saveindex"]["data"]):
|
||||
if int(data["saveindex"]["index"][index]) == 101:
|
||||
self.data.profile.put_profile(
|
||||
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||
)
|
||||
if (
|
||||
int(data["saveindex"]["index"][index]) >= 700000
|
||||
and int(data["saveindex"]["index"][index]) <= 701000
|
||||
):
|
||||
self.data.profile.put_profile(
|
||||
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||
)
|
||||
if (
|
||||
int(data["saveindex"]["index"][index]) >= 500
|
||||
and int(data["saveindex"]["index"][index]) <= 510
|
||||
):
|
||||
self.data.profile.put_profile(
|
||||
aimeId, self.version, data["saveindex"]["index"][index], value
|
||||
)
|
||||
if "playedUserId" in value:
|
||||
self.data.profile.put_profile(
|
||||
aimeId,
|
||||
self.version,
|
||||
data["saveindex"]["index"][index],
|
||||
json.loads(value),
|
||||
)
|
||||
if "mcode" not in value and "normalCR" not in value:
|
||||
self.data.profile.put_profile(
|
||||
aimeId,
|
||||
self.version,
|
||||
data["saveindex"]["index"][index],
|
||||
json.loads(value),
|
||||
)
|
||||
if "shopId" in value:
|
||||
continue
|
||||
|
||||
# MusicList Index for the profile
|
||||
indexSongList = []
|
||||
|
@ -397,47 +412,48 @@ class CxbBase:
|
|||
if int(value) in range(100000, 110000):
|
||||
indexSongList.append(value)
|
||||
|
||||
for index, value in enumerate(data["saveindex"]["data"]):
|
||||
if "mcode" not in value:
|
||||
continue
|
||||
if "playedUserId" in value:
|
||||
continue
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
for index, value in enumerate(data["saveindex"]["data"]):
|
||||
if "mcode" not in value:
|
||||
continue
|
||||
if "playedUserId" in value:
|
||||
continue
|
||||
|
||||
data1 = json.loads(value)
|
||||
data1 = json.loads(value)
|
||||
|
||||
songCode = []
|
||||
songCode.append(
|
||||
{
|
||||
"mcode": data1["mcode"],
|
||||
"musicState": data1["musicState"],
|
||||
"playCount": data1["playCount"],
|
||||
"totalScore": data1["totalScore"],
|
||||
"highScore": data1["highScore"],
|
||||
"everHighScore": data1["everHighScore"],
|
||||
"clearRate": data1["clearRate"],
|
||||
"rankPoint": data1["rankPoint"],
|
||||
"normalCR": data1["normalCR"],
|
||||
"survivalCR": data1["survivalCR"],
|
||||
"ultimateCR": data1["ultimateCR"],
|
||||
"nohopeCR": data1["nohopeCR"],
|
||||
"combo": data1["combo"],
|
||||
"coupleUserId": data1["coupleUserId"],
|
||||
"difficulty": data1["difficulty"],
|
||||
"isFullCombo": data1["isFullCombo"],
|
||||
"clearGaugeType": data1["clearGaugeType"],
|
||||
"fieldType": data1["fieldType"],
|
||||
"gameType": data1["gameType"],
|
||||
"grade": data1["grade"],
|
||||
"unlockState": data1["unlockState"],
|
||||
"extraState": data1["extraState"],
|
||||
"index": indexSongList[i],
|
||||
}
|
||||
)
|
||||
songCode = []
|
||||
songCode.append(
|
||||
{
|
||||
"mcode": data1["mcode"],
|
||||
"musicState": data1["musicState"],
|
||||
"playCount": data1["playCount"],
|
||||
"totalScore": data1["totalScore"],
|
||||
"highScore": data1["highScore"],
|
||||
"everHighScore": data1["everHighScore"],
|
||||
"clearRate": data1["clearRate"],
|
||||
"rankPoint": data1["rankPoint"],
|
||||
"normalCR": data1["normalCR"],
|
||||
"survivalCR": data1["survivalCR"],
|
||||
"ultimateCR": data1["ultimateCR"],
|
||||
"nohopeCR": data1["nohopeCR"],
|
||||
"combo": data1["combo"],
|
||||
"coupleUserId": data1["coupleUserId"],
|
||||
"difficulty": data1["difficulty"],
|
||||
"isFullCombo": data1["isFullCombo"],
|
||||
"clearGaugeType": data1["clearGaugeType"],
|
||||
"fieldType": data1["fieldType"],
|
||||
"gameType": data1["gameType"],
|
||||
"grade": data1["grade"],
|
||||
"unlockState": data1["unlockState"],
|
||||
"extraState": data1["extraState"],
|
||||
"index": indexSongList[i],
|
||||
}
|
||||
)
|
||||
|
||||
self.data.score.put_best_score(
|
||||
aimeId, data1["mcode"], self.version, indexSongList[i], songCode[0]
|
||||
)
|
||||
i += 1
|
||||
self.data.score.put_best_score(
|
||||
aimeId, data1["mcode"], self.version, indexSongList[i], songCode[0]
|
||||
)
|
||||
i += 1
|
||||
return {}
|
||||
|
||||
def handle_action_sprankreq_request(self, data: Dict) -> Dict:
|
||||
|
@ -485,43 +501,44 @@ class CxbBase:
|
|||
uid = data["rankreg"]["uid"]
|
||||
self.logger.info(f"Put {len(data['rankreg']['data'])} rankings for {uid}")
|
||||
|
||||
for rid in data["rankreg"]["data"]:
|
||||
# REV S2
|
||||
if "clear" in rid:
|
||||
try:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=int(rid["sc"][1]),
|
||||
score=int(rid["sc"][0]),
|
||||
clear=rid["clear"],
|
||||
)
|
||||
except Exception:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=0,
|
||||
score=int(rid["sc"][0]),
|
||||
clear=rid["clear"],
|
||||
)
|
||||
# REV
|
||||
else:
|
||||
try:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=int(rid["sc"][1]),
|
||||
score=int(rid["sc"][0]),
|
||||
clear=0,
|
||||
)
|
||||
except Exception:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=0,
|
||||
score=int(rid["sc"][0]),
|
||||
clear=0,
|
||||
)
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
for rid in data["rankreg"]["data"]:
|
||||
# REV S2
|
||||
if "clear" in rid:
|
||||
try:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=int(rid["sc"][1]),
|
||||
score=int(rid["sc"][0]),
|
||||
clear=rid["clear"],
|
||||
)
|
||||
except Exception:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=0,
|
||||
score=int(rid["sc"][0]),
|
||||
clear=rid["clear"],
|
||||
)
|
||||
# REV
|
||||
else:
|
||||
try:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=int(rid["sc"][1]),
|
||||
score=int(rid["sc"][0]),
|
||||
clear=0,
|
||||
)
|
||||
except Exception:
|
||||
self.data.score.put_ranking(
|
||||
user_id=uid,
|
||||
rev_id=int(rid["rid"]),
|
||||
song_id=0,
|
||||
score=int(rid["sc"][0]),
|
||||
clear=0,
|
||||
)
|
||||
return {}
|
||||
|
||||
def handle_action_addenergy_request(self, data: Dict) -> Dict:
|
||||
|
@ -532,20 +549,21 @@ class CxbBase:
|
|||
p = self.data.item.get_energy(uid)
|
||||
energy = p["energy"]
|
||||
|
||||
if not p:
|
||||
self.data.item.put_energy(uid, 5)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if not p:
|
||||
self.data.item.put_energy(uid, 5)
|
||||
|
||||
return {
|
||||
"class": data1["myClass"],
|
||||
"granted": "5",
|
||||
"total": "5",
|
||||
"threshold": "1000",
|
||||
}
|
||||
return {
|
||||
"class": data1["myClass"],
|
||||
"granted": "5",
|
||||
"total": "5",
|
||||
"threshold": "1000",
|
||||
}
|
||||
|
||||
array = []
|
||||
array = []
|
||||
|
||||
newenergy = int(energy) + 5
|
||||
self.data.item.put_energy(uid, newenergy)
|
||||
newenergy = int(energy) + 5
|
||||
self.data.item.put_energy(uid, newenergy)
|
||||
|
||||
if int(energy) <= 995:
|
||||
array.append(
|
||||
|
@ -573,4 +591,4 @@ class CxbBase:
|
|||
|
||||
def handle_action_stampreq_request(self, data: Dict) -> Dict:
|
||||
self.logger.info(data)
|
||||
return {"stampreq": ""}
|
||||
return {"stampreq": ""}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from typing import Optional, Dict, List
|
||||
from os import walk, path
|
||||
from contextlib import nullcontext
|
||||
import urllib
|
||||
import csv
|
||||
|
||||
|
@ -37,7 +38,8 @@ class CxbReader(BaseReader):
|
|||
pull_bin_ram = False
|
||||
|
||||
if pull_bin_ram:
|
||||
self.read_csv(f"{self.bin_dir}")
|
||||
with self.data.static.conn.begin() or nullcontext():
|
||||
self.read_csv(f"{self.bin_dir}")
|
||||
|
||||
def read_csv(self, bin_dir: str) -> None:
|
||||
self.logger.info(f"Read csv from {bin_dir}")
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from titles.cxb.schema.profile import CxbProfileData
|
||||
from titles.cxb.schema.score import CxbScoreData
|
||||
from titles.cxb.schema.item import CxbItemData
|
||||
from titles.cxb.schema.static import CxbStaticData
|
||||
|
||||
__all__ = [CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData]
|
||||
from titles.cxb.schema.profile import CxbProfileData
|
||||
from titles.cxb.schema.score import CxbScoreData
|
||||
from titles.cxb.schema.item import CxbItemData
|
||||
from titles.cxb.schema.static import CxbStaticData
|
||||
|
||||
__all__ = [CxbProfileData, CxbScoreData, CxbItemData, CxbStaticData]
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
energy = Table(
|
||||
"cxb_rev_energy",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("energy", Integer, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", name="cxb_rev_energy_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbItemData(BaseData):
|
||||
def put_energy(self, user_id: int, rev_energy: int) -> Optional[int]:
|
||||
sql = insert(energy).values(user=user_id, energy=rev_energy)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(energy=rev_energy)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert item! user: {user_id}, energy: {rev_energy}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_energy(self, user_id: int) -> Optional[Dict]:
|
||||
sql = energy.select(and_(energy.c.user == user_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
energy = Table(
|
||||
"cxb_rev_energy",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("energy", Integer, nullable=False, server_default="0"),
|
||||
UniqueConstraint("user", name="cxb_rev_energy_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbItemData(BaseData):
|
||||
def put_energy(self, user_id: int, rev_energy: int) -> Optional[int]:
|
||||
sql = insert(energy).values(user=user_id, energy=rev_energy)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(energy=rev_energy))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert item! user: {user_id}, energy: {rev_energy}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_energy(self, user_id: int) -> Optional[Dict]:
|
||||
sql = energy.select(and_(energy.c.user == user_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -1,78 +1,78 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
profile = Table(
|
||||
"cxb_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("index", Integer, nullable=False),
|
||||
Column("data", JSON, nullable=False),
|
||||
UniqueConstraint("user", "index", name="cxb_profile_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbProfileData(BaseData):
|
||||
def put_profile(
|
||||
self, user_id: int, version: int, index: int, data: JSON
|
||||
) -> Optional[int]:
|
||||
sql = insert(profile).values(
|
||||
user=user_id, version=version, index=index, data=data
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(index=index, data=data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to update! user: {user_id}, index: {index}, data: {data}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
sql = profile.select(
|
||||
and_(profile.c.version == version, profile.c.user == aime_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_profile_index(
|
||||
self, index: int, aime_id: int = None, version: int = None
|
||||
) -> Optional[Dict]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
if aime_id is not None and version is not None and index is not None:
|
||||
sql = profile.select(
|
||||
and_(
|
||||
profile.c.version == version,
|
||||
profile.c.user == aime_id,
|
||||
profile.c.index == index,
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.logger.error(
|
||||
f"get_profile: Bad arguments!! aime_id {aime_id} version {version}"
|
||||
)
|
||||
return None
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
profile = Table(
|
||||
"cxb_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("index", Integer, nullable=False),
|
||||
Column("data", JSON, nullable=False),
|
||||
UniqueConstraint("user", "index", name="cxb_profile_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbProfileData(BaseData):
|
||||
def put_profile(
|
||||
self, user_id: int, version: int, index: int, data: JSON
|
||||
) -> Optional[int]:
|
||||
sql = insert(profile).values(
|
||||
user=user_id, version=version, index=index, data=data
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(index=index, data=data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to update! user: {user_id}, index: {index}, data: {data}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
sql = profile.select(
|
||||
and_(profile.c.version == version, profile.c.user == aime_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_profile_index(
|
||||
self, index: int, aime_id: int = None, version: int = None
|
||||
) -> Optional[Dict]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
if aime_id is not None and version is not None and index is not None:
|
||||
sql = profile.select(
|
||||
and_(
|
||||
profile.c.version == version,
|
||||
profile.c.user == aime_id,
|
||||
profile.c.index == index,
|
||||
)
|
||||
)
|
||||
else:
|
||||
self.logger.error(
|
||||
f"get_profile: Bad arguments!! aime_id {aime_id} version {version}"
|
||||
)
|
||||
return None
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -1,187 +1,187 @@
|
|||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
score = Table(
|
||||
"cxb_score",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("game_version", Integer),
|
||||
Column("song_mcode", String(7)),
|
||||
Column("song_index", Integer),
|
||||
Column("data", JSON),
|
||||
UniqueConstraint("user", "song_mcode", "song_index", name="cxb_score_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"cxb_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("song_mcode", String(7)),
|
||||
Column("chart_id", Integer),
|
||||
Column("score", Integer),
|
||||
Column("clear", Integer),
|
||||
Column("flawless", Integer),
|
||||
Column("super", Integer),
|
||||
Column("cool", Integer),
|
||||
Column("fast", Integer),
|
||||
Column("fast2", Integer),
|
||||
Column("slow", Integer),
|
||||
Column("slow2", Integer),
|
||||
Column("fail", Integer),
|
||||
Column("combo", Integer),
|
||||
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
ranking = Table(
|
||||
"cxb_ranking",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("rev_id", Integer),
|
||||
Column("song_id", Integer),
|
||||
Column("score", Integer),
|
||||
Column("clear", Integer),
|
||||
UniqueConstraint("user", "rev_id", name="cxb_ranking_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbScoreData(BaseData):
|
||||
def put_best_score(
|
||||
self,
|
||||
user_id: int,
|
||||
song_mcode: str,
|
||||
game_version: int,
|
||||
song_index: int,
|
||||
data: JSON,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Update the user's best score for a chart
|
||||
"""
|
||||
sql = insert(score).values(
|
||||
user=user_id,
|
||||
song_mcode=song_mcode,
|
||||
game_version=game_version,
|
||||
song_index=song_index,
|
||||
data=data,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(data=sql.inserted.data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert best score! profile: {user_id}, song: {song_mcode}, data: {data}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(
|
||||
self,
|
||||
user_id: int,
|
||||
song_mcode: str,
|
||||
chart_id: int,
|
||||
score: int,
|
||||
clear: int,
|
||||
flawless: int,
|
||||
this_super: int,
|
||||
cool: int,
|
||||
this_fast: int,
|
||||
this_fast2: int,
|
||||
this_slow: int,
|
||||
this_slow2: int,
|
||||
fail: int,
|
||||
combo: int,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's play log
|
||||
"""
|
||||
sql = playlog.insert().values(
|
||||
user=user_id,
|
||||
song_mcode=song_mcode,
|
||||
chart_id=chart_id,
|
||||
score=score,
|
||||
clear=clear,
|
||||
flawless=flawless,
|
||||
super=this_super,
|
||||
cool=cool,
|
||||
fast=this_fast,
|
||||
fast2=this_fast2,
|
||||
slow=this_slow,
|
||||
slow2=this_slow2,
|
||||
fail=fail,
|
||||
combo=combo,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_mcode}, chart: {chart_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_ranking(
|
||||
self, user_id: int, rev_id: int, song_id: int, score: int, clear: int
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's ranking logs
|
||||
"""
|
||||
if song_id == 0:
|
||||
sql = insert(ranking).values(
|
||||
user=user_id, rev_id=rev_id, score=score, clear=clear
|
||||
)
|
||||
else:
|
||||
sql = insert(ranking).values(
|
||||
user=user_id, rev_id=rev_id, song_id=song_id, score=score, clear=clear
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(score=score)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert ranking log! profile: {user_id}, score: {score}, clear: {clear}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_score(self, user_id: int, song_mcode: int) -> Optional[Dict]:
|
||||
sql = score.select(
|
||||
and_(score.c.user == user_id, score.c.song_mcode == song_mcode)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_best_scores(self, user_id: int) -> Optional[Dict]:
|
||||
sql = score.select(score.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_rankings(self, user_id: int) -> Optional[List[Dict]]:
|
||||
sql = ranking.select(ranking.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
score = Table(
|
||||
"cxb_score",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("game_version", Integer),
|
||||
Column("song_mcode", String(7)),
|
||||
Column("song_index", Integer),
|
||||
Column("data", JSON),
|
||||
UniqueConstraint("user", "song_mcode", "song_index", name="cxb_score_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"cxb_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("song_mcode", String(7)),
|
||||
Column("chart_id", Integer),
|
||||
Column("score", Integer),
|
||||
Column("clear", Integer),
|
||||
Column("flawless", Integer),
|
||||
Column("super", Integer),
|
||||
Column("cool", Integer),
|
||||
Column("fast", Integer),
|
||||
Column("fast2", Integer),
|
||||
Column("slow", Integer),
|
||||
Column("slow2", Integer),
|
||||
Column("fail", Integer),
|
||||
Column("combo", Integer),
|
||||
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
ranking = Table(
|
||||
"cxb_ranking",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("rev_id", Integer),
|
||||
Column("song_id", Integer),
|
||||
Column("score", Integer),
|
||||
Column("clear", Integer),
|
||||
UniqueConstraint("user", "rev_id", name="cxb_ranking_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbScoreData(BaseData):
|
||||
def put_best_score(
|
||||
self,
|
||||
user_id: int,
|
||||
song_mcode: str,
|
||||
game_version: int,
|
||||
song_index: int,
|
||||
data: JSON,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Update the user's best score for a chart
|
||||
"""
|
||||
sql = insert(score).values(
|
||||
user=user_id,
|
||||
song_mcode=song_mcode,
|
||||
game_version=game_version,
|
||||
song_index=song_index,
|
||||
data=data,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(data=sql.inserted.data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert best score! profile: {user_id}, song: {song_mcode}, data: {data}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(
|
||||
self,
|
||||
user_id: int,
|
||||
song_mcode: str,
|
||||
chart_id: int,
|
||||
score: int,
|
||||
clear: int,
|
||||
flawless: int,
|
||||
this_super: int,
|
||||
cool: int,
|
||||
this_fast: int,
|
||||
this_fast2: int,
|
||||
this_slow: int,
|
||||
this_slow2: int,
|
||||
fail: int,
|
||||
combo: int,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's play log
|
||||
"""
|
||||
sql = playlog.insert().values(
|
||||
user=user_id,
|
||||
song_mcode=song_mcode,
|
||||
chart_id=chart_id,
|
||||
score=score,
|
||||
clear=clear,
|
||||
flawless=flawless,
|
||||
super=this_super,
|
||||
cool=cool,
|
||||
fast=this_fast,
|
||||
fast2=this_fast2,
|
||||
slow=this_slow,
|
||||
slow2=this_slow2,
|
||||
fail=fail,
|
||||
combo=combo,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_mcode}, chart: {chart_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_ranking(
|
||||
self, user_id: int, rev_id: int, song_id: int, score: int, clear: int
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's ranking logs
|
||||
"""
|
||||
if song_id == 0:
|
||||
sql = insert(ranking).values(
|
||||
user=user_id, rev_id=rev_id, score=score, clear=clear
|
||||
)
|
||||
else:
|
||||
sql = insert(ranking).values(
|
||||
user=user_id, rev_id=rev_id, song_id=song_id, score=score, clear=clear
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(score=score))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert ranking log! profile: {user_id}, score: {score}, clear: {clear}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_score(self, user_id: int, song_mcode: int) -> Optional[Dict]:
|
||||
sql = score.select(
|
||||
and_(score.c.user == user_id, score.c.song_mcode == song_mcode)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_best_scores(self, user_id: int) -> Optional[Dict]:
|
||||
sql = score.select(score.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_rankings(self, user_id: int) -> Optional[List[Dict]]:
|
||||
sql = ranking.select(ranking.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
|
|
@ -1,95 +1,95 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
music = Table(
|
||||
"cxb_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", String(255)),
|
||||
Column("index", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("category", String(255)),
|
||||
Column("level", Float),
|
||||
UniqueConstraint(
|
||||
"version", "songId", "chartId", "index", name="cxb_static_music_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbStaticData(BaseData):
|
||||
def put_music(
|
||||
self,
|
||||
version: int,
|
||||
mcode: str,
|
||||
index: int,
|
||||
chart: int,
|
||||
title: str,
|
||||
artist: str,
|
||||
category: str,
|
||||
level: float,
|
||||
) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=mcode,
|
||||
index=index,
|
||||
chartId=chart,
|
||||
title=title,
|
||||
artist=artist,
|
||||
category=category,
|
||||
level=level,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
title=title, artist=artist, category=category, level=level
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_music(
|
||||
self, version: int, song_id: Optional[int] = None
|
||||
) -> Optional[List[Row]]:
|
||||
if song_id is None:
|
||||
sql = select(music).where(music.c.version == version)
|
||||
else:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
music = Table(
|
||||
"cxb_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", String(255)),
|
||||
Column("index", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("category", String(255)),
|
||||
Column("level", Float),
|
||||
UniqueConstraint(
|
||||
"version", "songId", "chartId", "index", name="cxb_static_music_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class CxbStaticData(BaseData):
|
||||
def put_music(
|
||||
self,
|
||||
version: int,
|
||||
mcode: str,
|
||||
index: int,
|
||||
chart: int,
|
||||
title: str,
|
||||
artist: str,
|
||||
category: str,
|
||||
level: float,
|
||||
) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=mcode,
|
||||
index=index,
|
||||
chartId=chart,
|
||||
title=title,
|
||||
artist=artist,
|
||||
category=category,
|
||||
level=level,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(title=title, artist=artist, category=category, level=level)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_music(
|
||||
self, version: int, song_id: Optional[int] = None
|
||||
) -> Optional[List[Row]]:
|
||||
if song_id is None:
|
||||
sql = select(music).where(music.c.version == version)
|
||||
else:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -3,6 +3,7 @@ from typing import Any, List, Dict
|
|||
import logging
|
||||
import json
|
||||
import urllib
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.diva.config import DivaConfig
|
||||
|
@ -735,102 +736,103 @@ class DivaBase:
|
|||
pd_song_worst_cnt = data["stg_wt_wg_cnt"].split(",")
|
||||
pd_song_max_combo = data["stg_max_cmb"].split(",")
|
||||
|
||||
for index, value in enumerate(pd_song_list):
|
||||
if "-1" not in pd_song_list[index]:
|
||||
profile_pd_db_song = self.data.score.get_best_user_score(
|
||||
data["pd_id"],
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
)
|
||||
if profile_pd_db_song is None:
|
||||
self.data.score.put_best_score(
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
for index, value in enumerate(pd_song_list):
|
||||
if "-1" not in pd_song_list[index]:
|
||||
profile_pd_db_song = self.data.score.get_best_user_score(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
self.data.score.put_playlog(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
elif int(pd_song_max_score[index]) >= int(profile_pd_db_song["score"]):
|
||||
self.data.score.put_best_score(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
self.data.score.put_playlog(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
elif int(pd_song_max_score[index]) != int(profile_pd_db_song["score"]):
|
||||
self.data.score.put_playlog(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
if profile_pd_db_song is None:
|
||||
self.data.score.put_best_score(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
self.data.score.put_playlog(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
elif int(pd_song_max_score[index]) >= int(profile_pd_db_song["score"]):
|
||||
self.data.score.put_best_score(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
self.data.score.put_playlog(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
elif int(pd_song_max_score[index]) != int(profile_pd_db_song["score"]):
|
||||
self.data.score.put_playlog(
|
||||
data["pd_id"],
|
||||
self.version,
|
||||
pd_song_list[index],
|
||||
pd_song_difficulty[index],
|
||||
pd_song_edition[index],
|
||||
pd_song_max_score[index],
|
||||
pd_song_max_atn_pnt[index],
|
||||
pd_song_ranking[index],
|
||||
pd_song_sort_kind,
|
||||
pd_song_cool_cnt[index],
|
||||
pd_song_fine_cnt[index],
|
||||
pd_song_safe_cnt[index],
|
||||
pd_song_sad_cnt[index],
|
||||
pd_song_worst_cnt[index],
|
||||
pd_song_max_combo[index],
|
||||
)
|
||||
|
||||
# Profile saving based on registration list
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Optional, Dict, List
|
||||
from os import walk, path
|
||||
import urllib
|
||||
from contextlib import nullcontext
|
||||
|
||||
from read import BaseReader
|
||||
from core.config import CoreConfig
|
||||
|
@ -47,13 +48,14 @@ class DivaReader(BaseReader):
|
|||
pull_opt_rom = False
|
||||
self.logger.warn("No option directory specified, skipping")
|
||||
|
||||
if pull_bin_ram:
|
||||
self.read_ram(f"{self.bin_dir}/ram")
|
||||
if pull_bin_rom:
|
||||
self.read_rom(f"{self.bin_dir}/rom")
|
||||
if pull_opt_rom:
|
||||
for dir in opt_dirs:
|
||||
self.read_rom(f"{dir}/rom")
|
||||
with self.data.static.conn.begin() or nullcontext():
|
||||
if pull_bin_ram:
|
||||
self.read_ram(f"{self.bin_dir}/ram")
|
||||
if pull_bin_rom:
|
||||
self.read_rom(f"{self.bin_dir}/rom")
|
||||
if pull_opt_rom:
|
||||
for dir in opt_dirs:
|
||||
self.read_rom(f"{dir}/rom")
|
||||
|
||||
def read_ram(self, ram_root_dir: str) -> None:
|
||||
self.logger.info(f"Read RAM from {ram_root_dir}")
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
from titles.diva.schema.profile import DivaProfileData
|
||||
from titles.diva.schema.score import DivaScoreData
|
||||
from titles.diva.schema.module import DivaModuleData
|
||||
from titles.diva.schema.customize import DivaCustomizeItemData
|
||||
from titles.diva.schema.pv_customize import DivaPvCustomizeData
|
||||
from titles.diva.schema.item import DivaItemData
|
||||
from titles.diva.schema.static import DivaStaticData
|
||||
|
||||
__all__ = [
|
||||
DivaProfileData,
|
||||
DivaScoreData,
|
||||
DivaModuleData,
|
||||
DivaCustomizeItemData,
|
||||
DivaPvCustomizeData,
|
||||
DivaItemData,
|
||||
DivaStaticData,
|
||||
]
|
||||
from titles.diva.schema.profile import DivaProfileData
|
||||
from titles.diva.schema.score import DivaScoreData
|
||||
from titles.diva.schema.module import DivaModuleData
|
||||
from titles.diva.schema.customize import DivaCustomizeItemData
|
||||
from titles.diva.schema.pv_customize import DivaPvCustomizeData
|
||||
from titles.diva.schema.item import DivaItemData
|
||||
from titles.diva.schema.static import DivaStaticData
|
||||
|
||||
__all__ = [
|
||||
DivaProfileData,
|
||||
DivaScoreData,
|
||||
DivaModuleData,
|
||||
DivaCustomizeItemData,
|
||||
DivaPvCustomizeData,
|
||||
DivaItemData,
|
||||
DivaStaticData,
|
||||
]
|
||||
|
|
|
@ -1,66 +1,66 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, and_
|
||||
from sqlalchemy.types import Integer
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
customize = Table(
|
||||
"diva_profile_customize_item",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("item_id", Integer, nullable=False),
|
||||
UniqueConstraint(
|
||||
"user", "version", "item_id", name="diva_profile_customize_item_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaCustomizeItemData(BaseData):
|
||||
def put_customize_item(self, aime_id: int, version: int, item_id: int) -> None:
|
||||
sql = insert(customize).values(version=version, user=aime_id, item_id=item_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile customize item! aime id: {aime_id} item: {item_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_customize_items(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and an aime id, return all the customize items, not used directly
|
||||
"""
|
||||
sql = customize.select(
|
||||
and_(customize.c.version == version, customize.c.user == aime_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_customize_items_have_string(self, aime_id: int, version: int) -> str:
|
||||
"""
|
||||
Given a game version and an aime id, return the cstmz_itm_have hex string
|
||||
required for diva directly
|
||||
"""
|
||||
items_list = self.get_customize_items(aime_id, version)
|
||||
if items_list is None:
|
||||
items_list = []
|
||||
item_have = 0
|
||||
|
||||
for item in items_list:
|
||||
item_have |= 1 << item["item_id"]
|
||||
|
||||
# convert the int to a 250 digit long hex string
|
||||
return "{0:0>250}".format(hex(item_have).upper()[2:])
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, and_
|
||||
from sqlalchemy.types import Integer
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
customize = Table(
|
||||
"diva_profile_customize_item",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("item_id", Integer, nullable=False),
|
||||
UniqueConstraint(
|
||||
"user", "version", "item_id", name="diva_profile_customize_item_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaCustomizeItemData(BaseData):
|
||||
def put_customize_item(self, aime_id: int, version: int, item_id: int) -> None:
|
||||
sql = insert(customize).values(version=version, user=aime_id, item_id=item_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile customize item! aime id: {aime_id} item: {item_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_customize_items(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and an aime id, return all the customize items, not used directly
|
||||
"""
|
||||
sql = customize.select(
|
||||
and_(customize.c.version == version, customize.c.user == aime_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_customize_items_have_string(self, aime_id: int, version: int) -> str:
|
||||
"""
|
||||
Given a game version and an aime id, return the cstmz_itm_have hex string
|
||||
required for diva directly
|
||||
"""
|
||||
items_list = self.get_customize_items(aime_id, version)
|
||||
if items_list is None:
|
||||
items_list = []
|
||||
item_have = 0
|
||||
|
||||
for item in items_list:
|
||||
item_have |= 1 << item["item_id"]
|
||||
|
||||
# convert the int to a 250 digit long hex string
|
||||
return "{0:0>250}".format(hex(item_have).upper()[2:])
|
||||
|
|
|
@ -1,68 +1,70 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
shop = Table(
|
||||
"diva_profile_shop",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("mdl_eqp_ary", String(32)),
|
||||
Column("c_itm_eqp_ary", String(59)),
|
||||
Column("ms_itm_flg_ary", String(59)),
|
||||
UniqueConstraint("user", "version", name="diva_profile_shop_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaItemData(BaseData):
|
||||
def put_shop(
|
||||
self,
|
||||
aime_id: int,
|
||||
version: int,
|
||||
mdl_eqp_ary: str,
|
||||
c_itm_eqp_ary: str,
|
||||
ms_itm_flg_ary: str,
|
||||
) -> None:
|
||||
sql = insert(shop).values(
|
||||
version=version,
|
||||
user=aime_id,
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile! aime id: {aime_id} array: {mdl_eqp_ary}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_shop(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
sql = shop.select(and_(shop.c.version == version, shop.c.user == aime_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
shop = Table(
|
||||
"diva_profile_shop",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("mdl_eqp_ary", String(32)),
|
||||
Column("c_itm_eqp_ary", String(59)),
|
||||
Column("ms_itm_flg_ary", String(59)),
|
||||
UniqueConstraint("user", "version", name="diva_profile_shop_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaItemData(BaseData):
|
||||
def put_shop(
|
||||
self,
|
||||
aime_id: int,
|
||||
version: int,
|
||||
mdl_eqp_ary: str,
|
||||
c_itm_eqp_ary: str,
|
||||
ms_itm_flg_ary: str,
|
||||
) -> None:
|
||||
sql = insert(shop).values(
|
||||
version=version,
|
||||
user=aime_id,
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile! aime id: {aime_id} array: {mdl_eqp_ary}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_shop(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
sql = shop.select(and_(shop.c.version == version, shop.c.user == aime_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -1,62 +1,62 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, and_
|
||||
from sqlalchemy.types import Integer
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
module = Table(
|
||||
"diva_profile_module",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("module_id", Integer, nullable=False),
|
||||
UniqueConstraint("user", "version", "module_id", name="diva_profile_module_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaModuleData(BaseData):
|
||||
def put_module(self, aime_id: int, version: int, module_id: int) -> None:
|
||||
sql = insert(module).values(version=version, user=aime_id, module_id=module_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile module! aime id: {aime_id} module: {module_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_modules(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and an aime id, return all the modules, not used directly
|
||||
"""
|
||||
sql = module.select(and_(module.c.version == version, module.c.user == aime_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_modules_have_string(self, aime_id: int, version: int) -> str:
|
||||
"""
|
||||
Given a game version and an aime id, return the mdl_have hex string
|
||||
required for diva directly
|
||||
"""
|
||||
module_list = self.get_modules(aime_id, version)
|
||||
if module_list is None:
|
||||
module_list = []
|
||||
module_have = 0
|
||||
|
||||
for module in module_list:
|
||||
module_have |= 1 << module["module_id"]
|
||||
|
||||
# convert the int to a 250 digit long hex string
|
||||
return "{0:0>250}".format(hex(module_have).upper()[2:])
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, and_
|
||||
from sqlalchemy.types import Integer
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
module = Table(
|
||||
"diva_profile_module",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("module_id", Integer, nullable=False),
|
||||
UniqueConstraint("user", "version", "module_id", name="diva_profile_module_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaModuleData(BaseData):
|
||||
def put_module(self, aime_id: int, version: int, module_id: int) -> None:
|
||||
sql = insert(module).values(version=version, user=aime_id, module_id=module_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile module! aime id: {aime_id} module: {module_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_modules(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and an aime id, return all the modules, not used directly
|
||||
"""
|
||||
sql = module.select(and_(module.c.version == version, module.c.user == aime_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_modules_have_string(self, aime_id: int, version: int) -> str:
|
||||
"""
|
||||
Given a game version and an aime id, return the mdl_have hex string
|
||||
required for diva directly
|
||||
"""
|
||||
module_list = self.get_modules(aime_id, version)
|
||||
if module_list is None:
|
||||
module_list = []
|
||||
module_have = 0
|
||||
|
||||
for module in module_list:
|
||||
module_have |= 1 << module["module_id"]
|
||||
|
||||
# convert the int to a 250 digit long hex string
|
||||
return "{0:0>250}".format(hex(module_have).upper()[2:])
|
||||
|
|
|
@ -1,117 +1,119 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
profile = Table(
|
||||
"diva_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("player_name", String(10), nullable=False),
|
||||
Column("lv_str", String(24), nullable=False, server_default="Dab on 'em"),
|
||||
Column("lv_num", Integer, nullable=False, server_default="0"),
|
||||
Column("lv_pnt", Integer, nullable=False, server_default="0"),
|
||||
Column("vcld_pts", Integer, nullable=False, server_default="0"),
|
||||
Column("hp_vol", Integer, nullable=False, server_default="100"),
|
||||
Column("btn_se_vol", Integer, nullable=False, server_default="100"),
|
||||
Column("btn_se_vol2", Integer, nullable=False, server_default="100"),
|
||||
Column("sldr_se_vol2", Integer, nullable=False, server_default="100"),
|
||||
Column("sort_kind", Integer, nullable=False, server_default="2"),
|
||||
Column("use_pv_mdl_eqp", Boolean, nullable=False, server_default="1"),
|
||||
Column("use_mdl_pri", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_skn_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_btn_se_eqp", Boolean, nullable=False, server_default="1"),
|
||||
Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_chn_sld_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_sldr_tch_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("btn_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("sld_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("chn_sld_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("sldr_tch_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("nxt_pv_id", Integer, nullable=False, server_default="708"),
|
||||
Column("nxt_dffclty", Integer, nullable=False, server_default="2"),
|
||||
Column("nxt_edtn", Integer, nullable=False, server_default="0"),
|
||||
Column("cnp_cid", Integer, nullable=False, server_default="-1"),
|
||||
Column("cnp_val", Integer, nullable=False, server_default="-1"),
|
||||
Column("cnp_rr", Integer, nullable=False, server_default="-1"),
|
||||
Column("cnp_sp", String(255), nullable=False, server_default=""),
|
||||
Column("dsp_clr_brdr", Integer, nullable=False, server_default="7"),
|
||||
Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"),
|
||||
Column("dsp_clr_sts", Integer, nullable=False, server_default="1"),
|
||||
Column("rgo_sts", Integer, nullable=False, server_default="1"),
|
||||
Column("lv_efct_id", Integer, nullable=False, server_default="0"),
|
||||
Column("lv_plt_id", Integer, nullable=False, server_default="1"),
|
||||
Column("passwd_stat", Integer, nullable=False, server_default="0"),
|
||||
Column("passwd", String(12), nullable=False, server_default="**********"),
|
||||
Column(
|
||||
"my_qst_id",
|
||||
String(128),
|
||||
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
|
||||
),
|
||||
Column(
|
||||
"my_qst_sts",
|
||||
String(128),
|
||||
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
|
||||
),
|
||||
UniqueConstraint("user", "version", name="diva_profile_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaProfileData(BaseData):
|
||||
def create_profile(
|
||||
self, version: int, aime_id: int, player_name: str
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Given a game version, aime id, and player_name, create a profile and return it's ID
|
||||
"""
|
||||
sql = insert(profile).values(
|
||||
version=version, user=aime_id, player_name=player_name
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(player_name=sql.inserted.player_name)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile! aime id: {aime_id} username: {player_name}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def update_profile(self, aime_id: int, **profile_args) -> None:
|
||||
"""
|
||||
Given an aime_id update the profile corresponding to the arguments
|
||||
which are the diva_profile Columns
|
||||
"""
|
||||
sql = profile.update(profile.c.user == aime_id).values(**profile_args)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"update_profile: failed to update profile! profile: {aime_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
sql = profile.select(
|
||||
and_(profile.c.version == version, profile.c.user == aime_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
profile = Table(
|
||||
"diva_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("player_name", String(10), nullable=False),
|
||||
Column("lv_str", String(24), nullable=False, server_default="Dab on 'em"),
|
||||
Column("lv_num", Integer, nullable=False, server_default="0"),
|
||||
Column("lv_pnt", Integer, nullable=False, server_default="0"),
|
||||
Column("vcld_pts", Integer, nullable=False, server_default="0"),
|
||||
Column("hp_vol", Integer, nullable=False, server_default="100"),
|
||||
Column("btn_se_vol", Integer, nullable=False, server_default="100"),
|
||||
Column("btn_se_vol2", Integer, nullable=False, server_default="100"),
|
||||
Column("sldr_se_vol2", Integer, nullable=False, server_default="100"),
|
||||
Column("sort_kind", Integer, nullable=False, server_default="2"),
|
||||
Column("use_pv_mdl_eqp", Boolean, nullable=False, server_default="1"),
|
||||
Column("use_mdl_pri", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_skn_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_btn_se_eqp", Boolean, nullable=False, server_default="1"),
|
||||
Column("use_pv_sld_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_chn_sld_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("use_pv_sldr_tch_se_eqp", Boolean, nullable=False, server_default="0"),
|
||||
Column("btn_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("sld_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("chn_sld_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("sldr_tch_se_eqp", Integer, nullable=False, server_default="-1"),
|
||||
Column("nxt_pv_id", Integer, nullable=False, server_default="708"),
|
||||
Column("nxt_dffclty", Integer, nullable=False, server_default="2"),
|
||||
Column("nxt_edtn", Integer, nullable=False, server_default="0"),
|
||||
Column("cnp_cid", Integer, nullable=False, server_default="-1"),
|
||||
Column("cnp_val", Integer, nullable=False, server_default="-1"),
|
||||
Column("cnp_rr", Integer, nullable=False, server_default="-1"),
|
||||
Column("cnp_sp", String(255), nullable=False, server_default=""),
|
||||
Column("dsp_clr_brdr", Integer, nullable=False, server_default="7"),
|
||||
Column("dsp_intrm_rnk", Integer, nullable=False, server_default="1"),
|
||||
Column("dsp_clr_sts", Integer, nullable=False, server_default="1"),
|
||||
Column("rgo_sts", Integer, nullable=False, server_default="1"),
|
||||
Column("lv_efct_id", Integer, nullable=False, server_default="0"),
|
||||
Column("lv_plt_id", Integer, nullable=False, server_default="1"),
|
||||
Column("passwd_stat", Integer, nullable=False, server_default="0"),
|
||||
Column("passwd", String(12), nullable=False, server_default="**********"),
|
||||
Column(
|
||||
"my_qst_id",
|
||||
String(128),
|
||||
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
|
||||
),
|
||||
Column(
|
||||
"my_qst_sts",
|
||||
String(128),
|
||||
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
|
||||
),
|
||||
UniqueConstraint("user", "version", name="diva_profile_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaProfileData(BaseData):
|
||||
def create_profile(
|
||||
self, version: int, aime_id: int, player_name: str
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Given a game version, aime id, and player_name, create a profile and return it's ID
|
||||
"""
|
||||
sql = insert(profile).values(
|
||||
version=version, user=aime_id, player_name=player_name
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(player_name=sql.inserted.player_name)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva profile! aime id: {aime_id} username: {player_name}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def update_profile(self, aime_id: int, **profile_args) -> None:
|
||||
"""
|
||||
Given an aime_id update the profile corresponding to the arguments
|
||||
which are the diva_profile Columns
|
||||
"""
|
||||
sql = profile.update(profile.c.user == aime_id).values(**profile_args)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"update_profile: failed to update profile! profile: {aime_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given a game version and either a profile or aime id, return the profile
|
||||
"""
|
||||
sql = profile.select(
|
||||
and_(profile.c.version == version, profile.c.user == aime_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -1,86 +1,88 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, and_
|
||||
from sqlalchemy.types import Integer, String
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
pv_customize = Table(
|
||||
"diva_profile_pv_customize",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("pv_id", Integer, nullable=False),
|
||||
Column("mdl_eqp_ary", String(14), server_default="-999,-999,-999"),
|
||||
Column(
|
||||
"c_itm_eqp_ary",
|
||||
String(59),
|
||||
server_default="-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999",
|
||||
),
|
||||
Column(
|
||||
"ms_itm_flg_ary",
|
||||
String(59),
|
||||
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
|
||||
),
|
||||
Column("skin", Integer, server_default="-1"),
|
||||
Column("btn_se", Integer, server_default="-1"),
|
||||
Column("sld_se", Integer, server_default="-1"),
|
||||
Column("chsld_se", Integer, server_default="-1"),
|
||||
Column("sldtch_se", Integer, server_default="-1"),
|
||||
UniqueConstraint("user", "version", "pv_id", name="diva_profile_pv_customize_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaPvCustomizeData(BaseData):
|
||||
def put_pv_customize(
|
||||
self,
|
||||
aime_id: int,
|
||||
version: int,
|
||||
pv_id: int,
|
||||
mdl_eqp_ary: str,
|
||||
c_itm_eqp_ary: str,
|
||||
ms_itm_flg_ary: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(pv_customize).values(
|
||||
version=version,
|
||||
user=aime_id,
|
||||
pv_id=pv_id,
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
pv_id=pv_id,
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva pv customize! aime id: {aime_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_pv_customize(self, aime_id: int, pv_id: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given either a profile or aime id, return a Pv Customize row
|
||||
"""
|
||||
sql = pv_customize.select(
|
||||
and_(pv_customize.c.user == aime_id, pv_customize.c.pv_id == pv_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, and_
|
||||
from sqlalchemy.types import Integer, String
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
pv_customize = Table(
|
||||
"diva_profile_pv_customize",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("pv_id", Integer, nullable=False),
|
||||
Column("mdl_eqp_ary", String(14), server_default="-999,-999,-999"),
|
||||
Column(
|
||||
"c_itm_eqp_ary",
|
||||
String(59),
|
||||
server_default="-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999",
|
||||
),
|
||||
Column(
|
||||
"ms_itm_flg_ary",
|
||||
String(59),
|
||||
server_default="-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1",
|
||||
),
|
||||
Column("skin", Integer, server_default="-1"),
|
||||
Column("btn_se", Integer, server_default="-1"),
|
||||
Column("sld_se", Integer, server_default="-1"),
|
||||
Column("chsld_se", Integer, server_default="-1"),
|
||||
Column("sldtch_se", Integer, server_default="-1"),
|
||||
UniqueConstraint("user", "version", "pv_id", name="diva_profile_pv_customize_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaPvCustomizeData(BaseData):
|
||||
def put_pv_customize(
|
||||
self,
|
||||
aime_id: int,
|
||||
version: int,
|
||||
pv_id: int,
|
||||
mdl_eqp_ary: str,
|
||||
c_itm_eqp_ary: str,
|
||||
ms_itm_flg_ary: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(pv_customize).values(
|
||||
version=version,
|
||||
user=aime_id,
|
||||
pv_id=pv_id,
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
pv_id=pv_id,
|
||||
mdl_eqp_ary=mdl_eqp_ary,
|
||||
c_itm_eqp_ary=c_itm_eqp_ary,
|
||||
ms_itm_flg_ary=ms_itm_flg_ary,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} Failed to insert diva pv customize! aime id: {aime_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_pv_customize(self, aime_id: int, pv_id: int) -> Optional[List[Dict]]:
|
||||
"""
|
||||
Given either a profile or aime id, return a Pv Customize row
|
||||
"""
|
||||
sql = pv_customize.select(
|
||||
and_(pv_customize.c.user == aime_id, pv_customize.c.pv_id == pv_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -1,241 +1,243 @@
|
|||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
from sqlalchemy.engine import Row
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
score = Table(
|
||||
"diva_score",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("pv_id", Integer),
|
||||
Column("difficulty", Integer),
|
||||
Column("edition", Integer),
|
||||
Column("score", Integer),
|
||||
Column("atn_pnt", Integer),
|
||||
Column("clr_kind", Integer),
|
||||
Column("sort_kind", Integer),
|
||||
Column("cool", Integer),
|
||||
Column("fine", Integer),
|
||||
Column("safe", Integer),
|
||||
Column("sad", Integer),
|
||||
Column("worst", Integer),
|
||||
Column("max_combo", Integer),
|
||||
UniqueConstraint("user", "pv_id", "difficulty", "edition", name="diva_score_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"diva_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("pv_id", Integer),
|
||||
Column("difficulty", Integer),
|
||||
Column("edition", Integer),
|
||||
Column("score", Integer),
|
||||
Column("atn_pnt", Integer),
|
||||
Column("clr_kind", Integer),
|
||||
Column("sort_kind", Integer),
|
||||
Column("cool", Integer),
|
||||
Column("fine", Integer),
|
||||
Column("safe", Integer),
|
||||
Column("sad", Integer),
|
||||
Column("worst", Integer),
|
||||
Column("max_combo", Integer),
|
||||
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaScoreData(BaseData):
|
||||
def put_best_score(
|
||||
self,
|
||||
user_id: int,
|
||||
game_version: int,
|
||||
song_id: int,
|
||||
difficulty: int,
|
||||
edition: int,
|
||||
song_score: int,
|
||||
atn_pnt: int,
|
||||
clr_kind: int,
|
||||
sort_kind: int,
|
||||
cool: int,
|
||||
fine: int,
|
||||
safe: int,
|
||||
sad: int,
|
||||
worst: int,
|
||||
max_combo: int,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Update the user's best score for a chart
|
||||
"""
|
||||
sql = insert(score).values(
|
||||
user=user_id,
|
||||
version=game_version,
|
||||
pv_id=song_id,
|
||||
difficulty=difficulty,
|
||||
edition=edition,
|
||||
score=song_score,
|
||||
atn_pnt=atn_pnt,
|
||||
clr_kind=clr_kind,
|
||||
sort_kind=sort_kind,
|
||||
cool=cool,
|
||||
fine=fine,
|
||||
safe=safe,
|
||||
sad=sad,
|
||||
worst=worst,
|
||||
max_combo=max_combo,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
score=song_score,
|
||||
atn_pnt=atn_pnt,
|
||||
clr_kind=clr_kind,
|
||||
sort_kind=sort_kind,
|
||||
cool=cool,
|
||||
fine=fine,
|
||||
safe=safe,
|
||||
sad=sad,
|
||||
worst=worst,
|
||||
max_combo=max_combo,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert best score! profile: {user_id}, song: {song_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(
|
||||
self,
|
||||
user_id: int,
|
||||
game_version: int,
|
||||
song_id: int,
|
||||
difficulty: int,
|
||||
edition: int,
|
||||
song_score: int,
|
||||
atn_pnt: int,
|
||||
clr_kind: int,
|
||||
sort_kind: int,
|
||||
cool: int,
|
||||
fine: int,
|
||||
safe: int,
|
||||
sad: int,
|
||||
worst: int,
|
||||
max_combo: int,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's play log
|
||||
"""
|
||||
sql = playlog.insert().values(
|
||||
user=user_id,
|
||||
version=game_version,
|
||||
pv_id=song_id,
|
||||
difficulty=difficulty,
|
||||
edition=edition,
|
||||
score=song_score,
|
||||
atn_pnt=atn_pnt,
|
||||
clr_kind=clr_kind,
|
||||
sort_kind=sort_kind,
|
||||
cool=cool,
|
||||
fine=fine,
|
||||
safe=safe,
|
||||
sad=sad,
|
||||
worst=worst,
|
||||
max_combo=max_combo,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {difficulty}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_user_score(
|
||||
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
||||
) -> Optional[Row]:
|
||||
sql = score.select(
|
||||
and_(
|
||||
score.c.user == user_id,
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_top3_scores(
|
||||
self, pv_id: int, difficulty: int, edition: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = (
|
||||
score.select(
|
||||
and_(
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
)
|
||||
.order_by(score.c.score.desc())
|
||||
.limit(3)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_global_ranking(
|
||||
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
||||
) -> Optional[List[Row]]:
|
||||
# get the subquery max score of a user with pv_id, difficulty and
|
||||
# edition
|
||||
sql_sub = (
|
||||
select([score.c.score])
|
||||
.filter(
|
||||
score.c.user == user_id,
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
# Perform the main query, also rename the resulting column to ranking
|
||||
sql = select(func.count(score.c.id).label("ranking")).filter(
|
||||
score.c.score >= sql_sub,
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_best_scores(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = score.select(score.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
from sqlalchemy.engine import Row
|
||||
from typing import Optional, List, Dict, Any
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
score = Table(
|
||||
"diva_score",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("pv_id", Integer),
|
||||
Column("difficulty", Integer),
|
||||
Column("edition", Integer),
|
||||
Column("score", Integer),
|
||||
Column("atn_pnt", Integer),
|
||||
Column("clr_kind", Integer),
|
||||
Column("sort_kind", Integer),
|
||||
Column("cool", Integer),
|
||||
Column("fine", Integer),
|
||||
Column("safe", Integer),
|
||||
Column("sad", Integer),
|
||||
Column("worst", Integer),
|
||||
Column("max_combo", Integer),
|
||||
UniqueConstraint("user", "pv_id", "difficulty", "edition", name="diva_score_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"diva_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade"), nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("pv_id", Integer),
|
||||
Column("difficulty", Integer),
|
||||
Column("edition", Integer),
|
||||
Column("score", Integer),
|
||||
Column("atn_pnt", Integer),
|
||||
Column("clr_kind", Integer),
|
||||
Column("sort_kind", Integer),
|
||||
Column("cool", Integer),
|
||||
Column("fine", Integer),
|
||||
Column("safe", Integer),
|
||||
Column("sad", Integer),
|
||||
Column("worst", Integer),
|
||||
Column("max_combo", Integer),
|
||||
Column("date_scored", TIMESTAMP, server_default=func.now()),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaScoreData(BaseData):
|
||||
def put_best_score(
|
||||
self,
|
||||
user_id: int,
|
||||
game_version: int,
|
||||
song_id: int,
|
||||
difficulty: int,
|
||||
edition: int,
|
||||
song_score: int,
|
||||
atn_pnt: int,
|
||||
clr_kind: int,
|
||||
sort_kind: int,
|
||||
cool: int,
|
||||
fine: int,
|
||||
safe: int,
|
||||
sad: int,
|
||||
worst: int,
|
||||
max_combo: int,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Update the user's best score for a chart
|
||||
"""
|
||||
sql = insert(score).values(
|
||||
user=user_id,
|
||||
version=game_version,
|
||||
pv_id=song_id,
|
||||
difficulty=difficulty,
|
||||
edition=edition,
|
||||
score=song_score,
|
||||
atn_pnt=atn_pnt,
|
||||
clr_kind=clr_kind,
|
||||
sort_kind=sort_kind,
|
||||
cool=cool,
|
||||
fine=fine,
|
||||
safe=safe,
|
||||
sad=sad,
|
||||
worst=worst,
|
||||
max_combo=max_combo,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
score=song_score,
|
||||
atn_pnt=atn_pnt,
|
||||
clr_kind=clr_kind,
|
||||
sort_kind=sort_kind,
|
||||
cool=cool,
|
||||
fine=fine,
|
||||
safe=safe,
|
||||
sad=sad,
|
||||
worst=worst,
|
||||
max_combo=max_combo,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert best score! profile: {user_id}, song: {song_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(
|
||||
self,
|
||||
user_id: int,
|
||||
game_version: int,
|
||||
song_id: int,
|
||||
difficulty: int,
|
||||
edition: int,
|
||||
song_score: int,
|
||||
atn_pnt: int,
|
||||
clr_kind: int,
|
||||
sort_kind: int,
|
||||
cool: int,
|
||||
fine: int,
|
||||
safe: int,
|
||||
sad: int,
|
||||
worst: int,
|
||||
max_combo: int,
|
||||
) -> Optional[int]:
|
||||
"""
|
||||
Add an entry to the user's play log
|
||||
"""
|
||||
sql = playlog.insert().values(
|
||||
user=user_id,
|
||||
version=game_version,
|
||||
pv_id=song_id,
|
||||
difficulty=difficulty,
|
||||
edition=edition,
|
||||
score=song_score,
|
||||
atn_pnt=atn_pnt,
|
||||
clr_kind=clr_kind,
|
||||
sort_kind=sort_kind,
|
||||
cool=cool,
|
||||
fine=fine,
|
||||
safe=safe,
|
||||
sad=sad,
|
||||
worst=worst,
|
||||
max_combo=max_combo,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {difficulty}"
|
||||
)
|
||||
return None
|
||||
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_user_score(
|
||||
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
||||
) -> Optional[Row]:
|
||||
sql = score.select(
|
||||
and_(
|
||||
score.c.user == user_id,
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_top3_scores(
|
||||
self, pv_id: int, difficulty: int, edition: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = (
|
||||
score.select(
|
||||
and_(
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
)
|
||||
.order_by(score.c.score.desc())
|
||||
.limit(3)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_global_ranking(
|
||||
self, user_id: int, pv_id: int, difficulty: int, edition: int
|
||||
) -> Optional[List[Row]]:
|
||||
# get the subquery max score of a user with pv_id, difficulty and
|
||||
# edition
|
||||
sql_sub = (
|
||||
select([score.c.score])
|
||||
.filter(
|
||||
score.c.user == user_id,
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
.scalar_subquery()
|
||||
)
|
||||
|
||||
# Perform the main query, also rename the resulting column to ranking
|
||||
sql = select(func.count(score.c.id).label("ranking")).filter(
|
||||
score.c.score >= sql_sub,
|
||||
score.c.pv_id == pv_id,
|
||||
score.c.difficulty == difficulty,
|
||||
score.c.edition == edition,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_best_scores(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = score.select(score.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
|
|
@ -1,311 +1,313 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
music = Table(
|
||||
"diva_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("vocaloid_arranger", String(255)),
|
||||
Column("pv_illustrator", String(255)),
|
||||
Column("lyrics", String(255)),
|
||||
Column("bg_music", String(255)),
|
||||
Column("level", Float),
|
||||
Column("bpm", Integer),
|
||||
Column("date", String(255)),
|
||||
UniqueConstraint("version", "songId", "chartId", name="diva_static_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
quests = Table(
|
||||
"diva_static_quests",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("questId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("quest_enable", Boolean, server_default="1"),
|
||||
Column("kind", Integer),
|
||||
Column("unknown_0", Integer),
|
||||
Column("unknown_1", Integer),
|
||||
Column("unknown_2", Integer),
|
||||
Column("quest_order", Integer),
|
||||
Column("start_datetime", String(255)),
|
||||
Column("end_datetime", String(255)),
|
||||
UniqueConstraint("version", "questId", name="diva_static_quests_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
shop = Table(
|
||||
"diva_static_shop",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("shopId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("points", Integer),
|
||||
Column("unknown_0", Integer),
|
||||
Column("start_date", String(255)),
|
||||
Column("end_date", String(255)),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "shopId", name="diva_static_shop_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
items = Table(
|
||||
"diva_static_items",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("itemId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("points", Integer),
|
||||
Column("unknown_0", Integer),
|
||||
Column("start_date", String(255)),
|
||||
Column("end_date", String(255)),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "itemId", name="diva_static_items_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaStaticData(BaseData):
|
||||
def put_quests(
|
||||
self,
|
||||
version: int,
|
||||
questId: int,
|
||||
name: str,
|
||||
kind: int,
|
||||
unknown_0: int,
|
||||
unknown_1: int,
|
||||
unknown_2: int,
|
||||
quest_order: int,
|
||||
start_datetime: str,
|
||||
end_datetime: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(quests).values(
|
||||
version=version,
|
||||
questId=questId,
|
||||
name=name,
|
||||
kind=kind,
|
||||
unknown_0=unknown_0,
|
||||
unknown_1=unknown_1,
|
||||
unknown_2=unknown_2,
|
||||
quest_order=quest_order,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(name=name)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_quests(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(quests).where(
|
||||
and_(quests.c.version == version, quests.c.quest_enable == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_shop(
|
||||
self,
|
||||
version: int,
|
||||
shopId: int,
|
||||
name: str,
|
||||
type: int,
|
||||
points: int,
|
||||
unknown_0: int,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(shop).values(
|
||||
version=version,
|
||||
shopId=shopId,
|
||||
name=name,
|
||||
type=type,
|
||||
points=points,
|
||||
unknown_0=unknown_0,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(name=name)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_shop(self, version: int, shopId: int) -> Optional[Row]:
|
||||
sql = select(shop).where(
|
||||
and_(
|
||||
shop.c.version == version,
|
||||
shop.c.shopId == shopId,
|
||||
shop.c.enabled == True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_enabled_shops(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(shop).where(
|
||||
and_(shop.c.version == version, shop.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_items(
|
||||
self,
|
||||
version: int,
|
||||
itemId: int,
|
||||
name: str,
|
||||
type: int,
|
||||
points: int,
|
||||
unknown_0: int,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(items).values(
|
||||
version=version,
|
||||
itemId=itemId,
|
||||
name=name,
|
||||
type=type,
|
||||
points=points,
|
||||
unknown_0=unknown_0,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(name=name)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_item(self, version: int, itemId: int) -> Optional[Row]:
|
||||
sql = select(items).where(
|
||||
and_(
|
||||
items.c.version == version,
|
||||
items.c.itemId == itemId,
|
||||
items.c.enabled == True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_enabled_items(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(items).where(
|
||||
and_(items.c.version == version, items.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_music(
|
||||
self,
|
||||
version: int,
|
||||
song: int,
|
||||
chart: int,
|
||||
title: str,
|
||||
arranger: str,
|
||||
illustrator: str,
|
||||
lyrics: str,
|
||||
music_comp: str,
|
||||
level: float,
|
||||
bpm: int,
|
||||
date: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=song,
|
||||
chartId=chart,
|
||||
title=title,
|
||||
vocaloid_arranger=arranger,
|
||||
pv_illustrator=illustrator,
|
||||
lyrics=lyrics,
|
||||
bg_music=music_comp,
|
||||
level=level,
|
||||
bpm=bpm,
|
||||
date=date,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
title=title,
|
||||
vocaloid_arranger=arranger,
|
||||
pv_illustrator=illustrator,
|
||||
lyrics=lyrics,
|
||||
bg_music=music_comp,
|
||||
level=level,
|
||||
bpm=bpm,
|
||||
date=date,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_music(
|
||||
self, version: int, song_id: Optional[int] = None
|
||||
) -> Optional[List[Row]]:
|
||||
if song_id is None:
|
||||
sql = select(music).where(music.c.version == version)
|
||||
else:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
music = Table(
|
||||
"diva_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("vocaloid_arranger", String(255)),
|
||||
Column("pv_illustrator", String(255)),
|
||||
Column("lyrics", String(255)),
|
||||
Column("bg_music", String(255)),
|
||||
Column("level", Float),
|
||||
Column("bpm", Integer),
|
||||
Column("date", String(255)),
|
||||
UniqueConstraint("version", "songId", "chartId", name="diva_static_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
quests = Table(
|
||||
"diva_static_quests",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("questId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("quest_enable", Boolean, server_default="1"),
|
||||
Column("kind", Integer),
|
||||
Column("unknown_0", Integer),
|
||||
Column("unknown_1", Integer),
|
||||
Column("unknown_2", Integer),
|
||||
Column("quest_order", Integer),
|
||||
Column("start_datetime", String(255)),
|
||||
Column("end_datetime", String(255)),
|
||||
UniqueConstraint("version", "questId", name="diva_static_quests_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
shop = Table(
|
||||
"diva_static_shop",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("shopId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("points", Integer),
|
||||
Column("unknown_0", Integer),
|
||||
Column("start_date", String(255)),
|
||||
Column("end_date", String(255)),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "shopId", name="diva_static_shop_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
items = Table(
|
||||
"diva_static_items",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("itemId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("points", Integer),
|
||||
Column("unknown_0", Integer),
|
||||
Column("start_date", String(255)),
|
||||
Column("end_date", String(255)),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "itemId", name="diva_static_items_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class DivaStaticData(BaseData):
|
||||
def put_quests(
|
||||
self,
|
||||
version: int,
|
||||
questId: int,
|
||||
name: str,
|
||||
kind: int,
|
||||
unknown_0: int,
|
||||
unknown_1: int,
|
||||
unknown_2: int,
|
||||
quest_order: int,
|
||||
start_datetime: str,
|
||||
end_datetime: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(quests).values(
|
||||
version=version,
|
||||
questId=questId,
|
||||
name=name,
|
||||
kind=kind,
|
||||
unknown_0=unknown_0,
|
||||
unknown_1=unknown_1,
|
||||
unknown_2=unknown_2,
|
||||
quest_order=quest_order,
|
||||
start_datetime=start_datetime,
|
||||
end_datetime=end_datetime,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(name=name))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_quests(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(quests).where(
|
||||
and_(quests.c.version == version, quests.c.quest_enable == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_shop(
|
||||
self,
|
||||
version: int,
|
||||
shopId: int,
|
||||
name: str,
|
||||
type: int,
|
||||
points: int,
|
||||
unknown_0: int,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(shop).values(
|
||||
version=version,
|
||||
shopId=shopId,
|
||||
name=name,
|
||||
type=type,
|
||||
points=points,
|
||||
unknown_0=unknown_0,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(name=name))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_shop(self, version: int, shopId: int) -> Optional[Row]:
|
||||
sql = select(shop).where(
|
||||
and_(
|
||||
shop.c.version == version,
|
||||
shop.c.shopId == shopId,
|
||||
shop.c.enabled == True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_enabled_shops(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(shop).where(
|
||||
and_(shop.c.version == version, shop.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_items(
|
||||
self,
|
||||
version: int,
|
||||
itemId: int,
|
||||
name: str,
|
||||
type: int,
|
||||
points: int,
|
||||
unknown_0: int,
|
||||
start_date: str,
|
||||
end_date: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(items).values(
|
||||
version=version,
|
||||
itemId=itemId,
|
||||
name=name,
|
||||
type=type,
|
||||
points=points,
|
||||
unknown_0=unknown_0,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(name=name))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_item(self, version: int, itemId: int) -> Optional[Row]:
|
||||
sql = select(items).where(
|
||||
and_(
|
||||
items.c.version == version,
|
||||
items.c.itemId == itemId,
|
||||
items.c.enabled == True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_enabled_items(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(items).where(
|
||||
and_(items.c.version == version, items.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_music(
|
||||
self,
|
||||
version: int,
|
||||
song: int,
|
||||
chart: int,
|
||||
title: str,
|
||||
arranger: str,
|
||||
illustrator: str,
|
||||
lyrics: str,
|
||||
music_comp: str,
|
||||
level: float,
|
||||
bpm: int,
|
||||
date: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=song,
|
||||
chartId=chart,
|
||||
title=title,
|
||||
vocaloid_arranger=arranger,
|
||||
pv_illustrator=illustrator,
|
||||
lyrics=lyrics,
|
||||
bg_music=music_comp,
|
||||
level=level,
|
||||
bpm=bpm,
|
||||
date=date,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
title=title,
|
||||
vocaloid_arranger=arranger,
|
||||
pv_illustrator=illustrator,
|
||||
lyrics=lyrics,
|
||||
bg_music=music_comp,
|
||||
level=level,
|
||||
bpm=bpm,
|
||||
date=date,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_music(
|
||||
self, version: int, song_id: Optional[int] = None
|
||||
) -> Optional[List[Row]]:
|
||||
if song_id is None:
|
||||
sql = select(music).where(music.c.version == version)
|
||||
else:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -83,13 +83,15 @@ class IDZUserDBProtocol(Protocol):
|
|||
def dataReceived(self, data: bytes) -> None:
|
||||
self.logger.debug(f"Receive data {data.hex()}")
|
||||
crypt = AES.new(self.static_key, AES.MODE_ECB)
|
||||
|
||||
|
||||
try:
|
||||
data_dec = crypt.decrypt(data)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Failed to decrypt UserDB request from {self.transport.getPeer().host} because {e} - {data.hex()}")
|
||||
|
||||
self.logger.error(
|
||||
f"Failed to decrypt UserDB request from {self.transport.getPeer().host} because {e} - {data.hex()}"
|
||||
)
|
||||
|
||||
self.logger.debug(f"Decrypt data {data_dec.hex()}")
|
||||
|
||||
magic = struct.unpack_from("<I", data_dec, 0)[0]
|
||||
|
|
|
@ -4,6 +4,7 @@ import logging
|
|||
from base64 import b64decode
|
||||
from os import path, stat, remove
|
||||
from PIL import ImageFile
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.const import Mai2Constants
|
||||
|
@ -21,15 +22,15 @@ class Mai2Base:
|
|||
self.can_deliver = False
|
||||
self.can_usbdl = False
|
||||
self.old_server = ""
|
||||
|
||||
|
||||
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEY/197/"
|
||||
|
||||
|
||||
else:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}/SDEY/197/"
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict):
|
||||
return {
|
||||
return {
|
||||
"isDevelop": False,
|
||||
"isAouAccession": False,
|
||||
"gameSetting": {
|
||||
|
@ -39,10 +40,16 @@ class Mai2Base:
|
|||
"rebootEndTime": "2020-01-01 07:59:59.0",
|
||||
"movieUploadLimit": 100,
|
||||
"movieStatus": 1,
|
||||
"movieServerUri": self.old_server + "api/movie" if self.game_config.uploads.movies else "movie",
|
||||
"deliverServerUri": self.old_server + "deliver/" if self.can_deliver and self.game_config.deliver.enable else "",
|
||||
"movieServerUri": self.old_server + "api/movie"
|
||||
if self.game_config.uploads.movies
|
||||
else "movie",
|
||||
"deliverServerUri": self.old_server + "deliver/"
|
||||
if self.can_deliver and self.game_config.deliver.enable
|
||||
else "",
|
||||
"oldServerUri": self.old_server + "old",
|
||||
"usbDlServerUri": self.old_server + "usbdl/" if self.can_deliver and self.game_config.deliver.udbdl_enable else "",
|
||||
"usbDlServerUri": self.old_server + "usbdl/"
|
||||
if self.can_deliver and self.game_config.deliver.udbdl_enable
|
||||
else "",
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -154,32 +161,37 @@ class Mai2Base:
|
|||
else:
|
||||
loginCt = 0
|
||||
lastLoginDate = "2017-12-05 07:00:00.0"
|
||||
|
||||
|
||||
if consec is None or not consec:
|
||||
consec_ct = 1
|
||||
|
||||
|
||||
else:
|
||||
lastlogindate_ = datetime.strptime(profile["lastLoginDate"], "%Y-%m-%d %H:%M:%S.%f").timestamp()
|
||||
today_midnight = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0).timestamp()
|
||||
lastlogindate_ = datetime.strptime(
|
||||
profile["lastLoginDate"], "%Y-%m-%d %H:%M:%S.%f"
|
||||
).timestamp()
|
||||
today_midnight = (
|
||||
datetime.now()
|
||||
.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
.timestamp()
|
||||
)
|
||||
yesterday_midnight = today_midnight - 86400
|
||||
|
||||
if lastlogindate_ < today_midnight:
|
||||
consec_ct = consec['logins'] + 1
|
||||
consec_ct = consec["logins"] + 1
|
||||
self.data.profile.add_consec_login(data["userId"], self.version)
|
||||
|
||||
|
||||
elif lastlogindate_ < yesterday_midnight:
|
||||
consec_ct = 1
|
||||
self.data.profile.reset_consec_login(data["userId"], self.version)
|
||||
|
||||
else:
|
||||
consec_ct = consec['logins']
|
||||
|
||||
consec_ct = consec["logins"]
|
||||
|
||||
return {
|
||||
"returnCode": 1,
|
||||
"lastLoginDate": lastLoginDate,
|
||||
"loginCount": loginCt,
|
||||
"consecutiveLoginCount": consec_ct, # Number of consecutive days we've logged in.
|
||||
"consecutiveLoginCount": consec_ct, # Number of consecutive days we've logged in.
|
||||
}
|
||||
|
||||
def handle_upload_user_playlog_api_request(self, data: Dict) -> Dict:
|
||||
|
@ -210,140 +222,132 @@ class Mai2Base:
|
|||
user_id = data["userId"]
|
||||
upsert = data["upsertUserAll"]
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
upsert["userData"][0].pop("accessCode")
|
||||
upsert["userData"][0].pop("userId")
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
upsert["userData"][0].pop("accessCode")
|
||||
upsert["userData"][0].pop("userId")
|
||||
|
||||
self.data.profile.put_profile_detail(
|
||||
user_id, self.version, upsert["userData"][0], False
|
||||
)
|
||||
|
||||
if "userWebOption" in upsert and len(upsert["userWebOption"]) > 0:
|
||||
upsert["userWebOption"][0]["isNetMember"] = True
|
||||
self.data.profile.put_web_option(
|
||||
user_id, self.version, upsert["userWebOption"][0]
|
||||
)
|
||||
|
||||
if "userGradeStatusList" in upsert and len(upsert["userGradeStatusList"]) > 0:
|
||||
self.data.profile.put_grade_status(
|
||||
user_id, upsert["userGradeStatusList"][0]
|
||||
)
|
||||
|
||||
if "userBossList" in upsert and len(upsert["userBossList"]) > 0:
|
||||
self.data.profile.put_boss_list(
|
||||
user_id, upsert["userBossList"][0]
|
||||
)
|
||||
|
||||
if "userPlaylogList" in upsert and len(upsert["userPlaylogList"]) > 0:
|
||||
for playlog in upsert["userPlaylogList"]:
|
||||
self.data.score.put_playlog(
|
||||
user_id, playlog, False
|
||||
self.data.profile.put_profile_detail(
|
||||
user_id, self.version, upsert["userData"][0], False
|
||||
)
|
||||
|
||||
if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
|
||||
self.data.profile.put_profile_extend(
|
||||
user_id, self.version, upsert["userExtend"][0]
|
||||
)
|
||||
|
||||
if "userGhost" in upsert:
|
||||
for ghost in upsert["userGhost"]:
|
||||
self.data.profile.put_profile_ghost(user_id, self.version, ghost)
|
||||
|
||||
if "userRecentRatingList" in upsert:
|
||||
self.data.profile.put_recent_rating(user_id, upsert["userRecentRatingList"])
|
||||
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
upsert["userOption"][0].pop("userId")
|
||||
self.data.profile.put_profile_option(
|
||||
user_id, self.version, upsert["userOption"][0], False
|
||||
)
|
||||
|
||||
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
|
||||
self.data.profile.put_profile_rating(
|
||||
user_id, self.version, upsert["userRatingList"][0]
|
||||
)
|
||||
|
||||
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
|
||||
for act in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(user_id, act)
|
||||
|
||||
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
|
||||
for charge in upsert["userChargeList"]:
|
||||
# remove the ".0" from the date string, festival only?
|
||||
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
|
||||
self.data.item.put_charge(
|
||||
user_id,
|
||||
charge["chargeId"],
|
||||
charge["stock"],
|
||||
datetime.strptime(
|
||||
charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
datetime.strptime(
|
||||
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
if "userWebOption" in upsert and len(upsert["userWebOption"]) > 0:
|
||||
upsert["userWebOption"][0]["isNetMember"] = True
|
||||
self.data.profile.put_web_option(
|
||||
user_id, self.version, upsert["userWebOption"][0]
|
||||
)
|
||||
|
||||
if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
|
||||
for char in upsert["userCharacterList"]:
|
||||
self.data.item.put_character_(
|
||||
user_id,
|
||||
char
|
||||
if "userGradeStatusList" in upsert and len(upsert["userGradeStatusList"]) > 0:
|
||||
self.data.profile.put_grade_status(
|
||||
user_id, upsert["userGradeStatusList"][0]
|
||||
)
|
||||
|
||||
if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(
|
||||
user_id,
|
||||
int(item["itemKind"]),
|
||||
item["itemId"],
|
||||
item["stock"],
|
||||
True
|
||||
if "userBossList" in upsert and len(upsert["userBossList"]) > 0:
|
||||
self.data.profile.put_boss_list(user_id, upsert["userBossList"][0])
|
||||
|
||||
if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
|
||||
self.data.profile.put_profile_extend(
|
||||
user_id, self.version, upsert["userExtend"][0]
|
||||
)
|
||||
|
||||
if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
|
||||
for login_bonus in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id,
|
||||
login_bonus["bonusId"],
|
||||
login_bonus["point"],
|
||||
login_bonus["isCurrent"],
|
||||
login_bonus["isComplete"],
|
||||
if "userGhost" in upsert:
|
||||
for ghost in upsert["userGhost"]:
|
||||
self.data.profile.put_profile_ghost(user_id, self.version, ghost)
|
||||
|
||||
if "userRecentRatingList" in upsert:
|
||||
self.data.profile.put_recent_rating(user_id, upsert["userRecentRatingList"])
|
||||
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
upsert["userOption"][0].pop("userId")
|
||||
self.data.profile.put_profile_option(
|
||||
user_id, self.version, upsert["userOption"][0], False
|
||||
)
|
||||
|
||||
if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(
|
||||
user_id,
|
||||
map["mapId"],
|
||||
map["distance"],
|
||||
map["isLock"],
|
||||
map["isClear"],
|
||||
map["isComplete"],
|
||||
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
|
||||
self.data.profile.put_profile_rating(
|
||||
user_id, self.version, upsert["userRatingList"][0]
|
||||
)
|
||||
|
||||
if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
|
||||
for music in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, music, False)
|
||||
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
|
||||
for act in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(user_id, act)
|
||||
|
||||
if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
if "userPlaylogList" in upsert and len(upsert["userPlaylogList"]) > 0:
|
||||
for playlog in upsert["userPlaylogList"]:
|
||||
self.data.score.put_playlog(user_id, playlog, False)
|
||||
|
||||
if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
|
||||
for fav in upsert["userFavoriteList"]:
|
||||
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||
if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
|
||||
for music in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, music, False)
|
||||
|
||||
if (
|
||||
"userFriendSeasonRankingList" in upsert
|
||||
and len(upsert["userFriendSeasonRankingList"]) > 0
|
||||
):
|
||||
for fsr in upsert["userFriendSeasonRankingList"]:
|
||||
fsr["recordDate"] = (
|
||||
datetime.strptime(
|
||||
fsr["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
|
||||
),
|
||||
)
|
||||
self.data.item.put_friend_season_ranking(user_id, fsr)
|
||||
if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
|
||||
for charge in upsert["userChargeList"]:
|
||||
# remove the ".0" from the date string, festival only?
|
||||
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
|
||||
self.data.item.put_charge(
|
||||
user_id,
|
||||
charge["chargeId"],
|
||||
charge["stock"],
|
||||
datetime.strptime(
|
||||
charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
datetime.strptime(
|
||||
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
)
|
||||
|
||||
if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
|
||||
for char in upsert["userCharacterList"]:
|
||||
self.data.item.put_character_(user_id, char)
|
||||
|
||||
if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(
|
||||
user_id, int(item["itemKind"]), item["itemId"], item["stock"], True
|
||||
)
|
||||
|
||||
if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
|
||||
for login_bonus in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id,
|
||||
login_bonus["bonusId"],
|
||||
login_bonus["point"],
|
||||
login_bonus["isCurrent"],
|
||||
login_bonus["isComplete"],
|
||||
)
|
||||
|
||||
if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(
|
||||
user_id,
|
||||
map["mapId"],
|
||||
map["distance"],
|
||||
map["isLock"],
|
||||
map["isClear"],
|
||||
map["isComplete"],
|
||||
)
|
||||
|
||||
if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
|
||||
for fav in upsert["userFavoriteList"]:
|
||||
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||
|
||||
if (
|
||||
"userFriendSeasonRankingList" in upsert
|
||||
and len(upsert["userFriendSeasonRankingList"]) > 0
|
||||
):
|
||||
for fsr in upsert["userFriendSeasonRankingList"]:
|
||||
fsr["recordDate"] = (
|
||||
datetime.strptime(
|
||||
fsr["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
|
||||
),
|
||||
)
|
||||
self.data.item.put_friend_season_ranking(user_id, fsr)
|
||||
|
||||
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
||||
|
||||
|
@ -351,7 +355,9 @@ class Mai2Base:
|
|||
return {"returnCode": 1}
|
||||
|
||||
def handle_get_user_data_api_request(self, data: Dict) -> Dict:
|
||||
profile = self.data.profile.get_profile_detail(data["userId"], self.version, False)
|
||||
profile = self.data.profile.get_profile_detail(
|
||||
data["userId"], self.version, False
|
||||
)
|
||||
if profile is None:
|
||||
return
|
||||
|
||||
|
@ -375,7 +381,9 @@ class Mai2Base:
|
|||
return {"userId": data["userId"], "userExtend": extend_dict}
|
||||
|
||||
def handle_get_user_option_api_request(self, data: Dict) -> Dict:
|
||||
options = self.data.profile.get_profile_option(data["userId"], self.version, False)
|
||||
options = self.data.profile.get_profile_option(
|
||||
data["userId"], self.version, False
|
||||
)
|
||||
if options is None:
|
||||
return
|
||||
|
||||
|
@ -446,23 +454,27 @@ class Mai2Base:
|
|||
}
|
||||
|
||||
def handle_get_user_present_api_request(self, data: Dict) -> Dict:
|
||||
return { "userId": data.get("userId", 0), "length": 0, "userPresentList": []}
|
||||
|
||||
return {"userId": data.get("userId", 0), "length": 0, "userPresentList": []}
|
||||
|
||||
def handle_get_transfer_friend_api_request(self, data: Dict) -> Dict:
|
||||
return {}
|
||||
|
||||
def handle_get_user_present_event_api_request(self, data: Dict) -> Dict:
|
||||
return { "userId": data.get("userId", 0), "length": 0, "userPresentEventList": []}
|
||||
|
||||
return {
|
||||
"userId": data.get("userId", 0),
|
||||
"length": 0,
|
||||
"userPresentEventList": [],
|
||||
}
|
||||
|
||||
def handle_get_user_boss_api_request(self, data: Dict) -> Dict:
|
||||
b = self.data.profile.get_boss_list(data["userId"])
|
||||
if b is None:
|
||||
return { "userId": data.get("userId", 0), "userBossData": {}}
|
||||
return {"userId": data.get("userId", 0), "userBossData": {}}
|
||||
boss_lst = b._asdict()
|
||||
boss_lst.pop("id")
|
||||
boss_lst.pop("user")
|
||||
|
||||
return { "userId": data.get("userId", 0), "userBossData": boss_lst}
|
||||
return {"userId": data.get("userId", 0), "userBossData": boss_lst}
|
||||
|
||||
def handle_get_user_item_api_request(self, data: Dict) -> Dict:
|
||||
kind = int(data["nextIndex"] / 10000000000)
|
||||
|
@ -539,11 +551,15 @@ class Mai2Base:
|
|||
rating = self.data.profile.get_recent_rating(data["userId"])
|
||||
if rating is None:
|
||||
return
|
||||
|
||||
|
||||
r = rating._asdict()
|
||||
lst = r.get("userRecentRatingList", [])
|
||||
|
||||
return {"userId": data["userId"], "length": len(lst), "userRecentRatingList": lst}
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"length": len(lst),
|
||||
"userRecentRatingList": lst,
|
||||
}
|
||||
|
||||
def handle_get_user_rating_api_request(self, data: Dict) -> Dict:
|
||||
rating = self.data.profile.get_profile_rating(data["userId"], self.version)
|
||||
|
@ -707,13 +723,13 @@ class Mai2Base:
|
|||
|
||||
def handle_get_user_region_api_request(self, data: Dict) -> Dict:
|
||||
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
||||
|
||||
|
||||
def handle_get_user_web_option_api_request(self, data: Dict) -> Dict:
|
||||
w = self.data.profile.get_web_option(data["userId"], self.version)
|
||||
if w is None:
|
||||
if w is None:
|
||||
return {"userId": data["userId"], "userWebOption": {}}
|
||||
|
||||
web_opt = w._asdict()
|
||||
|
||||
web_opt = w._asdict()
|
||||
web_opt.pop("id")
|
||||
web_opt.pop("user")
|
||||
web_opt.pop("version")
|
||||
|
@ -726,32 +742,46 @@ class Mai2Base:
|
|||
def handle_get_user_grade_api_request(self, data: Dict) -> Dict:
|
||||
g = self.data.profile.get_grade_status(data["userId"])
|
||||
if g is None:
|
||||
return {"userId": data["userId"], "userGradeStatus": {}, "length": 0, "userGradeList": []}
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userGradeStatus": {},
|
||||
"length": 0,
|
||||
"userGradeList": [],
|
||||
}
|
||||
grade_stat = g._asdict()
|
||||
grade_stat.pop("id")
|
||||
grade_stat.pop("user")
|
||||
|
||||
return {"userId": data["userId"], "userGradeStatus": grade_stat, "length": 0, "userGradeList": []}
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userGradeStatus": grade_stat,
|
||||
"length": 0,
|
||||
"userGradeList": [],
|
||||
}
|
||||
|
||||
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||
user_id = data.get("userId", 0)
|
||||
user_id = data.get("userId", 0)
|
||||
next_index = data.get("nextIndex", 0)
|
||||
max_ct = data.get("maxCount", 50)
|
||||
upper_lim = next_index + max_ct
|
||||
music_detail_list = []
|
||||
|
||||
if user_id <= 0:
|
||||
self.logger.warn("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
|
||||
self.logger.warn(
|
||||
"handle_get_user_music_api_request: Could not find userid in data, or userId is 0"
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
songs = self.data.score.get_best_scores(user_id, is_dx=False)
|
||||
if songs is None:
|
||||
self.logger.debug("handle_get_user_music_api_request: get_best_scores returned None!")
|
||||
self.logger.debug(
|
||||
"handle_get_user_music_api_request: get_best_scores returned None!"
|
||||
)
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"nextIndex": 0,
|
||||
"userMusicList": [],
|
||||
}
|
||||
"userId": data["userId"],
|
||||
"nextIndex": 0,
|
||||
"userMusicList": [],
|
||||
}
|
||||
|
||||
num_user_songs = len(songs)
|
||||
|
||||
|
@ -764,8 +794,14 @@ class Mai2Base:
|
|||
tmp.pop("user")
|
||||
music_detail_list.append(tmp)
|
||||
|
||||
next_index = 0 if len(music_detail_list) < max_ct or num_user_songs == upper_lim else upper_lim
|
||||
self.logger.info(f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})")
|
||||
next_index = (
|
||||
0
|
||||
if len(music_detail_list) < max_ct or num_user_songs == upper_lim
|
||||
else upper_lim
|
||||
)
|
||||
self.logger.info(
|
||||
f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})"
|
||||
)
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"nextIndex": next_index,
|
||||
|
@ -776,14 +812,17 @@ class Mai2Base:
|
|||
self.logger.debug(data)
|
||||
|
||||
def handle_upload_user_photo_api_request(self, data: Dict) -> Dict:
|
||||
if not self.game_config.uploads.photos or not self.game_config.uploads.photos_dir:
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
if (
|
||||
not self.game_config.uploads.photos
|
||||
or not self.game_config.uploads.photos_dir
|
||||
):
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
photo = data.get("userPhoto", {})
|
||||
|
||||
if photo is None or not photo:
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
order_id = int(photo.get("orderId", -1))
|
||||
user_id = int(photo.get("userId", -1))
|
||||
div_num = int(photo.get("divNumber", -1))
|
||||
|
@ -793,72 +832,95 @@ class Mai2Base:
|
|||
track_num = int(photo.get("trackNo", -1))
|
||||
upload_date = photo.get("uploadDate", "")
|
||||
|
||||
if order_id < 0 or user_id <= 0 or div_num < 0 or div_len <= 0 or not div_data or playlog_id < 0 or track_num <= 0 or not upload_date:
|
||||
if (
|
||||
order_id < 0
|
||||
or user_id <= 0
|
||||
or div_num < 0
|
||||
or div_len <= 0
|
||||
or not div_data
|
||||
or playlog_id < 0
|
||||
or track_num <= 0
|
||||
or not upload_date
|
||||
):
|
||||
self.logger.warn(f"Malformed photo upload request")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
if order_id == 0 and div_num > 0:
|
||||
self.logger.warn(f"Failed to set orderId properly (still 0 after first chunk)")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
self.logger.warn(
|
||||
f"Failed to set orderId properly (still 0 after first chunk)"
|
||||
)
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
if div_num == 0 and order_id > 0:
|
||||
self.logger.warn(f"First chuck re-send, Ignore")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
if div_num >= div_len:
|
||||
self.logger.warn(f"Sent extra chunks ({div_num} >= {div_len})")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
if div_len >= 100:
|
||||
self.logger.warn(f"Photo too large ({div_len} * 10240 = {div_len * 10240} bytes)")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
self.logger.warn(
|
||||
f"Photo too large ({div_len} * 10240 = {div_len * 10240} bytes)"
|
||||
)
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
ret_code = order_id + 1
|
||||
photo_chunk = b64decode(div_data)
|
||||
|
||||
if len(photo_chunk) > 10240 or (len(photo_chunk) < 10240 and div_num + 1 != div_len):
|
||||
self.logger.warn(f"Incorrect data size after decoding (Expected 10240, got {len(photo_chunk)})")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
out_name = f"{self.game_config.uploads.photos_dir}/{user_id}_{playlog_id}_{track_num}"
|
||||
if len(photo_chunk) > 10240 or (
|
||||
len(photo_chunk) < 10240 and div_num + 1 != div_len
|
||||
):
|
||||
self.logger.warn(
|
||||
f"Incorrect data size after decoding (Expected 10240, got {len(photo_chunk)})"
|
||||
)
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
out_name = (
|
||||
f"{self.game_config.uploads.photos_dir}/{user_id}_{playlog_id}_{track_num}"
|
||||
)
|
||||
|
||||
if not path.exists(f"{out_name}.bin") and div_num != 0:
|
||||
self.logger.warn(f"Out of order photo upload (div_num {div_num})")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
if path.exists(f"{out_name}.bin") and div_num == 0:
|
||||
self.logger.warn(f"Duplicate file upload")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
elif path.exists(f"{out_name}.bin"):
|
||||
fstats = stat(f"{out_name}.bin")
|
||||
if fstats.st_size != 10240 * div_num:
|
||||
self.logger.warn(f"Out of order photo upload (trying to upload div {div_num}, expected div {fstats.st_size / 10240} for file sized {fstats.st_size} bytes)")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
|
||||
self.logger.warn(
|
||||
f"Out of order photo upload (trying to upload div {div_num}, expected div {fstats.st_size / 10240} for file sized {fstats.st_size} bytes)"
|
||||
)
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
try:
|
||||
with open(f"{out_name}.bin", "ab") as f:
|
||||
f.write(photo_chunk)
|
||||
|
||||
|
||||
except Exception:
|
||||
self.logger.error(f"Failed writing to {out_name}.bin")
|
||||
return {'returnCode': 0, 'apiName': 'UploadUserPhotoApi'}
|
||||
return {"returnCode": 0, "apiName": "UploadUserPhotoApi"}
|
||||
|
||||
if div_num + 1 == div_len and path.exists(f"{out_name}.bin"):
|
||||
try:
|
||||
p = ImageFile.Parser()
|
||||
with open(f"{out_name}.bin", "rb") as f:
|
||||
p.feed(f.read())
|
||||
|
||||
|
||||
im = p.close()
|
||||
im.save(f"{out_name}.jpeg")
|
||||
except Exception:
|
||||
self.logger.error(f"File {out_name}.bin failed image validation")
|
||||
|
||||
|
||||
try:
|
||||
remove(f"{out_name}.bin")
|
||||
|
||||
except Exception:
|
||||
self.logger.error(f"Failed to delete {out_name}.bin, please remove it manually")
|
||||
|
||||
return {'returnCode': ret_code, 'apiName': 'UploadUserPhotoApi'}
|
||||
except Exception:
|
||||
self.logger.error(
|
||||
f"Failed to delete {out_name}.bin, please remove it manually"
|
||||
)
|
||||
|
||||
return {"returnCode": ret_code, "apiName": "UploadUserPhotoApi"}
|
||||
|
|
|
@ -19,6 +19,7 @@ class Mai2ServerConfig:
|
|||
)
|
||||
)
|
||||
|
||||
|
||||
class Mai2DeliverConfig:
|
||||
def __init__(self, parent: "Mai2Config") -> None:
|
||||
self.__config = parent
|
||||
|
@ -34,17 +35,18 @@ class Mai2DeliverConfig:
|
|||
return CoreConfig.get_config_field(
|
||||
self.__config, "mai2", "deliver", "udbdl_enable", default=False
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def content_folder(self) -> int:
|
||||
return CoreConfig.get_config_field(
|
||||
self.__config, "mai2", "server", "content_folder", default=""
|
||||
)
|
||||
|
||||
|
||||
class Mai2UploadsConfig:
|
||||
def __init__(self, parent: "Mai2Config") -> None:
|
||||
self.__config = parent
|
||||
|
||||
|
||||
@property
|
||||
def photos(self) -> bool:
|
||||
return CoreConfig.get_config_field(
|
||||
|
|
|
@ -20,13 +20,13 @@ class Mai2Constants:
|
|||
|
||||
DATE_TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
|
||||
GAME_CODE = "SBXL"
|
||||
GAME_CODE_GREEN = "SBZF"
|
||||
GAME_CODE_ORANGE = "SDBM"
|
||||
GAME_CODE_PINK = "SDCQ"
|
||||
GAME_CODE_MURASAKI = "SDDK"
|
||||
GAME_CODE_MILK = "SDDZ"
|
||||
GAME_CODE_FINALE = "SDEY"
|
||||
GAME_CODE = "SBXL"
|
||||
GAME_CODE_GREEN = "SBZF"
|
||||
GAME_CODE_ORANGE = "SDBM"
|
||||
GAME_CODE_PINK = "SDCQ"
|
||||
GAME_CODE_MURASAKI = "SDDK"
|
||||
GAME_CODE_MILK = "SDDZ"
|
||||
GAME_CODE_FINALE = "SDEY"
|
||||
GAME_CODE_DX = "SDEZ"
|
||||
|
||||
CONFIG_NAME = "mai2.yaml"
|
||||
|
@ -66,7 +66,7 @@ class Mai2Constants:
|
|||
"maimai MURASAKi PLUS",
|
||||
"maimai MiLK",
|
||||
"maimai MiLK PLUS",
|
||||
"maimai FiNALE",
|
||||
"maimai FiNALE",
|
||||
"maimai DX",
|
||||
"maimai DX PLUS",
|
||||
"maimai DX Splash",
|
||||
|
|
|
@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
|||
import pytz
|
||||
import json
|
||||
from random import randint
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.base import Mai2Base
|
||||
|
@ -14,13 +15,13 @@ class Mai2DX(Mai2Base):
|
|||
def __init__(self, cfg: CoreConfig, game_cfg: Mai2Config) -> None:
|
||||
super().__init__(cfg, game_cfg)
|
||||
self.version = Mai2Constants.VER_MAIMAI_DX
|
||||
|
||||
|
||||
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEZ/100/"
|
||||
|
||||
|
||||
else:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}/SDEZ/100/"
|
||||
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict):
|
||||
return {
|
||||
"gameSetting": {
|
||||
|
@ -31,9 +32,13 @@ class Mai2DX(Mai2Base):
|
|||
"movieUploadLimit": 100,
|
||||
"movieStatus": 1,
|
||||
"movieServerUri": self.old_server + "movie/",
|
||||
"deliverServerUri": self.old_server + "deliver/" if self.can_deliver and self.game_config.deliver.enable else "",
|
||||
"deliverServerUri": self.old_server + "deliver/"
|
||||
if self.can_deliver and self.game_config.deliver.enable
|
||||
else "",
|
||||
"oldServerUri": self.old_server + "old",
|
||||
"usbDlServerUri": self.old_server + "usbdl/" if self.can_deliver and self.game_config.deliver.udbdl_enable else "",
|
||||
"usbDlServerUri": self.old_server + "usbdl/"
|
||||
if self.can_deliver and self.game_config.deliver.udbdl_enable
|
||||
else "",
|
||||
"rebootInterval": 0,
|
||||
},
|
||||
"isAouAccession": False,
|
||||
|
@ -103,117 +108,119 @@ class Mai2DX(Mai2Base):
|
|||
user_id = data["userId"]
|
||||
upsert = data["upsertUserAll"]
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
upsert["userData"][0]["isNetMember"] = 1
|
||||
upsert["userData"][0].pop("accessCode")
|
||||
self.data.profile.put_profile_detail(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
|
||||
self.data.profile.put_profile_extend(
|
||||
user_id, self.version, upsert["userExtend"][0]
|
||||
)
|
||||
|
||||
if "userGhost" in upsert:
|
||||
for ghost in upsert["userGhost"]:
|
||||
self.data.profile.put_profile_ghost(user_id, self.version, ghost)
|
||||
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
self.data.profile.put_profile_option(
|
||||
user_id, self.version, upsert["userOption"][0]
|
||||
)
|
||||
|
||||
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
|
||||
self.data.profile.put_profile_rating(
|
||||
user_id, self.version, upsert["userRatingList"][0]
|
||||
)
|
||||
|
||||
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
|
||||
for k, v in upsert["userActivityList"][0].items():
|
||||
for act in v:
|
||||
self.data.profile.put_profile_activity(user_id, act)
|
||||
|
||||
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
|
||||
for charge in upsert["userChargeList"]:
|
||||
# remove the ".0" from the date string, festival only?
|
||||
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
|
||||
self.data.item.put_charge(
|
||||
user_id,
|
||||
charge["chargeId"],
|
||||
charge["stock"],
|
||||
datetime.strptime(
|
||||
charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
datetime.strptime(
|
||||
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
upsert["userData"][0]["isNetMember"] = 1
|
||||
upsert["userData"][0].pop("accessCode")
|
||||
self.data.profile.put_profile_detail(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
|
||||
for char in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(
|
||||
user_id,
|
||||
char["characterId"],
|
||||
char["level"],
|
||||
char["awakening"],
|
||||
char["useCount"],
|
||||
if "userExtend" in upsert and len(upsert["userExtend"]) > 0:
|
||||
self.data.profile.put_profile_extend(
|
||||
user_id, self.version, upsert["userExtend"][0]
|
||||
)
|
||||
|
||||
if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(
|
||||
user_id,
|
||||
int(item["itemKind"]),
|
||||
item["itemId"],
|
||||
item["stock"],
|
||||
item["isValid"],
|
||||
if "userGhost" in upsert:
|
||||
for ghost in upsert["userGhost"]:
|
||||
self.data.profile.put_profile_ghost(user_id, self.version, ghost)
|
||||
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
self.data.profile.put_profile_option(
|
||||
user_id, self.version, upsert["userOption"][0]
|
||||
)
|
||||
|
||||
if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
|
||||
for login_bonus in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id,
|
||||
login_bonus["bonusId"],
|
||||
login_bonus["point"],
|
||||
login_bonus["isCurrent"],
|
||||
login_bonus["isComplete"],
|
||||
if "userRatingList" in upsert and len(upsert["userRatingList"]) > 0:
|
||||
self.data.profile.put_profile_rating(
|
||||
user_id, self.version, upsert["userRatingList"][0]
|
||||
)
|
||||
|
||||
if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(
|
||||
user_id,
|
||||
map["mapId"],
|
||||
map["distance"],
|
||||
map["isLock"],
|
||||
map["isClear"],
|
||||
map["isComplete"],
|
||||
)
|
||||
if "userActivityList" in upsert and len(upsert["userActivityList"]) > 0:
|
||||
for k, v in upsert["userActivityList"][0].items():
|
||||
for act in v:
|
||||
self.data.profile.put_profile_activity(user_id, act)
|
||||
|
||||
if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
|
||||
for music in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, music)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userChargeList" in upsert and len(upsert["userChargeList"]) > 0:
|
||||
for charge in upsert["userChargeList"]:
|
||||
# remove the ".0" from the date string, festival only?
|
||||
charge["purchaseDate"] = charge["purchaseDate"].replace(".0", "")
|
||||
self.data.item.put_charge(
|
||||
user_id,
|
||||
charge["chargeId"],
|
||||
charge["stock"],
|
||||
datetime.strptime(
|
||||
charge["purchaseDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
datetime.strptime(
|
||||
charge["validDate"], Mai2Constants.DATE_TIME_FORMAT
|
||||
),
|
||||
)
|
||||
|
||||
if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
if "userCharacterList" in upsert and len(upsert["userCharacterList"]) > 0:
|
||||
for char in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(
|
||||
user_id,
|
||||
char["characterId"],
|
||||
char["level"],
|
||||
char["awakening"],
|
||||
char["useCount"],
|
||||
)
|
||||
|
||||
if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
|
||||
for fav in upsert["userFavoriteList"]:
|
||||
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||
if "userItemList" in upsert and len(upsert["userItemList"]) > 0:
|
||||
for item in upsert["userItemList"]:
|
||||
self.data.item.put_item(
|
||||
user_id,
|
||||
int(item["itemKind"]),
|
||||
item["itemId"],
|
||||
item["stock"],
|
||||
item["isValid"],
|
||||
)
|
||||
|
||||
if (
|
||||
"userFriendSeasonRankingList" in upsert
|
||||
and len(upsert["userFriendSeasonRankingList"]) > 0
|
||||
):
|
||||
for fsr in upsert["userFriendSeasonRankingList"]:
|
||||
fsr["recordDate"] = (
|
||||
datetime.strptime(
|
||||
fsr["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
|
||||
),
|
||||
)
|
||||
self.data.item.put_friend_season_ranking(user_id, fsr)
|
||||
if "userLoginBonusList" in upsert and len(upsert["userLoginBonusList"]) > 0:
|
||||
for login_bonus in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(
|
||||
user_id,
|
||||
login_bonus["bonusId"],
|
||||
login_bonus["point"],
|
||||
login_bonus["isCurrent"],
|
||||
login_bonus["isComplete"],
|
||||
)
|
||||
|
||||
if "userMapList" in upsert and len(upsert["userMapList"]) > 0:
|
||||
for map in upsert["userMapList"]:
|
||||
self.data.item.put_map(
|
||||
user_id,
|
||||
map["mapId"],
|
||||
map["distance"],
|
||||
map["isLock"],
|
||||
map["isClear"],
|
||||
map["isComplete"],
|
||||
)
|
||||
if "userFavoriteList" in upsert and len(upsert["userFavoriteList"]) > 0:
|
||||
for fav in upsert["userFavoriteList"]:
|
||||
self.data.item.put_favorite(user_id, fav["kind"], fav["itemIdList"])
|
||||
|
||||
if (
|
||||
"userFriendSeasonRankingList" in upsert
|
||||
and len(upsert["userFriendSeasonRankingList"]) > 0
|
||||
):
|
||||
for fsr in upsert["userFriendSeasonRankingList"]:
|
||||
fsr["recordDate"] = (
|
||||
datetime.strptime(
|
||||
fsr["recordDate"], f"{Mai2Constants.DATE_TIME_FORMAT}.0"
|
||||
),
|
||||
)
|
||||
self.data.item.put_friend_season_ranking(user_id, fsr)
|
||||
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
if "userMusicDetailList" in upsert and len(upsert["userMusicDetailList"]) > 0:
|
||||
for music in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, music)
|
||||
|
||||
if "userCourseList" in upsert and len(upsert["userCourseList"]) > 0:
|
||||
for course in upsert["userCourseList"]:
|
||||
self.data.score.put_course(user_id, course)
|
||||
|
||||
return {"returnCode": 1, "apiName": "UpsertUserAllApi"}
|
||||
|
||||
|
@ -545,24 +552,28 @@ class Mai2DX(Mai2Base):
|
|||
return {"userId": data["userId"], "length": 0, "userRegionList": []}
|
||||
|
||||
def handle_get_user_music_api_request(self, data: Dict) -> Dict:
|
||||
user_id = data.get("userId", 0)
|
||||
user_id = data.get("userId", 0)
|
||||
next_index = data.get("nextIndex", 0)
|
||||
max_ct = data.get("maxCount", 50)
|
||||
upper_lim = next_index + max_ct
|
||||
music_detail_list = []
|
||||
|
||||
if user_id <= 0:
|
||||
self.logger.warn("handle_get_user_music_api_request: Could not find userid in data, or userId is 0")
|
||||
self.logger.warn(
|
||||
"handle_get_user_music_api_request: Could not find userid in data, or userId is 0"
|
||||
)
|
||||
return {}
|
||||
|
||||
|
||||
songs = self.data.score.get_best_scores(user_id)
|
||||
if songs is None:
|
||||
self.logger.debug("handle_get_user_music_api_request: get_best_scores returned None!")
|
||||
self.logger.debug(
|
||||
"handle_get_user_music_api_request: get_best_scores returned None!"
|
||||
)
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"nextIndex": 0,
|
||||
"userMusicList": [],
|
||||
}
|
||||
"userId": data["userId"],
|
||||
"nextIndex": 0,
|
||||
"userMusicList": [],
|
||||
}
|
||||
|
||||
num_user_songs = len(songs)
|
||||
|
||||
|
@ -575,8 +586,14 @@ class Mai2DX(Mai2Base):
|
|||
tmp.pop("user")
|
||||
music_detail_list.append(tmp)
|
||||
|
||||
next_index = 0 if len(music_detail_list) < max_ct or num_user_songs == upper_lim else upper_lim
|
||||
self.logger.info(f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})")
|
||||
next_index = (
|
||||
0
|
||||
if len(music_detail_list) < max_ct or num_user_songs == upper_lim
|
||||
else upper_lim
|
||||
)
|
||||
self.logger.info(
|
||||
f"Send songs {next_index}-{upper_lim} ({len(music_detail_list)}) out of {num_user_songs} for user {user_id} (next idx {next_index})"
|
||||
)
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"nextIndex": next_index,
|
||||
|
@ -587,5 +604,5 @@ class Mai2DX(Mai2Base):
|
|||
ret = super().handle_user_login_api_request(data)
|
||||
if ret is None or not ret:
|
||||
return ret
|
||||
ret['loginId'] = ret.get('loginCount', 0)
|
||||
ret["loginId"] = ret.get("loginCount", 0)
|
||||
return ret
|
||||
|
|
|
@ -15,9 +15,9 @@ class Mai2Finale(Mai2Base):
|
|||
self.version = Mai2Constants.VER_MAIMAI_FINALE
|
||||
self.can_deliver = True
|
||||
self.can_usbdl = True
|
||||
|
||||
|
||||
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEY/197/"
|
||||
|
||||
|
||||
else:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}/SDEY/197/"
|
||||
|
|
|
@ -47,7 +47,7 @@ class Mai2Servlet:
|
|||
None,
|
||||
None,
|
||||
None,
|
||||
Mai2Finale,
|
||||
Mai2Finale,
|
||||
Mai2DX,
|
||||
Mai2DXPlus,
|
||||
Mai2Splash,
|
||||
|
@ -110,22 +110,34 @@ class Mai2Servlet:
|
|||
)
|
||||
|
||||
def setup(self):
|
||||
if self.game_cfg.uploads.photos and self.game_cfg.uploads.photos_dir and not path.exists(self.game_cfg.uploads.photos_dir):
|
||||
if (
|
||||
self.game_cfg.uploads.photos
|
||||
and self.game_cfg.uploads.photos_dir
|
||||
and not path.exists(self.game_cfg.uploads.photos_dir)
|
||||
):
|
||||
try:
|
||||
mkdir(self.game_cfg.uploads.photos_dir)
|
||||
except Exception:
|
||||
self.logger.error(f"Failed to make photo upload directory at {self.game_cfg.uploads.photos_dir}")
|
||||
self.logger.error(
|
||||
f"Failed to make photo upload directory at {self.game_cfg.uploads.photos_dir}"
|
||||
)
|
||||
|
||||
if self.game_cfg.uploads.movies and self.game_cfg.uploads.movies_dir and not path.exists(self.game_cfg.uploads.movies_dir):
|
||||
if (
|
||||
self.game_cfg.uploads.movies
|
||||
and self.game_cfg.uploads.movies_dir
|
||||
and not path.exists(self.game_cfg.uploads.movies_dir)
|
||||
):
|
||||
try:
|
||||
mkdir(self.game_cfg.uploads.movies_dir)
|
||||
except Exception:
|
||||
self.logger.error(f"Failed to make movie upload directory at {self.game_cfg.uploads.movies_dir}")
|
||||
self.logger.error(
|
||||
f"Failed to make movie upload directory at {self.game_cfg.uploads.movies_dir}"
|
||||
)
|
||||
|
||||
def render_POST(self, request: Request, version: int, url_path: str) -> bytes:
|
||||
if url_path.lower() == "ping":
|
||||
return zlib.compress(b'{"returnCode": "1"}')
|
||||
|
||||
|
||||
elif url_path.startswith("api/movie/"):
|
||||
self.logger.info(f"Movie data: {url_path} - {request.content.getvalue()}")
|
||||
return b""
|
||||
|
@ -180,7 +192,7 @@ class Mai2Servlet:
|
|||
internal_ver = Mai2Constants.VER_MAIMAI_MILK_PLUS
|
||||
elif version >= 197: # Finale
|
||||
internal_ver = Mai2Constants.VER_MAIMAI_FINALE
|
||||
|
||||
|
||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||
# If we get a 32 character long hex string, it's a hash and we're
|
||||
# doing encrypted. The likelyhood of false positives is low but
|
||||
|
@ -228,9 +240,11 @@ class Mai2Servlet:
|
|||
self.logger.info(f"v{version} GET {url_path}")
|
||||
url_split = url_path.split("/")
|
||||
|
||||
if (url_split[0] == "api" and url_split[1] == "movie") or url_split[0] == "movie":
|
||||
if (url_split[0] == "api" and url_split[1] == "movie") or url_split[
|
||||
0
|
||||
] == "movie":
|
||||
if url_split[2] == "moviestart":
|
||||
return json.dumps({"moviestart":{"status":"OK"}}).encode()
|
||||
return json.dumps({"moviestart": {"status": "OK"}}).encode()
|
||||
|
||||
if url_split[0] == "old":
|
||||
if url_split[1] == "ping":
|
||||
|
@ -244,7 +258,7 @@ class Mai2Servlet:
|
|||
elif url_split[1].startswith("friend"):
|
||||
self.logger.info(f"v{version} old server friend inquire")
|
||||
return zlib.compress(b"{}")
|
||||
|
||||
|
||||
elif url_split[0] == "usbdl":
|
||||
if url_split[1] == "CONNECTIONTEST":
|
||||
self.logger.info(f"v{version} usbdl server test")
|
||||
|
@ -253,9 +267,11 @@ class Mai2Servlet:
|
|||
elif url_split[0] == "deliver":
|
||||
file = url_split[len(url_split) - 1]
|
||||
self.logger.info(f"v{version} {file} deliver inquire")
|
||||
|
||||
if not self.game_cfg.deliver.enable or not path.exists(f"{self.game_cfg.deliver.content_folder}/{file}"):
|
||||
|
||||
if not self.game_cfg.deliver.enable or not path.exists(
|
||||
f"{self.game_cfg.deliver.content_folder}/{file}"
|
||||
):
|
||||
return zlib.compress(b"")
|
||||
|
||||
|
||||
else:
|
||||
return zlib.compress(b"{}")
|
||||
|
|
|
@ -7,6 +7,7 @@ from typing import Any, Dict, List, Optional
|
|||
from Crypto.Cipher import AES
|
||||
import zlib
|
||||
import codecs
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from core.data import Data
|
||||
|
@ -46,64 +47,84 @@ class Mai2Reader(BaseReader):
|
|||
|
||||
for dir in data_dirs:
|
||||
self.logger.info(f"Read from {dir}")
|
||||
self.get_events(f"{dir}/event")
|
||||
self.disable_events(f"{dir}/information", f"{dir}/scoreRanking")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_tickets(f"{dir}/ticket")
|
||||
|
||||
|
||||
with self.data.static.conn.begin() or nullcontext():
|
||||
self.get_events(f"{dir}/event")
|
||||
self.disable_events(f"{dir}/information", f"{dir}/scoreRanking")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_tickets(f"{dir}/ticket")
|
||||
|
||||
else:
|
||||
if not os.path.exists(f"{self.bin_dir}/tables"):
|
||||
self.logger.error(f"tables directory not found in {self.bin_dir}")
|
||||
return
|
||||
|
||||
|
||||
if self.version >= Mai2Constants.VER_MAIMAI_MILK:
|
||||
if self.extra is None:
|
||||
self.logger.error("Milk - Finale requre an AES key via a hex string send as the --extra flag")
|
||||
self.logger.error(
|
||||
"Milk - Finale requre an AES key via a hex string send as the --extra flag"
|
||||
)
|
||||
return
|
||||
|
||||
|
||||
key = bytes.fromhex(self.extra)
|
||||
|
||||
|
||||
else:
|
||||
key = None
|
||||
|
||||
evt_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmEvent.bin", key)
|
||||
txt_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmtextout_jp.bin", key)
|
||||
score_table = self.load_table_raw(f"{self.bin_dir}/tables", "mmScore.bin", key)
|
||||
|
||||
|
||||
evt_table = self.load_table_raw(
|
||||
f"{self.bin_dir}/tables", "mmEvent.bin", key
|
||||
)
|
||||
txt_table = self.load_table_raw(
|
||||
f"{self.bin_dir}/tables", "mmtextout_jp.bin", key
|
||||
)
|
||||
score_table = self.load_table_raw(
|
||||
f"{self.bin_dir}/tables", "mmScore.bin", key
|
||||
)
|
||||
|
||||
self.read_old_events(evt_table)
|
||||
self.read_old_music(score_table, txt_table)
|
||||
|
||||
|
||||
if self.opt_dir is not None:
|
||||
evt_table = self.load_table_raw(f"{self.opt_dir}/tables", "mmEvent.bin", key)
|
||||
txt_table = self.load_table_raw(f"{self.opt_dir}/tables", "mmtextout_jp.bin", key)
|
||||
score_table = self.load_table_raw(f"{self.opt_dir}/tables", "mmScore.bin", key)
|
||||
evt_table = self.load_table_raw(
|
||||
f"{self.opt_dir}/tables", "mmEvent.bin", key
|
||||
)
|
||||
txt_table = self.load_table_raw(
|
||||
f"{self.opt_dir}/tables", "mmtextout_jp.bin", key
|
||||
)
|
||||
score_table = self.load_table_raw(
|
||||
f"{self.opt_dir}/tables", "mmScore.bin", key
|
||||
)
|
||||
|
||||
self.read_old_events(evt_table)
|
||||
self.read_old_music(score_table, txt_table)
|
||||
|
||||
return
|
||||
|
||||
def load_table_raw(self, dir: str, file: str, key: Optional[bytes]) -> Optional[List[Dict[str, str]]]:
|
||||
|
||||
def load_table_raw(
|
||||
self, dir: str, file: str, key: Optional[bytes]
|
||||
) -> Optional[List[Dict[str, str]]]:
|
||||
if not os.path.exists(f"{dir}/{file}"):
|
||||
self.logger.warn(f"file {file} does not exist in directory {dir}, skipping")
|
||||
return
|
||||
|
||||
|
||||
self.logger.info(f"Load table {file} from {dir}")
|
||||
if key is not None:
|
||||
cipher = AES.new(key, AES.MODE_CBC)
|
||||
with open(f"{dir}/{file}", "rb") as f:
|
||||
f_encrypted = f.read()
|
||||
f_data = cipher.decrypt(f_encrypted)[0x10:]
|
||||
|
||||
|
||||
else:
|
||||
with open(f"{dir}/{file}", "rb") as f:
|
||||
f_data = f.read()[0x10:]
|
||||
|
||||
|
||||
if f_data is None or not f_data:
|
||||
self.logger.warn(f"file {dir} could not be read, skipping")
|
||||
return
|
||||
|
||||
f_data_deflate = zlib.decompress(f_data, wbits = zlib.MAX_WBITS | 16)[0x12:] # lop off the junk at the beginning
|
||||
|
||||
f_data_deflate = zlib.decompress(f_data, wbits=zlib.MAX_WBITS | 16)[
|
||||
0x12:
|
||||
] # lop off the junk at the beginning
|
||||
f_decoded = codecs.utf_16_le_decode(f_data_deflate)[0]
|
||||
f_split = f_decoded.splitlines()
|
||||
|
||||
|
@ -118,31 +139,31 @@ class Mai2Reader(BaseReader):
|
|||
is_struct = True
|
||||
struct_name = x[7:-1]
|
||||
continue
|
||||
|
||||
|
||||
if x.startswith("};"):
|
||||
is_struct = False
|
||||
break
|
||||
|
||||
if is_struct:
|
||||
try:
|
||||
struct_def.append(x[x.rindex(" ") + 2: -1])
|
||||
struct_def.append(x[x.rindex(" ") + 2 : -1])
|
||||
except ValueError:
|
||||
self.logger.warn(f"rindex failed on line {x}")
|
||||
|
||||
|
||||
if is_struct:
|
||||
self.logger.warn("Struct not formatted properly")
|
||||
|
||||
|
||||
if not struct_def:
|
||||
self.logger.warn("Struct def not found")
|
||||
|
||||
name = file[:file.index(".")]
|
||||
|
||||
name = file[: file.index(".")]
|
||||
if "_" in name:
|
||||
name = name[:file.index("_")]
|
||||
|
||||
name = name[: file.index("_")]
|
||||
|
||||
for x in f_split:
|
||||
if not x.startswith(name.upper()):
|
||||
continue
|
||||
|
||||
|
||||
line_match = re.match(r"(\w+)\((.*?)\)([ ]+\/{3}<[ ]+(.*))?", x)
|
||||
if line_match is None:
|
||||
continue
|
||||
|
@ -150,31 +171,31 @@ class Mai2Reader(BaseReader):
|
|||
if not line_match.group(1) == name.upper():
|
||||
self.logger.warn(f"Strange regex match for line {x} -> {line_match}")
|
||||
continue
|
||||
|
||||
|
||||
vals = line_match.group(2)
|
||||
comment = line_match.group(4)
|
||||
line_dict = {}
|
||||
|
||||
|
||||
vals_split = vals.split(",")
|
||||
for y in range(len(vals_split)):
|
||||
stripped = vals_split[y].strip().lstrip("L\"").lstrip("\"").rstrip("\"")
|
||||
stripped = vals_split[y].strip().lstrip('L"').lstrip('"').rstrip('"')
|
||||
if not stripped or stripped is None:
|
||||
continue
|
||||
|
||||
if has_struct_def and len(struct_def) > y:
|
||||
line_dict[struct_def[y]] = stripped
|
||||
|
||||
|
||||
else:
|
||||
line_dict[f'item_{y}'] = stripped
|
||||
|
||||
line_dict[f"item_{y}"] = stripped
|
||||
|
||||
if comment:
|
||||
line_dict['comment'] = comment
|
||||
|
||||
line_dict["comment"] = comment
|
||||
|
||||
tbl_content.append(line_dict)
|
||||
|
||||
if tbl_content:
|
||||
return tbl_content
|
||||
|
||||
|
||||
else:
|
||||
self.logger.warning("Failed load table content, skipping")
|
||||
return
|
||||
|
@ -324,20 +345,24 @@ class Mai2Reader(BaseReader):
|
|||
def read_old_events(self, events: Optional[List[Dict[str, str]]]) -> None:
|
||||
if events is None:
|
||||
return
|
||||
|
||||
|
||||
for event in events:
|
||||
evt_id = int(event.get('イベントID', '0'))
|
||||
evt_expire_time = float(event.get('オフ時強制時期', '0.0'))
|
||||
is_exp = bool(int(event.get('海外許可', '0')))
|
||||
is_aou = bool(int(event.get('AOU許可', '0')))
|
||||
name = event.get('comment', f'evt_{evt_id}')
|
||||
evt_id = int(event.get("イベントID", "0"))
|
||||
evt_expire_time = float(event.get("オフ時強制時期", "0.0"))
|
||||
is_exp = bool(int(event.get("海外許可", "0")))
|
||||
is_aou = bool(int(event.get("AOU許可", "0")))
|
||||
name = event.get("comment", f"evt_{evt_id}")
|
||||
|
||||
self.data.static.put_game_event(self.version, 0, evt_id, name)
|
||||
|
||||
|
||||
if not (is_exp or is_aou):
|
||||
self.data.static.toggle_game_event(self.version, evt_id, False)
|
||||
|
||||
def read_old_music(self, scores: Optional[List[Dict[str, str]]], text: Optional[List[Dict[str, str]]]) -> None:
|
||||
|
||||
def read_old_music(
|
||||
self,
|
||||
scores: Optional[List[Dict[str, str]]],
|
||||
text: Optional[List[Dict[str, str]]],
|
||||
) -> None:
|
||||
if scores is None or text is None:
|
||||
return
|
||||
# TODO
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from titles.mai2.schema.profile import Mai2ProfileData
|
||||
from titles.mai2.schema.item import Mai2ItemData
|
||||
from titles.mai2.schema.static import Mai2StaticData
|
||||
from titles.mai2.schema.score import Mai2ScoreData
|
||||
|
||||
__all__ = [Mai2ProfileData, Mai2ItemData, Mai2StaticData, Mai2ScoreData]
|
||||
from titles.mai2.schema.profile import Mai2ProfileData
|
||||
from titles.mai2.schema.item import Mai2ItemData
|
||||
from titles.mai2.schema.static import Mai2StaticData
|
||||
from titles.mai2.schema.score import Mai2ScoreData
|
||||
|
||||
__all__ = [Mai2ProfileData, Mai2ItemData, Mai2StaticData, Mai2ScoreData]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -1,365 +1,376 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
best_score = Table(
|
||||
"mai2_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("comboStatus", Integer),
|
||||
Column("syncStatus", Integer),
|
||||
Column("deluxscoreMax", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("extNum1", Integer, server_default="0"),
|
||||
UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"mai2_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("userId", BigInteger),
|
||||
Column("orderId", Integer),
|
||||
Column("playlogId", BigInteger),
|
||||
Column("version", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("loginDate", BigInteger),
|
||||
Column("playDate", String(255)),
|
||||
Column("userPlayDate", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("trackNo", Integer),
|
||||
Column("vsMode", Integer),
|
||||
Column("vsUserName", String(255)),
|
||||
Column("vsStatus", Integer),
|
||||
Column("vsUserRating", Integer),
|
||||
Column("vsUserAchievement", Integer),
|
||||
Column("vsUserGradeRank", Integer),
|
||||
Column("vsRank", Integer),
|
||||
Column("playerNum", Integer),
|
||||
Column("playedUserId1", BigInteger),
|
||||
Column("playedUserName1", String(255)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedUserId2", BigInteger),
|
||||
Column("playedUserName2", String(255)),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedUserId3", BigInteger),
|
||||
Column("playedUserName3", String(255)),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("characterId1", Integer),
|
||||
Column("characterLevel1", Integer),
|
||||
Column("characterAwakening1", Integer),
|
||||
Column("characterId2", Integer),
|
||||
Column("characterLevel2", Integer),
|
||||
Column("characterAwakening2", Integer),
|
||||
Column("characterId3", Integer),
|
||||
Column("characterLevel3", Integer),
|
||||
Column("characterAwakening3", Integer),
|
||||
Column("characterId4", Integer),
|
||||
Column("characterLevel4", Integer),
|
||||
Column("characterAwakening4", Integer),
|
||||
Column("characterId5", Integer),
|
||||
Column("characterLevel5", Integer),
|
||||
Column("characterAwakening5", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("deluxscore", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("totalCombo", Integer),
|
||||
Column("maxSync", Integer),
|
||||
Column("totalSync", Integer),
|
||||
Column("tapCriticalPerfect", Integer),
|
||||
Column("tapPerfect", Integer),
|
||||
Column("tapGreat", Integer),
|
||||
Column("tapGood", Integer),
|
||||
Column("tapMiss", Integer),
|
||||
Column("holdCriticalPerfect", Integer),
|
||||
Column("holdPerfect", Integer),
|
||||
Column("holdGreat", Integer),
|
||||
Column("holdGood", Integer),
|
||||
Column("holdMiss", Integer),
|
||||
Column("slideCriticalPerfect", Integer),
|
||||
Column("slidePerfect", Integer),
|
||||
Column("slideGreat", Integer),
|
||||
Column("slideGood", Integer),
|
||||
Column("slideMiss", Integer),
|
||||
Column("touchCriticalPerfect", Integer),
|
||||
Column("touchPerfect", Integer),
|
||||
Column("touchGreat", Integer),
|
||||
Column("touchGood", Integer),
|
||||
Column("touchMiss", Integer),
|
||||
Column("breakCriticalPerfect", Integer),
|
||||
Column("breakPerfect", Integer),
|
||||
Column("breakGreat", Integer),
|
||||
Column("breakGood", Integer),
|
||||
Column("breakMiss", Integer),
|
||||
Column("isTap", Boolean),
|
||||
Column("isHold", Boolean),
|
||||
Column("isSlide", Boolean),
|
||||
Column("isTouch", Boolean),
|
||||
Column("isBreak", Boolean),
|
||||
Column("isCriticalDisp", Boolean),
|
||||
Column("isFastLateDisp", Boolean),
|
||||
Column("fastCount", Integer),
|
||||
Column("lateCount", Integer),
|
||||
Column("isAchieveNewRecord", Boolean),
|
||||
Column("isDeluxscoreNewRecord", Boolean),
|
||||
Column("comboStatus", Integer),
|
||||
Column("syncStatus", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("beforeRating", Integer),
|
||||
Column("afterRating", Integer),
|
||||
Column("beforeGrade", Integer),
|
||||
Column("afterGrade", Integer),
|
||||
Column("afterGradeRank", Integer),
|
||||
Column("beforeDeluxRating", Integer),
|
||||
Column("afterDeluxRating", Integer),
|
||||
Column("isPlayTutorial", Boolean),
|
||||
Column("isEventMode", Boolean),
|
||||
Column("isFreedomMode", Boolean),
|
||||
Column("playMode", Integer),
|
||||
Column("isNewFree", Boolean),
|
||||
Column("extNum1", Integer),
|
||||
Column("extNum2", Integer),
|
||||
Column("extNum4", Integer, server_default="0"),
|
||||
Column("trialPlayAchievement", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
course = Table(
|
||||
"mai2_score_course",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("courseId", Integer),
|
||||
Column("isLastClear", Boolean),
|
||||
Column("totalRestlife", Integer),
|
||||
Column("totalAchievement", Integer),
|
||||
Column("totalDeluxscore", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("clearDate", String(25)),
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("bestAchievement", Integer),
|
||||
Column("bestAchievementDate", String(25)),
|
||||
Column("bestDeluxscore", Integer),
|
||||
Column("bestDeluxscoreDate", String(25)),
|
||||
UniqueConstraint("user", "courseId", name="mai2_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog_old = Table(
|
||||
"maimai_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer),
|
||||
# Pop access code
|
||||
Column("orderId", Integer),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("country", String(255)),
|
||||
Column("regionId", Integer),
|
||||
Column("playDate", String(255)),
|
||||
Column("userPlayDate", String(255)),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("gameMode", Integer),
|
||||
Column("rivalNum", Integer),
|
||||
Column("track", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("isFreeToPlay", Boolean),
|
||||
Column("playerRating", Integer),
|
||||
Column("playedUserId1", Integer),
|
||||
Column("playedUserId2", Integer),
|
||||
Column("playedUserId3", Integer),
|
||||
Column("playedUserName1", String(255)),
|
||||
Column("playedUserName2", String(255)),
|
||||
Column("playedUserName3", String(255)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("score", Integer),
|
||||
Column("tapScore", Integer),
|
||||
Column("holdScore", Integer),
|
||||
Column("slideScore", Integer),
|
||||
Column("breakScore", Integer),
|
||||
Column("syncRate", Integer),
|
||||
Column("vsWin", Integer),
|
||||
Column("isAllPerfect", Boolean),
|
||||
Column("fullCombo", Integer),
|
||||
Column("maxFever", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("tapPerfect", Integer),
|
||||
Column("tapGreat", Integer),
|
||||
Column("tapGood", Integer),
|
||||
Column("tapBad", Integer),
|
||||
Column("holdPerfect", Integer),
|
||||
Column("holdGreat", Integer),
|
||||
Column("holdGood", Integer),
|
||||
Column("holdBad", Integer),
|
||||
Column("slidePerfect", Integer),
|
||||
Column("slideGreat", Integer),
|
||||
Column("slideGood", Integer),
|
||||
Column("slideBad", Integer),
|
||||
Column("breakPerfect", Integer),
|
||||
Column("breakGreat", Integer),
|
||||
Column("breakGood", Integer),
|
||||
Column("breakBad", Integer),
|
||||
Column("judgeStyle", Integer),
|
||||
Column("isTrackSkip", Boolean),
|
||||
Column("isHighScore", Boolean),
|
||||
Column("isChallengeTrack", Boolean),
|
||||
Column("challengeLife", Integer),
|
||||
Column("challengeRemain", Integer),
|
||||
Column("isAllPerfectPlus", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
best_score_old = Table(
|
||||
"maimai_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("scoreMax", Integer),
|
||||
Column("syncRateMax", Integer),
|
||||
Column("isAllPerfect", Boolean),
|
||||
Column("isAllPerfectPlus", Integer),
|
||||
Column("fullCombo", Integer),
|
||||
Column("maxFever", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="maimai_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
class Mai2ScoreData(BaseData):
|
||||
def put_best_score(self, user_id: int, score_data: Dict, is_dx: bool = True) -> Optional[int]:
|
||||
score_data["user"] = user_id
|
||||
|
||||
if is_dx:
|
||||
sql = insert(best_score).values(**score_data)
|
||||
else:
|
||||
sql = insert(best_score_old).values(**score_data)
|
||||
conflict = sql.on_duplicate_key_update(**score_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"put_best_score: Failed to insert best score! user_id {user_id} is_dx {is_dx}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
@cached(2)
|
||||
def get_best_scores(self, user_id: int, song_id: int = None, is_dx: bool = True) -> Optional[List[Row]]:
|
||||
if is_dx:
|
||||
sql = best_score.select(
|
||||
and_(
|
||||
best_score.c.user == user_id,
|
||||
(best_score.c.song_id == song_id) if song_id is not None else True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
sql = best_score_old.select(
|
||||
and_(
|
||||
best_score_old.c.user == user_id,
|
||||
(best_score_old.c.song_id == song_id) if song_id is not None else True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_score(
|
||||
self, user_id: int, song_id: int, chart_id: int
|
||||
) -> Optional[Row]:
|
||||
sql = best_score.select(
|
||||
and_(
|
||||
best_score.c.user == user_id,
|
||||
best_score.c.song_id == song_id,
|
||||
best_score.c.chart_id == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_playlog(self, user_id: int, playlog_data: Dict, is_dx: bool = True) -> Optional[int]:
|
||||
playlog_data["user"] = user_id
|
||||
|
||||
if is_dx:
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
else:
|
||||
sql = insert(playlog_old).values(**playlog_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**playlog_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_playlog: Failed to insert! user_id {user_id} is_dx {is_dx}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_course(self, user_id: int, course_data: Dict) -> Optional[int]:
|
||||
course_data["user"] = user_id
|
||||
sql = insert(course).values(**course_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**course_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_course: Failed to insert! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_courses(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = course.select(course.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, BigInteger
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from core.data import cached
|
||||
|
||||
best_score = Table(
|
||||
"mai2_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("comboStatus", Integer),
|
||||
Column("syncStatus", Integer),
|
||||
Column("deluxscoreMax", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("extNum1", Integer, server_default="0"),
|
||||
UniqueConstraint("user", "musicId", "level", name="mai2_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"mai2_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("userId", BigInteger),
|
||||
Column("orderId", Integer),
|
||||
Column("playlogId", BigInteger),
|
||||
Column("version", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("loginDate", BigInteger),
|
||||
Column("playDate", String(255)),
|
||||
Column("userPlayDate", String(255)),
|
||||
Column("type", Integer),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("trackNo", Integer),
|
||||
Column("vsMode", Integer),
|
||||
Column("vsUserName", String(255)),
|
||||
Column("vsStatus", Integer),
|
||||
Column("vsUserRating", Integer),
|
||||
Column("vsUserAchievement", Integer),
|
||||
Column("vsUserGradeRank", Integer),
|
||||
Column("vsRank", Integer),
|
||||
Column("playerNum", Integer),
|
||||
Column("playedUserId1", BigInteger),
|
||||
Column("playedUserName1", String(255)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedUserId2", BigInteger),
|
||||
Column("playedUserName2", String(255)),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedUserId3", BigInteger),
|
||||
Column("playedUserName3", String(255)),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("characterId1", Integer),
|
||||
Column("characterLevel1", Integer),
|
||||
Column("characterAwakening1", Integer),
|
||||
Column("characterId2", Integer),
|
||||
Column("characterLevel2", Integer),
|
||||
Column("characterAwakening2", Integer),
|
||||
Column("characterId3", Integer),
|
||||
Column("characterLevel3", Integer),
|
||||
Column("characterAwakening3", Integer),
|
||||
Column("characterId4", Integer),
|
||||
Column("characterLevel4", Integer),
|
||||
Column("characterAwakening4", Integer),
|
||||
Column("characterId5", Integer),
|
||||
Column("characterLevel5", Integer),
|
||||
Column("characterAwakening5", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("deluxscore", Integer),
|
||||
Column("scoreRank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("totalCombo", Integer),
|
||||
Column("maxSync", Integer),
|
||||
Column("totalSync", Integer),
|
||||
Column("tapCriticalPerfect", Integer),
|
||||
Column("tapPerfect", Integer),
|
||||
Column("tapGreat", Integer),
|
||||
Column("tapGood", Integer),
|
||||
Column("tapMiss", Integer),
|
||||
Column("holdCriticalPerfect", Integer),
|
||||
Column("holdPerfect", Integer),
|
||||
Column("holdGreat", Integer),
|
||||
Column("holdGood", Integer),
|
||||
Column("holdMiss", Integer),
|
||||
Column("slideCriticalPerfect", Integer),
|
||||
Column("slidePerfect", Integer),
|
||||
Column("slideGreat", Integer),
|
||||
Column("slideGood", Integer),
|
||||
Column("slideMiss", Integer),
|
||||
Column("touchCriticalPerfect", Integer),
|
||||
Column("touchPerfect", Integer),
|
||||
Column("touchGreat", Integer),
|
||||
Column("touchGood", Integer),
|
||||
Column("touchMiss", Integer),
|
||||
Column("breakCriticalPerfect", Integer),
|
||||
Column("breakPerfect", Integer),
|
||||
Column("breakGreat", Integer),
|
||||
Column("breakGood", Integer),
|
||||
Column("breakMiss", Integer),
|
||||
Column("isTap", Boolean),
|
||||
Column("isHold", Boolean),
|
||||
Column("isSlide", Boolean),
|
||||
Column("isTouch", Boolean),
|
||||
Column("isBreak", Boolean),
|
||||
Column("isCriticalDisp", Boolean),
|
||||
Column("isFastLateDisp", Boolean),
|
||||
Column("fastCount", Integer),
|
||||
Column("lateCount", Integer),
|
||||
Column("isAchieveNewRecord", Boolean),
|
||||
Column("isDeluxscoreNewRecord", Boolean),
|
||||
Column("comboStatus", Integer),
|
||||
Column("syncStatus", Integer),
|
||||
Column("isClear", Boolean),
|
||||
Column("beforeRating", Integer),
|
||||
Column("afterRating", Integer),
|
||||
Column("beforeGrade", Integer),
|
||||
Column("afterGrade", Integer),
|
||||
Column("afterGradeRank", Integer),
|
||||
Column("beforeDeluxRating", Integer),
|
||||
Column("afterDeluxRating", Integer),
|
||||
Column("isPlayTutorial", Boolean),
|
||||
Column("isEventMode", Boolean),
|
||||
Column("isFreedomMode", Boolean),
|
||||
Column("playMode", Integer),
|
||||
Column("isNewFree", Boolean),
|
||||
Column("extNum1", Integer),
|
||||
Column("extNum2", Integer),
|
||||
Column("extNum4", Integer, server_default="0"),
|
||||
Column("trialPlayAchievement", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
course = Table(
|
||||
"mai2_score_course",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("courseId", Integer),
|
||||
Column("isLastClear", Boolean),
|
||||
Column("totalRestlife", Integer),
|
||||
Column("totalAchievement", Integer),
|
||||
Column("totalDeluxscore", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("clearDate", String(25)),
|
||||
Column("lastPlayDate", String(25)),
|
||||
Column("bestAchievement", Integer),
|
||||
Column("bestAchievementDate", String(25)),
|
||||
Column("bestDeluxscore", Integer),
|
||||
Column("bestDeluxscoreDate", String(25)),
|
||||
UniqueConstraint("user", "courseId", name="mai2_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog_old = Table(
|
||||
"maimai_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("version", Integer),
|
||||
# Pop access code
|
||||
Column("orderId", Integer),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("country", String(255)),
|
||||
Column("regionId", Integer),
|
||||
Column("playDate", String(255)),
|
||||
Column("userPlayDate", String(255)),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("gameMode", Integer),
|
||||
Column("rivalNum", Integer),
|
||||
Column("track", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("isFreeToPlay", Boolean),
|
||||
Column("playerRating", Integer),
|
||||
Column("playedUserId1", Integer),
|
||||
Column("playedUserId2", Integer),
|
||||
Column("playedUserId3", Integer),
|
||||
Column("playedUserName1", String(255)),
|
||||
Column("playedUserName2", String(255)),
|
||||
Column("playedUserName3", String(255)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("score", Integer),
|
||||
Column("tapScore", Integer),
|
||||
Column("holdScore", Integer),
|
||||
Column("slideScore", Integer),
|
||||
Column("breakScore", Integer),
|
||||
Column("syncRate", Integer),
|
||||
Column("vsWin", Integer),
|
||||
Column("isAllPerfect", Boolean),
|
||||
Column("fullCombo", Integer),
|
||||
Column("maxFever", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("tapPerfect", Integer),
|
||||
Column("tapGreat", Integer),
|
||||
Column("tapGood", Integer),
|
||||
Column("tapBad", Integer),
|
||||
Column("holdPerfect", Integer),
|
||||
Column("holdGreat", Integer),
|
||||
Column("holdGood", Integer),
|
||||
Column("holdBad", Integer),
|
||||
Column("slidePerfect", Integer),
|
||||
Column("slideGreat", Integer),
|
||||
Column("slideGood", Integer),
|
||||
Column("slideBad", Integer),
|
||||
Column("breakPerfect", Integer),
|
||||
Column("breakGreat", Integer),
|
||||
Column("breakGood", Integer),
|
||||
Column("breakBad", Integer),
|
||||
Column("judgeStyle", Integer),
|
||||
Column("isTrackSkip", Boolean),
|
||||
Column("isHighScore", Boolean),
|
||||
Column("isChallengeTrack", Boolean),
|
||||
Column("challengeLife", Integer),
|
||||
Column("challengeRemain", Integer),
|
||||
Column("isAllPerfectPlus", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
best_score_old = Table(
|
||||
"maimai_score_best",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playCount", Integer),
|
||||
Column("achievement", Integer),
|
||||
Column("scoreMax", Integer),
|
||||
Column("syncRateMax", Integer),
|
||||
Column("isAllPerfect", Boolean),
|
||||
Column("isAllPerfectPlus", Integer),
|
||||
Column("fullCombo", Integer),
|
||||
Column("maxFever", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="maimai_score_best_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class Mai2ScoreData(BaseData):
|
||||
def put_best_score(
|
||||
self, user_id: int, score_data: Dict, is_dx: bool = True
|
||||
) -> Optional[int]:
|
||||
score_data["user"] = user_id
|
||||
|
||||
if is_dx:
|
||||
sql = insert(best_score).values(**score_data)
|
||||
else:
|
||||
sql = insert(best_score_old).values(**score_data)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**score_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"put_best_score: Failed to insert best score! user_id {user_id} is_dx {is_dx}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
@cached(2)
|
||||
def get_best_scores(
|
||||
self, user_id: int, song_id: int = None, is_dx: bool = True
|
||||
) -> Optional[List[Row]]:
|
||||
if is_dx:
|
||||
sql = best_score.select(
|
||||
and_(
|
||||
best_score.c.user == user_id,
|
||||
(best_score.c.song_id == song_id) if song_id is not None else True,
|
||||
)
|
||||
)
|
||||
else:
|
||||
sql = best_score_old.select(
|
||||
and_(
|
||||
best_score_old.c.user == user_id,
|
||||
(best_score_old.c.song_id == song_id)
|
||||
if song_id is not None
|
||||
else True,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_score(
|
||||
self, user_id: int, song_id: int, chart_id: int
|
||||
) -> Optional[Row]:
|
||||
sql = best_score.select(
|
||||
and_(
|
||||
best_score.c.user == user_id,
|
||||
best_score.c.song_id == song_id,
|
||||
best_score.c.chart_id == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_playlog(
|
||||
self, user_id: int, playlog_data: Dict, is_dx: bool = True
|
||||
) -> Optional[int]:
|
||||
playlog_data["user"] = user_id
|
||||
|
||||
if is_dx:
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
else:
|
||||
sql = insert(playlog_old).values(**playlog_data)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**playlog_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"put_playlog: Failed to insert! user_id {user_id} is_dx {is_dx}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_course(self, user_id: int, course_data: Dict) -> Optional[int]:
|
||||
course_data["user"] = user_id
|
||||
sql = insert(course).values(**course_data)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**course_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"put_course: Failed to insert! user_id {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_courses(self, user_id: int) -> Optional[List[Row]]:
|
||||
sql = course.select(course.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
|
|
@ -1,250 +1,252 @@
|
|||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
event = Table(
|
||||
"mai2_static_event",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("eventId", Integer),
|
||||
Column("type", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("startDate", TIMESTAMP, server_default=func.now()),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
music = Table(
|
||||
"mai2_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("genre", String(255)),
|
||||
Column("bpm", Integer),
|
||||
Column("addedVersion", String(255)),
|
||||
Column("difficulty", Float),
|
||||
Column("noteDesigner", String(255)),
|
||||
UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
ticket = Table(
|
||||
"mai2_static_ticket",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("ticketId", Integer),
|
||||
Column("kind", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("price", Integer, server_default="1"),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "ticketId", name="mai2_static_ticket_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
cards = Table(
|
||||
"mai2_static_cards",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("cardId", Integer, nullable=False),
|
||||
Column("cardName", String(255), nullable=False),
|
||||
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "cardId", "cardName", name="mai2_static_cards_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class Mai2StaticData(BaseData):
|
||||
def put_game_event(
|
||||
self, version: int, type: int, event_id: int, name: str
|
||||
) -> Optional[int]:
|
||||
sql = insert(event).values(
|
||||
version=version,
|
||||
type=type,
|
||||
eventId=event_id,
|
||||
name=name,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(eventId=event_id)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warning(
|
||||
f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}"
|
||||
)
|
||||
return result.lastrowid
|
||||
|
||||
def get_game_events(self, version: int) -> Optional[List[Row]]:
|
||||
sql = event.select(event.c.version == version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_enabled_events(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(event).where(
|
||||
and_(event.c.version == version, event.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def toggle_game_event(
|
||||
self, version: int, event_id: int, toggle: bool
|
||||
) -> Optional[List]:
|
||||
sql = event.update(
|
||||
and_(event.c.version == version, event.c.eventId == event_id)
|
||||
).values(enabled=int(toggle))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warning(
|
||||
f"toggle_game_event: Failed to update event! event_id {event_id} toggle {toggle}"
|
||||
)
|
||||
return result.last_updated_params()
|
||||
|
||||
def put_game_music(
|
||||
self,
|
||||
version: int,
|
||||
song_id: int,
|
||||
chart_id: int,
|
||||
title: str,
|
||||
artist: str,
|
||||
genre: str,
|
||||
bpm: str,
|
||||
added_version: str,
|
||||
difficulty: float,
|
||||
note_designer: str,
|
||||
) -> None:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=song_id,
|
||||
chartId=chart_id,
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
bpm=bpm,
|
||||
addedVersion=added_version,
|
||||
difficulty=difficulty,
|
||||
noteDesigner=note_designer,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
bpm=bpm,
|
||||
addedVersion=added_version,
|
||||
difficulty=difficulty,
|
||||
noteDesigner=note_designer,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_game_ticket(
|
||||
self,
|
||||
version: int,
|
||||
ticket_id: int,
|
||||
ticket_type: int,
|
||||
ticket_price: int,
|
||||
name: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(ticket).values(
|
||||
version=version,
|
||||
ticketId=ticket_id,
|
||||
kind=ticket_type,
|
||||
price=ticket_price,
|
||||
name=name,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(price=ticket_price)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(price=ticket_price)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_tickets(
|
||||
self, version: int, kind: int = None
|
||||
) -> Optional[List[Row]]:
|
||||
if kind is not None:
|
||||
sql = select(ticket).where(
|
||||
and_(
|
||||
ticket.c.version == version,
|
||||
ticket.c.enabled == True,
|
||||
ticket.c.kind == kind,
|
||||
)
|
||||
)
|
||||
else:
|
||||
sql = select(ticket).where(
|
||||
and_(ticket.c.version == version, ticket.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_card(self, version: int, card_id: int, card_name: str, **card_data) -> int:
|
||||
sql = insert(cards).values(
|
||||
version=version, cardId=card_id, cardName=card_name, **card_data
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**card_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert card {card_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_cards(self, version: int) -> Optional[List[Row]]:
|
||||
sql = cards.select(and_(cards.c.version == version, cards.c.enabled == True))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
from core.data.schema.base import BaseData, metadata
|
||||
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
event = Table(
|
||||
"mai2_static_event",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("eventId", Integer),
|
||||
Column("type", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("startDate", TIMESTAMP, server_default=func.now()),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "eventId", "type", name="mai2_static_event_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
music = Table(
|
||||
"mai2_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("genre", String(255)),
|
||||
Column("bpm", Integer),
|
||||
Column("addedVersion", String(255)),
|
||||
Column("difficulty", Float),
|
||||
Column("noteDesigner", String(255)),
|
||||
UniqueConstraint("songId", "chartId", "version", name="mai2_static_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
ticket = Table(
|
||||
"mai2_static_ticket",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("ticketId", Integer),
|
||||
Column("kind", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("price", Integer, server_default="1"),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "ticketId", name="mai2_static_ticket_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
cards = Table(
|
||||
"mai2_static_cards",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("cardId", Integer, nullable=False),
|
||||
Column("cardName", String(255), nullable=False),
|
||||
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "cardId", "cardName", name="mai2_static_cards_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class Mai2StaticData(BaseData):
|
||||
def put_game_event(
|
||||
self, version: int, type: int, event_id: int, name: str
|
||||
) -> Optional[int]:
|
||||
sql = insert(event).values(
|
||||
version=version,
|
||||
type=type,
|
||||
eventId=event_id,
|
||||
name=name,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(eventId=event_id))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warning(
|
||||
f"put_game_event: Failed to insert event! event_id {event_id} type {type} name {name}"
|
||||
)
|
||||
return result.lastrowid
|
||||
|
||||
def get_game_events(self, version: int) -> Optional[List[Row]]:
|
||||
sql = event.select(event.c.version == version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_enabled_events(self, version: int) -> Optional[List[Row]]:
|
||||
sql = select(event).where(
|
||||
and_(event.c.version == version, event.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def toggle_game_event(
|
||||
self, version: int, event_id: int, toggle: bool
|
||||
) -> Optional[List]:
|
||||
sql = event.update(
|
||||
and_(event.c.version == version, event.c.eventId == event_id)
|
||||
).values(enabled=int(toggle))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warning(
|
||||
f"toggle_game_event: Failed to update event! event_id {event_id} toggle {toggle}"
|
||||
)
|
||||
return result.last_updated_params()
|
||||
|
||||
def put_game_music(
|
||||
self,
|
||||
version: int,
|
||||
song_id: int,
|
||||
chart_id: int,
|
||||
title: str,
|
||||
artist: str,
|
||||
genre: str,
|
||||
bpm: str,
|
||||
added_version: str,
|
||||
difficulty: float,
|
||||
note_designer: str,
|
||||
) -> None:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=song_id,
|
||||
chartId=chart_id,
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
bpm=bpm,
|
||||
addedVersion=added_version,
|
||||
difficulty=difficulty,
|
||||
noteDesigner=note_designer,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
bpm=bpm,
|
||||
addedVersion=added_version,
|
||||
difficulty=difficulty,
|
||||
noteDesigner=note_designer,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert song {song_id} chart {chart_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_game_ticket(
|
||||
self,
|
||||
version: int,
|
||||
ticket_id: int,
|
||||
ticket_type: int,
|
||||
ticket_price: int,
|
||||
name: str,
|
||||
) -> Optional[int]:
|
||||
sql = insert(ticket).values(
|
||||
version=version,
|
||||
ticketId=ticket_id,
|
||||
kind=ticket_type,
|
||||
price=ticket_price,
|
||||
name=name,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(price=ticket_price))
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(price=ticket_price))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert charge {ticket_id} type {ticket_type}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_tickets(
|
||||
self, version: int, kind: int = None
|
||||
) -> Optional[List[Row]]:
|
||||
if kind is not None:
|
||||
sql = select(ticket).where(
|
||||
and_(
|
||||
ticket.c.version == version,
|
||||
ticket.c.enabled == True,
|
||||
ticket.c.kind == kind,
|
||||
)
|
||||
)
|
||||
else:
|
||||
sql = select(ticket).where(
|
||||
and_(ticket.c.version == version, ticket.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_card(self, version: int, card_id: int, card_name: str, **card_data) -> int:
|
||||
sql = insert(cards).values(
|
||||
version=version, cardId=card_id, cardName=card_name, **card_data
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**card_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert card {card_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_enabled_cards(self, version: int) -> Optional[List[Row]]:
|
||||
sql = cards.select(and_(cards.c.version == version, cards.c.enabled == True))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
|
|
@ -3,6 +3,7 @@ from random import randint
|
|||
from datetime import datetime, timedelta
|
||||
import pytz
|
||||
import json
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.splashplus import Mai2SplashPlus
|
||||
|
@ -70,8 +71,12 @@ class Mai2Universe(Mai2SplashPlus):
|
|||
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["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
|
||||
)
|
||||
|
@ -163,38 +168,43 @@ class Mai2Universe(Mai2SplashPlus):
|
|||
end_date = datetime.utcnow() + timedelta(days=15)
|
||||
|
||||
user_card = upsert["userCard"]
|
||||
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 = 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"]
|
||||
with (
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
):
|
||||
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,
|
||||
)
|
||||
|
||||
# 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"])
|
||||
# get the profile extend to save the new bought card
|
||||
extend = 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"]
|
||||
|
||||
extend["selectedCardList"] = selected_cards
|
||||
self.data.profile.put_profile_extend(user_id, self.version, extend)
|
||||
# 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"])
|
||||
|
||||
# properly format userPrintDetail for the database
|
||||
upsert.pop("userCard")
|
||||
upsert.pop("serialId")
|
||||
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
||||
extend["selectedCardList"] = selected_cards
|
||||
self.data.profile.put_profile_extend(user_id, self.version, extend)
|
||||
|
||||
self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
||||
# properly format userPrintDetail for the database
|
||||
upsert.pop("userCard")
|
||||
upsert.pop("serialId")
|
||||
upsert["printDate"] = datetime.strptime(upsert["printDate"], "%Y-%m-%d")
|
||||
|
||||
self.data.item.put_user_print_detail(user_id, serial_id, upsert)
|
||||
|
||||
return {
|
||||
"returnCode": 1,
|
||||
|
|
|
@ -3,6 +3,7 @@ from typing import Any, Dict, List
|
|||
import json
|
||||
import logging
|
||||
from enum import Enum
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from core.data.cache import cached
|
||||
|
@ -852,125 +853,127 @@ class OngekiBase:
|
|||
user_id = data["userId"]
|
||||
|
||||
# The isNew fields are new as of Red and up. We just won't use them for now.
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
self.data.profile.put_profile_options(user_id, upsert["userOption"][0])
|
||||
|
||||
if "userPlaylogList" in upsert:
|
||||
for playlog in upsert["userPlaylogList"]:
|
||||
self.data.score.put_playlog(user_id, playlog)
|
||||
|
||||
if "userActivityList" in upsert:
|
||||
for act in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(
|
||||
user_id,
|
||||
act["kind"],
|
||||
act["id"],
|
||||
act["sortNumber"],
|
||||
act["param1"],
|
||||
act["param2"],
|
||||
act["param3"],
|
||||
act["param4"],
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userRecentRatingList" in upsert:
|
||||
self.data.profile.put_profile_recent_rating(
|
||||
user_id, upsert["userRecentRatingList"]
|
||||
)
|
||||
if "userOption" in upsert and len(upsert["userOption"]) > 0:
|
||||
self.data.profile.put_profile_options(user_id, upsert["userOption"][0])
|
||||
|
||||
if "userBpBaseList" in upsert:
|
||||
self.data.profile.put_profile_bp_list(user_id, upsert["userBpBaseList"])
|
||||
if "userActivityList" in upsert:
|
||||
for act in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(
|
||||
user_id,
|
||||
act["kind"],
|
||||
act["id"],
|
||||
act["sortNumber"],
|
||||
act["param1"],
|
||||
act["param2"],
|
||||
act["param3"],
|
||||
act["param4"],
|
||||
)
|
||||
|
||||
if "userMusicDetailList" in upsert:
|
||||
for x in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, x)
|
||||
|
||||
if "userCharacterList" in upsert:
|
||||
for x in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, x)
|
||||
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
|
||||
if "userDeckList" in upsert:
|
||||
for x in upsert["userDeckList"]:
|
||||
self.data.item.put_deck(user_id, x)
|
||||
|
||||
if "userTrainingRoomList" in upsert:
|
||||
for x in upsert["userTrainingRoomList"]:
|
||||
self.data.profile.put_training_room(user_id, x)
|
||||
|
||||
if "userStoryList" in upsert:
|
||||
for x in upsert["userStoryList"]:
|
||||
self.data.item.put_story(user_id, x)
|
||||
|
||||
if "userChapterList" in upsert:
|
||||
for x in upsert["userChapterList"]:
|
||||
self.data.item.put_chapter(user_id, x)
|
||||
|
||||
if "userMemoryChapterList" in upsert:
|
||||
for x in upsert["userMemoryChapterList"]:
|
||||
self.data.item.put_memorychapter(user_id, x)
|
||||
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
|
||||
if "userMusicItemList" in upsert:
|
||||
for x in upsert["userMusicItemList"]:
|
||||
self.data.item.put_music_item(user_id, x)
|
||||
|
||||
if "userLoginBonusList" in upsert:
|
||||
for x in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(user_id, x)
|
||||
|
||||
if "userEventPointList" in upsert:
|
||||
for x in upsert["userEventPointList"]:
|
||||
self.data.item.put_event_point(user_id, x)
|
||||
|
||||
if "userMissionPointList" in upsert:
|
||||
for x in upsert["userMissionPointList"]:
|
||||
self.data.item.put_mission_point(user_id, x)
|
||||
|
||||
if "userRatinglogList" in upsert:
|
||||
for x in upsert["userRatinglogList"]:
|
||||
self.data.profile.put_profile_rating_log(
|
||||
user_id, x["dataVersion"], x["highestRating"]
|
||||
if "userRecentRatingList" in upsert:
|
||||
self.data.profile.put_profile_recent_rating(
|
||||
user_id, upsert["userRecentRatingList"]
|
||||
)
|
||||
|
||||
if "userBossList" in upsert:
|
||||
for x in upsert["userBossList"]:
|
||||
self.data.item.put_boss(user_id, x)
|
||||
if "userBpBaseList" in upsert:
|
||||
self.data.profile.put_profile_bp_list(user_id, upsert["userBpBaseList"])
|
||||
|
||||
if "userTechCountList" in upsert:
|
||||
for x in upsert["userTechCountList"]:
|
||||
self.data.score.put_tech_count(user_id, x)
|
||||
if "userTrainingRoomList" in upsert:
|
||||
for x in upsert["userTrainingRoomList"]:
|
||||
self.data.profile.put_training_room(user_id, x)
|
||||
|
||||
if "userScenerioList" in upsert:
|
||||
for x in upsert["userScenerioList"]:
|
||||
self.data.item.put_scenerio(user_id, x)
|
||||
if "userRatinglogList" in upsert:
|
||||
for x in upsert["userRatinglogList"]:
|
||||
self.data.profile.put_profile_rating_log(
|
||||
user_id, x["dataVersion"], x["highestRating"]
|
||||
)
|
||||
|
||||
if "userTradeItemList" in upsert:
|
||||
for x in upsert["userTradeItemList"]:
|
||||
self.data.item.put_trade_item(user_id, x)
|
||||
if "userKopList" in upsert:
|
||||
for x in upsert["userKopList"]:
|
||||
self.data.profile.put_kop(user_id, x)
|
||||
|
||||
if "userEventMusicList" in upsert:
|
||||
for x in upsert["userEventMusicList"]:
|
||||
self.data.item.put_event_music(user_id, x)
|
||||
with self.data.score.conn.begin() or nullcontext():
|
||||
if "userPlaylogList" in upsert:
|
||||
for playlog in upsert["userPlaylogList"]:
|
||||
self.data.score.put_playlog(user_id, playlog)
|
||||
|
||||
if "userTechEventList" in upsert:
|
||||
for x in upsert["userTechEventList"]:
|
||||
self.data.item.put_tech_event(user_id, x)
|
||||
if "userMusicDetailList" in upsert:
|
||||
for x in upsert["userMusicDetailList"]:
|
||||
self.data.score.put_best_score(user_id, x)
|
||||
|
||||
if "userKopList" in upsert:
|
||||
for x in upsert["userKopList"]:
|
||||
self.data.profile.put_kop(user_id, x)
|
||||
if "userTechCountList" in upsert:
|
||||
for x in upsert["userTechCountList"]:
|
||||
self.data.score.put_tech_count(user_id, x)
|
||||
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userCharacterList" in upsert:
|
||||
for x in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, x)
|
||||
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
|
||||
if "userDeckList" in upsert:
|
||||
for x in upsert["userDeckList"]:
|
||||
self.data.item.put_deck(user_id, x)
|
||||
|
||||
if "userStoryList" in upsert:
|
||||
for x in upsert["userStoryList"]:
|
||||
self.data.item.put_story(user_id, x)
|
||||
|
||||
if "userChapterList" in upsert:
|
||||
for x in upsert["userChapterList"]:
|
||||
self.data.item.put_chapter(user_id, x)
|
||||
|
||||
if "userMemoryChapterList" in upsert:
|
||||
for x in upsert["userMemoryChapterList"]:
|
||||
self.data.item.put_memorychapter(user_id, x)
|
||||
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
|
||||
if "userMusicItemList" in upsert:
|
||||
for x in upsert["userMusicItemList"]:
|
||||
self.data.item.put_music_item(user_id, x)
|
||||
|
||||
if "userLoginBonusList" in upsert:
|
||||
for x in upsert["userLoginBonusList"]:
|
||||
self.data.item.put_login_bonus(user_id, x)
|
||||
|
||||
if "userEventPointList" in upsert:
|
||||
for x in upsert["userEventPointList"]:
|
||||
self.data.item.put_event_point(user_id, x)
|
||||
|
||||
if "userMissionPointList" in upsert:
|
||||
for x in upsert["userMissionPointList"]:
|
||||
self.data.item.put_mission_point(user_id, x)
|
||||
|
||||
if "userBossList" in upsert:
|
||||
for x in upsert["userBossList"]:
|
||||
self.data.item.put_boss(user_id, x)
|
||||
|
||||
if "userScenerioList" in upsert:
|
||||
for x in upsert["userScenerioList"]:
|
||||
self.data.item.put_scenerio(user_id, x)
|
||||
|
||||
if "userTradeItemList" in upsert:
|
||||
for x in upsert["userTradeItemList"]:
|
||||
self.data.item.put_trade_item(user_id, x)
|
||||
|
||||
if "userEventMusicList" in upsert:
|
||||
for x in upsert["userEventMusicList"]:
|
||||
self.data.item.put_event_music(user_id, x)
|
||||
|
||||
if "userTechEventList" in upsert:
|
||||
for x in upsert["userTechEventList"]:
|
||||
self.data.item.put_tech_event(user_id, x)
|
||||
|
||||
return {"returnCode": 1, "apiName": "upsertUserAll"}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ from typing import Any, Dict
|
|||
from random import randint
|
||||
import pytz
|
||||
import json
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.ongeki.base import OngekiBase
|
||||
|
@ -358,46 +359,50 @@ class OngekiBright(OngekiBase):
|
|||
daily_gacha_date = play_date
|
||||
daily_gacha_cnt = 0
|
||||
|
||||
self.data.item.put_user_gacha(
|
||||
user_id,
|
||||
gacha_id,
|
||||
totalGachaCnt=total_gacha_count + gacha_count,
|
||||
ceilingGachaCnt=ceiling_gacha_count + gacha_count,
|
||||
selectPoint=select_point,
|
||||
useSelectPoint=use_select_point,
|
||||
dailyGachaCnt=daily_gacha_cnt + gacha_count,
|
||||
fiveGachaCnt=five_gacha_cnt + 1 if gacha_count == 5 else five_gacha_cnt,
|
||||
elevenGachaCnt=eleven_gacha_cnt + 1
|
||||
if gacha_count == 11
|
||||
else eleven_gacha_cnt,
|
||||
dailyGachaDate=daily_gacha_date,
|
||||
)
|
||||
with (
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
):
|
||||
self.data.item.put_user_gacha(
|
||||
user_id,
|
||||
gacha_id,
|
||||
totalGachaCnt=total_gacha_count + gacha_count,
|
||||
ceilingGachaCnt=ceiling_gacha_count + gacha_count,
|
||||
selectPoint=select_point,
|
||||
useSelectPoint=use_select_point,
|
||||
dailyGachaCnt=daily_gacha_cnt + gacha_count,
|
||||
fiveGachaCnt=five_gacha_cnt + 1 if gacha_count == 5 else five_gacha_cnt,
|
||||
elevenGachaCnt=eleven_gacha_cnt + 1
|
||||
if gacha_count == 11
|
||||
else eleven_gacha_cnt,
|
||||
dailyGachaDate=daily_gacha_date,
|
||||
)
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
# check if the profile is a bright memory profile
|
||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||
if p is not None:
|
||||
# save the bright memory profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
else:
|
||||
# save the bright profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
# check if the profile is a bright memory profile
|
||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||
if p is not None:
|
||||
# save the bright memory profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
else:
|
||||
# save the bright profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userCharacterList" in upsert:
|
||||
for x in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, x)
|
||||
if "userCharacterList" in upsert:
|
||||
for x in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, x)
|
||||
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
|
||||
# TODO?
|
||||
# if "gameGachaCardList" in upsert:
|
||||
|
@ -409,36 +414,38 @@ class OngekiBright(OngekiBase):
|
|||
upsert = data["cmUpsertUserSelectGacha"]
|
||||
user_id = data["userId"]
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
# check if the profile is a bright memory profile
|
||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||
if p is not None:
|
||||
# save the bright memory profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
else:
|
||||
# save the bright profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
# check if the profile is a bright memory profile
|
||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||
if p is not None:
|
||||
# save the bright memory profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
else:
|
||||
# save the bright profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userCharacterList" in upsert:
|
||||
for x in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, x)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userCharacterList" in upsert:
|
||||
for x in upsert["userCharacterList"]:
|
||||
self.data.item.put_character(user_id, x)
|
||||
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
|
||||
if "selectGachaLogList" in data:
|
||||
for x in data["selectGachaLogList"]:
|
||||
self.data.item.put_user_gacha(
|
||||
user_id,
|
||||
x["gachaId"],
|
||||
selectPoint=0,
|
||||
useSelectPoint=x["useSelectPoint"],
|
||||
)
|
||||
if "selectGachaLogList" in data:
|
||||
for x in data["selectGachaLogList"]:
|
||||
self.data.item.put_user_gacha(
|
||||
user_id,
|
||||
x["gachaId"],
|
||||
selectPoint=0,
|
||||
useSelectPoint=x["useSelectPoint"],
|
||||
)
|
||||
|
||||
return {"returnCode": 1, "apiName": "cmUpsertUserSelectGacha"}
|
||||
|
||||
|
@ -593,39 +600,41 @@ class OngekiBright(OngekiBase):
|
|||
upsert = data["cmUpsertUserAll"]
|
||||
user_id = data["userId"]
|
||||
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
# check if the profile is a bright memory profile
|
||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||
if p is not None:
|
||||
# save the bright memory profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
else:
|
||||
# save the bright profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
if "userData" in upsert and len(upsert["userData"]) > 0:
|
||||
# check if the profile is a bright memory profile
|
||||
p = self.data.profile.get_profile_data(data["userId"], self.version)
|
||||
if p is not None:
|
||||
# save the bright memory profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
else:
|
||||
# save the bright profile
|
||||
self.data.profile.put_profile_data(
|
||||
user_id, self.version, upsert["userData"][0]
|
||||
)
|
||||
|
||||
if "userActivityList" in upsert:
|
||||
for act in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(
|
||||
user_id,
|
||||
act["kind"],
|
||||
act["id"],
|
||||
act["sortNumber"],
|
||||
act["param1"],
|
||||
act["param2"],
|
||||
act["param3"],
|
||||
act["param4"],
|
||||
)
|
||||
if "userActivityList" in upsert:
|
||||
for act in upsert["userActivityList"]:
|
||||
self.data.profile.put_profile_activity(
|
||||
user_id,
|
||||
act["kind"],
|
||||
act["id"],
|
||||
act["sortNumber"],
|
||||
act["param1"],
|
||||
act["param2"],
|
||||
act["param3"],
|
||||
act["param4"],
|
||||
)
|
||||
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
if "userCardList" in upsert:
|
||||
for x in upsert["userCardList"]:
|
||||
self.data.item.put_card(user_id, x)
|
||||
|
||||
return {"returnCode": 1, "apiName": "cmUpsertUserAll"}
|
||||
|
|
|
@ -139,9 +139,7 @@ class OngekiServlet:
|
|||
|
||||
req_data = json.loads(unzip)
|
||||
|
||||
self.logger.info(
|
||||
f"v{version} {endpoint} request from {client_ip}"
|
||||
)
|
||||
self.logger.info(f"v{version} {endpoint} request from {client_ip}")
|
||||
self.logger.debug(req_data)
|
||||
|
||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||
|
|
|
@ -4,6 +4,7 @@ import os
|
|||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Any, Dict, List, Optional
|
||||
from contextlib import nullcontext
|
||||
|
||||
from read import BaseReader
|
||||
from core.config import CoreConfig
|
||||
|
@ -41,9 +42,10 @@ class OngekiReader(BaseReader):
|
|||
data_dirs += self.get_data_directories(self.opt_dir)
|
||||
|
||||
for dir in data_dirs:
|
||||
self.read_events(f"{dir}/event")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_card(f"{dir}/card")
|
||||
with self.data.static.conn.begin() or nullcontext():
|
||||
self.read_events(f"{dir}/event")
|
||||
self.read_music(f"{dir}/music")
|
||||
self.read_card(f"{dir}/card")
|
||||
|
||||
def read_card(self, base_dir: str) -> None:
|
||||
self.logger.info(f"Reading cards from {base_dir}...")
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from titles.ongeki.schema.profile import OngekiProfileData
|
||||
from titles.ongeki.schema.item import OngekiItemData
|
||||
from titles.ongeki.schema.static import OngekiStaticData
|
||||
from titles.ongeki.schema.score import OngekiScoreData
|
||||
from titles.ongeki.schema.log import OngekiLogData
|
||||
|
||||
__all__ = [
|
||||
OngekiProfileData,
|
||||
OngekiItemData,
|
||||
OngekiStaticData,
|
||||
OngekiScoreData,
|
||||
OngekiLogData,
|
||||
]
|
||||
from titles.ongeki.schema.profile import OngekiProfileData
|
||||
from titles.ongeki.schema.item import OngekiItemData
|
||||
from titles.ongeki.schema.static import OngekiStaticData
|
||||
from titles.ongeki.schema.score import OngekiScoreData
|
||||
from titles.ongeki.schema.log import OngekiLogData
|
||||
|
||||
__all__ = [
|
||||
OngekiProfileData,
|
||||
OngekiItemData,
|
||||
OngekiStaticData,
|
||||
OngekiScoreData,
|
||||
OngekiLogData,
|
||||
]
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,69 +1,69 @@
|
|||
from typing import Dict, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
gp_log = Table(
|
||||
"ongeki_gp_log",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
|
||||
Column("usedCredit", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("trxnDate", String(255)),
|
||||
Column(
|
||||
"placeId", Integer
|
||||
), # Making this an FK would mess with people playing with default KC
|
||||
Column("kind", Integer),
|
||||
Column("pattern", Integer),
|
||||
Column("currentGP", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
session_log = Table(
|
||||
"ongeki_session_log",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("playDate", String(10)),
|
||||
Column("userPlayDate", String(25)),
|
||||
Column("isPaid", Boolean),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class OngekiLogData(BaseData):
|
||||
def put_gp_log(
|
||||
self,
|
||||
aime_id: Optional[int],
|
||||
used_credit: int,
|
||||
place_name: str,
|
||||
tx_date: str,
|
||||
place_id: int,
|
||||
kind: int,
|
||||
pattern: int,
|
||||
current_gp: int,
|
||||
) -> Optional[int]:
|
||||
sql = insert(gp_log).values(
|
||||
user=aime_id,
|
||||
usedCredit=used_credit,
|
||||
placeName=place_name,
|
||||
trxnDate=tx_date,
|
||||
placeId=place_id,
|
||||
kind=kind,
|
||||
pattern=pattern,
|
||||
currentGP=current_gp,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"put_gp_log: Failed to insert GP log! aime_id: {aime_id} kind {kind} pattern {pattern} current_gp {current_gp}"
|
||||
)
|
||||
return result.lastrowid
|
||||
from typing import Dict, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
gp_log = Table(
|
||||
"ongeki_gp_log",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
|
||||
Column("usedCredit", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("trxnDate", String(255)),
|
||||
Column(
|
||||
"placeId", Integer
|
||||
), # Making this an FK would mess with people playing with default KC
|
||||
Column("kind", Integer),
|
||||
Column("pattern", Integer),
|
||||
Column("currentGP", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
session_log = Table(
|
||||
"ongeki_session_log",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("playDate", String(10)),
|
||||
Column("userPlayDate", String(25)),
|
||||
Column("isPaid", Boolean),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class OngekiLogData(BaseData):
|
||||
def put_gp_log(
|
||||
self,
|
||||
aime_id: Optional[int],
|
||||
used_credit: int,
|
||||
place_name: str,
|
||||
tx_date: str,
|
||||
place_id: int,
|
||||
kind: int,
|
||||
pattern: int,
|
||||
current_gp: int,
|
||||
) -> Optional[int]:
|
||||
sql = insert(gp_log).values(
|
||||
user=aime_id,
|
||||
usedCredit=used_credit,
|
||||
placeName=place_name,
|
||||
trxnDate=tx_date,
|
||||
placeId=place_id,
|
||||
kind=kind,
|
||||
pattern=pattern,
|
||||
currentGP=current_gp,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"put_gp_log: Failed to insert GP log! aime_id: {aime_id} kind {kind} pattern {pattern} current_gp {current_gp}"
|
||||
)
|
||||
return result.lastrowid
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,180 +1,180 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
score_best = Table(
|
||||
"ongeki_score_best",
|
||||
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),
|
||||
Column("level", Integer, nullable=False),
|
||||
Column("playCount", Integer, nullable=False),
|
||||
Column("techScoreMax", Integer, nullable=False),
|
||||
Column("techScoreRank", Integer, nullable=False),
|
||||
Column("battleScoreMax", Integer, nullable=False),
|
||||
Column("battleScoreRank", Integer, nullable=False),
|
||||
Column("maxComboCount", Integer, nullable=False),
|
||||
Column("maxOverKill", Float, nullable=False),
|
||||
Column("maxTeamOverKill", Float, nullable=False),
|
||||
Column("isFullBell", Boolean, nullable=False),
|
||||
Column("isFullCombo", Boolean, nullable=False),
|
||||
Column("isAllBreake", Boolean, nullable=False),
|
||||
Column("isLock", Boolean, nullable=False),
|
||||
Column("clearStatus", Boolean, nullable=False),
|
||||
Column("isStoryWatched", Boolean, nullable=False),
|
||||
Column("platinumScoreMax", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"ongeki_score_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("playDate", TIMESTAMP),
|
||||
Column("userPlayDate", TIMESTAMP),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playKind", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("eventName", String(255)),
|
||||
Column("eventPoint", Integer),
|
||||
Column("playedUserId1", Integer),
|
||||
Column("playedUserId2", Integer),
|
||||
Column("playedUserId3", Integer),
|
||||
Column("playedUserName1", String(8)),
|
||||
Column("playedUserName2", String(8)),
|
||||
Column("playedUserName3", String(8)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("cardId1", Integer),
|
||||
Column("cardId2", Integer),
|
||||
Column("cardId3", Integer),
|
||||
Column("cardLevel1", Integer),
|
||||
Column("cardLevel2", Integer),
|
||||
Column("cardLevel3", Integer),
|
||||
Column("cardAttack1", Integer),
|
||||
Column("cardAttack2", Integer),
|
||||
Column("cardAttack3", Integer),
|
||||
Column("bossCharaId", Integer),
|
||||
Column("bossLevel", Integer),
|
||||
Column("bossAttribute", Integer),
|
||||
Column("clearStatus", Integer),
|
||||
Column("techScore", Integer),
|
||||
Column("techScoreRank", Integer),
|
||||
Column("battleScore", Integer),
|
||||
Column("battleScoreRank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("judgeMiss", Integer),
|
||||
Column("judgeHit", Integer),
|
||||
Column("judgeBreak", Integer),
|
||||
Column("judgeCriticalBreak", Integer),
|
||||
Column("rateTap", Integer),
|
||||
Column("rateHold", Integer),
|
||||
Column("rateFlick", Integer),
|
||||
Column("rateSideTap", Integer),
|
||||
Column("rateSideHold", Integer),
|
||||
Column("bellCount", Integer),
|
||||
Column("totalBellCount", Integer),
|
||||
Column("damageCount", Integer),
|
||||
Column("overDamage", Integer),
|
||||
Column("isTechNewRecord", Boolean),
|
||||
Column("isBattleNewRecord", Boolean),
|
||||
Column("isOverDamageNewRecord", Boolean),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("isFullBell", Boolean),
|
||||
Column("isAllBreak", Boolean),
|
||||
Column("playerRating", Integer),
|
||||
Column("battlePoint", Integer),
|
||||
Column("platinumScore", Integer),
|
||||
Column("platinumScoreMax", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
tech_count = Table(
|
||||
"ongeki_score_tech_count",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("levelId", Integer, nullable=False),
|
||||
Column("allBreakCount", Integer),
|
||||
Column("allBreakPlusCount", Integer),
|
||||
UniqueConstraint("user", "levelId", name="ongeki_tech_count_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class OngekiScoreData(BaseData):
|
||||
def get_tech_count(self, aime_id: int) -> Optional[List[Dict]]:
|
||||
return []
|
||||
|
||||
def put_tech_count(self, aime_id: int, tech_count_data: Dict) -> Optional[int]:
|
||||
tech_count_data["user"] = aime_id
|
||||
|
||||
sql = insert(tech_count).values(**tech_count_data)
|
||||
conflict = sql.on_duplicate_key_update(**tech_count_data)
|
||||
result = self.execute(conflict)
|
||||
|
||||
if result is None:
|
||||
self.logger.warn(f"put_tech_count: Failed to update! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_scores(self, aime_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(score_best).where(score_best.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_score(
|
||||
self, aime_id: int, song_id: int, chart_id: int = None
|
||||
) -> Optional[List[Dict]]:
|
||||
return []
|
||||
|
||||
def put_best_score(self, aime_id: int, music_detail: Dict) -> Optional[int]:
|
||||
music_detail["user"] = aime_id
|
||||
|
||||
sql = insert(score_best).values(**music_detail)
|
||||
conflict = sql.on_duplicate_key_update(**music_detail)
|
||||
result = self.execute(conflict)
|
||||
|
||||
if result is None:
|
||||
self.logger.warn(f"put_best_score: Failed to add score! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
|
||||
playlog_data["user"] = aime_id
|
||||
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_playlog: Failed to add playlog! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
score_best = Table(
|
||||
"ongeki_score_best",
|
||||
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),
|
||||
Column("level", Integer, nullable=False),
|
||||
Column("playCount", Integer, nullable=False),
|
||||
Column("techScoreMax", Integer, nullable=False),
|
||||
Column("techScoreRank", Integer, nullable=False),
|
||||
Column("battleScoreMax", Integer, nullable=False),
|
||||
Column("battleScoreRank", Integer, nullable=False),
|
||||
Column("maxComboCount", Integer, nullable=False),
|
||||
Column("maxOverKill", Float, nullable=False),
|
||||
Column("maxTeamOverKill", Float, nullable=False),
|
||||
Column("isFullBell", Boolean, nullable=False),
|
||||
Column("isFullCombo", Boolean, nullable=False),
|
||||
Column("isAllBreake", Boolean, nullable=False),
|
||||
Column("isLock", Boolean, nullable=False),
|
||||
Column("clearStatus", Boolean, nullable=False),
|
||||
Column("isStoryWatched", Boolean, nullable=False),
|
||||
Column("platinumScoreMax", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
playlog = Table(
|
||||
"ongeki_score_playlog",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("sortNumber", Integer),
|
||||
Column("placeId", Integer),
|
||||
Column("placeName", String(255)),
|
||||
Column("playDate", TIMESTAMP),
|
||||
Column("userPlayDate", TIMESTAMP),
|
||||
Column("musicId", Integer),
|
||||
Column("level", Integer),
|
||||
Column("playKind", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("eventName", String(255)),
|
||||
Column("eventPoint", Integer),
|
||||
Column("playedUserId1", Integer),
|
||||
Column("playedUserId2", Integer),
|
||||
Column("playedUserId3", Integer),
|
||||
Column("playedUserName1", String(8)),
|
||||
Column("playedUserName2", String(8)),
|
||||
Column("playedUserName3", String(8)),
|
||||
Column("playedMusicLevel1", Integer),
|
||||
Column("playedMusicLevel2", Integer),
|
||||
Column("playedMusicLevel3", Integer),
|
||||
Column("cardId1", Integer),
|
||||
Column("cardId2", Integer),
|
||||
Column("cardId3", Integer),
|
||||
Column("cardLevel1", Integer),
|
||||
Column("cardLevel2", Integer),
|
||||
Column("cardLevel3", Integer),
|
||||
Column("cardAttack1", Integer),
|
||||
Column("cardAttack2", Integer),
|
||||
Column("cardAttack3", Integer),
|
||||
Column("bossCharaId", Integer),
|
||||
Column("bossLevel", Integer),
|
||||
Column("bossAttribute", Integer),
|
||||
Column("clearStatus", Integer),
|
||||
Column("techScore", Integer),
|
||||
Column("techScoreRank", Integer),
|
||||
Column("battleScore", Integer),
|
||||
Column("battleScoreRank", Integer),
|
||||
Column("maxCombo", Integer),
|
||||
Column("judgeMiss", Integer),
|
||||
Column("judgeHit", Integer),
|
||||
Column("judgeBreak", Integer),
|
||||
Column("judgeCriticalBreak", Integer),
|
||||
Column("rateTap", Integer),
|
||||
Column("rateHold", Integer),
|
||||
Column("rateFlick", Integer),
|
||||
Column("rateSideTap", Integer),
|
||||
Column("rateSideHold", Integer),
|
||||
Column("bellCount", Integer),
|
||||
Column("totalBellCount", Integer),
|
||||
Column("damageCount", Integer),
|
||||
Column("overDamage", Integer),
|
||||
Column("isTechNewRecord", Boolean),
|
||||
Column("isBattleNewRecord", Boolean),
|
||||
Column("isOverDamageNewRecord", Boolean),
|
||||
Column("isFullCombo", Boolean),
|
||||
Column("isFullBell", Boolean),
|
||||
Column("isAllBreak", Boolean),
|
||||
Column("playerRating", Integer),
|
||||
Column("battlePoint", Integer),
|
||||
Column("platinumScore", Integer),
|
||||
Column("platinumScoreMax", Integer),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
tech_count = Table(
|
||||
"ongeki_score_tech_count",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("levelId", Integer, nullable=False),
|
||||
Column("allBreakCount", Integer),
|
||||
Column("allBreakPlusCount", Integer),
|
||||
UniqueConstraint("user", "levelId", name="ongeki_tech_count_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class OngekiScoreData(BaseData):
|
||||
def get_tech_count(self, aime_id: int) -> Optional[List[Dict]]:
|
||||
return []
|
||||
|
||||
def put_tech_count(self, aime_id: int, tech_count_data: Dict) -> Optional[int]:
|
||||
tech_count_data["user"] = aime_id
|
||||
|
||||
sql = insert(tech_count).values(**tech_count_data)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**tech_count_data))
|
||||
result = self.execute(conflict)
|
||||
|
||||
if result is None:
|
||||
self.logger.warn(f"put_tech_count: Failed to update! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_best_scores(self, aime_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(score_best).where(score_best.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_best_score(
|
||||
self, aime_id: int, song_id: int, chart_id: int = None
|
||||
) -> Optional[List[Dict]]:
|
||||
return []
|
||||
|
||||
def put_best_score(self, aime_id: int, music_detail: Dict) -> Optional[int]:
|
||||
music_detail["user"] = aime_id
|
||||
|
||||
sql = insert(score_best).values(**music_detail)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**music_detail))
|
||||
result = self.execute(conflict)
|
||||
|
||||
if result is None:
|
||||
self.logger.warn(f"put_best_score: Failed to add score! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_playlog(self, aime_id: int, playlog_data: Dict) -> Optional[int]:
|
||||
playlog_data["user"] = aime_id
|
||||
|
||||
sql = insert(playlog).values(**playlog_data)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"put_playlog: Failed to add playlog! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
|
|
@ -1,335 +1,341 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
events = Table(
|
||||
"ongeki_static_events",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("type", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("startDate", TIMESTAMP, server_default=func.now()),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "eventId", "type", name="ongeki_static_events_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
music = Table(
|
||||
"ongeki_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("genre", String(255)),
|
||||
Column("level", Float),
|
||||
UniqueConstraint("version", "songId", "chartId", name="ongeki_static_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
gachas = Table(
|
||||
"ongeki_static_gachas",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("gachaId", Integer, nullable=False),
|
||||
Column("gachaName", String(255), nullable=False),
|
||||
Column("type", Integer, nullable=False, server_default="0"),
|
||||
Column("kind", Integer, nullable=False, server_default="0"),
|
||||
Column("isCeiling", Boolean, server_default="0"),
|
||||
Column("maxSelectPoint", Integer, server_default="0"),
|
||||
Column("ceilingCnt", Integer, server_default="10"),
|
||||
Column("changeRateCnt1", Integer, server_default="0"),
|
||||
Column("changeRateCnt2", Integer, server_default="0"),
|
||||
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("convertEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
UniqueConstraint("version", "gachaId", "gachaName", name="ongeki_static_gachas_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
gacha_cards = Table(
|
||||
"ongeki_static_gacha_cards",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("gachaId", Integer, nullable=False),
|
||||
Column("cardId", Integer, nullable=False),
|
||||
Column("rarity", Integer, nullable=False),
|
||||
Column("weight", Integer, server_default="1"),
|
||||
Column("isPickup", Boolean, server_default="0"),
|
||||
Column("isSelect", Boolean, server_default="0"),
|
||||
UniqueConstraint("gachaId", "cardId", name="ongeki_static_gacha_cards_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
cards = Table(
|
||||
"ongeki_static_cards",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("cardId", Integer, nullable=False),
|
||||
Column("name", String(255), nullable=False),
|
||||
Column("charaId", Integer, nullable=False),
|
||||
Column("nickName", String(255)),
|
||||
Column("school", String(255), nullable=False),
|
||||
Column("attribute", String(5), nullable=False),
|
||||
Column("gakunen", String(255), nullable=False),
|
||||
Column("rarity", Integer, nullable=False),
|
||||
Column("levelParam", String(255), nullable=False),
|
||||
Column("skillId", Integer, nullable=False),
|
||||
Column("choKaikaSkillId", Integer, nullable=False),
|
||||
Column("cardNumber", String(255)),
|
||||
UniqueConstraint("version", "cardId", name="ongeki_static_cards_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class OngekiStaticData(BaseData):
|
||||
def put_card(self, version: int, card_id: int, **card_data) -> Optional[int]:
|
||||
sql = insert(cards).values(version=version, cardId=card_id, **card_data)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(**card_data)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert card! card_id {card_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_card(self, version: int, card_id: int) -> Optional[Dict]:
|
||||
sql = cards.select(and_(cards.c.version <= version, cards.c.cardId == card_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_card_by_card_number(self, version: int, card_number: str) -> Optional[Dict]:
|
||||
if not card_number.startswith("[O.N.G.E.K.I.]"):
|
||||
card_number = f"[O.N.G.E.K.I.]{card_number}"
|
||||
|
||||
sql = cards.select(
|
||||
and_(cards.c.version <= version, cards.c.cardNumber == card_number)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_card_by_name(self, version: int, name: str) -> Optional[Dict]:
|
||||
sql = cards.select(and_(cards.c.version <= version, cards.c.name == name))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_cards(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = cards.select(cards.c.version <= version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_cards_by_rarity(self, version: int, rarity: int) -> Optional[List[Dict]]:
|
||||
sql = cards.select(and_(cards.c.version <= version, cards.c.rarity == rarity))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_gacha(
|
||||
self,
|
||||
version: int,
|
||||
gacha_id: int,
|
||||
gacha_name: int,
|
||||
gacha_kind: int,
|
||||
**gacha_data,
|
||||
) -> Optional[int]:
|
||||
sql = insert(gachas).values(
|
||||
version=version,
|
||||
gachaId=gacha_id,
|
||||
gachaName=gacha_name,
|
||||
kind=gacha_kind,
|
||||
**gacha_data,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
version=version,
|
||||
gachaId=gacha_id,
|
||||
gachaName=gacha_name,
|
||||
kind=gacha_kind,
|
||||
**gacha_data,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert gacha! gacha_id {gacha_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_gacha(self, version: int, gacha_id: int) -> Optional[Dict]:
|
||||
sql = gachas.select(
|
||||
and_(gachas.c.version <= version, gachas.c.gachaId == gacha_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_gachas(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = gachas.select(gachas.c.version == version).order_by(
|
||||
gachas.c.gachaId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_gacha_card(
|
||||
self, gacha_id: int, card_id: int, **gacha_card
|
||||
) -> Optional[int]:
|
||||
sql = insert(gacha_cards).values(gachaId=gacha_id, cardId=card_id, **gacha_card)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
gachaId=gacha_id, cardId=card_id, **gacha_card
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_gacha_cards(self, gacha_id: int) -> Optional[List[Dict]]:
|
||||
sql = gacha_cards.select(gacha_cards.c.gachaId == gacha_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_event(
|
||||
self, version: int, event_id: int, event_type: int, event_name: str
|
||||
) -> Optional[int]:
|
||||
sql = insert(events).values(
|
||||
version=version,
|
||||
eventId=event_id,
|
||||
type=event_type,
|
||||
name=event_name,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
name=event_name,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert event! event_id {event_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_event(self, version: int, event_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(events).where(
|
||||
and_(events.c.version == version, events.c.eventId == event_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_events(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = select(events).where(events.c.version == version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_enabled_events(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = select(events).where(
|
||||
and_(events.c.version == version, events.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_chart(
|
||||
self,
|
||||
version: int,
|
||||
song_id: int,
|
||||
chart_id: int,
|
||||
title: str,
|
||||
artist: str,
|
||||
genre: str,
|
||||
level: float,
|
||||
) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=song_id,
|
||||
chartId=chart_id,
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
level=level,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
level=level,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to insert chart! song_id: {song_id}, chart_id: {chart_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_chart(
|
||||
self, version: int, song_id: int, chart_id: int = None
|
||||
) -> Optional[List[Dict]]:
|
||||
pass
|
||||
|
||||
def get_music(self, version: int) -> Optional[List[Dict]]:
|
||||
pass
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
events = Table(
|
||||
"ongeki_static_events",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("eventId", Integer),
|
||||
Column("type", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("startDate", TIMESTAMP, server_default=func.now()),
|
||||
Column("enabled", Boolean, server_default="1"),
|
||||
UniqueConstraint("version", "eventId", "type", name="ongeki_static_events_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
music = Table(
|
||||
"ongeki_static_music",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("songId", Integer),
|
||||
Column("chartId", Integer),
|
||||
Column("title", String(255)),
|
||||
Column("artist", String(255)),
|
||||
Column("genre", String(255)),
|
||||
Column("level", Float),
|
||||
UniqueConstraint("version", "songId", "chartId", name="ongeki_static_music_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
gachas = Table(
|
||||
"ongeki_static_gachas",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("gachaId", Integer, nullable=False),
|
||||
Column("gachaName", String(255), nullable=False),
|
||||
Column("type", Integer, nullable=False, server_default="0"),
|
||||
Column("kind", Integer, nullable=False, server_default="0"),
|
||||
Column("isCeiling", Boolean, server_default="0"),
|
||||
Column("maxSelectPoint", Integer, server_default="0"),
|
||||
Column("ceilingCnt", Integer, server_default="10"),
|
||||
Column("changeRateCnt1", Integer, server_default="0"),
|
||||
Column("changeRateCnt2", Integer, server_default="0"),
|
||||
Column("startDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("endDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("noticeStartDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
|
||||
Column("noticeEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
Column("convertEndDate", TIMESTAMP, server_default="2038-01-01 00:00:00.0"),
|
||||
UniqueConstraint("version", "gachaId", "gachaName", name="ongeki_static_gachas_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
gacha_cards = Table(
|
||||
"ongeki_static_gacha_cards",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("gachaId", Integer, nullable=False),
|
||||
Column("cardId", Integer, nullable=False),
|
||||
Column("rarity", Integer, nullable=False),
|
||||
Column("weight", Integer, server_default="1"),
|
||||
Column("isPickup", Boolean, server_default="0"),
|
||||
Column("isSelect", Boolean, server_default="0"),
|
||||
UniqueConstraint("gachaId", "cardId", name="ongeki_static_gacha_cards_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
cards = Table(
|
||||
"ongeki_static_cards",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer, nullable=False),
|
||||
Column("cardId", Integer, nullable=False),
|
||||
Column("name", String(255), nullable=False),
|
||||
Column("charaId", Integer, nullable=False),
|
||||
Column("nickName", String(255)),
|
||||
Column("school", String(255), nullable=False),
|
||||
Column("attribute", String(5), nullable=False),
|
||||
Column("gakunen", String(255), nullable=False),
|
||||
Column("rarity", Integer, nullable=False),
|
||||
Column("levelParam", String(255), nullable=False),
|
||||
Column("skillId", Integer, nullable=False),
|
||||
Column("choKaikaSkillId", Integer, nullable=False),
|
||||
Column("cardNumber", String(255)),
|
||||
UniqueConstraint("version", "cardId", name="ongeki_static_cards_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class OngekiStaticData(BaseData):
|
||||
def put_card(self, version: int, card_id: int, **card_data) -> Optional[int]:
|
||||
sql = insert(cards).values(version=version, cardId=card_id, **card_data)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(**card_data))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert card! card_id {card_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_card(self, version: int, card_id: int) -> Optional[Dict]:
|
||||
sql = cards.select(and_(cards.c.version <= version, cards.c.cardId == card_id))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_card_by_card_number(self, version: int, card_number: str) -> Optional[Dict]:
|
||||
if not card_number.startswith("[O.N.G.E.K.I.]"):
|
||||
card_number = f"[O.N.G.E.K.I.]{card_number}"
|
||||
|
||||
sql = cards.select(
|
||||
and_(cards.c.version <= version, cards.c.cardNumber == card_number)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_card_by_name(self, version: int, name: str) -> Optional[Dict]:
|
||||
sql = cards.select(and_(cards.c.version <= version, cards.c.name == name))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_cards(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = cards.select(cards.c.version <= version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_cards_by_rarity(self, version: int, rarity: int) -> Optional[List[Dict]]:
|
||||
sql = cards.select(and_(cards.c.version <= version, cards.c.rarity == rarity))
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_gacha(
|
||||
self,
|
||||
version: int,
|
||||
gacha_id: int,
|
||||
gacha_name: int,
|
||||
gacha_kind: int,
|
||||
**gacha_data,
|
||||
) -> Optional[int]:
|
||||
sql = insert(gachas).values(
|
||||
version=version,
|
||||
gachaId=gacha_id,
|
||||
gachaName=gacha_name,
|
||||
kind=gacha_kind,
|
||||
**gacha_data,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
version=version,
|
||||
gachaId=gacha_id,
|
||||
gachaName=gacha_name,
|
||||
kind=gacha_kind,
|
||||
**gacha_data,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert gacha! gacha_id {gacha_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_gacha(self, version: int, gacha_id: int) -> Optional[Dict]:
|
||||
sql = gachas.select(
|
||||
and_(gachas.c.version <= version, gachas.c.gachaId == gacha_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_gachas(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = gachas.select(gachas.c.version == version).order_by(
|
||||
gachas.c.gachaId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_gacha_card(
|
||||
self, gacha_id: int, card_id: int, **gacha_card
|
||||
) -> Optional[int]:
|
||||
sql = insert(gacha_cards).values(gachaId=gacha_id, cardId=card_id, **gacha_card)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(gachaId=gacha_id, cardId=card_id, **gacha_card)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert gacha card! gacha_id {gacha_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_gacha_cards(self, gacha_id: int) -> Optional[List[Dict]]:
|
||||
sql = gacha_cards.select(gacha_cards.c.gachaId == gacha_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_event(
|
||||
self, version: int, event_id: int, event_type: int, event_name: str
|
||||
) -> Optional[int]:
|
||||
sql = insert(events).values(
|
||||
version=version,
|
||||
eventId=event_id,
|
||||
type=event_type,
|
||||
name=event_name,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
name=event_name,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert event! event_id {event_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_event(self, version: int, event_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(events).where(
|
||||
and_(events.c.version == version, events.c.eventId == event_id)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_events(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = select(events).where(events.c.version == version)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def get_enabled_events(self, version: int) -> Optional[List[Dict]]:
|
||||
sql = select(events).where(
|
||||
and_(events.c.version == version, events.c.enabled == True)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_chart(
|
||||
self,
|
||||
version: int,
|
||||
song_id: int,
|
||||
chart_id: int,
|
||||
title: str,
|
||||
artist: str,
|
||||
genre: str,
|
||||
level: float,
|
||||
) -> Optional[int]:
|
||||
sql = insert(music).values(
|
||||
version=version,
|
||||
songId=song_id,
|
||||
chartId=chart_id,
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
level=level,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
title=title,
|
||||
artist=artist,
|
||||
genre=genre,
|
||||
level=level,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to insert chart! song_id: {song_id}, chart_id: {chart_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_chart(
|
||||
self, version: int, song_id: int, chart_id: int = None
|
||||
) -> Optional[List[Dict]]:
|
||||
pass
|
||||
|
||||
def get_music(self, version: int) -> Optional[List[Dict]]:
|
||||
pass
|
||||
|
||||
def get_music_chart(
|
||||
self, version: int, song_id: int, chart_id: int
|
||||
) -> Optional[List[Row]]:
|
||||
sql = select(music).where(
|
||||
and_(
|
||||
music.c.version == version,
|
||||
music.c.songId == song_id,
|
||||
music.c.chartId == chart_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -2,6 +2,7 @@ from datetime import datetime, timedelta
|
|||
import json, logging
|
||||
from typing import Any, Dict, List
|
||||
import random
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.data import Data
|
||||
from core import CoreConfig
|
||||
|
@ -57,7 +58,7 @@ class PokkenBase:
|
|||
"port": self.game_cfg.server.stun_server_port,
|
||||
},
|
||||
"AdmissionUrl": f"ws://{self.game_cfg.server.hostname}:{self.game_cfg.ports.admission}",
|
||||
"locationId": 123, # FIXME: Get arcade's ID from the database
|
||||
"locationId": 123, # FIXME: Get arcade's ID from the database
|
||||
"logfilename": "JackalMatchingLibrary.log",
|
||||
"biwalogfilename": "./biwa.log",
|
||||
}
|
||||
|
@ -147,7 +148,7 @@ class PokkenBase:
|
|||
res.load_user.CopyFrom(load_usr)
|
||||
return res.SerializeToString()
|
||||
|
||||
"""
|
||||
"""
|
||||
TODO: Add repeated values
|
||||
tutorial_progress_flag
|
||||
rankmatch_progress
|
||||
|
@ -278,13 +279,15 @@ class PokkenBase:
|
|||
|
||||
req = request.save_user
|
||||
user_id = req.banapass_id
|
||||
|
||||
|
||||
tut_flgs: List[int] = []
|
||||
ach_flgs: List[int] = []
|
||||
evt_flgs: List[int] = []
|
||||
evt_params: List[int] = []
|
||||
|
||||
get_rank_pts: int = req.get_trainer_rank_point if req.get_trainer_rank_point else 0
|
||||
get_rank_pts: int = (
|
||||
req.get_trainer_rank_point if req.get_trainer_rank_point else 0
|
||||
)
|
||||
get_money: int = req.get_money
|
||||
get_score_pts: int = req.get_score_point if req.get_score_point else 0
|
||||
grade_max: int = req.grade_max_num
|
||||
|
@ -294,7 +297,7 @@ class PokkenBase:
|
|||
total_play_days: int = req.total_play_days
|
||||
awake_num: int = req.awake_num # ?
|
||||
use_support_ct: int = req.use_support_num
|
||||
beat_num: int = req.beat_num # ?
|
||||
beat_num: int = req.beat_num # ?
|
||||
evt_state: int = req.event_state
|
||||
aid_skill: int = req.aid_skill
|
||||
last_evt: int = req.last_play_event_id
|
||||
|
@ -302,73 +305,106 @@ class PokkenBase:
|
|||
battle = req.battle_data
|
||||
mon = req.pokemon_data
|
||||
|
||||
p = self.data.profile.touch_profile(user_id)
|
||||
if p is None or not p:
|
||||
self.data.profile.create_profile(user_id)
|
||||
with self.data.profile.conn.begin() or nullcontext():
|
||||
p = self.data.profile.touch_profile(user_id)
|
||||
if p is None or not p:
|
||||
self.data.profile.create_profile(user_id)
|
||||
|
||||
if req.trainer_name_pending is not None and req.trainer_name_pending: # we're saving for the first time
|
||||
self.data.profile.set_profile_name(user_id, req.trainer_name_pending, req.avatar_gender if req.avatar_gender else None)
|
||||
if (
|
||||
req.trainer_name_pending is not None and req.trainer_name_pending
|
||||
): # we're saving for the first time
|
||||
self.data.profile.set_profile_name(
|
||||
user_id,
|
||||
req.trainer_name_pending,
|
||||
req.avatar_gender if req.avatar_gender else None,
|
||||
)
|
||||
|
||||
for tut_flg in req.tutorial_progress_flag:
|
||||
tut_flgs.append(tut_flg)
|
||||
|
||||
self.data.profile.update_profile_tutorial_flags(user_id, tut_flgs)
|
||||
for tut_flg in req.tutorial_progress_flag:
|
||||
tut_flgs.append(tut_flg)
|
||||
|
||||
for ach_flg in req.achievement_flag:
|
||||
ach_flgs.append(ach_flg)
|
||||
|
||||
self.data.profile.update_profile_tutorial_flags(user_id, ach_flg)
|
||||
self.data.profile.update_profile_tutorial_flags(user_id, tut_flgs)
|
||||
|
||||
for evt_flg in req.event_achievement_flag:
|
||||
evt_flgs.append(evt_flg)
|
||||
for ach_flg in req.achievement_flag:
|
||||
ach_flgs.append(ach_flg)
|
||||
|
||||
for evt_param in req.event_achievement_param:
|
||||
evt_params.append(evt_param)
|
||||
self.data.profile.update_profile_tutorial_flags(user_id, ach_flg)
|
||||
|
||||
self.data.profile.update_profile_event(user_id, evt_state, evt_flgs, evt_params, )
|
||||
|
||||
for reward in req.reward_data:
|
||||
self.data.item.add_reward(user_id, reward.get_category_id, reward.get_content_id, reward.get_type_id)
|
||||
|
||||
self.data.profile.add_profile_points(user_id, get_rank_pts, get_money, get_score_pts, grade_max)
|
||||
|
||||
self.data.profile.update_support_team(user_id, 1, req.support_set_1[0], req.support_set_1[1])
|
||||
self.data.profile.update_support_team(user_id, 2, req.support_set_2[0], req.support_set_2[1])
|
||||
self.data.profile.update_support_team(user_id, 3, req.support_set_3[0], req.support_set_3[1])
|
||||
for evt_flg in req.event_achievement_flag:
|
||||
evt_flgs.append(evt_flg)
|
||||
|
||||
self.data.profile.put_pokemon(user_id, mon.char_id, mon.illustration_book_no, mon.bp_point_atk, mon.bp_point_res, mon.bp_point_def, mon.bp_point_sp)
|
||||
self.data.profile.add_pokemon_xp(user_id, mon.char_id, mon.get_pokemon_exp)
|
||||
|
||||
for x in range(len(battle.play_mode)):
|
||||
self.data.profile.put_pokemon_battle_result(
|
||||
user_id,
|
||||
mon.char_id,
|
||||
PokkenConstants.BATTLE_TYPE(battle.play_mode[x]),
|
||||
PokkenConstants.BATTLE_RESULT(battle.result[x])
|
||||
for evt_param in req.event_achievement_param:
|
||||
evt_params.append(evt_param)
|
||||
|
||||
self.data.profile.update_profile_event(
|
||||
user_id,
|
||||
evt_state,
|
||||
evt_flgs,
|
||||
evt_params,
|
||||
)
|
||||
|
||||
self.data.profile.put_stats(
|
||||
user_id,
|
||||
battle.ex_ko_num,
|
||||
battle.wko_num,
|
||||
battle.timeup_win_num,
|
||||
battle.cool_ko_num,
|
||||
battle.perfect_ko_num,
|
||||
num_continues
|
||||
)
|
||||
self.data.profile.add_profile_points(
|
||||
user_id, get_rank_pts, get_money, get_score_pts, grade_max
|
||||
)
|
||||
|
||||
self.data.profile.put_extra(
|
||||
user_id,
|
||||
extra_counter,
|
||||
evt_reward_get_flg,
|
||||
total_play_days,
|
||||
awake_num,
|
||||
use_support_ct,
|
||||
beat_num,
|
||||
aid_skill,
|
||||
last_evt
|
||||
)
|
||||
self.data.profile.update_support_team(
|
||||
user_id, 1, req.support_set_1[0], req.support_set_1[1]
|
||||
)
|
||||
self.data.profile.update_support_team(
|
||||
user_id, 2, req.support_set_2[0], req.support_set_2[1]
|
||||
)
|
||||
self.data.profile.update_support_team(
|
||||
user_id, 3, req.support_set_3[0], req.support_set_3[1]
|
||||
)
|
||||
|
||||
self.data.profile.put_pokemon(
|
||||
user_id,
|
||||
mon.char_id,
|
||||
mon.illustration_book_no,
|
||||
mon.bp_point_atk,
|
||||
mon.bp_point_res,
|
||||
mon.bp_point_def,
|
||||
mon.bp_point_sp,
|
||||
)
|
||||
self.data.profile.add_pokemon_xp(user_id, mon.char_id, mon.get_pokemon_exp)
|
||||
|
||||
for x in range(len(battle.play_mode)):
|
||||
self.data.profile.put_pokemon_battle_result(
|
||||
user_id,
|
||||
mon.char_id,
|
||||
PokkenConstants.BATTLE_TYPE(battle.play_mode[x]),
|
||||
PokkenConstants.BATTLE_RESULT(battle.result[x]),
|
||||
)
|
||||
|
||||
self.data.profile.put_stats(
|
||||
user_id,
|
||||
battle.ex_ko_num,
|
||||
battle.wko_num,
|
||||
battle.timeup_win_num,
|
||||
battle.cool_ko_num,
|
||||
battle.perfect_ko_num,
|
||||
num_continues,
|
||||
)
|
||||
|
||||
self.data.profile.put_extra(
|
||||
user_id,
|
||||
extra_counter,
|
||||
evt_reward_get_flg,
|
||||
total_play_days,
|
||||
awake_num,
|
||||
use_support_ct,
|
||||
beat_num,
|
||||
aid_skill,
|
||||
last_evt,
|
||||
)
|
||||
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
for reward in req.reward_data:
|
||||
self.data.item.add_reward(
|
||||
user_id,
|
||||
reward.get_category_id,
|
||||
reward.get_content_id,
|
||||
reward.get_type_id,
|
||||
)
|
||||
|
||||
return res.SerializeToString()
|
||||
|
||||
|
@ -398,20 +434,17 @@ class PokkenBase:
|
|||
self, data: Dict = {}, client_ip: str = "127.0.0.1"
|
||||
) -> Dict:
|
||||
"""
|
||||
"sessionId":"12345678",
|
||||
"A":{
|
||||
"pcb_id": data["data"]["must"]["pcb_id"],
|
||||
"gip": client_ip
|
||||
},
|
||||
"sessionId":"12345678",
|
||||
"A":{
|
||||
"pcb_id": data["data"]["must"]["pcb_id"],
|
||||
"gip": client_ip
|
||||
},
|
||||
"""
|
||||
return {
|
||||
"data": {
|
||||
"sessionId":"12345678",
|
||||
"A":{
|
||||
"pcb_id": data["data"]["must"]["pcb_id"],
|
||||
"gip": client_ip
|
||||
},
|
||||
"list":[]
|
||||
"sessionId": "12345678",
|
||||
"A": {"pcb_id": data["data"]["must"]["pcb_id"], "gip": client_ip},
|
||||
"list": [],
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -422,11 +455,9 @@ class PokkenBase:
|
|||
|
||||
def handle_admission_noop(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict:
|
||||
return {}
|
||||
|
||||
def handle_admission_joinsession(self, data: Dict, req_ip: str = "127.0.0.1") -> Dict:
|
||||
|
||||
def handle_admission_joinsession(
|
||||
self, data: Dict, req_ip: str = "127.0.0.1"
|
||||
) -> Dict:
|
||||
self.logger.info(f"Admission: JoinSession from {req_ip}")
|
||||
return {
|
||||
'data': {
|
||||
"id": 12345678
|
||||
}
|
||||
}
|
||||
return {"data": {"id": 12345678}}
|
||||
|
|
|
@ -43,14 +43,18 @@ class PokkenServerConfig:
|
|||
return CoreConfig.get_config_field(
|
||||
self.__config, "pokken", "server", "enable_matching", default=False
|
||||
)
|
||||
|
||||
|
||||
@property
|
||||
def stun_server_host(self) -> str:
|
||||
"""
|
||||
Hostname of the EXTERNAL stun server the game should connect to. This is not handled by artemis.
|
||||
"""
|
||||
return CoreConfig.get_config_field(
|
||||
self.__config, "pokken", "server", "stun_server_host", default="stunserver.stunprotocol.org"
|
||||
self.__config,
|
||||
"pokken",
|
||||
"server",
|
||||
"stun_server_host",
|
||||
default="stunserver.stunprotocol.org",
|
||||
)
|
||||
|
||||
@property
|
||||
|
@ -62,10 +66,11 @@ class PokkenServerConfig:
|
|||
self.__config, "pokken", "server", "stun_server_port", default=3478
|
||||
)
|
||||
|
||||
|
||||
class PokkenPortsConfig:
|
||||
def __init__(self, parent_config: "PokkenConfig"):
|
||||
self.__config = parent_config
|
||||
|
||||
|
||||
@property
|
||||
def game(self) -> int:
|
||||
return CoreConfig.get_config_field(
|
||||
|
@ -77,7 +82,7 @@ class PokkenPortsConfig:
|
|||
return CoreConfig.get_config_field(
|
||||
self.__config, "pokken", "ports", "admission", default=9001
|
||||
)
|
||||
|
||||
|
||||
|
||||
class PokkenConfig(dict):
|
||||
def __init__(self) -> None:
|
||||
|
|
|
@ -35,5 +35,5 @@ class PokkenFrontend(FE_Base):
|
|||
return template.render(
|
||||
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||
game_list=self.environment.globals["game_list"],
|
||||
sesh=vars(usr_sesh)
|
||||
sesh=vars(usr_sesh),
|
||||
).encode("utf-16")
|
||||
|
|
|
@ -94,7 +94,8 @@ class PokkenServlet(resource.Resource):
|
|||
def setup(self) -> None:
|
||||
if self.game_cfg.server.enable_matching:
|
||||
reactor.listenTCP(
|
||||
self.game_cfg.ports.admission, PokkenAdmissionFactory(self.core_cfg, self.game_cfg)
|
||||
self.game_cfg.ports.admission,
|
||||
PokkenAdmissionFactory(self.core_cfg, self.game_cfg),
|
||||
)
|
||||
|
||||
def render_POST(
|
||||
|
@ -134,7 +135,7 @@ class PokkenServlet(resource.Resource):
|
|||
def handle_matching(self, request: Request) -> bytes:
|
||||
if not self.game_cfg.server.enable_matching:
|
||||
return b""
|
||||
|
||||
|
||||
content = request.content.getvalue()
|
||||
client_ip = Utils.get_ip_addr(request)
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .profile import PokkenProfileData
|
||||
from .match import PokkenMatchData
|
||||
from .item import PokkenItemData
|
||||
from .static import PokkenStaticData
|
||||
from .profile import PokkenProfileData
|
||||
from .match import PokkenMatchData
|
||||
from .item import PokkenItemData
|
||||
from .static import PokkenStaticData
|
||||
|
|
|
@ -1,46 +1,50 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
item = Table(
|
||||
"pokken_item",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
Column("category", Integer),
|
||||
Column("content", Integer),
|
||||
Column("type", Integer),
|
||||
UniqueConstraint("user", "category", "content", "type", name="pokken_item_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PokkenItemData(BaseData):
|
||||
"""
|
||||
Items obtained as rewards
|
||||
"""
|
||||
|
||||
def add_reward(self, user_id: int, category: int, content: int, item_type: int) -> Optional[int]:
|
||||
sql = insert(item).values(
|
||||
user=user_id,
|
||||
category=category,
|
||||
content=content,
|
||||
type=item_type,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert reward for user {user_id}: {category}-{content}-{item_type}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
item = Table(
|
||||
"pokken_item",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
Column("category", Integer),
|
||||
Column("content", Integer),
|
||||
Column("type", Integer),
|
||||
UniqueConstraint("user", "category", "content", "type", name="pokken_item_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PokkenItemData(BaseData):
|
||||
"""
|
||||
Items obtained as rewards
|
||||
"""
|
||||
|
||||
def add_reward(
|
||||
self, user_id: int, category: int, content: int, item_type: int
|
||||
) -> Optional[int]:
|
||||
sql = insert(item).values(
|
||||
user=user_id,
|
||||
category=category,
|
||||
content=content,
|
||||
type=item_type,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to insert reward for user {user_id}: {category}-{content}-{item_type}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
|
|
@ -1,52 +1,52 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
# Pokken sends depressingly little match data...
|
||||
match_data = Table(
|
||||
"pokken_match_data",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("num_games", Integer),
|
||||
Column("play_modes", JSON),
|
||||
Column("results", JSON),
|
||||
Column("ex_ko_num", Integer),
|
||||
Column("wko_num", Integer),
|
||||
Column("timeup_win_num", Integer),
|
||||
Column("cool_ko_num", Integer),
|
||||
Column("perfect_ko_num", Integer),
|
||||
Column("use_navi", Integer),
|
||||
Column("use_navi_cloth", Integer),
|
||||
Column("use_aid_skill", Integer),
|
||||
Column("play_date", TIMESTAMP),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PokkenMatchData(BaseData):
|
||||
"""
|
||||
Match logs
|
||||
"""
|
||||
|
||||
def save_match(self, user_id: int, match_data: Dict) -> Optional[int]:
|
||||
pass
|
||||
|
||||
def get_match(self, match_id: int) -> Optional[Row]:
|
||||
pass
|
||||
|
||||
def get_matches_by_user(self, user_id: int) -> Optional[List[Row]]:
|
||||
pass
|
||||
|
||||
def get_matches(self, limit: int = 20) -> Optional[List[Row]]:
|
||||
pass
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
# Pokken sends depressingly little match data...
|
||||
match_data = Table(
|
||||
"pokken_match_data",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("num_games", Integer),
|
||||
Column("play_modes", JSON),
|
||||
Column("results", JSON),
|
||||
Column("ex_ko_num", Integer),
|
||||
Column("wko_num", Integer),
|
||||
Column("timeup_win_num", Integer),
|
||||
Column("cool_ko_num", Integer),
|
||||
Column("perfect_ko_num", Integer),
|
||||
Column("use_navi", Integer),
|
||||
Column("use_navi_cloth", Integer),
|
||||
Column("use_aid_skill", Integer),
|
||||
Column("play_date", TIMESTAMP),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PokkenMatchData(BaseData):
|
||||
"""
|
||||
Match logs
|
||||
"""
|
||||
|
||||
def save_match(self, user_id: int, match_data: Dict) -> Optional[int]:
|
||||
pass
|
||||
|
||||
def get_match(self, match_id: int) -> Optional[Row]:
|
||||
pass
|
||||
|
||||
def get_matches_by_user(self, user_id: int) -> Optional[List[Row]]:
|
||||
pass
|
||||
|
||||
def get_matches(self, limit: int = 20) -> Optional[List[Row]]:
|
||||
pass
|
||||
|
|
|
@ -1,362 +1,464 @@
|
|||
from typing import Optional, Dict, List, Union
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from ..const import PokkenConstants
|
||||
|
||||
# Some more of the repeated fields could probably be their own tables, for now I just did the ones that made sense to me
|
||||
# Having the profile table be this massive kinda blows for updates but w/e, **kwargs to the rescue
|
||||
profile = Table(
|
||||
"pokken_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
Column("trainer_name", String(16)), # optional
|
||||
Column("home_region_code", Integer),
|
||||
Column("home_loc_name", String(255)),
|
||||
Column("pref_code", Integer),
|
||||
Column("navi_newbie_flag", Boolean),
|
||||
Column("navi_enable_flag", Boolean),
|
||||
Column("pad_vibrate_flag", Boolean),
|
||||
Column("trainer_rank_point", Integer),
|
||||
Column("wallet", Integer),
|
||||
Column("fight_money", Integer),
|
||||
Column("score_point", Integer),
|
||||
Column("grade_max_num", Integer),
|
||||
Column("extra_counter", Integer), # Optional
|
||||
Column("tutorial_progress_flag", JSON), # Repeated, Integer
|
||||
Column("total_play_days", Integer),
|
||||
Column("play_date_time", Integer),
|
||||
Column("achievement_flag", JSON), # Repeated, Integer
|
||||
Column("lucky_box_fail_num", Integer),
|
||||
Column("event_reward_get_flag", Integer),
|
||||
Column("rank_pvp_all", Integer),
|
||||
Column("rank_pvp_loc", Integer),
|
||||
Column("rank_cpu_all", Integer),
|
||||
Column("rank_cpu_loc", Integer),
|
||||
Column("rank_event", Integer),
|
||||
Column("awake_num", Integer),
|
||||
Column("use_support_num", Integer),
|
||||
Column("rankmatch_flag", Integer),
|
||||
Column("rankmatch_max", Integer), # Optional
|
||||
Column("rankmatch_progress", JSON), # Repeated, Integer
|
||||
Column("rankmatch_success", Integer), # Optional
|
||||
Column("beat_num", Integer), # Optional
|
||||
Column("title_text_id", Integer),
|
||||
Column("title_plate_id", Integer),
|
||||
Column("title_decoration_id", Integer),
|
||||
Column("support_pokemon_list", JSON), # Repeated, Integer
|
||||
Column("support_set_1_1", Integer), # Repeated, Integer
|
||||
Column("support_set_1_2", Integer),
|
||||
Column("support_set_2_1", Integer), # Repeated, Integer
|
||||
Column("support_set_2_2", Integer),
|
||||
Column("support_set_3_1", Integer), # Repeated, Integer
|
||||
Column("support_set_3_2", Integer),
|
||||
Column("navi_trainer", Integer),
|
||||
Column("navi_version_id", Integer),
|
||||
Column("aid_skill_list", JSON), # Repeated, Integer
|
||||
Column("aid_skill", Integer),
|
||||
Column("comment_text_id", Integer),
|
||||
Column("comment_word_id", Integer),
|
||||
Column("latest_use_pokemon", Integer),
|
||||
Column("ex_ko_num", Integer),
|
||||
Column("wko_num", Integer),
|
||||
Column("timeup_win_num", Integer),
|
||||
Column("cool_ko_num", Integer),
|
||||
Column("perfect_ko_num", Integer),
|
||||
Column("record_flag", Integer),
|
||||
Column("continue_num", Integer),
|
||||
Column("avatar_body", Integer), # Optional
|
||||
Column("avatar_gender", Integer), # Optional
|
||||
Column("avatar_background", Integer), # Optional
|
||||
Column("avatar_head", Integer), # Optional
|
||||
Column("avatar_battleglass", Integer), # Optional
|
||||
Column("avatar_face0", Integer), # Optional
|
||||
Column("avatar_face1", Integer), # Optional
|
||||
Column("avatar_face2", Integer), # Optional
|
||||
Column("avatar_bodyall", Integer), # Optional
|
||||
Column("avatar_wear", Integer), # Optional
|
||||
Column("avatar_accessory", Integer), # Optional
|
||||
Column("avatar_stamp", Integer), # Optional
|
||||
Column("event_state", Integer),
|
||||
Column("event_id", Integer),
|
||||
Column("sp_bonus_category_id_1", Integer),
|
||||
Column("sp_bonus_key_value_1", Integer),
|
||||
Column("sp_bonus_category_id_2", Integer),
|
||||
Column("sp_bonus_key_value_2", Integer),
|
||||
Column("last_play_event_id", Integer), # Optional
|
||||
Column("event_achievement_flag", JSON), # Repeated, Integer
|
||||
Column("event_achievement_param", JSON), # Repeated, Integer
|
||||
Column("battle_num_vs_wan", Integer), # 4?
|
||||
Column("win_vs_wan", Integer),
|
||||
Column("battle_num_vs_lan", Integer), # 3?
|
||||
Column("win_vs_lan", Integer),
|
||||
Column("battle_num_vs_cpu", Integer), # 2
|
||||
Column("win_cpu", Integer),
|
||||
Column("battle_num_tutorial", Integer), # 1?
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
pokemon_data = Table(
|
||||
"pokken_pokemon_data",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("char_id", Integer, nullable=False),
|
||||
Column("illustration_book_no", Integer),
|
||||
Column("pokemon_exp", Integer),
|
||||
Column("battle_num_vs_wan", Integer), # 4?
|
||||
Column("win_vs_wan", Integer),
|
||||
Column("battle_num_vs_lan", Integer), # 3?
|
||||
Column("win_vs_lan", Integer),
|
||||
Column("battle_num_vs_cpu", Integer), # 2
|
||||
Column("win_cpu", Integer),
|
||||
Column("battle_all_num_tutorial", Integer), # ???
|
||||
Column("battle_num_tutorial", Integer), # 1?
|
||||
Column("bp_point_atk", Integer),
|
||||
Column("bp_point_res", Integer),
|
||||
Column("bp_point_def", Integer),
|
||||
Column("bp_point_sp", Integer),
|
||||
UniqueConstraint("user", "char_id", name="pokken_pokemon_data_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PokkenProfileData(BaseData):
|
||||
def touch_profile(self, user_id: int) -> Optional[int]:
|
||||
sql = select([profile.c.id]).where(profile.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()['id']
|
||||
|
||||
def create_profile(self, user_id: int) -> Optional[int]:
|
||||
sql = insert(profile).values(user=user_id)
|
||||
conflict = sql.on_duplicate_key_update(user=user_id)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to create pokken profile for user {user_id}!")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def set_profile_name(self, user_id: int, new_name: str, gender: Union[int, None] = None) -> None:
|
||||
sql = update(profile).where(profile.c.user == user_id).values(
|
||||
trainer_name=new_name,
|
||||
avatar_gender=gender if gender is not None else profile.c.avatar_gender
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile name for user {user_id}!"
|
||||
)
|
||||
|
||||
def put_extra(
|
||||
self,
|
||||
user_id: int,
|
||||
extra_counter: int,
|
||||
evt_reward_get_flg: int,
|
||||
total_play_days: int,
|
||||
awake_num: int,
|
||||
use_support_ct: int,
|
||||
beat_num: int,
|
||||
aid_skill: int,
|
||||
last_evt: int
|
||||
) -> None:
|
||||
sql = update(profile).where(profile.c.user == user_id).values(
|
||||
extra_counter=extra_counter,
|
||||
event_reward_get_flag=evt_reward_get_flg,
|
||||
total_play_days=total_play_days,
|
||||
awake_num=awake_num,
|
||||
use_support_num=use_support_ct,
|
||||
beat_num=beat_num,
|
||||
aid_skill=aid_skill,
|
||||
last_play_event_id=last_evt
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to put extra data for user {user_id}")
|
||||
|
||||
def update_profile_tutorial_flags(self, user_id: int, tutorial_flags: List) -> None:
|
||||
sql = update(profile).where(profile.c.user == user_id).values(
|
||||
tutorial_progress_flag=tutorial_flags,
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile tutorial flags for user {user_id}!"
|
||||
)
|
||||
|
||||
def update_profile_achievement_flags(self, user_id: int, achievement_flags: List) -> None:
|
||||
sql = update(profile).where(profile.c.user == user_id).values(
|
||||
achievement_flag=achievement_flags,
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile achievement flags for user {user_id}!"
|
||||
)
|
||||
|
||||
def update_profile_event(self, user_id: int, event_state: List, event_flags: List[int], event_param: List[int], last_evt: int = None) -> None:
|
||||
sql = update(profile).where(profile.c.user == user_id).values(
|
||||
event_state=event_state,
|
||||
event_achievement_flag=event_flags,
|
||||
event_achievement_param=event_param,
|
||||
last_play_event_id=last_evt if last_evt is not None else profile.c.last_play_event_id,
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile event state for user {user_id}!"
|
||||
)
|
||||
|
||||
def add_profile_points(
|
||||
self, user_id: int, rank_pts: int, money: int, score_pts: int, grade_max: int
|
||||
) -> None:
|
||||
sql = update(profile).where(profile.c.user == user_id).values(
|
||||
trainer_rank_point = profile.c.trainer_rank_point + rank_pts,
|
||||
fight_money = profile.c.fight_money + money,
|
||||
score_point = profile.c.score_point + score_pts,
|
||||
grade_max_num = grade_max
|
||||
)
|
||||
|
||||
def get_profile(self, user_id: int) -> Optional[Row]:
|
||||
sql = profile.select(profile.c.user == user_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_pokemon(
|
||||
self,
|
||||
user_id: int,
|
||||
pokemon_id: int,
|
||||
illust_no: int,
|
||||
atk: int,
|
||||
res: int,
|
||||
defe: int,
|
||||
sp: int
|
||||
) -> Optional[int]:
|
||||
sql = insert(pokemon_data).values(
|
||||
user=user_id,
|
||||
char_id=pokemon_id,
|
||||
illustration_book_no=illust_no,
|
||||
bp_point_atk=atk,
|
||||
bp_point_res=res,
|
||||
bp_point_defe=defe,
|
||||
bp_point_sp=sp,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
illustration_book_no=illust_no,
|
||||
bp_point_atk=atk,
|
||||
bp_point_res=res,
|
||||
bp_point_defe=defe,
|
||||
bp_point_sp=sp,
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to insert pokemon ID {pokemon_id} for user {user_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def add_pokemon_xp(
|
||||
self,
|
||||
user_id: int,
|
||||
pokemon_id: int,
|
||||
xp: int
|
||||
) -> None:
|
||||
sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values(
|
||||
pokemon_exp=pokemon_data.c.pokemon_exp + xp
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}")
|
||||
|
||||
def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]:
|
||||
pass
|
||||
|
||||
def get_all_pokemon_data(self, user_id: int) -> Optional[List[Row]]:
|
||||
pass
|
||||
|
||||
def put_pokemon_battle_result(
|
||||
self, user_id: int, pokemon_id: int, match_type: PokkenConstants.BATTLE_TYPE, match_result: PokkenConstants.BATTLE_RESULT
|
||||
) -> None:
|
||||
"""
|
||||
Records the match stats (type and win/loss) for the pokemon and profile
|
||||
"""
|
||||
sql = update(pokemon_data).where(and_(pokemon_data.c.user==user_id, pokemon_data.c.char_id==pokemon_id)).values(
|
||||
battle_num_tutorial=pokemon_data.c.battle_num_tutorial + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else pokemon_data.c.battle_num_tutorial,
|
||||
battle_all_num_tutorial=pokemon_data.c.battle_all_num_tutorial + 1 if match_type==PokkenConstants.BATTLE_TYPE.TUTORIAL else pokemon_data.c.battle_all_num_tutorial,
|
||||
|
||||
battle_num_vs_cpu=pokemon_data.c.battle_num_vs_cpu + 1 if match_type==PokkenConstants.BATTLE_TYPE.AI else pokemon_data.c.battle_num_vs_cpu,
|
||||
win_cpu=pokemon_data.c.win_cpu + 1 if match_type==PokkenConstants.BATTLE_TYPE.AI and match_result==PokkenConstants.BATTLE_RESULT.WIN else pokemon_data.c.win_cpu,
|
||||
|
||||
battle_num_vs_lan=pokemon_data.c.battle_num_vs_lan + 1 if match_type==PokkenConstants.BATTLE_TYPE.LAN else pokemon_data.c.battle_num_vs_lan,
|
||||
win_vs_lan=pokemon_data.c.win_vs_lan + 1 if match_type==PokkenConstants.BATTLE_TYPE.LAN and match_result==PokkenConstants.BATTLE_RESULT.WIN else pokemon_data.c.win_vs_lan,
|
||||
|
||||
battle_num_vs_wan=pokemon_data.c.battle_num_vs_wan + 1 if match_type==PokkenConstants.BATTLE_TYPE.WAN else pokemon_data.c.battle_num_vs_wan,
|
||||
win_vs_wan=pokemon_data.c.win_vs_wan + 1 if match_type==PokkenConstants.BATTLE_TYPE.WAN and match_result==PokkenConstants.BATTLE_RESULT.WIN else pokemon_data.c.win_vs_wan,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to record match stats for user {user_id}'s pokemon {pokemon_id} (type {match_type.name} | result {match_result.name})")
|
||||
|
||||
def put_stats(
|
||||
self,
|
||||
user_id: int,
|
||||
exkos: int,
|
||||
wkos: int,
|
||||
timeout_wins: int,
|
||||
cool_kos: int,
|
||||
perfects: int,
|
||||
continues: int,
|
||||
) -> None:
|
||||
"""
|
||||
Records profile stats
|
||||
"""
|
||||
sql = update(profile).where(profile.c.user==user_id).values(
|
||||
ex_ko_num=profile.c.ex_ko_num + exkos,
|
||||
wko_num=profile.c.wko_num + wkos,
|
||||
timeup_win_num=profile.c.timeup_win_num + timeout_wins,
|
||||
cool_ko_num=profile.c.cool_ko_num + cool_kos,
|
||||
perfect_ko_num=profile.c.perfect_ko_num + perfects,
|
||||
continue_num=continues,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to update stats for user {user_id}")
|
||||
|
||||
def update_support_team(self, user_id: int, support_id: int, support1: int = 4294967295, support2: int = 4294967295) -> None:
|
||||
sql = update(profile).where(profile.c.user==user_id).values(
|
||||
support_set_1_1=support1 if support_id == 1 else profile.c.support_set_1_1,
|
||||
support_set_1_2=support2 if support_id == 1 else profile.c.support_set_1_2,
|
||||
support_set_2_1=support1 if support_id == 2 else profile.c.support_set_2_1,
|
||||
support_set_2_2=support2 if support_id == 2 else profile.c.support_set_2_2,
|
||||
support_set_3_1=support1 if support_id == 3 else profile.c.support_set_3_1,
|
||||
support_set_3_2=support2 if support_id == 3 else profile.c.support_set_3_2,
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to update support team {support_id} for user {user_id}")
|
||||
from typing import Optional, Dict, List, Union
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from ..const import PokkenConstants
|
||||
|
||||
# Some more of the repeated fields could probably be their own tables, for now I just did the ones that made sense to me
|
||||
# Having the profile table be this massive kinda blows for updates but w/e, **kwargs to the rescue
|
||||
profile = Table(
|
||||
"pokken_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
Column("trainer_name", String(16)), # optional
|
||||
Column("home_region_code", Integer),
|
||||
Column("home_loc_name", String(255)),
|
||||
Column("pref_code", Integer),
|
||||
Column("navi_newbie_flag", Boolean),
|
||||
Column("navi_enable_flag", Boolean),
|
||||
Column("pad_vibrate_flag", Boolean),
|
||||
Column("trainer_rank_point", Integer),
|
||||
Column("wallet", Integer),
|
||||
Column("fight_money", Integer),
|
||||
Column("score_point", Integer),
|
||||
Column("grade_max_num", Integer),
|
||||
Column("extra_counter", Integer), # Optional
|
||||
Column("tutorial_progress_flag", JSON), # Repeated, Integer
|
||||
Column("total_play_days", Integer),
|
||||
Column("play_date_time", Integer),
|
||||
Column("achievement_flag", JSON), # Repeated, Integer
|
||||
Column("lucky_box_fail_num", Integer),
|
||||
Column("event_reward_get_flag", Integer),
|
||||
Column("rank_pvp_all", Integer),
|
||||
Column("rank_pvp_loc", Integer),
|
||||
Column("rank_cpu_all", Integer),
|
||||
Column("rank_cpu_loc", Integer),
|
||||
Column("rank_event", Integer),
|
||||
Column("awake_num", Integer),
|
||||
Column("use_support_num", Integer),
|
||||
Column("rankmatch_flag", Integer),
|
||||
Column("rankmatch_max", Integer), # Optional
|
||||
Column("rankmatch_progress", JSON), # Repeated, Integer
|
||||
Column("rankmatch_success", Integer), # Optional
|
||||
Column("beat_num", Integer), # Optional
|
||||
Column("title_text_id", Integer),
|
||||
Column("title_plate_id", Integer),
|
||||
Column("title_decoration_id", Integer),
|
||||
Column("support_pokemon_list", JSON), # Repeated, Integer
|
||||
Column("support_set_1_1", Integer), # Repeated, Integer
|
||||
Column("support_set_1_2", Integer),
|
||||
Column("support_set_2_1", Integer), # Repeated, Integer
|
||||
Column("support_set_2_2", Integer),
|
||||
Column("support_set_3_1", Integer), # Repeated, Integer
|
||||
Column("support_set_3_2", Integer),
|
||||
Column("navi_trainer", Integer),
|
||||
Column("navi_version_id", Integer),
|
||||
Column("aid_skill_list", JSON), # Repeated, Integer
|
||||
Column("aid_skill", Integer),
|
||||
Column("comment_text_id", Integer),
|
||||
Column("comment_word_id", Integer),
|
||||
Column("latest_use_pokemon", Integer),
|
||||
Column("ex_ko_num", Integer),
|
||||
Column("wko_num", Integer),
|
||||
Column("timeup_win_num", Integer),
|
||||
Column("cool_ko_num", Integer),
|
||||
Column("perfect_ko_num", Integer),
|
||||
Column("record_flag", Integer),
|
||||
Column("continue_num", Integer),
|
||||
Column("avatar_body", Integer), # Optional
|
||||
Column("avatar_gender", Integer), # Optional
|
||||
Column("avatar_background", Integer), # Optional
|
||||
Column("avatar_head", Integer), # Optional
|
||||
Column("avatar_battleglass", Integer), # Optional
|
||||
Column("avatar_face0", Integer), # Optional
|
||||
Column("avatar_face1", Integer), # Optional
|
||||
Column("avatar_face2", Integer), # Optional
|
||||
Column("avatar_bodyall", Integer), # Optional
|
||||
Column("avatar_wear", Integer), # Optional
|
||||
Column("avatar_accessory", Integer), # Optional
|
||||
Column("avatar_stamp", Integer), # Optional
|
||||
Column("event_state", Integer),
|
||||
Column("event_id", Integer),
|
||||
Column("sp_bonus_category_id_1", Integer),
|
||||
Column("sp_bonus_key_value_1", Integer),
|
||||
Column("sp_bonus_category_id_2", Integer),
|
||||
Column("sp_bonus_key_value_2", Integer),
|
||||
Column("last_play_event_id", Integer), # Optional
|
||||
Column("event_achievement_flag", JSON), # Repeated, Integer
|
||||
Column("event_achievement_param", JSON), # Repeated, Integer
|
||||
Column("battle_num_vs_wan", Integer), # 4?
|
||||
Column("win_vs_wan", Integer),
|
||||
Column("battle_num_vs_lan", Integer), # 3?
|
||||
Column("win_vs_lan", Integer),
|
||||
Column("battle_num_vs_cpu", Integer), # 2
|
||||
Column("win_cpu", Integer),
|
||||
Column("battle_num_tutorial", Integer), # 1?
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
pokemon_data = Table(
|
||||
"pokken_pokemon_data",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
),
|
||||
Column("char_id", Integer, nullable=False),
|
||||
Column("illustration_book_no", Integer),
|
||||
Column("pokemon_exp", Integer),
|
||||
Column("battle_num_vs_wan", Integer), # 4?
|
||||
Column("win_vs_wan", Integer),
|
||||
Column("battle_num_vs_lan", Integer), # 3?
|
||||
Column("win_vs_lan", Integer),
|
||||
Column("battle_num_vs_cpu", Integer), # 2
|
||||
Column("win_cpu", Integer),
|
||||
Column("battle_all_num_tutorial", Integer), # ???
|
||||
Column("battle_num_tutorial", Integer), # 1?
|
||||
Column("bp_point_atk", Integer),
|
||||
Column("bp_point_res", Integer),
|
||||
Column("bp_point_def", Integer),
|
||||
Column("bp_point_sp", Integer),
|
||||
UniqueConstraint("user", "char_id", name="pokken_pokemon_data_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class PokkenProfileData(BaseData):
|
||||
def touch_profile(self, user_id: int) -> Optional[int]:
|
||||
sql = select([profile.c.id]).where(profile.c.user == user_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()["id"]
|
||||
|
||||
def create_profile(self, user_id: int) -> Optional[int]:
|
||||
sql = insert(profile).values(user=user_id)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(user=user_id))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to create pokken profile for user {user_id}!")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def set_profile_name(
|
||||
self, user_id: int, new_name: str, gender: Union[int, None] = None
|
||||
) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
trainer_name=new_name,
|
||||
avatar_gender=gender if gender is not None else profile.c.avatar_gender,
|
||||
)
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile name for user {user_id}!"
|
||||
)
|
||||
|
||||
def put_extra(
|
||||
self,
|
||||
user_id: int,
|
||||
extra_counter: int,
|
||||
evt_reward_get_flg: int,
|
||||
total_play_days: int,
|
||||
awake_num: int,
|
||||
use_support_ct: int,
|
||||
beat_num: int,
|
||||
aid_skill: int,
|
||||
last_evt: int,
|
||||
) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
extra_counter=extra_counter,
|
||||
event_reward_get_flag=evt_reward_get_flg,
|
||||
total_play_days=total_play_days,
|
||||
awake_num=awake_num,
|
||||
use_support_num=use_support_ct,
|
||||
beat_num=beat_num,
|
||||
aid_skill=aid_skill,
|
||||
last_play_event_id=last_evt,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to put extra data for user {user_id}")
|
||||
|
||||
def update_profile_tutorial_flags(self, user_id: int, tutorial_flags: List) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
tutorial_progress_flag=tutorial_flags,
|
||||
)
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile tutorial flags for user {user_id}!"
|
||||
)
|
||||
|
||||
def update_profile_achievement_flags(
|
||||
self, user_id: int, achievement_flags: List
|
||||
) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
achievement_flag=achievement_flags,
|
||||
)
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile achievement flags for user {user_id}!"
|
||||
)
|
||||
|
||||
def update_profile_event(
|
||||
self,
|
||||
user_id: int,
|
||||
event_state: List,
|
||||
event_flags: List[int],
|
||||
event_param: List[int],
|
||||
last_evt: int = None,
|
||||
) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
event_state=event_state,
|
||||
event_achievement_flag=event_flags,
|
||||
event_achievement_param=event_param,
|
||||
last_play_event_id=last_evt
|
||||
if last_evt is not None
|
||||
else profile.c.last_play_event_id,
|
||||
)
|
||||
)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"Failed to update pokken profile event state for user {user_id}!"
|
||||
)
|
||||
|
||||
def add_profile_points(
|
||||
self, user_id: int, rank_pts: int, money: int, score_pts: int, grade_max: int
|
||||
) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
trainer_rank_point=profile.c.trainer_rank_point + rank_pts,
|
||||
fight_money=profile.c.fight_money + money,
|
||||
score_point=profile.c.score_point + score_pts,
|
||||
grade_max_num=grade_max,
|
||||
)
|
||||
)
|
||||
|
||||
def get_profile(self, user_id: int) -> Optional[Row]:
|
||||
sql = profile.select(profile.c.user == user_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def put_pokemon(
|
||||
self,
|
||||
user_id: int,
|
||||
pokemon_id: int,
|
||||
illust_no: int,
|
||||
atk: int,
|
||||
res: int,
|
||||
defe: int,
|
||||
sp: int,
|
||||
) -> Optional[int]:
|
||||
sql = insert(pokemon_data).values(
|
||||
user=user_id,
|
||||
char_id=pokemon_id,
|
||||
illustration_book_no=illust_no,
|
||||
bp_point_atk=atk,
|
||||
bp_point_res=res,
|
||||
bp_point_defe=defe,
|
||||
bp_point_sp=sp,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
illustration_book_no=illust_no,
|
||||
bp_point_atk=atk,
|
||||
bp_point_res=res,
|
||||
bp_point_defe=defe,
|
||||
bp_point_sp=sp,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to insert pokemon ID {pokemon_id} for user {user_id}"
|
||||
)
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def add_pokemon_xp(self, user_id: int, pokemon_id: int, xp: int) -> None:
|
||||
sql = (
|
||||
update(pokemon_data)
|
||||
.where(
|
||||
and_(
|
||||
pokemon_data.c.user == user_id, pokemon_data.c.char_id == pokemon_id
|
||||
)
|
||||
)
|
||||
.values(pokemon_exp=pokemon_data.c.pokemon_exp + xp)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to add {xp} XP to pokemon ID {pokemon_id} for user {user_id}"
|
||||
)
|
||||
|
||||
def get_pokemon_data(self, user_id: int, pokemon_id: int) -> Optional[Row]:
|
||||
pass
|
||||
|
||||
def get_all_pokemon_data(self, user_id: int) -> Optional[List[Row]]:
|
||||
pass
|
||||
|
||||
def put_pokemon_battle_result(
|
||||
self,
|
||||
user_id: int,
|
||||
pokemon_id: int,
|
||||
match_type: PokkenConstants.BATTLE_TYPE,
|
||||
match_result: PokkenConstants.BATTLE_RESULT,
|
||||
) -> None:
|
||||
"""
|
||||
Records the match stats (type and win/loss) for the pokemon and profile
|
||||
"""
|
||||
sql = (
|
||||
update(pokemon_data)
|
||||
.where(
|
||||
and_(
|
||||
pokemon_data.c.user == user_id, pokemon_data.c.char_id == pokemon_id
|
||||
)
|
||||
)
|
||||
.values(
|
||||
battle_num_tutorial=pokemon_data.c.battle_num_tutorial + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.TUTORIAL
|
||||
else pokemon_data.c.battle_num_tutorial,
|
||||
battle_all_num_tutorial=pokemon_data.c.battle_all_num_tutorial + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.TUTORIAL
|
||||
else pokemon_data.c.battle_all_num_tutorial,
|
||||
battle_num_vs_cpu=pokemon_data.c.battle_num_vs_cpu + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.AI
|
||||
else pokemon_data.c.battle_num_vs_cpu,
|
||||
win_cpu=pokemon_data.c.win_cpu + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.AI
|
||||
and match_result == PokkenConstants.BATTLE_RESULT.WIN
|
||||
else pokemon_data.c.win_cpu,
|
||||
battle_num_vs_lan=pokemon_data.c.battle_num_vs_lan + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.LAN
|
||||
else pokemon_data.c.battle_num_vs_lan,
|
||||
win_vs_lan=pokemon_data.c.win_vs_lan + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.LAN
|
||||
and match_result == PokkenConstants.BATTLE_RESULT.WIN
|
||||
else pokemon_data.c.win_vs_lan,
|
||||
battle_num_vs_wan=pokemon_data.c.battle_num_vs_wan + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.WAN
|
||||
else pokemon_data.c.battle_num_vs_wan,
|
||||
win_vs_wan=pokemon_data.c.win_vs_wan + 1
|
||||
if match_type == PokkenConstants.BATTLE_TYPE.WAN
|
||||
and match_result == PokkenConstants.BATTLE_RESULT.WIN
|
||||
else pokemon_data.c.win_vs_wan,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to record match stats for user {user_id}'s pokemon {pokemon_id} (type {match_type.name} | result {match_result.name})"
|
||||
)
|
||||
|
||||
def put_stats(
|
||||
self,
|
||||
user_id: int,
|
||||
exkos: int,
|
||||
wkos: int,
|
||||
timeout_wins: int,
|
||||
cool_kos: int,
|
||||
perfects: int,
|
||||
continues: int,
|
||||
) -> None:
|
||||
"""
|
||||
Records profile stats
|
||||
"""
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
ex_ko_num=profile.c.ex_ko_num + exkos,
|
||||
wko_num=profile.c.wko_num + wkos,
|
||||
timeup_win_num=profile.c.timeup_win_num + timeout_wins,
|
||||
cool_ko_num=profile.c.cool_ko_num + cool_kos,
|
||||
perfect_ko_num=profile.c.perfect_ko_num + perfects,
|
||||
continue_num=continues,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(f"Failed to update stats for user {user_id}")
|
||||
|
||||
def update_support_team(
|
||||
self,
|
||||
user_id: int,
|
||||
support_id: int,
|
||||
support1: int = 4294967295,
|
||||
support2: int = 4294967295,
|
||||
) -> None:
|
||||
sql = (
|
||||
update(profile)
|
||||
.where(profile.c.user == user_id)
|
||||
.values(
|
||||
support_set_1_1=support1
|
||||
if support_id == 1
|
||||
else profile.c.support_set_1_1,
|
||||
support_set_1_2=support2
|
||||
if support_id == 1
|
||||
else profile.c.support_set_1_2,
|
||||
support_set_2_1=support1
|
||||
if support_id == 2
|
||||
else profile.c.support_set_2_1,
|
||||
support_set_2_2=support2
|
||||
if support_id == 2
|
||||
else profile.c.support_set_2_2,
|
||||
support_set_3_1=support1
|
||||
if support_id == 3
|
||||
else profile.c.support_set_3_1,
|
||||
support_set_3_2=support2
|
||||
if support_id == 3
|
||||
else profile.c.support_set_3_2,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
self.logger.warn(
|
||||
f"Failed to update support team {support_id} for user {user_id}"
|
||||
)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
|
||||
class PokkenStaticData(BaseData):
|
||||
pass
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
|
||||
class PokkenStaticData(BaseData):
|
||||
pass
|
||||
|
|
|
@ -10,6 +10,7 @@ from core.config import CoreConfig
|
|||
from .config import PokkenConfig
|
||||
from .base import PokkenBase
|
||||
|
||||
|
||||
class PokkenAdmissionProtocol(WebSocketServerProtocol):
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: PokkenConfig):
|
||||
super().__init__()
|
||||
|
@ -18,48 +19,51 @@ class PokkenAdmissionProtocol(WebSocketServerProtocol):
|
|||
self.logger = logging.getLogger("pokken")
|
||||
|
||||
self.base = PokkenBase(cfg, game_cfg)
|
||||
|
||||
|
||||
def onConnect(self, request: ConnectionRequest) -> None:
|
||||
self.logger.debug(f"Admission: Connection from {request.peer}")
|
||||
|
||||
|
||||
def onClose(self, wasClean: bool, code: int, reason: str) -> None:
|
||||
self.logger.debug(f"Admission: Connection with {self.transport.getPeer().host} closed {'cleanly ' if wasClean else ''}with code {code} - {reason}")
|
||||
self.logger.debug(
|
||||
f"Admission: Connection with {self.transport.getPeer().host} closed {'cleanly ' if wasClean else ''}with code {code} - {reason}"
|
||||
)
|
||||
|
||||
def onMessage(self, payload, isBinary: bool) -> None:
|
||||
msg: Dict = json.loads(payload)
|
||||
self.logger.debug(f"Admission: Message from {self.transport.getPeer().host}:{self.transport.getPeer().port} - {msg}")
|
||||
|
||||
self.logger.debug(
|
||||
f"Admission: Message from {self.transport.getPeer().host}:{self.transport.getPeer().port} - {msg}"
|
||||
)
|
||||
|
||||
api = msg.get("api", "noop")
|
||||
handler = getattr(self.base, f"handle_admission_{api.lower()}")
|
||||
resp = handler(msg, self.transport.getPeer().host)
|
||||
|
||||
|
||||
if resp is None:
|
||||
resp = {}
|
||||
|
||||
if "type" not in resp:
|
||||
resp['type'] = "res"
|
||||
resp["type"] = "res"
|
||||
if "data" not in resp:
|
||||
resp['data'] = {}
|
||||
resp["data"] = {}
|
||||
if "api" not in resp:
|
||||
resp['api'] = api
|
||||
resp["api"] = api
|
||||
if "result" not in resp:
|
||||
resp['result'] = 'true'
|
||||
|
||||
resp["result"] = "true"
|
||||
|
||||
self.logger.debug(f"Websocket response: {resp}")
|
||||
self.sendMessage(json.dumps(resp).encode(), isBinary)
|
||||
|
||||
|
||||
class PokkenAdmissionFactory(WebSocketServerFactory):
|
||||
protocol = PokkenAdmissionProtocol
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
cfg: CoreConfig,
|
||||
game_cfg: PokkenConfig
|
||||
) -> None:
|
||||
def __init__(self, cfg: CoreConfig, game_cfg: PokkenConfig) -> None:
|
||||
self.core_config = cfg
|
||||
self.game_config = game_cfg
|
||||
super().__init__(f"ws://{self.game_config.server.hostname}:{self.game_config.ports.admission}")
|
||||
|
||||
super().__init__(
|
||||
f"ws://{self.game_config.server.hostname}:{self.game_config.ports.admission}"
|
||||
)
|
||||
|
||||
def buildProtocol(self, addr: IAddress) -> Protocol:
|
||||
p = self.protocol(self.core_config, self.game_config)
|
||||
p.factory = self
|
||||
|
|
2048
titles/sao/base.py
2048
titles/sao/base.py
File diff suppressed because it is too large
Load Diff
|
@ -8,7 +8,7 @@ class SaoConstants:
|
|||
|
||||
VER_SAO = 0
|
||||
|
||||
VERSION_NAMES = ("Sword Art Online Arcade")
|
||||
VERSION_NAMES = "Sword Art Online Arcade"
|
||||
|
||||
@classmethod
|
||||
def game_ver_to_string(cls, ver: int):
|
||||
|
|
|
@ -10,4 +10,4 @@ class SaoData(Data):
|
|||
|
||||
self.item = SaoItemData(cfg, self.session)
|
||||
self.profile = SaoProfileData(cfg, self.session)
|
||||
self.static = SaoStaticData(cfg, self.session)
|
||||
self.static = SaoStaticData(cfg, self.session)
|
||||
|
|
|
@ -1 +1 @@
|
|||
from titles.sao.handlers.base import *
|
||||
from titles.sao.handlers.base import *
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -97,8 +97,10 @@ class SaoServlet(resource.Resource):
|
|||
req_url = request.uri.decode()
|
||||
if req_url == "/matching":
|
||||
self.logger.info("Matching request")
|
||||
|
||||
request.responseHeaders.addRawHeader(b"content-type", b"text/html; charset=utf-8")
|
||||
|
||||
request.responseHeaders.addRawHeader(
|
||||
b"content-type", b"text/html; charset=utf-8"
|
||||
)
|
||||
|
||||
sao_request = request.content.getvalue().hex()
|
||||
|
||||
|
@ -106,7 +108,9 @@ class SaoServlet(resource.Resource):
|
|||
if handler is None:
|
||||
self.logger.info(f"Generic Handler for {req_url} - {sao_request[:4]}")
|
||||
self.logger.debug(f"Request: {request.content.getvalue().hex()}")
|
||||
resp = SaoNoopResponse(int.from_bytes(bytes.fromhex(sao_request[:4]), "big")+1)
|
||||
resp = SaoNoopResponse(
|
||||
int.from_bytes(bytes.fromhex(sao_request[:4]), "big") + 1
|
||||
)
|
||||
self.logger.debug(f"Response: {resp.make().hex()}")
|
||||
return resp.make()
|
||||
|
||||
|
@ -114,4 +118,4 @@ class SaoServlet(resource.Resource):
|
|||
self.logger.debug(f"Request: {request.content.getvalue().hex()}")
|
||||
resp = handler(sao_request)
|
||||
self.logger.debug(f"Response: {resp.hex()}")
|
||||
return resp
|
||||
return resp
|
||||
|
|
|
@ -2,6 +2,7 @@ from typing import Optional, Dict, List
|
|||
from os import walk, path
|
||||
import urllib
|
||||
import csv
|
||||
from contextlib import nullcontext
|
||||
|
||||
from read import BaseReader
|
||||
from core.config import CoreConfig
|
||||
|
@ -37,7 +38,8 @@ class SaoReader(BaseReader):
|
|||
pull_bin_ram = False
|
||||
|
||||
if pull_bin_ram:
|
||||
self.read_csv(f"{self.bin_dir}")
|
||||
with self.data.static.conn.begin() or nullcontext():
|
||||
self.read_csv(f"{self.bin_dir}")
|
||||
|
||||
def read_csv(self, bin_dir: str) -> None:
|
||||
self.logger.info(f"Read csv from {bin_dir}")
|
||||
|
@ -54,20 +56,16 @@ class SaoReader(BaseReader):
|
|||
enabled = True
|
||||
|
||||
self.logger.info(f"Added quest {questSceneId} | Name: {name}")
|
||||
|
||||
|
||||
try:
|
||||
self.data.static.put_quest(
|
||||
questSceneId,
|
||||
0,
|
||||
sortNo,
|
||||
name,
|
||||
enabled
|
||||
questSceneId, 0, sortNo, name, enabled
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
except Exception:
|
||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||
|
||||
|
||||
self.logger.info("Now reading HeroLog.csv")
|
||||
try:
|
||||
fullPath = bin_dir + "/HeroLog.csv"
|
||||
|
@ -84,7 +82,7 @@ class SaoReader(BaseReader):
|
|||
enabled = True
|
||||
|
||||
self.logger.info(f"Added hero {heroLogId} | Name: {name}")
|
||||
|
||||
|
||||
try:
|
||||
self.data.static.put_hero(
|
||||
0,
|
||||
|
@ -95,13 +93,13 @@ class SaoReader(BaseReader):
|
|||
skillTableSubId,
|
||||
awakeningExp,
|
||||
flavorText,
|
||||
enabled
|
||||
enabled,
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
except Exception:
|
||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||
|
||||
|
||||
self.logger.info("Now reading Equipment.csv")
|
||||
try:
|
||||
fullPath = bin_dir + "/Equipment.csv"
|
||||
|
@ -117,7 +115,7 @@ class SaoReader(BaseReader):
|
|||
enabled = True
|
||||
|
||||
self.logger.info(f"Added equipment {equipmentId} | Name: {name}")
|
||||
|
||||
|
||||
try:
|
||||
self.data.static.put_equipment(
|
||||
0,
|
||||
|
@ -127,7 +125,7 @@ class SaoReader(BaseReader):
|
|||
weaponTypeId,
|
||||
rarity,
|
||||
flavorText,
|
||||
enabled
|
||||
enabled,
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
@ -148,22 +146,16 @@ class SaoReader(BaseReader):
|
|||
enabled = True
|
||||
|
||||
self.logger.info(f"Added item {itemId} | Name: {name}")
|
||||
|
||||
|
||||
try:
|
||||
self.data.static.put_item(
|
||||
0,
|
||||
itemId,
|
||||
name,
|
||||
itemTypeId,
|
||||
rarity,
|
||||
flavorText,
|
||||
enabled
|
||||
0, itemId, name, itemTypeId, rarity, flavorText, enabled
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
except Exception:
|
||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||
|
||||
|
||||
self.logger.info("Now reading SupportLog.csv")
|
||||
try:
|
||||
fullPath = bin_dir + "/SupportLog.csv"
|
||||
|
@ -179,7 +171,7 @@ class SaoReader(BaseReader):
|
|||
enabled = True
|
||||
|
||||
self.logger.info(f"Added support log {supportLogId} | Name: {name}")
|
||||
|
||||
|
||||
try:
|
||||
self.data.static.put_support_log(
|
||||
0,
|
||||
|
@ -189,13 +181,13 @@ class SaoReader(BaseReader):
|
|||
rarity,
|
||||
salePrice,
|
||||
skillName,
|
||||
enabled
|
||||
enabled,
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
except Exception:
|
||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||
|
||||
|
||||
self.logger.info("Now reading Title.csv")
|
||||
try:
|
||||
fullPath = bin_dir + "/Title.csv"
|
||||
|
@ -210,7 +202,7 @@ class SaoReader(BaseReader):
|
|||
enabled = True
|
||||
|
||||
self.logger.info(f"Added title {titleId} | Name: {displayName}")
|
||||
|
||||
|
||||
if len(titleId) > 5:
|
||||
try:
|
||||
self.data.static.put_title(
|
||||
|
@ -220,11 +212,13 @@ class SaoReader(BaseReader):
|
|||
requirement,
|
||||
rank,
|
||||
imageFilePath,
|
||||
enabled
|
||||
enabled,
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
elif len(titleId) < 6: # current server code cannot have multiple lengths for the id
|
||||
elif (
|
||||
len(titleId) < 6
|
||||
): # current server code cannot have multiple lengths for the id
|
||||
continue
|
||||
except Exception:
|
||||
self.logger.warn(f"Couldn't read csv file in {self.bin_dir}, skipping")
|
||||
|
@ -239,14 +233,13 @@ class SaoReader(BaseReader):
|
|||
commonRewardId = row["CommonRewardId"]
|
||||
enabled = True
|
||||
|
||||
self.logger.info(f"Added rare drop {questRareDropId} | Reward: {commonRewardId}")
|
||||
|
||||
self.logger.info(
|
||||
f"Added rare drop {questRareDropId} | Reward: {commonRewardId}"
|
||||
)
|
||||
|
||||
try:
|
||||
self.data.static.put_rare_drop(
|
||||
0,
|
||||
questRareDropId,
|
||||
commonRewardId,
|
||||
enabled
|
||||
0, questRareDropId, commonRewardId, enabled
|
||||
)
|
||||
except Exception as err:
|
||||
print(err)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
from .profile import SaoProfileData
|
||||
from .static import SaoStaticData
|
||||
from .item import SaoItemData
|
||||
from .profile import SaoProfileData
|
||||
from .static import SaoStaticData
|
||||
from .item import SaoItemData
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,80 +1,92 @@
|
|||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from ..const import SaoConstants
|
||||
|
||||
profile = Table(
|
||||
"sao_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
Column("user_type", Integer, server_default="1"),
|
||||
Column("nick_name", String(16), server_default="PLAYER"),
|
||||
Column("rank_num", Integer, server_default="1"),
|
||||
Column("rank_exp", Integer, server_default="0"),
|
||||
Column("own_col", Integer, server_default="0"),
|
||||
Column("own_vp", Integer, server_default="0"),
|
||||
Column("own_yui_medal", Integer, server_default="0"),
|
||||
Column("setting_title_id", Integer, server_default="20005"),
|
||||
)
|
||||
|
||||
class SaoProfileData(BaseData):
|
||||
def create_profile(self, user_id: int) -> Optional[int]:
|
||||
sql = insert(profile).values(user=user_id)
|
||||
conflict = sql.on_duplicate_key_update(user=user_id)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to create SAO profile for user {user_id}!")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_profile(self, user_id: int, user_type: int, nick_name: str, rank_num: int, rank_exp: int, own_col: int, own_vp: int, own_yui_medal: int, setting_title_id: int) -> Optional[int]:
|
||||
sql = insert(profile).values(
|
||||
user=user_id,
|
||||
user_type=user_type,
|
||||
nick_name=nick_name,
|
||||
rank_num=rank_num,
|
||||
rank_exp=rank_exp,
|
||||
own_col=own_col,
|
||||
own_vp=own_vp,
|
||||
own_yui_medal=own_yui_medal,
|
||||
setting_title_id=setting_title_id
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
rank_num=rank_num,
|
||||
rank_exp=rank_exp,
|
||||
own_col=own_col,
|
||||
own_vp=own_vp,
|
||||
own_yui_medal=own_yui_medal,
|
||||
setting_title_id=setting_title_id
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(
|
||||
f"{__name__} failed to insert profile! user: {user_id}"
|
||||
)
|
||||
return None
|
||||
|
||||
print(result.lastrowid)
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile(self, user_id: int) -> Optional[Row]:
|
||||
sql = profile.select(profile.c.user == user_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
from typing import Optional, Dict, List
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_, case
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select, update, delete
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
from ..const import SaoConstants
|
||||
|
||||
profile = Table(
|
||||
"sao_profile",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column(
|
||||
"user",
|
||||
ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
|
||||
nullable=False,
|
||||
unique=True,
|
||||
),
|
||||
Column("user_type", Integer, server_default="1"),
|
||||
Column("nick_name", String(16), server_default="PLAYER"),
|
||||
Column("rank_num", Integer, server_default="1"),
|
||||
Column("rank_exp", Integer, server_default="0"),
|
||||
Column("own_col", Integer, server_default="0"),
|
||||
Column("own_vp", Integer, server_default="0"),
|
||||
Column("own_yui_medal", Integer, server_default="0"),
|
||||
Column("setting_title_id", Integer, server_default="20005"),
|
||||
)
|
||||
|
||||
|
||||
class SaoProfileData(BaseData):
|
||||
def create_profile(self, user_id: int) -> Optional[int]:
|
||||
sql = insert(profile).values(user=user_id)
|
||||
conflict = sql.on_conflict_do_update(set_=dict(user=user_id))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"Failed to create SAO profile for user {user_id}!")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_profile(
|
||||
self,
|
||||
user_id: int,
|
||||
user_type: int,
|
||||
nick_name: str,
|
||||
rank_num: int,
|
||||
rank_exp: int,
|
||||
own_col: int,
|
||||
own_vp: int,
|
||||
own_yui_medal: int,
|
||||
setting_title_id: int,
|
||||
) -> Optional[int]:
|
||||
sql = insert(profile).values(
|
||||
user=user_id,
|
||||
user_type=user_type,
|
||||
nick_name=nick_name,
|
||||
rank_num=rank_num,
|
||||
rank_exp=rank_exp,
|
||||
own_col=own_col,
|
||||
own_vp=own_vp,
|
||||
own_yui_medal=own_yui_medal,
|
||||
setting_title_id=setting_title_id,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
rank_num=rank_num,
|
||||
rank_exp=rank_exp,
|
||||
own_col=own_col,
|
||||
own_vp=own_vp,
|
||||
own_yui_medal=own_yui_medal,
|
||||
setting_title_id=setting_title_id,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
self.logger.error(f"{__name__} failed to insert profile! user: {user_id}")
|
||||
return None
|
||||
|
||||
print(result.lastrowid)
|
||||
return result.lastrowid
|
||||
|
||||
def get_profile(self, user_id: int) -> Optional[Row]:
|
||||
sql = profile.select(profile.c.user == user_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
|
|
@ -1,368 +1,413 @@
|
|||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.mysql import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
quest = Table(
|
||||
"sao_static_quest",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("questSceneId", Integer),
|
||||
Column("sortNo", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "questSceneId", name="sao_static_quest_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
hero = Table(
|
||||
"sao_static_hero_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("heroLogId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("nickname", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("skillTableSubId", Integer),
|
||||
Column("awakeningExp", Integer),
|
||||
Column("flavorText", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "heroLogId", name="sao_static_hero_list_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
equipment = Table(
|
||||
"sao_static_equipment_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("equipmentId", Integer),
|
||||
Column("equipmentType", Integer),
|
||||
Column("weaponTypeId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("flavorText", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "equipmentId", name="sao_static_equipment_list_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
item = Table(
|
||||
"sao_static_item_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("itemId", Integer),
|
||||
Column("itemTypeId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("flavorText", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "itemId", name="sao_static_item_list_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
support = Table(
|
||||
"sao_static_support_log_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("supportLogId", Integer),
|
||||
Column("charaId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("salePrice", Integer),
|
||||
Column("skillName", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "supportLogId", name="sao_static_support_log_list_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
rare_drop = Table(
|
||||
"sao_static_rare_drop_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("questRareDropId", Integer),
|
||||
Column("commonRewardId", Integer),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "questRareDropId", "commonRewardId", name="sao_static_rare_drop_list_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
title = Table(
|
||||
"sao_static_title_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("titleId", Integer),
|
||||
Column("displayName", String(255)),
|
||||
Column("requirement", Integer),
|
||||
Column("rank", Integer),
|
||||
Column("imageFilePath", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version", "titleId", name="sao_static_title_list_uk"
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
class SaoStaticData(BaseData):
|
||||
def put_quest( self, questSceneId: int, version: int, sortNo: int, name: str, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(quest).values(
|
||||
questSceneId=questSceneId,
|
||||
version=version,
|
||||
sortNo=sortNo,
|
||||
name=name,
|
||||
tutorial=tutorial,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
name=name, questSceneId=questSceneId, version=version
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_hero( self, version: int, heroLogId: int, name: str, nickname: str, rarity: int, skillTableSubId: int, awakeningExp: int, flavorText: str, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(hero).values(
|
||||
version=version,
|
||||
heroLogId=heroLogId,
|
||||
name=name,
|
||||
nickname=nickname,
|
||||
rarity=rarity,
|
||||
skillTableSubId=skillTableSubId,
|
||||
awakeningExp=awakeningExp,
|
||||
flavorText=flavorText,
|
||||
enabled=enabled
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
name=name, heroLogId=heroLogId
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_equipment( self, version: int, equipmentId: int, name: str, equipmentType: int, weaponTypeId:int, rarity: int, flavorText: str, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(equipment).values(
|
||||
version=version,
|
||||
equipmentId=equipmentId,
|
||||
name=name,
|
||||
equipmentType=equipmentType,
|
||||
weaponTypeId=weaponTypeId,
|
||||
rarity=rarity,
|
||||
flavorText=flavorText,
|
||||
enabled=enabled
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
name=name, equipmentId=equipmentId
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_item( self, version: int, itemId: int, name: str, itemTypeId: int, rarity: int, flavorText: str, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(item).values(
|
||||
version=version,
|
||||
itemId=itemId,
|
||||
name=name,
|
||||
itemTypeId=itemTypeId,
|
||||
rarity=rarity,
|
||||
flavorText=flavorText,
|
||||
enabled=enabled
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
name=name, itemId=itemId
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_support_log( self, version: int, supportLogId: int, charaId: int, name: str, rarity: int, salePrice: int, skillName: str, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(support).values(
|
||||
version=version,
|
||||
supportLogId=supportLogId,
|
||||
charaId=charaId,
|
||||
name=name,
|
||||
rarity=rarity,
|
||||
salePrice=salePrice,
|
||||
skillName=skillName,
|
||||
enabled=enabled
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
name=name, supportLogId=supportLogId
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_rare_drop( self, version: int, questRareDropId: int, commonRewardId: int, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(rare_drop).values(
|
||||
version=version,
|
||||
questRareDropId=questRareDropId,
|
||||
commonRewardId=commonRewardId,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
questRareDropId=questRareDropId, commonRewardId=commonRewardId, version=version
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_title( self, version: int, titleId: int, displayName: str, requirement: int, rank: int, imageFilePath: str, enabled: bool ) -> Optional[int]:
|
||||
sql = insert(title).values(
|
||||
version=version,
|
||||
titleId=titleId,
|
||||
displayName=displayName,
|
||||
requirement=requirement,
|
||||
rank=rank,
|
||||
imageFilePath=imageFilePath,
|
||||
enabled=enabled
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
displayName=displayName, titleId=titleId
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_quests_id(self, sortNo: int) -> Optional[Dict]:
|
||||
sql = quest.select(quest.c.sortNo == sortNo)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_quests_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = quest.select(quest.c.version == version and quest.c.enabled == enabled).order_by(
|
||||
quest.c.questSceneId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_hero_id(self, heroLogId: int) -> Optional[Dict]:
|
||||
sql = hero.select(hero.c.heroLogId == heroLogId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_hero_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = hero.select(hero.c.version == version and hero.c.enabled == enabled).order_by(
|
||||
hero.c.heroLogId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_equipment_id(self, equipmentId: int) -> Optional[Dict]:
|
||||
sql = equipment.select(equipment.c.equipmentId == equipmentId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_equipment_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = equipment.select(equipment.c.version == version and equipment.c.enabled == enabled).order_by(
|
||||
equipment.c.equipmentId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_item_id(self, itemId: int) -> Optional[Dict]:
|
||||
sql = item.select(item.c.itemId == itemId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_rare_drop_id(self, questRareDropId: int) -> Optional[Dict]:
|
||||
sql = rare_drop.select(rare_drop.c.questRareDropId == questRareDropId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_item_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = item.select(item.c.version == version and item.c.enabled == enabled).order_by(
|
||||
item.c.itemId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_support_log_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = support.select(support.c.version == version and support.c.enabled == enabled).order_by(
|
||||
support.c.supportLogId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_title_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = title.select(title.c.version == version and title.c.enabled == enabled).order_by(
|
||||
title.c.titleId.asc()
|
||||
)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
from typing import Dict, List, Optional
|
||||
from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
|
||||
from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON, Float
|
||||
from sqlalchemy.engine.base import Connection
|
||||
from sqlalchemy.engine import Row
|
||||
from sqlalchemy.schema import ForeignKey
|
||||
from sqlalchemy.sql import func, select
|
||||
from sqlalchemy.dialects.sqlite import insert
|
||||
|
||||
from core.data.schema import BaseData, metadata
|
||||
|
||||
quest = Table(
|
||||
"sao_static_quest",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("questSceneId", Integer),
|
||||
Column("sortNo", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint("version", "questSceneId", name="sao_static_quest_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
hero = Table(
|
||||
"sao_static_hero_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("heroLogId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("nickname", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("skillTableSubId", Integer),
|
||||
Column("awakeningExp", Integer),
|
||||
Column("flavorText", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint("version", "heroLogId", name="sao_static_hero_list_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
equipment = Table(
|
||||
"sao_static_equipment_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("equipmentId", Integer),
|
||||
Column("equipmentType", Integer),
|
||||
Column("weaponTypeId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("flavorText", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint("version", "equipmentId", name="sao_static_equipment_list_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
item = Table(
|
||||
"sao_static_item_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("itemId", Integer),
|
||||
Column("itemTypeId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("flavorText", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint("version", "itemId", name="sao_static_item_list_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
support = Table(
|
||||
"sao_static_support_log_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("supportLogId", Integer),
|
||||
Column("charaId", Integer),
|
||||
Column("name", String(255)),
|
||||
Column("rarity", Integer),
|
||||
Column("salePrice", Integer),
|
||||
Column("skillName", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint("version", "supportLogId", name="sao_static_support_log_list_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
rare_drop = Table(
|
||||
"sao_static_rare_drop_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("questRareDropId", Integer),
|
||||
Column("commonRewardId", Integer),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint(
|
||||
"version",
|
||||
"questRareDropId",
|
||||
"commonRewardId",
|
||||
name="sao_static_rare_drop_list_uk",
|
||||
),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
title = Table(
|
||||
"sao_static_title_list",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("version", Integer),
|
||||
Column("titleId", Integer),
|
||||
Column("displayName", String(255)),
|
||||
Column("requirement", Integer),
|
||||
Column("rank", Integer),
|
||||
Column("imageFilePath", String(255)),
|
||||
Column("enabled", Boolean),
|
||||
UniqueConstraint("version", "titleId", name="sao_static_title_list_uk"),
|
||||
mysql_charset="utf8mb4",
|
||||
)
|
||||
|
||||
|
||||
class SaoStaticData(BaseData):
|
||||
def put_quest(
|
||||
self, questSceneId: int, version: int, sortNo: int, name: str, enabled: bool
|
||||
) -> Optional[int]:
|
||||
sql = insert(quest).values(
|
||||
questSceneId=questSceneId,
|
||||
version=version,
|
||||
sortNo=sortNo,
|
||||
name=name,
|
||||
tutorial=tutorial,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(name=name, questSceneId=questSceneId, version=version)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_hero(
|
||||
self,
|
||||
version: int,
|
||||
heroLogId: int,
|
||||
name: str,
|
||||
nickname: str,
|
||||
rarity: int,
|
||||
skillTableSubId: int,
|
||||
awakeningExp: int,
|
||||
flavorText: str,
|
||||
enabled: bool,
|
||||
) -> Optional[int]:
|
||||
sql = insert(hero).values(
|
||||
version=version,
|
||||
heroLogId=heroLogId,
|
||||
name=name,
|
||||
nickname=nickname,
|
||||
rarity=rarity,
|
||||
skillTableSubId=skillTableSubId,
|
||||
awakeningExp=awakeningExp,
|
||||
flavorText=flavorText,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(name=name, heroLogId=heroLogId))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_equipment(
|
||||
self,
|
||||
version: int,
|
||||
equipmentId: int,
|
||||
name: str,
|
||||
equipmentType: int,
|
||||
weaponTypeId: int,
|
||||
rarity: int,
|
||||
flavorText: str,
|
||||
enabled: bool,
|
||||
) -> Optional[int]:
|
||||
sql = insert(equipment).values(
|
||||
version=version,
|
||||
equipmentId=equipmentId,
|
||||
name=name,
|
||||
equipmentType=equipmentType,
|
||||
weaponTypeId=weaponTypeId,
|
||||
rarity=rarity,
|
||||
flavorText=flavorText,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(name=name, equipmentId=equipmentId)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_item(
|
||||
self,
|
||||
version: int,
|
||||
itemId: int,
|
||||
name: str,
|
||||
itemTypeId: int,
|
||||
rarity: int,
|
||||
flavorText: str,
|
||||
enabled: bool,
|
||||
) -> Optional[int]:
|
||||
sql = insert(item).values(
|
||||
version=version,
|
||||
itemId=itemId,
|
||||
name=name,
|
||||
itemTypeId=itemTypeId,
|
||||
rarity=rarity,
|
||||
flavorText=flavorText,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(set_=dict(name=name, itemId=itemId))
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_support_log(
|
||||
self,
|
||||
version: int,
|
||||
supportLogId: int,
|
||||
charaId: int,
|
||||
name: str,
|
||||
rarity: int,
|
||||
salePrice: int,
|
||||
skillName: str,
|
||||
enabled: bool,
|
||||
) -> Optional[int]:
|
||||
sql = insert(support).values(
|
||||
version=version,
|
||||
supportLogId=supportLogId,
|
||||
charaId=charaId,
|
||||
name=name,
|
||||
rarity=rarity,
|
||||
salePrice=salePrice,
|
||||
skillName=skillName,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(name=name, supportLogId=supportLogId)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_rare_drop(
|
||||
self, version: int, questRareDropId: int, commonRewardId: int, enabled: bool
|
||||
) -> Optional[int]:
|
||||
sql = insert(rare_drop).values(
|
||||
version=version,
|
||||
questRareDropId=questRareDropId,
|
||||
commonRewardId=commonRewardId,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(
|
||||
questRareDropId=questRareDropId,
|
||||
commonRewardId=commonRewardId,
|
||||
version=version,
|
||||
)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def put_title(
|
||||
self,
|
||||
version: int,
|
||||
titleId: int,
|
||||
displayName: str,
|
||||
requirement: int,
|
||||
rank: int,
|
||||
imageFilePath: str,
|
||||
enabled: bool,
|
||||
) -> Optional[int]:
|
||||
sql = insert(title).values(
|
||||
version=version,
|
||||
titleId=titleId,
|
||||
displayName=displayName,
|
||||
requirement=requirement,
|
||||
rank=rank,
|
||||
imageFilePath=imageFilePath,
|
||||
enabled=enabled,
|
||||
)
|
||||
|
||||
conflict = sql.on_conflict_do_update(
|
||||
set_=dict(displayName=displayName, titleId=titleId)
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
if result is None:
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_quests_id(self, sortNo: int) -> Optional[Dict]:
|
||||
sql = quest.select(quest.c.sortNo == sortNo)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_quests_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = quest.select(
|
||||
quest.c.version == version and quest.c.enabled == enabled
|
||||
).order_by(quest.c.questSceneId.asc())
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_hero_id(self, heroLogId: int) -> Optional[Dict]:
|
||||
sql = hero.select(hero.c.heroLogId == heroLogId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_hero_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = hero.select(
|
||||
hero.c.version == version and hero.c.enabled == enabled
|
||||
).order_by(hero.c.heroLogId.asc())
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_equipment_id(self, equipmentId: int) -> Optional[Dict]:
|
||||
sql = equipment.select(equipment.c.equipmentId == equipmentId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_equipment_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = equipment.select(
|
||||
equipment.c.version == version and equipment.c.enabled == enabled
|
||||
).order_by(equipment.c.equipmentId.asc())
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_item_id(self, itemId: int) -> Optional[Dict]:
|
||||
sql = item.select(item.c.itemId == itemId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_rare_drop_id(self, questRareDropId: int) -> Optional[Dict]:
|
||||
sql = rare_drop.select(rare_drop.c.questRareDropId == questRareDropId)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_item_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = item.select(
|
||||
item.c.version == version and item.c.enabled == enabled
|
||||
).order_by(item.c.itemId.asc())
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_support_log_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = support.select(
|
||||
support.c.version == version and support.c.enabled == enabled
|
||||
).order_by(support.c.supportLogId.asc())
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
||||
def get_title_ids(self, version: int, enabled: bool) -> Optional[List[Dict]]:
|
||||
sql = title.select(
|
||||
title.c.version == version and title.c.enabled == enabled
|
||||
).order_by(title.c.titleId.asc())
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return [list[2] for list in result.fetchall()]
|
||||
|
|
|
@ -7,6 +7,7 @@ from core.config import CoreConfig
|
|||
from titles.wacca.config import WaccaConfig
|
||||
from titles.wacca.const import WaccaConstants
|
||||
from titles.wacca.database import WaccaData
|
||||
from contextlib import nullcontext
|
||||
|
||||
from titles.wacca.handlers import *
|
||||
from core.const import AllnetCountryCode
|
||||
|
@ -237,42 +238,43 @@ class WaccaBase:
|
|||
return BaseResponse().make()
|
||||
|
||||
# Insert starting items
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104002)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104003)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104005)
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104002)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104003)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["title"], 104005)
|
||||
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102002)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102001)
|
||||
self.data.item.put_item(req.aimeId, WaccaConstants.ITEM_TYPES["icon"], 102002)
|
||||
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 103001
|
||||
)
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 203001
|
||||
)
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 103001
|
||||
)
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_color"], 203001
|
||||
)
|
||||
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 105001
|
||||
)
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 205005
|
||||
) # Added lily
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 105001
|
||||
)
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["note_sound"], 205005
|
||||
) # Added lily
|
||||
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210001
|
||||
)
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210001
|
||||
)
|
||||
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["user_plate"], 211001
|
||||
) # Added lily
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["user_plate"], 211001
|
||||
) # Added lily
|
||||
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312000
|
||||
) # Added reverse
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312001
|
||||
) # Added reverse
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312000
|
||||
) # Added reverse
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["touch_effect"], 312001
|
||||
) # Added reverse
|
||||
|
||||
return UserStatusCreateResponseV2(profileId, req.username).make()
|
||||
|
||||
|
@ -500,83 +502,89 @@ class WaccaBase:
|
|||
user_id, self.version, req.stageId
|
||||
)
|
||||
|
||||
if old_stage is None:
|
||||
self.data.score.put_stageup(
|
||||
user_id,
|
||||
self.version,
|
||||
req.stageId,
|
||||
req.clearType.value,
|
||||
req.numSongsCleared,
|
||||
req.songScores[0],
|
||||
req.songScores[1],
|
||||
req.songScores[2],
|
||||
)
|
||||
|
||||
else:
|
||||
# We only care about total score for best of, even if one score happens to be lower (I think)
|
||||
if total_score > (
|
||||
old_stage["song1_score"]
|
||||
+ old_stage["song2_score"]
|
||||
+ old_stage["song3_score"]
|
||||
):
|
||||
best_score1 = req.songScores[0]
|
||||
best_score2 = req.songScores[1]
|
||||
best_score3 = req.songScores[2]
|
||||
else:
|
||||
best_score1 = old_stage["song1_score"]
|
||||
best_score2 = old_stage["song2_score"]
|
||||
best_score3 = old_stage["song3_score"]
|
||||
|
||||
self.data.score.put_stageup(
|
||||
user_id,
|
||||
self.version,
|
||||
req.stageId,
|
||||
req.clearType.value,
|
||||
req.numSongsCleared,
|
||||
best_score1,
|
||||
best_score2,
|
||||
best_score3,
|
||||
)
|
||||
|
||||
if (
|
||||
req.stageLevel > 0 and req.stageLevel <= 14 and req.clearType.value > 0
|
||||
): # For some reason, special stages send dan level 1001...
|
||||
if req.stageLevel > profile["dan_level"] or (
|
||||
req.stageLevel == profile["dan_level"]
|
||||
and req.clearType.value > profile["dan_type"]
|
||||
):
|
||||
self.data.profile.update_profile_dan(
|
||||
req.profileId, req.stageLevel, req.clearType.value
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
if old_stage is None:
|
||||
self.data.score.put_stageup(
|
||||
user_id,
|
||||
self.version,
|
||||
req.stageId,
|
||||
req.clearType.value,
|
||||
req.numSongsCleared,
|
||||
req.songScores[0],
|
||||
req.songScores[1],
|
||||
req.songScores[2],
|
||||
)
|
||||
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtained)
|
||||
else:
|
||||
# We only care about total score for best of, even if one score happens to be lower (I think)
|
||||
if total_score > (
|
||||
old_stage["song1_score"]
|
||||
+ old_stage["song2_score"]
|
||||
+ old_stage["song3_score"]
|
||||
):
|
||||
best_score1 = req.songScores[0]
|
||||
best_score2 = req.songScores[1]
|
||||
best_score3 = req.songScores[2]
|
||||
else:
|
||||
best_score1 = old_stage["song1_score"]
|
||||
best_score2 = old_stage["song2_score"]
|
||||
best_score3 = old_stage["song3_score"]
|
||||
|
||||
# user/status/update isn't called after stageup so we have to do some things now
|
||||
current_icon = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_icon_id"]
|
||||
)
|
||||
current_nav = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_nav_id"]
|
||||
)
|
||||
self.data.score.put_stageup(
|
||||
user_id,
|
||||
self.version,
|
||||
req.stageId,
|
||||
req.clearType.value,
|
||||
req.numSongsCleared,
|
||||
best_score1,
|
||||
best_score2,
|
||||
best_score3,
|
||||
)
|
||||
|
||||
if current_icon is None:
|
||||
current_icon = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
else:
|
||||
current_icon = current_icon["value"]
|
||||
if current_nav is None:
|
||||
current_nav = self.OPTIONS_DEFAULTS["set_nav_id"]
|
||||
else:
|
||||
current_nav = current_nav["value"]
|
||||
if (
|
||||
req.stageLevel > 0 and req.stageLevel <= 14 and req.clearType.value > 0
|
||||
): # For some reason, special stages send dan level 1001...
|
||||
if req.stageLevel > profile["dan_level"] or (
|
||||
req.stageLevel == profile["dan_level"]
|
||||
and req.clearType.value > profile["dan_type"]
|
||||
):
|
||||
self.data.profile.update_profile_dan(
|
||||
req.profileId, req.stageLevel, req.clearType.value
|
||||
)
|
||||
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon
|
||||
)
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav
|
||||
)
|
||||
self.data.profile.update_profile_playtype(
|
||||
req.profileId, 4, data["appVersion"][:7]
|
||||
)
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtained)
|
||||
|
||||
# user/status/update isn't called after stageup so we have to do some things now
|
||||
current_icon = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_icon_id"]
|
||||
)
|
||||
current_nav = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_nav_id"]
|
||||
)
|
||||
|
||||
if current_icon is None:
|
||||
current_icon = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
else:
|
||||
current_icon = current_icon["value"]
|
||||
if current_nav is None:
|
||||
current_nav = self.OPTIONS_DEFAULTS["set_nav_id"]
|
||||
else:
|
||||
current_nav = current_nav["value"]
|
||||
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon
|
||||
)
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav
|
||||
)
|
||||
|
||||
self.data.profile.update_profile_playtype(
|
||||
req.profileId, 4, data["appVersion"][:7]
|
||||
)
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_sugoroku_update_request(self, data: Dict) -> Dict:
|
||||
|
@ -598,17 +606,22 @@ class WaccaBase:
|
|||
)
|
||||
return resp.make()
|
||||
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtainted)
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtainted)
|
||||
|
||||
self.data.profile.update_gate(
|
||||
user_id,
|
||||
req.gateId,
|
||||
req.page,
|
||||
req.progress,
|
||||
req.loops,
|
||||
mission_flg,
|
||||
req.totalPts,
|
||||
)
|
||||
self.data.profile.update_gate(
|
||||
user_id,
|
||||
req.gateId,
|
||||
req.page,
|
||||
req.progress,
|
||||
req.loops,
|
||||
mission_flg,
|
||||
req.totalPts,
|
||||
)
|
||||
return resp.make()
|
||||
|
||||
def handle_user_info_getMyroom_request(self, data: Dict) -> Dict:
|
||||
|
@ -627,31 +640,37 @@ class WaccaBase:
|
|||
new_tickets: List[TicketItem] = []
|
||||
|
||||
for ticket in tickets:
|
||||
new_tickets.append(TicketItem(ticket["id"], ticket["ticket_id"], 9999999999))
|
||||
new_tickets.append(
|
||||
TicketItem(ticket["id"], ticket["ticket_id"], 9999999999)
|
||||
)
|
||||
|
||||
for item in req.itemsUsed:
|
||||
if (
|
||||
item.itemType == WaccaConstants.ITEM_TYPES["wp"]
|
||||
and not self.game_config.mods.infinite_wp
|
||||
):
|
||||
if current_wp >= item.quantity:
|
||||
current_wp -= item.quantity
|
||||
self.data.profile.spend_wp(req.profileId, item.quantity)
|
||||
else:
|
||||
return BaseResponse().make()
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
for item in req.itemsUsed:
|
||||
if (
|
||||
item.itemType == WaccaConstants.ITEM_TYPES["wp"]
|
||||
and not self.game_config.mods.infinite_wp
|
||||
):
|
||||
if current_wp >= item.quantity:
|
||||
current_wp -= item.quantity
|
||||
self.data.profile.spend_wp(req.profileId, item.quantity)
|
||||
else:
|
||||
return BaseResponse().make()
|
||||
|
||||
elif (
|
||||
item.itemType == WaccaConstants.ITEM_TYPES["ticket"]
|
||||
and not self.game_config.mods.infinite_tickets
|
||||
):
|
||||
for x in range(len(new_tickets)):
|
||||
if new_tickets[x].ticketId == item.itemId:
|
||||
self.logger.debug(
|
||||
f"Remove ticket ID {new_tickets[x].userTicketId} type {new_tickets[x].ticketId} from {user_id}"
|
||||
)
|
||||
self.data.item.spend_ticket(new_tickets[x].userTicketId)
|
||||
new_tickets.pop(x)
|
||||
break
|
||||
elif (
|
||||
item.itemType == WaccaConstants.ITEM_TYPES["ticket"]
|
||||
and not self.game_config.mods.infinite_tickets
|
||||
):
|
||||
for x in range(len(new_tickets)):
|
||||
if new_tickets[x].ticketId == item.itemId:
|
||||
self.logger.debug(
|
||||
f"Remove ticket ID {new_tickets[x].userTicketId} type {new_tickets[x].ticketId} from {user_id}"
|
||||
)
|
||||
self.data.item.spend_ticket(new_tickets[x].userTicketId)
|
||||
new_tickets.pop(x)
|
||||
break
|
||||
|
||||
# wp, ticket info
|
||||
if req.difficulty > WaccaConstants.Difficulty.HARD.value:
|
||||
|
@ -715,118 +734,124 @@ class WaccaBase:
|
|||
return resp.make()
|
||||
|
||||
user_id = profile["user"]
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtained)
|
||||
|
||||
playlog_clear_status = (
|
||||
req.songDetail.flagCleared
|
||||
+ req.songDetail.flagMissless
|
||||
+ req.songDetail.flagFullcombo
|
||||
+ req.songDetail.flagAllMarvelous
|
||||
)
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
self.util_put_items(req.profileId, user_id, req.itemsObtained)
|
||||
|
||||
self.data.score.put_playlog(
|
||||
user_id,
|
||||
req.songDetail.songId,
|
||||
req.songDetail.difficulty,
|
||||
req.songDetail.score,
|
||||
playlog_clear_status,
|
||||
req.songDetail.grade.value,
|
||||
req.songDetail.maxCombo,
|
||||
req.songDetail.judgements.marvCt,
|
||||
req.songDetail.judgements.greatCt,
|
||||
req.songDetail.judgements.goodCt,
|
||||
req.songDetail.judgements.missCt,
|
||||
req.songDetail.fastCt,
|
||||
req.songDetail.slowCt,
|
||||
self.season,
|
||||
)
|
||||
playlog_clear_status = (
|
||||
req.songDetail.flagCleared
|
||||
+ req.songDetail.flagMissless
|
||||
+ req.songDetail.flagFullcombo
|
||||
+ req.songDetail.flagAllMarvelous
|
||||
)
|
||||
|
||||
old_score = self.data.score.get_best_score(
|
||||
user_id, req.songDetail.songId, req.songDetail.difficulty
|
||||
)
|
||||
|
||||
if not old_score:
|
||||
grades = [0] * 13
|
||||
clears = [0] * 5
|
||||
|
||||
clears[0] = 1
|
||||
clears[1] = 1 if req.songDetail.flagCleared else 0
|
||||
clears[2] = 1 if req.songDetail.flagMissless else 0
|
||||
clears[3] = 1 if req.songDetail.flagFullcombo else 0
|
||||
clears[4] = 1 if req.songDetail.flagAllMarvelous else 0
|
||||
|
||||
grades[req.songDetail.grade.value - 1] = 1
|
||||
|
||||
self.data.score.put_best_score(
|
||||
self.data.score.put_playlog(
|
||||
user_id,
|
||||
req.songDetail.songId,
|
||||
req.songDetail.difficulty,
|
||||
req.songDetail.score,
|
||||
clears,
|
||||
grades,
|
||||
playlog_clear_status,
|
||||
req.songDetail.grade.value,
|
||||
req.songDetail.maxCombo,
|
||||
req.songDetail.judgements.marvCt,
|
||||
req.songDetail.judgements.greatCt,
|
||||
req.songDetail.judgements.goodCt,
|
||||
req.songDetail.judgements.missCt,
|
||||
req.songDetail.fastCt,
|
||||
req.songDetail.slowCt,
|
||||
self.season,
|
||||
)
|
||||
|
||||
resp.songDetail.score = req.songDetail.score
|
||||
resp.songDetail.lowestMissCount = req.songDetail.judgements.missCt
|
||||
|
||||
else:
|
||||
grades = [
|
||||
old_score["grade_d_ct"],
|
||||
old_score["grade_c_ct"],
|
||||
old_score["grade_b_ct"],
|
||||
old_score["grade_a_ct"],
|
||||
old_score["grade_aa_ct"],
|
||||
old_score["grade_aaa_ct"],
|
||||
old_score["grade_s_ct"],
|
||||
old_score["grade_ss_ct"],
|
||||
old_score["grade_sss_ct"],
|
||||
old_score["grade_master_ct"],
|
||||
old_score["grade_sp_ct"],
|
||||
old_score["grade_ssp_ct"],
|
||||
old_score["grade_sssp_ct"],
|
||||
]
|
||||
clears = [
|
||||
old_score["play_ct"],
|
||||
old_score["clear_ct"],
|
||||
old_score["missless_ct"],
|
||||
old_score["fullcombo_ct"],
|
||||
old_score["allmarv_ct"],
|
||||
]
|
||||
|
||||
clears[0] += 1
|
||||
clears[1] += 1 if req.songDetail.flagCleared else 0
|
||||
clears[2] += 1 if req.songDetail.flagMissless else 0
|
||||
clears[3] += 1 if req.songDetail.flagFullcombo else 0
|
||||
clears[4] += 1 if req.songDetail.flagAllMarvelous else 0
|
||||
|
||||
grades[req.songDetail.grade.value - 1] += 1
|
||||
|
||||
best_score = max(req.songDetail.score, old_score["score"])
|
||||
best_max_combo = max(req.songDetail.maxCombo, old_score["best_combo"])
|
||||
lowest_miss_ct = min(
|
||||
req.songDetail.judgements.missCt, old_score["lowest_miss_ct"]
|
||||
)
|
||||
best_rating = max(
|
||||
self.util_calc_song_rating(req.songDetail.score, req.songDetail.level),
|
||||
old_score["rating"],
|
||||
old_score = self.data.score.get_best_score(
|
||||
user_id, req.songDetail.songId, req.songDetail.difficulty
|
||||
)
|
||||
|
||||
self.data.score.put_best_score(
|
||||
user_id,
|
||||
req.songDetail.songId,
|
||||
req.songDetail.difficulty,
|
||||
best_score,
|
||||
clears,
|
||||
grades,
|
||||
best_max_combo,
|
||||
lowest_miss_ct,
|
||||
)
|
||||
if not old_score:
|
||||
grades = [0] * 13
|
||||
clears = [0] * 5
|
||||
|
||||
resp.songDetail.score = best_score
|
||||
resp.songDetail.lowestMissCount = lowest_miss_ct
|
||||
resp.songDetail.rating = best_rating
|
||||
clears[0] = 1
|
||||
clears[1] = 1 if req.songDetail.flagCleared else 0
|
||||
clears[2] = 1 if req.songDetail.flagMissless else 0
|
||||
clears[3] = 1 if req.songDetail.flagFullcombo else 0
|
||||
clears[4] = 1 if req.songDetail.flagAllMarvelous else 0
|
||||
|
||||
grades[req.songDetail.grade.value - 1] = 1
|
||||
|
||||
self.data.score.put_best_score(
|
||||
user_id,
|
||||
req.songDetail.songId,
|
||||
req.songDetail.difficulty,
|
||||
req.songDetail.score,
|
||||
clears,
|
||||
grades,
|
||||
req.songDetail.maxCombo,
|
||||
req.songDetail.judgements.missCt,
|
||||
)
|
||||
|
||||
resp.songDetail.score = req.songDetail.score
|
||||
resp.songDetail.lowestMissCount = req.songDetail.judgements.missCt
|
||||
|
||||
else:
|
||||
grades = [
|
||||
old_score["grade_d_ct"],
|
||||
old_score["grade_c_ct"],
|
||||
old_score["grade_b_ct"],
|
||||
old_score["grade_a_ct"],
|
||||
old_score["grade_aa_ct"],
|
||||
old_score["grade_aaa_ct"],
|
||||
old_score["grade_s_ct"],
|
||||
old_score["grade_ss_ct"],
|
||||
old_score["grade_sss_ct"],
|
||||
old_score["grade_master_ct"],
|
||||
old_score["grade_sp_ct"],
|
||||
old_score["grade_ssp_ct"],
|
||||
old_score["grade_sssp_ct"],
|
||||
]
|
||||
clears = [
|
||||
old_score["play_ct"],
|
||||
old_score["clear_ct"],
|
||||
old_score["missless_ct"],
|
||||
old_score["fullcombo_ct"],
|
||||
old_score["allmarv_ct"],
|
||||
]
|
||||
|
||||
clears[0] += 1
|
||||
clears[1] += 1 if req.songDetail.flagCleared else 0
|
||||
clears[2] += 1 if req.songDetail.flagMissless else 0
|
||||
clears[3] += 1 if req.songDetail.flagFullcombo else 0
|
||||
clears[4] += 1 if req.songDetail.flagAllMarvelous else 0
|
||||
|
||||
grades[req.songDetail.grade.value - 1] += 1
|
||||
|
||||
best_score = max(req.songDetail.score, old_score["score"])
|
||||
best_max_combo = max(req.songDetail.maxCombo, old_score["best_combo"])
|
||||
lowest_miss_ct = min(
|
||||
req.songDetail.judgements.missCt, old_score["lowest_miss_ct"]
|
||||
)
|
||||
best_rating = max(
|
||||
self.util_calc_song_rating(req.songDetail.score, req.songDetail.level),
|
||||
old_score["rating"],
|
||||
)
|
||||
|
||||
self.data.score.put_best_score(
|
||||
user_id,
|
||||
req.songDetail.songId,
|
||||
req.songDetail.difficulty,
|
||||
best_score,
|
||||
clears,
|
||||
grades,
|
||||
best_max_combo,
|
||||
lowest_miss_ct,
|
||||
)
|
||||
|
||||
resp.songDetail.score = best_score
|
||||
resp.songDetail.lowestMissCount = lowest_miss_ct
|
||||
resp.songDetail.rating = best_rating
|
||||
|
||||
resp.songDetail.clearCounts = SongDetailClearCounts(counts=clears)
|
||||
resp.songDetail.clearCountsSeason = SongDetailClearCounts(counts=clears)
|
||||
|
@ -858,13 +883,18 @@ class WaccaBase:
|
|||
if profile is None:
|
||||
return BaseResponse().make()
|
||||
|
||||
if len(req.itemsObtained) > 0:
|
||||
self.util_put_items(req.profileId, profile["user"], req.itemsObtained)
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
if len(req.itemsObtained) > 0:
|
||||
self.util_put_items(req.profileId, profile["user"], req.itemsObtained)
|
||||
|
||||
self.data.profile.update_bingo(
|
||||
profile["user"], req.bingoDetail.pageNumber, page_status
|
||||
)
|
||||
self.data.profile.update_tutorial_flags(req.profileId, req.params[3])
|
||||
self.data.profile.update_bingo(
|
||||
profile["user"], req.bingoDetail.pageNumber, page_status
|
||||
)
|
||||
self.data.profile.update_tutorial_flags(req.profileId, req.params[3])
|
||||
|
||||
return BaseResponse().make()
|
||||
|
||||
|
@ -891,7 +921,12 @@ class WaccaBase:
|
|||
f"User {req.profileId} Purchased item {req.itemObtained.itemType} id {req.itemObtained.itemId} for {req.cost} credits on machine {req.chipId}"
|
||||
)
|
||||
|
||||
self.util_put_items(req.profileId, user_id, [req.itemObtained])
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
self.util_put_items(req.profileId, user_id, [req.itemObtained])
|
||||
|
||||
if self.game_config.mods.infinite_tickets:
|
||||
for x in range(5):
|
||||
|
@ -949,33 +984,38 @@ class WaccaBase:
|
|||
)
|
||||
return BaseResponse().make()
|
||||
|
||||
self.util_put_items(req.profileId, user_id, req.itemsRecieved)
|
||||
self.data.profile.update_profile_playtype(
|
||||
req.profileId, req.playType.value, data["appVersion"][:7]
|
||||
)
|
||||
with (
|
||||
self.data.profile.conn.begin() or nullcontext(),
|
||||
self.data.score.conn.begin() or nullcontext(),
|
||||
self.data.item.conn.begin() or nullcontext(),
|
||||
):
|
||||
self.util_put_items(req.profileId, user_id, req.itemsRecieved)
|
||||
self.data.profile.update_profile_playtype(
|
||||
req.profileId, req.playType.value, data["appVersion"][:7]
|
||||
)
|
||||
|
||||
current_icon = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_icon_id"]
|
||||
)
|
||||
current_nav = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_nav_id"]
|
||||
)
|
||||
current_icon = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_icon_id"]
|
||||
)
|
||||
current_nav = self.data.profile.get_options(
|
||||
user_id, WaccaConstants.OPTIONS["set_nav_id"]
|
||||
)
|
||||
|
||||
if current_icon is None:
|
||||
current_icon = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
else:
|
||||
current_icon = current_icon["value"]
|
||||
if current_nav is None:
|
||||
current_nav = self.OPTIONS_DEFAULTS["set_nav_id"]
|
||||
else:
|
||||
current_nav = current_nav["value"]
|
||||
if current_icon is None:
|
||||
current_icon = self.OPTIONS_DEFAULTS["set_icon_id"]
|
||||
else:
|
||||
current_icon = current_icon["value"]
|
||||
if current_nav is None:
|
||||
current_nav = self.OPTIONS_DEFAULTS["set_nav_id"]
|
||||
else:
|
||||
current_nav = current_nav["value"]
|
||||
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon
|
||||
)
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav
|
||||
)
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["icon"], current_icon
|
||||
)
|
||||
self.data.item.put_item(
|
||||
user_id, WaccaConstants.ITEM_TYPES["navigator"], current_nav
|
||||
)
|
||||
return BaseResponse().make()
|
||||
|
||||
def handle_user_info_update_request(self, data: Dict) -> Dict:
|
||||
|
|
|
@ -30,9 +30,9 @@ class WaccaFrontend(FE_Base):
|
|||
)
|
||||
sesh: Session = request.getSession()
|
||||
usr_sesh = IUserSession(sesh)
|
||||
|
||||
|
||||
return template.render(
|
||||
title=f"{self.core_config.server.name} | {self.nav_name}",
|
||||
game_list=self.environment.globals["game_list"],
|
||||
sesh=vars(usr_sesh)
|
||||
sesh=vars(usr_sesh),
|
||||
).encode("utf-16")
|
||||
|
|
|
@ -93,7 +93,9 @@ class UserMusicUnlockRequest(BaseRequest):
|
|||
|
||||
|
||||
class UserMusicUnlockResponse(BaseResponse):
|
||||
def __init__(self, current_wp: int = 0, tickets_remaining: List[TicketItem] = []) -> None:
|
||||
def __init__(
|
||||
self, current_wp: int = 0, tickets_remaining: List[TicketItem] = []
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.wp = current_wp
|
||||
self.tickets = tickets_remaining
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
from typing import Any, List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
import json
|
||||
from contextlib import nullcontext
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.wacca.lily import WaccaLily
|
||||
|
@ -40,30 +41,31 @@ class WaccaLilyR(WaccaLily):
|
|||
req = UserStatusCreateRequest(data)
|
||||
resp = super().handle_user_status_create_request(data)
|
||||
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210054
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210055
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210056
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210057
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210058
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210059
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210060
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210061
|
||||
) # Added lily r
|
||||
with self.data.item.conn.begin() or nullcontext():
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210054
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210055
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210056
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210057
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210058
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210059
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210060
|
||||
) # Added lily r
|
||||
self.data.item.put_item(
|
||||
req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 210061
|
||||
) # Added lily r
|
||||
|
||||
return resp
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue