diff --git a/.gitignore b/.gitignore
index b5a0e6e..5c1952f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,4 +160,9 @@ config/*
deliver/*
*.gz
-dbdump-*.json
\ No newline at end of file
+dbdump-*.json
+
+*.sqlite3
+*.sqlite3-journal
+*.sqlite3-shm
+*.sqlite3-wal
diff --git a/core/allnet.py b/core/allnet.py
index 9ad5949..3c7f64b 100644
--- a/core/allnet.py
+++ b/core/allnet.py
@@ -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__()
diff --git a/core/config.py b/core/config.py
index 8b85353..3427014 100644
--- a/core/config.py
+++ b/core/config.py
@@ -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
diff --git a/core/data/__init__.py b/core/data/__init__.py
index eb30d05..cfd31c3 100644
--- a/core/data/__init__.py
+++ b/core/data/__init__.py
@@ -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
diff --git a/core/data/cache.py b/core/data/cache.py
index cabf597..df48ca7 100644
--- a/core/data/cache.py
+++ b/core/data/cache.py
@@ -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
diff --git a/core/data/database.py b/core/data/database.py
index 9fb2606..88c8837 100644
--- a/core/data/database.py
+++ b/core/data/database.py
@@ -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']}")
diff --git a/core/data/schema/__init__.py b/core/data/schema/__init__.py
index 45931d7..810e24a 100644
--- a/core/data/schema/__init__.py
+++ b/core/data/schema/__init__.py
@@ -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"]
diff --git a/core/data/schema/arcade.py b/core/data/schema/arcade.py
index e1d9b1f..c6753f2 100644
--- a/core/data/schema/arcade.py
+++ b/core/data/schema/arcade.py
@@ -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
diff --git a/core/data/schema/base.py b/core/data/schema/base.py
index a53392f..e26640e 100644
--- a/core/data/schema/base.py
+++ b/core/data/schema/base.py
@@ -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
diff --git a/core/data/schema/card.py b/core/data/schema/card.py
index d8f5fc0..f5800f2 100644
--- a/core/data/schema/card.py
+++ b/core/data/schema/card.py
@@ -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}"
diff --git a/core/data/schema/user.py b/core/data/schema/user.py
index 6a95005..3d18436 100644
--- a/core/data/schema/user.py
+++ b/core/data/schema/user.py
@@ -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()
diff --git a/core/frontend.py b/core/frontend.py
index f01be50..128032b 100644
--- a/core/frontend.py
+++ b/core/frontend.py
@@ -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")
diff --git a/core/frontend/user/index.jinja b/core/frontend/user/index.jinja
index 2911e67..1c7c9c9 100644
--- a/core/frontend/user/index.jinja
+++ b/core/frontend/user/index.jinja
@@ -1,6 +1,6 @@
{% extends "core/frontend/index.jinja" %}
{% block content %}
-
Management for {{ username }}
+Management for {{ username.decode("utf-8") }}
Cards
{% for c in cards %}
@@ -18,7 +18,7 @@
HOW TO:
Scan your card on any networked game and press the "View Access Code" button (varies by game) and enter the 20 digit code below.
!!FOR AMUSEIC CARDS: DO NOT ENTER THE CODE SHOWN ON THE BACK OF THE CARD ITSELF OR IT WILL NOT WORK!!
-
+
-{% endblock content %}
\ No newline at end of file
+{% endblock content %}
diff --git a/core/mucha.py b/core/mucha.py
index 8d9dd8e..ace4df6 100644
--- a/core/mucha.py
+++ b/core/mucha.py
@@ -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", "")
diff --git a/core/title.py b/core/title.py
index ab53773..9fa2817 100644
--- a/core/title.py
+++ b/core/title.py
@@ -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"]
diff --git a/dbutils.py b/dbutils.py
index caae9d8..e5e698e 100644
--- a/dbutils.py
+++ b/dbutils.py
@@ -84,7 +84,7 @@ if __name__ == "__main__":
elif args.action == "cleanup":
data.delete_hanging_users()
-
+
elif args.action == "version":
data.show_versions()
diff --git a/example_config/core.yaml b/example_config/core.yaml
index ebf99f1..893edeb 100644
--- a/example_config/core.yaml
+++ b/example_config/core.yaml
@@ -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
diff --git a/index.py b/index.py
index 2ff7c04..86df865 100644
--- a/index.py
+++ b/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""
diff --git a/readme.md b/readme.md
index 2c49faa..82dd498 100644
--- a/readme.md
+++ b/readme.md
@@ -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.
diff --git a/titles/chuni/__init__.py b/titles/chuni/__init__.py
index 0c3cc79..5d9f32f 100644
--- a/titles/chuni/__init__.py
+++ b/titles/chuni/__init__.py
@@ -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
diff --git a/titles/chuni/base.py b/titles/chuni/base.py
index 4c4361c..5d1709d 100644
--- a/titles/chuni/base.py
+++ b/titles/chuni/base.py
@@ -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:
diff --git a/titles/chuni/const.py b/titles/chuni/const.py
index b3a4cb5..e988fa0 100644
--- a/titles/chuni/const.py
+++ b/titles/chuni/const.py
@@ -33,7 +33,7 @@ class ChuniConstants:
"CHUNITHM PARADISE",
"CHUNITHM NEW!!",
"CHUNITHM NEW PLUS!!",
- "CHUNITHM SUN"
+ "CHUNITHM SUN",
]
@classmethod
diff --git a/titles/chuni/index.py b/titles/chuni/index.py
index 5d185e9..649e416 100644
--- a/titles/chuni/index.py
+++ b/titles/chuni/index.py
@@ -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(
diff --git a/titles/chuni/new.py b/titles/chuni/new.py
index 40dee9b..bf83d7f 100644
--- a/titles/chuni/new.py
+++ b/titles/chuni/new.py
@@ -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"}
diff --git a/titles/chuni/read.py b/titles/chuni/read.py
index 9ac13c4..77a8f77 100644
--- a/titles/chuni/read.py
+++ b/titles/chuni/read.py
@@ -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"):
diff --git a/titles/chuni/schema/__init__.py b/titles/chuni/schema/__init__.py
index 51d950b..25bb896 100644
--- a/titles/chuni/schema/__init__.py
+++ b/titles/chuni/schema/__init__.py
@@ -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"]
diff --git a/titles/chuni/schema/item.py b/titles/chuni/schema/item.py
index 47ff633..ea0af80 100644
--- a/titles/chuni/schema/item.py
+++ b/titles/chuni/schema/item.py
@@ -1,596 +1,591 @@
-from typing import Dict, List, Optional
-from sqlalchemy import (
- Table,
- Column,
- UniqueConstraint,
- PrimaryKeyConstraint,
- and_,
- delete,
-)
-from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
-from sqlalchemy.engine.base import Connection
-from sqlalchemy.schema import ForeignKey
-from sqlalchemy.sql import func, select
-from sqlalchemy.dialects.mysql import insert
-from sqlalchemy.engine import Row
-
-from core.data.schema import BaseData, metadata
-
-character = Table(
- "chuni_item_character",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("characterId", Integer),
- Column("level", Integer),
- Column("param1", Integer),
- Column("param2", Integer),
- Column("isValid", Boolean),
- Column("skillId", Integer),
- Column("isNewMark", Boolean),
- Column("playCount", Integer),
- Column("friendshipExp", Integer),
- Column("assignIllust", Integer),
- Column("exMaxLv", Integer),
- UniqueConstraint("user", "characterId", name="chuni_item_character_uk"),
- mysql_charset="utf8mb4",
-)
-
-item = Table(
- "chuni_item_item",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("itemId", Integer),
- Column("itemKind", Integer),
- Column("stock", Integer),
- Column("isValid", Boolean),
- UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"),
- mysql_charset="utf8mb4",
-)
-
-duel = Table(
- "chuni_item_duel",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("duelId", Integer),
- Column("progress", Integer),
- Column("point", Integer),
- Column("isClear", Boolean),
- Column("lastPlayDate", String(25)),
- Column("param1", Integer),
- Column("param2", Integer),
- Column("param3", Integer),
- Column("param4", Integer),
- UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"),
- mysql_charset="utf8mb4",
-)
-
-map = Table(
- "chuni_item_map",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("mapId", Integer),
- Column("position", Integer),
- Column("isClear", Boolean),
- Column("areaId", Integer),
- Column("routeNumber", Integer),
- Column("eventId", Integer),
- Column("rate", Integer),
- Column("statusCount", Integer),
- Column("isValid", Boolean),
- UniqueConstraint("user", "mapId", name="chuni_item_map_uk"),
- mysql_charset="utf8mb4",
-)
-
-map_area = Table(
- "chuni_item_map_area",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("mapAreaId", Integer),
- Column("rate", Integer),
- Column("isClear", Boolean),
- Column("isLocked", Boolean),
- Column("position", Integer),
- Column("statusCount", Integer),
- Column("remainGridCount", Integer),
- UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"),
- mysql_charset="utf8mb4",
-)
-
-gacha = Table(
- "chuni_item_gacha",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("gachaId", Integer, nullable=False),
- Column("totalGachaCnt", Integer, server_default="0"),
- Column("ceilingGachaCnt", Integer, server_default="0"),
- Column("dailyGachaCnt", Integer, server_default="0"),
- Column("fiveGachaCnt", Integer, server_default="0"),
- Column("elevenGachaCnt", Integer, server_default="0"),
- Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "gachaId", name="chuni_item_gacha_uk"),
- mysql_charset="utf8mb4",
-)
-
-print_state = Table(
- "chuni_item_print_state",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("hasCompleted", Boolean, nullable=False, server_default="0"),
- Column(
- "limitDate", TIMESTAMP, nullable=False, server_default="2038-01-01 00:00:00.0"
- ),
- Column("placeId", Integer),
- Column("cardId", Integer),
- Column("gachaId", Integer),
- UniqueConstraint("id", "user", name="chuni_item_print_state_uk"),
- mysql_charset="utf8mb4",
-)
-
-print_detail = Table(
- "chuni_item_print_detail",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("cardId", Integer, nullable=False),
- Column("printDate", TIMESTAMP, nullable=False),
- Column("serialId", String(20), nullable=False),
- Column("placeId", Integer, nullable=False),
- Column("clientId", String(11), nullable=False),
- Column("printerSerialId", String(20), nullable=False),
- Column("printOption1", Boolean, server_default="0"),
- Column("printOption2", Boolean, server_default="0"),
- Column("printOption3", Boolean, server_default="0"),
- Column("printOption4", Boolean, server_default="0"),
- Column("printOption5", Boolean, server_default="0"),
- Column("printOption6", Boolean, server_default="0"),
- Column("printOption7", Boolean, server_default="0"),
- Column("printOption8", Boolean, server_default="0"),
- Column("printOption9", Boolean, server_default="0"),
- Column("printOption10", Boolean, server_default="0"),
- Column("created", String(255), server_default=""),
- UniqueConstraint("serialId", name="chuni_item_print_detail_uk"),
- mysql_charset="utf8mb4",
-)
-
-login_bonus = Table(
- "chuni_item_login_bonus",
- 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("presetId", Integer, nullable=False),
- Column("bonusCount", Integer, nullable=False, server_default="0"),
- Column("lastUpdateDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
- Column("isWatched", Boolean, server_default="0"),
- Column("isFinished", Boolean, server_default="0"),
- UniqueConstraint("version", "user", "presetId", name="chuni_item_login_bonus_uk"),
- mysql_charset="utf8mb4",
-)
-
-favorite = Table(
- "chuni_item_favorite",
- 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("favId", Integer, nullable=False),
- Column("favKind", Integer, nullable=False, server_default="1"),
- UniqueConstraint("version", "user", "favId", name="chuni_item_favorite_uk"),
- mysql_charset="utf8mb4",
-)
-
-matching = Table(
- "chuni_item_matching",
- metadata,
- Column("roomId", Integer, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("version", Integer, nullable=False),
- Column("restMSec", Integer, nullable=False, server_default="60"),
- Column("isFull", Boolean, nullable=False, server_default="0"),
- PrimaryKeyConstraint("roomId", "version", name="chuni_item_matching_pk"),
- Column("matchingMemberInfoList", JSON, nullable=False),
- mysql_charset="utf8mb4",
-)
-
-
-class ChuniItemData(BaseData):
- def get_oldest_free_matching(self, version: int) -> Optional[Row]:
- sql = matching.select(
- and_(
- matching.c.version == version,
- matching.c.isFull == False
- )
- ).order_by(matching.c.roomId.asc())
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_newest_matching(self, version: int) -> Optional[Row]:
- sql = matching.select(
- and_(
- matching.c.version == version
- )
- ).order_by(matching.c.roomId.desc())
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_all_matchings(self, version: int) -> Optional[List[Row]]:
- sql = matching.select(
- and_(
- matching.c.version == version
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_matching(self, version: int, room_id: int) -> Optional[Row]:
- sql = matching.select(
- and_(matching.c.version == version, matching.c.roomId == room_id)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_matching(
- self,
- version: int,
- room_id: int,
- matching_member_info_list: List,
- user_id: int = None,
- rest_sec: int = 60,
- is_full: bool = False
- ) -> Optional[int]:
- sql = insert(matching).values(
- roomId=room_id,
- version=version,
- restMSec=rest_sec,
- user=user_id,
- isFull=is_full,
- matchingMemberInfoList=matching_member_info_list,
- )
-
- conflict = sql.on_duplicate_key_update(
- restMSec=rest_sec, matchingMemberInfoList=matching_member_info_list
- )
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def delete_matching(self, version: int, room_id: int):
- sql = delete(matching).where(
- and_(matching.c.roomId == room_id, matching.c.version == version)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.lastrowid
-
- def get_all_favorites(
- self, user_id: int, version: int, fav_kind: int = 1
- ) -> Optional[List[Row]]:
- sql = favorite.select(
- and_(
- favorite.c.version == version,
- favorite.c.user == user_id,
- favorite.c.favKind == fav_kind,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_login_bonus(
- self, user_id: int, version: int, preset_id: int, **login_bonus_data
- ) -> Optional[int]:
- sql = insert(login_bonus).values(
- version=version, user=user_id, presetId=preset_id, **login_bonus_data
- )
-
- conflict = sql.on_duplicate_key_update(presetId=preset_id, **login_bonus_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_all_login_bonus(
- self, user_id: int, version: int, is_finished: bool = False
- ) -> Optional[List[Row]]:
- sql = login_bonus.select(
- and_(
- login_bonus.c.version == version,
- login_bonus.c.user == user_id,
- login_bonus.c.isFinished == is_finished,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_login_bonus(
- self, user_id: int, version: int, preset_id: int
- ) -> Optional[Row]:
- sql = login_bonus.select(
- and_(
- login_bonus.c.version == version,
- login_bonus.c.user == user_id,
- login_bonus.c.presetId == preset_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
- character_data["user"] = user_id
-
- character_data = self.fix_bools(character_data)
-
- sql = insert(character).values(**character_data)
- conflict = sql.on_duplicate_key_update(**character_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_character(self, user_id: int, character_id: int) -> Optional[Dict]:
- sql = select(character).where(
- and_(character.c.user == user_id, character.c.characterId == character_id)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_characters(self, user_id: int) -> Optional[List[Row]]:
- sql = select(character).where(character.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_item(self, user_id: int, item_data: Dict) -> Optional[int]:
- item_data["user"] = user_id
-
- item_data = self.fix_bools(item_data)
-
- sql = insert(item).values(**item_data)
- conflict = sql.on_duplicate_key_update(**item_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]:
- if kind is None:
- sql = select(item).where(item.c.user == user_id)
- else:
- sql = select(item).where(
- and_(item.c.user == user_id, item.c.itemKind == kind)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]:
- duel_data["user"] = user_id
-
- duel_data = self.fix_bools(duel_data)
-
- sql = insert(duel).values(**duel_data)
- conflict = sql.on_duplicate_key_update(**duel_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_duels(self, user_id: int) -> Optional[List[Row]]:
- sql = select(duel).where(duel.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_map(self, user_id: int, map_data: Dict) -> Optional[int]:
- map_data["user"] = user_id
-
- map_data = self.fix_bools(map_data)
-
- sql = insert(map).values(**map_data)
- conflict = sql.on_duplicate_key_update(**map_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_maps(self, user_id: int) -> Optional[List[Row]]:
- sql = select(map).where(map.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]:
- map_area_data["user"] = user_id
-
- map_area_data = self.fix_bools(map_area_data)
-
- sql = insert(map_area).values(**map_area_data)
- conflict = sql.on_duplicate_key_update(**map_area_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_map_areas(self, user_id: int) -> Optional[List[Row]]:
- sql = select(map_area).where(map_area.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
- sql = gacha.select(gacha.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_user_gacha(
- self, aime_id: int, gacha_id: int, gacha_data: Dict
- ) -> Optional[int]:
- sql = insert(gacha).values(user=aime_id, gachaId=gacha_id, **gacha_data)
-
- conflict = sql.on_duplicate_key_update(
- user=aime_id, gachaId=gacha_id, **gacha_data
- )
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_user_print_states(
- self, aime_id: int, has_completed: bool = False
- ) -> Optional[List[Row]]:
- sql = print_state.select(
- and_(
- print_state.c.user == aime_id,
- print_state.c.hasCompleted == has_completed,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_user_print_states_by_gacha(
- self, aime_id: int, gacha_id: int, has_completed: bool = False
- ) -> Optional[List[Row]]:
- sql = print_state.select(
- and_(
- print_state.c.user == aime_id,
- print_state.c.gachaId == gacha_id,
- print_state.c.hasCompleted == has_completed,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_user_print_state(self, aime_id: int, **print_data) -> Optional[int]:
- sql = insert(print_state).values(user=aime_id, **print_data)
-
- conflict = sql.on_duplicate_key_update(user=aime_id, **print_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_user_print_state: Failed to insert! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def put_user_print_detail(
- self, aime_id: int, serial_id: str, user_print_data: Dict
- ) -> Optional[int]:
- sql = insert(print_detail).values(
- user=aime_id, serialId=serial_id, **user_print_data
- )
-
- conflict = sql.on_duplicate_key_update(user=aime_id, **user_print_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
+from typing import Dict, List, Optional
+from sqlalchemy import (
+ Table,
+ Column,
+ UniqueConstraint,
+ PrimaryKeyConstraint,
+ and_,
+ delete,
+)
+from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
+from sqlalchemy.engine.base import Connection
+from sqlalchemy.schema import ForeignKey
+from sqlalchemy.sql import func, select
+from sqlalchemy.dialects.sqlite import insert
+from sqlalchemy.engine import Row
+
+from core.data.schema import BaseData, metadata
+
+character = Table(
+ "chuni_item_character",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("characterId", Integer),
+ Column("level", Integer),
+ Column("param1", Integer),
+ Column("param2", Integer),
+ Column("isValid", Boolean),
+ Column("skillId", Integer),
+ Column("isNewMark", Boolean),
+ Column("playCount", Integer),
+ Column("friendshipExp", Integer),
+ Column("assignIllust", Integer),
+ Column("exMaxLv", Integer),
+ UniqueConstraint("user", "characterId", name="chuni_item_character_uk"),
+ mysql_charset="utf8mb4",
+)
+
+item = Table(
+ "chuni_item_item",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("itemId", Integer),
+ Column("itemKind", Integer),
+ Column("stock", Integer),
+ Column("isValid", Boolean),
+ UniqueConstraint("user", "itemId", "itemKind", name="chuni_item_item_uk"),
+ mysql_charset="utf8mb4",
+)
+
+duel = Table(
+ "chuni_item_duel",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("duelId", Integer),
+ Column("progress", Integer),
+ Column("point", Integer),
+ Column("isClear", Boolean),
+ Column("lastPlayDate", String(25)),
+ Column("param1", Integer),
+ Column("param2", Integer),
+ Column("param3", Integer),
+ Column("param4", Integer),
+ UniqueConstraint("user", "duelId", name="chuni_item_duel_uk"),
+ mysql_charset="utf8mb4",
+)
+
+map = Table(
+ "chuni_item_map",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("mapId", Integer),
+ Column("position", Integer),
+ Column("isClear", Boolean),
+ Column("areaId", Integer),
+ Column("routeNumber", Integer),
+ Column("eventId", Integer),
+ Column("rate", Integer),
+ Column("statusCount", Integer),
+ Column("isValid", Boolean),
+ UniqueConstraint("user", "mapId", name="chuni_item_map_uk"),
+ mysql_charset="utf8mb4",
+)
+
+map_area = Table(
+ "chuni_item_map_area",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("mapAreaId", Integer),
+ Column("rate", Integer),
+ Column("isClear", Boolean),
+ Column("isLocked", Boolean),
+ Column("position", Integer),
+ Column("statusCount", Integer),
+ Column("remainGridCount", Integer),
+ UniqueConstraint("user", "mapAreaId", name="chuni_item_map_area_uk"),
+ mysql_charset="utf8mb4",
+)
+
+gacha = Table(
+ "chuni_item_gacha",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("gachaId", Integer, nullable=False),
+ Column("totalGachaCnt", Integer, server_default="0"),
+ Column("ceilingGachaCnt", Integer, server_default="0"),
+ Column("dailyGachaCnt", Integer, server_default="0"),
+ Column("fiveGachaCnt", Integer, server_default="0"),
+ Column("elevenGachaCnt", Integer, server_default="0"),
+ Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "gachaId", name="chuni_item_gacha_uk"),
+ mysql_charset="utf8mb4",
+)
+
+print_state = Table(
+ "chuni_item_print_state",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("hasCompleted", Boolean, nullable=False, server_default="0"),
+ Column(
+ "limitDate", TIMESTAMP, nullable=False, server_default="2038-01-01 00:00:00.0"
+ ),
+ Column("placeId", Integer),
+ Column("cardId", Integer),
+ Column("gachaId", Integer),
+ UniqueConstraint("id", "user", name="chuni_item_print_state_uk"),
+ mysql_charset="utf8mb4",
+)
+
+print_detail = Table(
+ "chuni_item_print_detail",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("cardId", Integer, nullable=False),
+ Column("printDate", TIMESTAMP, nullable=False),
+ Column("serialId", String(20), nullable=False),
+ Column("placeId", Integer, nullable=False),
+ Column("clientId", String(11), nullable=False),
+ Column("printerSerialId", String(20), nullable=False),
+ Column("printOption1", Boolean, server_default="0"),
+ Column("printOption2", Boolean, server_default="0"),
+ Column("printOption3", Boolean, server_default="0"),
+ Column("printOption4", Boolean, server_default="0"),
+ Column("printOption5", Boolean, server_default="0"),
+ Column("printOption6", Boolean, server_default="0"),
+ Column("printOption7", Boolean, server_default="0"),
+ Column("printOption8", Boolean, server_default="0"),
+ Column("printOption9", Boolean, server_default="0"),
+ Column("printOption10", Boolean, server_default="0"),
+ Column("created", String(255), server_default=""),
+ UniqueConstraint("serialId", name="chuni_item_print_detail_uk"),
+ mysql_charset="utf8mb4",
+)
+
+login_bonus = Table(
+ "chuni_item_login_bonus",
+ 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("presetId", Integer, nullable=False),
+ Column("bonusCount", Integer, nullable=False, server_default="0"),
+ Column("lastUpdateDate", TIMESTAMP, server_default="2018-01-01 00:00:00.0"),
+ Column("isWatched", Boolean, server_default="0"),
+ Column("isFinished", Boolean, server_default="0"),
+ UniqueConstraint("version", "user", "presetId", name="chuni_item_login_bonus_uk"),
+ mysql_charset="utf8mb4",
+)
+
+favorite = Table(
+ "chuni_item_favorite",
+ 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("favId", Integer, nullable=False),
+ Column("favKind", Integer, nullable=False, server_default="1"),
+ UniqueConstraint("version", "user", "favId", name="chuni_item_favorite_uk"),
+ mysql_charset="utf8mb4",
+)
+
+matching = Table(
+ "chuni_item_matching",
+ metadata,
+ Column("roomId", Integer, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("version", Integer, nullable=False),
+ Column("restMSec", Integer, nullable=False, server_default="60"),
+ Column("isFull", Boolean, nullable=False, server_default="0"),
+ PrimaryKeyConstraint("roomId", "version", name="chuni_item_matching_pk"),
+ Column("matchingMemberInfoList", JSON, nullable=False),
+ mysql_charset="utf8mb4",
+)
+
+
+class ChuniItemData(BaseData):
+ def get_oldest_free_matching(self, version: int) -> Optional[Row]:
+ sql = matching.select(
+ and_(matching.c.version == version, matching.c.isFull == False)
+ ).order_by(matching.c.roomId.asc())
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_newest_matching(self, version: int) -> Optional[Row]:
+ sql = matching.select(and_(matching.c.version == version)).order_by(
+ matching.c.roomId.desc()
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_all_matchings(self, version: int) -> Optional[List[Row]]:
+ sql = matching.select(and_(matching.c.version == version))
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_matching(self, version: int, room_id: int) -> Optional[Row]:
+ sql = matching.select(
+ and_(matching.c.version == version, matching.c.roomId == room_id)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_matching(
+ self,
+ version: int,
+ room_id: int,
+ matching_member_info_list: List,
+ user_id: int = None,
+ rest_sec: int = 60,
+ is_full: bool = False,
+ ) -> Optional[int]:
+ sql = insert(matching).values(
+ roomId=room_id,
+ version=version,
+ restMSec=rest_sec,
+ user=user_id,
+ isFull=is_full,
+ matchingMemberInfoList=matching_member_info_list,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ restMSec=rest_sec, matchingMemberInfoList=matching_member_info_list
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def delete_matching(self, version: int, room_id: int):
+ sql = delete(matching).where(
+ and_(matching.c.roomId == room_id, matching.c.version == version)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_all_favorites(
+ self, user_id: int, version: int, fav_kind: int = 1
+ ) -> Optional[List[Row]]:
+ sql = favorite.select(
+ and_(
+ favorite.c.version == version,
+ favorite.c.user == user_id,
+ favorite.c.favKind == fav_kind,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_login_bonus(
+ self, user_id: int, version: int, preset_id: int, **login_bonus_data
+ ) -> Optional[int]:
+ sql = insert(login_bonus).values(
+ version=version, user=user_id, presetId=preset_id, **login_bonus_data
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(presetId=preset_id, **login_bonus_data)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_all_login_bonus(
+ self, user_id: int, version: int, is_finished: bool = False
+ ) -> Optional[List[Row]]:
+ sql = login_bonus.select(
+ and_(
+ login_bonus.c.version == version,
+ login_bonus.c.user == user_id,
+ login_bonus.c.isFinished == is_finished,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_login_bonus(
+ self, user_id: int, version: int, preset_id: int
+ ) -> Optional[Row]:
+ sql = login_bonus.select(
+ and_(
+ login_bonus.c.version == version,
+ login_bonus.c.user == user_id,
+ login_bonus.c.presetId == preset_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_character(self, user_id: int, character_data: Dict) -> Optional[int]:
+ character_data["user"] = user_id
+
+ character_data = self.fix_bools(character_data)
+
+ sql = insert(character).values(**character_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**character_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_character(self, user_id: int, character_id: int) -> Optional[Dict]:
+ sql = select(character).where(
+ and_(character.c.user == user_id, character.c.characterId == character_id)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_characters(self, user_id: int) -> Optional[List[Row]]:
+ sql = select(character).where(character.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_item(self, user_id: int, item_data: Dict) -> Optional[int]:
+ item_data["user"] = user_id
+
+ item_data = self.fix_bools(item_data)
+
+ sql = insert(item).values(**item_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**item_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_items(self, user_id: int, kind: int = None) -> Optional[List[Row]]:
+ if kind is None:
+ sql = select(item).where(item.c.user == user_id)
+ else:
+ sql = select(item).where(
+ and_(item.c.user == user_id, item.c.itemKind == kind)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_duel(self, user_id: int, duel_data: Dict) -> Optional[int]:
+ duel_data["user"] = user_id
+
+ duel_data = self.fix_bools(duel_data)
+
+ sql = insert(duel).values(**duel_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**duel_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_duels(self, user_id: int) -> Optional[List[Row]]:
+ sql = select(duel).where(duel.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_map(self, user_id: int, map_data: Dict) -> Optional[int]:
+ map_data["user"] = user_id
+
+ map_data = self.fix_bools(map_data)
+
+ sql = insert(map).values(**map_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**map_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_maps(self, user_id: int) -> Optional[List[Row]]:
+ sql = select(map).where(map.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_map_area(self, user_id: int, map_area_data: Dict) -> Optional[int]:
+ map_area_data["user"] = user_id
+
+ map_area_data = self.fix_bools(map_area_data)
+
+ sql = insert(map_area).values(**map_area_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**map_area_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_map_areas(self, user_id: int) -> Optional[List[Row]]:
+ sql = select(map_area).where(map_area.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
+ sql = gacha.select(gacha.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_user_gacha(
+ self, aime_id: int, gacha_id: int, gacha_data: Dict
+ ) -> Optional[int]:
+ sql = insert(gacha).values(user=aime_id, gachaId=gacha_id, **gacha_data)
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(user=aime_id, gachaId=gacha_id, **gacha_data)
+ )
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_user_print_states(
+ self, aime_id: int, has_completed: bool = False
+ ) -> Optional[List[Row]]:
+ sql = print_state.select(
+ and_(
+ print_state.c.user == aime_id,
+ print_state.c.hasCompleted == has_completed,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_user_print_states_by_gacha(
+ self, aime_id: int, gacha_id: int, has_completed: bool = False
+ ) -> Optional[List[Row]]:
+ sql = print_state.select(
+ and_(
+ print_state.c.user == aime_id,
+ print_state.c.gachaId == gacha_id,
+ print_state.c.hasCompleted == has_completed,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_user_print_state(self, aime_id: int, **print_data) -> Optional[int]:
+ sql = insert(print_state).values(user=aime_id, **print_data)
+
+ conflict = sql.on_conflict_do_update(set_=dict(user=aime_id, **print_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_user_print_state: Failed to insert! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_user_print_detail(
+ self, aime_id: int, serial_id: str, user_print_data: Dict
+ ) -> Optional[int]:
+ sql = insert(print_detail).values(
+ user=aime_id, serialId=serial_id, **user_print_data
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(user=aime_id, **user_print_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
diff --git a/titles/chuni/schema/profile.py b/titles/chuni/schema/profile.py
index f8edc33..7245f19 100644
--- a/titles/chuni/schema/profile.py
+++ b/titles/chuni/schema/profile.py
@@ -1,639 +1,647 @@
-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
-
-profile = Table(
- "chuni_profile_data",
- 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("exp", Integer),
- Column("level", Integer),
- Column("point", Integer),
- Column("frameId", Integer),
- Column("isMaimai", Boolean),
- Column("trophyId", Integer),
- Column("userName", String(25)),
- Column("isWebJoin", Boolean),
- Column("playCount", Integer),
- Column("lastGameId", String(25)),
- Column("totalPoint", BigInteger),
- Column("characterId", Integer),
- Column("firstGameId", String(25)),
- Column("friendCount", Integer),
- Column("lastPlaceId", Integer),
- Column("nameplateId", Integer),
- Column("totalMapNum", Integer),
- Column("lastAllNetId", Integer),
- Column("lastClientId", String(25)),
- Column("lastPlayDate", String(25)),
- Column("lastRegionId", Integer),
- Column("playerRating", Integer),
- Column("totalHiScore", Integer),
- Column("webLimitDate", String(25)),
- Column("firstPlayDate", String(25)),
- Column("highestRating", Integer),
- Column("lastPlaceName", String(25)),
- Column("multiWinCount", Integer),
- Column("acceptResCount", Integer),
- Column("lastRegionName", String(25)),
- Column("lastRomVersion", String(25)),
- Column("multiPlayCount", Integer),
- Column("firstRomVersion", String(25)),
- Column("lastDataVersion", String(25)),
- Column("requestResCount", Integer),
- Column("successResCount", Integer),
- Column("eventWatchedDate", String(25)),
- Column("firstDataVersion", String(25)),
- Column("reincarnationNum", Integer),
- Column("playedTutorialBit", Integer),
- Column("totalBasicHighScore", Integer),
- Column("totalExpertHighScore", Integer),
- Column("totalMasterHighScore", Integer),
- Column("totalRepertoireCount", Integer),
- Column("firstTutorialCancelNum", Integer),
- Column("totalAdvancedHighScore", Integer),
- Column("masterTutorialCancelNum", Integer),
- Column("ext1", Integer), # Added in chunew
- Column("ext2", Integer),
- Column("ext3", Integer),
- Column("ext4", Integer),
- Column("ext5", Integer),
- Column("ext6", Integer),
- Column("ext7", Integer),
- Column("ext8", Integer),
- Column("ext9", Integer),
- Column("ext10", Integer),
- Column("extStr1", String(255)),
- Column("extStr2", String(255)),
- Column("extLong1", Integer),
- Column("extLong2", Integer),
- Column("mapIconId", Integer),
- Column("compatibleCmVersion", String(25)),
- Column("medal", Integer),
- Column("voiceId", Integer),
- Column(
- "teamId",
- Integer,
- ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL"),
- ),
- Column("eliteRankPoint", Integer, server_default="0"),
- Column("stockedGridCount", Integer, server_default="0"),
- Column("netBattleLoseCount", Integer, server_default="0"),
- Column("netBattleHostErrCnt", Integer, server_default="0"),
- Column("netBattle4thCount", Integer, server_default="0"),
- Column("overPowerRate", Integer, server_default="0"),
- Column("battleRewardStatus", Integer, server_default="0"),
- Column("netBattle1stCount", Integer, server_default="0"),
- Column("charaIllustId", Integer, server_default="0"),
- Column("userNameEx", String(8), server_default=""),
- Column("netBattleWinCount", Integer, server_default="0"),
- Column("netBattleCorrection", Integer, server_default="0"),
- Column("classEmblemMedal", Integer, server_default="0"),
- Column("overPowerPoint", Integer, server_default="0"),
- Column("netBattleErrCnt", Integer, server_default="0"),
- Column("battleRankId", Integer, server_default="0"),
- Column("netBattle3rdCount", Integer, server_default="0"),
- Column("netBattleConsecutiveWinCount", Integer, server_default="0"),
- Column("overPowerLowerRank", Integer, server_default="0"),
- Column("classEmblemBase", Integer, server_default="0"),
- Column("battleRankPoint", Integer, server_default="0"),
- Column("netBattle2ndCount", Integer, server_default="0"),
- Column("totalUltimaHighScore", Integer, server_default="0"),
- Column("skillId", Integer, server_default="0"),
- Column("lastCountryCode", String(5), server_default="JPN"),
- Column("isNetBattleHost", Boolean, server_default="0"),
- Column("battleRewardCount", Integer, server_default="0"),
- Column("battleRewardIndex", Integer, server_default="0"),
- Column("netBattlePlayCount", Integer, server_default="0"),
- Column("exMapLoopCount", Integer, server_default="0"),
- Column("netBattleEndState", Integer, server_default="0"),
- Column("rankUpChallengeResults", JSON),
- Column("avatarBack", Integer, server_default="0"),
- Column("avatarFace", Integer, server_default="0"),
- Column("avatarPoint", Integer, server_default="0"),
- Column("avatarItem", Integer, server_default="0"),
- Column("avatarWear", Integer, server_default="0"),
- Column("avatarFront", Integer, server_default="0"),
- Column("avatarSkin", Integer, server_default="0"),
- Column("avatarHead", Integer, server_default="0"),
- UniqueConstraint("user", "version", name="chuni_profile_profile_uk"),
- mysql_charset="utf8mb4",
-)
-
-profile_ex = Table(
- "chuni_profile_data_ex",
- 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("ext1", Integer),
- Column("ext2", Integer),
- Column("ext3", Integer),
- Column("ext4", Integer),
- Column("ext5", Integer),
- Column("ext6", Integer),
- Column("ext7", Integer),
- Column("ext8", Integer),
- Column("ext9", Integer),
- Column("ext10", Integer),
- Column("ext11", Integer),
- Column("ext12", Integer),
- Column("ext13", Integer),
- Column("ext14", Integer),
- Column("ext15", Integer),
- Column("ext16", Integer),
- Column("ext17", Integer),
- Column("ext18", Integer),
- Column("ext19", Integer),
- Column("ext20", Integer),
- Column("medal", Integer),
- Column("extStr1", String(255)),
- Column("extStr2", String(255)),
- Column("extStr3", String(255)),
- Column("extStr4", String(255)),
- Column("extStr5", String(255)),
- Column("voiceId", Integer),
- Column("extLong1", Integer),
- Column("extLong2", Integer),
- Column("extLong3", Integer),
- Column("extLong4", Integer),
- Column("extLong5", Integer),
- Column("mapIconId", Integer),
- Column("compatibleCmVersion", String(25)),
- UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"),
- mysql_charset="utf8mb4",
-)
-
-option = Table(
- "chuni_profile_option",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("speed", Integer),
- Column("bgInfo", Integer),
- Column("rating", Integer),
- Column("privacy", Integer),
- Column("judgePos", Integer),
- Column("matching", Integer),
- Column("guideLine", Integer),
- Column("headphone", Integer),
- Column("optionSet", Integer),
- Column("fieldColor", Integer),
- Column("guideSound", Integer),
- Column("successAir", Integer),
- Column("successTap", Integer),
- Column("judgeAttack", Integer),
- Column("playerLevel", Integer),
- Column("soundEffect", Integer),
- Column("judgeJustice", Integer),
- Column("successExTap", Integer),
- Column("successFlick", Integer),
- Column("successSkill", Integer),
- Column("successSlideHold", Integer),
- Column("successTapTimbre", Integer),
- Column("ext1", Integer), # Added in chunew
- Column("ext2", Integer),
- Column("ext3", Integer),
- Column("ext4", Integer),
- Column("ext5", Integer),
- Column("ext6", Integer),
- Column("ext7", Integer),
- Column("ext8", Integer),
- Column("ext9", Integer),
- Column("ext10", Integer),
- Column("categoryDetail", Integer, server_default="0"),
- Column("judgeTimingOffset_120", Integer, server_default="0"),
- Column("resultVoiceShort", Integer, server_default="0"),
- Column("judgeAppendSe", Integer, server_default="0"),
- Column("judgeCritical", Integer, server_default="0"),
- Column("trackSkip", Integer, server_default="0"),
- Column("selectMusicFilterLv", Integer, server_default="0"),
- Column("sortMusicFilterLv", Integer, server_default="0"),
- Column("sortMusicGenre", Integer, server_default="0"),
- Column("speed_120", Integer, server_default="0"),
- Column("judgeTimingOffset", Integer, server_default="0"),
- Column("mirrorFumen", Integer, server_default="0"),
- Column("playTimingOffset_120", Integer, server_default="0"),
- Column("hardJudge", Integer, server_default="0"),
- Column("notesThickness", Integer, server_default="0"),
- Column("fieldWallPosition", Integer, server_default="0"),
- Column("playTimingOffset", Integer, server_default="0"),
- Column("fieldWallPosition_120", Integer, server_default="0"),
- UniqueConstraint("user", name="chuni_profile_option_uk"),
- mysql_charset="utf8mb4",
-)
-
-option_ex = Table(
- "chuni_profile_option_ex",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("ext1", Integer),
- Column("ext2", Integer),
- Column("ext3", Integer),
- Column("ext4", Integer),
- Column("ext5", Integer),
- Column("ext6", Integer),
- Column("ext7", Integer),
- Column("ext8", Integer),
- Column("ext9", Integer),
- Column("ext10", Integer),
- Column("ext11", Integer),
- Column("ext12", Integer),
- Column("ext13", Integer),
- Column("ext14", Integer),
- Column("ext15", Integer),
- Column("ext16", Integer),
- Column("ext17", Integer),
- Column("ext18", Integer),
- Column("ext19", Integer),
- Column("ext20", Integer),
- UniqueConstraint("user", name="chuni_profile_option_ex_uk"),
- mysql_charset="utf8mb4",
-)
-
-recent_rating = Table(
- "chuni_profile_recent_rating",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("recentRating", JSON),
- UniqueConstraint("user", name="chuni_profile_recent_rating_uk"),
- mysql_charset="utf8mb4",
-)
-
-region = Table(
- "chuni_profile_region",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("regionId", Integer),
- Column("playCount", Integer),
- UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"),
- mysql_charset="utf8mb4",
-)
-
-activity = Table(
- "chuni_profile_activity",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("kind", Integer),
- Column(
- "activityId", Integer
- ), # Reminder: Change this to ID in base.py or the game will be sad
- Column("sortNumber", Integer),
- Column("param1", Integer),
- Column("param2", Integer),
- Column("param3", Integer),
- Column("param4", Integer),
- UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"),
- mysql_charset="utf8mb4",
-)
-
-charge = Table(
- "chuni_profile_charge",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("chargeId", Integer),
- Column("stock", Integer),
- Column("purchaseDate", String(25)),
- Column("validDate", String(25)),
- Column("param1", Integer),
- Column("param2", Integer),
- Column("paramDate", String(25)),
- UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"),
- mysql_charset="utf8mb4",
-)
-
-emoney = Table(
- "chuni_profile_emoney",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("ext1", Integer),
- Column("ext2", Integer),
- Column("ext3", Integer),
- Column("type", Integer),
- Column("emoneyBrand", Integer),
- Column("emoneyCredit", Integer),
- UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"),
- mysql_charset="utf8mb4",
-)
-
-overpower = Table(
- "chuni_profile_overpower",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("genreId", Integer),
- Column("difficulty", Integer),
- Column("rate", Integer),
- Column("point", Integer),
- UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"),
- mysql_charset="utf8mb4",
-)
-
-team = Table(
- "chuni_profile_team",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("teamName", String(255)),
- Column("teamPoint", Integer),
- mysql_charset="utf8mb4",
-)
-
-
-class ChuniProfileData(BaseData):
- def put_profile_data(
- self, aime_id: int, version: int, profile_data: Dict
- ) -> Optional[int]:
- profile_data["user"] = aime_id
- profile_data["version"] = version
- if "accessCode" in profile_data:
- profile_data.pop("accessCode")
-
- profile_data = self.fix_bools(profile_data)
-
- sql = insert(profile).values(**profile_data)
- conflict = sql.on_duplicate_key_update(**profile_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
- sql = (
- select([profile, option])
- .join(option, profile.c.user == option.c.user)
- .filter(and_(profile.c.user == aime_id, profile.c.version <= version))
- ).order_by(profile.c.version.desc())
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
- sql = select(profile).where(
- and_(
- profile.c.user == aime_id,
- profile.c.version <= version,
- )
- ).order_by(profile.c.version.desc())
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_data_ex(
- self, aime_id: int, version: int, profile_ex_data: Dict
- ) -> Optional[int]:
- profile_ex_data["user"] = aime_id
- profile_ex_data["version"] = version
- if "accessCode" in profile_ex_data:
- profile_ex_data.pop("accessCode")
-
- sql = insert(profile_ex).values(**profile_ex_data)
- conflict = sql.on_duplicate_key_update(**profile_ex_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_profile_data_ex: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]:
- sql = select(profile_ex).where(
- and_(
- profile_ex.c.user == aime_id,
- profile_ex.c.version <= version,
- )
- ).order_by(profile_ex.c.version.desc())
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]:
- option_data["user"] = aime_id
-
- sql = insert(option).values(**option_data)
- conflict = sql.on_duplicate_key_update(**option_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_profile_option: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_option(self, aime_id: int) -> Optional[Row]:
- sql = select(option).where(option.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_option_ex(
- self, aime_id: int, option_ex_data: Dict
- ) -> Optional[int]:
- option_ex_data["user"] = aime_id
-
- sql = insert(option_ex).values(**option_ex_data)
- conflict = sql.on_duplicate_key_update(**option_ex_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_profile_option_ex: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_option_ex(self, aime_id: int) -> Optional[Row]:
- sql = select(option_ex).where(option_ex.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_recent_rating(
- self, aime_id: int, recent_rating_data: List[Dict]
- ) -> Optional[int]:
- sql = insert(recent_rating).values(
- user=aime_id, recentRating=recent_rating_data
- )
- conflict = sql.on_duplicate_key_update(recentRating=recent_rating_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_recent_rating(self, aime_id: int) -> Optional[Row]:
- sql = select(recent_rating).where(recent_rating.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]:
- # The game just uses "id" but we need to distinguish that from the db column "id"
- activity_data["user"] = aime_id
- activity_data["activityId"] = activity_data["id"]
- activity_data.pop("id")
-
- sql = insert(activity).values(**activity_data)
- conflict = sql.on_duplicate_key_update(**activity_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_profile_activity: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]:
- sql = (
- select(activity)
- .where(and_(activity.c.user == aime_id, activity.c.kind == kind))
- .order_by(activity.c.sortNumber.desc()) # to get the last played track
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]:
- charge_data["user"] = aime_id
-
- sql = insert(charge).values(**charge_data)
- conflict = sql.on_duplicate_key_update(**charge_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_profile_charge: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_charge(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(charge).where(charge.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]:
- pass
-
- def get_profile_regions(self, aime_id: int) -> Optional[List[Row]]:
- pass
-
- def put_profile_emoney(self, aime_id: int, emoney_data: Dict) -> Optional[int]:
- emoney_data["user"] = aime_id
-
- sql = insert(emoney).values(**emoney_data)
- conflict = sql.on_duplicate_key_update(**emoney_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(emoney).where(emoney.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_profile_overpower(
- self, aime_id: int, overpower_data: Dict
- ) -> Optional[int]:
- overpower_data["user"] = aime_id
-
- sql = insert(overpower).values(**overpower_data)
- conflict = sql.on_duplicate_key_update(**overpower_data)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(overpower).where(overpower.c.user == aime_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.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
+
+profile = Table(
+ "chuni_profile_data",
+ 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("exp", Integer),
+ Column("level", Integer),
+ Column("point", Integer),
+ Column("frameId", Integer),
+ Column("isMaimai", Boolean),
+ Column("trophyId", Integer),
+ Column("userName", String(25)),
+ Column("isWebJoin", Boolean),
+ Column("playCount", Integer),
+ Column("lastGameId", String(25)),
+ Column("totalPoint", BigInteger),
+ Column("characterId", Integer),
+ Column("firstGameId", String(25)),
+ Column("friendCount", Integer),
+ Column("lastPlaceId", Integer),
+ Column("nameplateId", Integer),
+ Column("totalMapNum", Integer),
+ Column("lastAllNetId", Integer),
+ Column("lastClientId", String(25)),
+ Column("lastPlayDate", String(25)),
+ Column("lastRegionId", Integer),
+ Column("playerRating", Integer),
+ Column("totalHiScore", Integer),
+ Column("webLimitDate", String(25)),
+ Column("firstPlayDate", String(25)),
+ Column("highestRating", Integer),
+ Column("lastPlaceName", String(25)),
+ Column("multiWinCount", Integer),
+ Column("acceptResCount", Integer),
+ Column("lastRegionName", String(25)),
+ Column("lastRomVersion", String(25)),
+ Column("multiPlayCount", Integer),
+ Column("firstRomVersion", String(25)),
+ Column("lastDataVersion", String(25)),
+ Column("requestResCount", Integer),
+ Column("successResCount", Integer),
+ Column("eventWatchedDate", String(25)),
+ Column("firstDataVersion", String(25)),
+ Column("reincarnationNum", Integer),
+ Column("playedTutorialBit", Integer),
+ Column("totalBasicHighScore", Integer),
+ Column("totalExpertHighScore", Integer),
+ Column("totalMasterHighScore", Integer),
+ Column("totalRepertoireCount", Integer),
+ Column("firstTutorialCancelNum", Integer),
+ Column("totalAdvancedHighScore", Integer),
+ Column("masterTutorialCancelNum", Integer),
+ Column("ext1", Integer), # Added in chunew
+ Column("ext2", Integer),
+ Column("ext3", Integer),
+ Column("ext4", Integer),
+ Column("ext5", Integer),
+ Column("ext6", Integer),
+ Column("ext7", Integer),
+ Column("ext8", Integer),
+ Column("ext9", Integer),
+ Column("ext10", Integer),
+ Column("extStr1", String(255)),
+ Column("extStr2", String(255)),
+ Column("extLong1", Integer),
+ Column("extLong2", Integer),
+ Column("mapIconId", Integer),
+ Column("compatibleCmVersion", String(25)),
+ Column("medal", Integer),
+ Column("voiceId", Integer),
+ Column(
+ "teamId",
+ Integer,
+ ForeignKey("chuni_profile_team.id", ondelete="SET NULL", onupdate="SET NULL"),
+ ),
+ Column("eliteRankPoint", Integer, server_default="0"),
+ Column("stockedGridCount", Integer, server_default="0"),
+ Column("netBattleLoseCount", Integer, server_default="0"),
+ Column("netBattleHostErrCnt", Integer, server_default="0"),
+ Column("netBattle4thCount", Integer, server_default="0"),
+ Column("overPowerRate", Integer, server_default="0"),
+ Column("battleRewardStatus", Integer, server_default="0"),
+ Column("netBattle1stCount", Integer, server_default="0"),
+ Column("charaIllustId", Integer, server_default="0"),
+ Column("userNameEx", String(8), server_default=""),
+ Column("netBattleWinCount", Integer, server_default="0"),
+ Column("netBattleCorrection", Integer, server_default="0"),
+ Column("classEmblemMedal", Integer, server_default="0"),
+ Column("overPowerPoint", Integer, server_default="0"),
+ Column("netBattleErrCnt", Integer, server_default="0"),
+ Column("battleRankId", Integer, server_default="0"),
+ Column("netBattle3rdCount", Integer, server_default="0"),
+ Column("netBattleConsecutiveWinCount", Integer, server_default="0"),
+ Column("overPowerLowerRank", Integer, server_default="0"),
+ Column("classEmblemBase", Integer, server_default="0"),
+ Column("battleRankPoint", Integer, server_default="0"),
+ Column("netBattle2ndCount", Integer, server_default="0"),
+ Column("totalUltimaHighScore", Integer, server_default="0"),
+ Column("skillId", Integer, server_default="0"),
+ Column("lastCountryCode", String(5), server_default="JPN"),
+ Column("isNetBattleHost", Boolean, server_default="0"),
+ Column("battleRewardCount", Integer, server_default="0"),
+ Column("battleRewardIndex", Integer, server_default="0"),
+ Column("netBattlePlayCount", Integer, server_default="0"),
+ Column("exMapLoopCount", Integer, server_default="0"),
+ Column("netBattleEndState", Integer, server_default="0"),
+ Column("rankUpChallengeResults", JSON),
+ Column("avatarBack", Integer, server_default="0"),
+ Column("avatarFace", Integer, server_default="0"),
+ Column("avatarPoint", Integer, server_default="0"),
+ Column("avatarItem", Integer, server_default="0"),
+ Column("avatarWear", Integer, server_default="0"),
+ Column("avatarFront", Integer, server_default="0"),
+ Column("avatarSkin", Integer, server_default="0"),
+ Column("avatarHead", Integer, server_default="0"),
+ UniqueConstraint("user", "version", name="chuni_profile_profile_uk"),
+ mysql_charset="utf8mb4",
+)
+
+profile_ex = Table(
+ "chuni_profile_data_ex",
+ 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("ext1", Integer),
+ Column("ext2", Integer),
+ Column("ext3", Integer),
+ Column("ext4", Integer),
+ Column("ext5", Integer),
+ Column("ext6", Integer),
+ Column("ext7", Integer),
+ Column("ext8", Integer),
+ Column("ext9", Integer),
+ Column("ext10", Integer),
+ Column("ext11", Integer),
+ Column("ext12", Integer),
+ Column("ext13", Integer),
+ Column("ext14", Integer),
+ Column("ext15", Integer),
+ Column("ext16", Integer),
+ Column("ext17", Integer),
+ Column("ext18", Integer),
+ Column("ext19", Integer),
+ Column("ext20", Integer),
+ Column("medal", Integer),
+ Column("extStr1", String(255)),
+ Column("extStr2", String(255)),
+ Column("extStr3", String(255)),
+ Column("extStr4", String(255)),
+ Column("extStr5", String(255)),
+ Column("voiceId", Integer),
+ Column("extLong1", Integer),
+ Column("extLong2", Integer),
+ Column("extLong3", Integer),
+ Column("extLong4", Integer),
+ Column("extLong5", Integer),
+ Column("mapIconId", Integer),
+ Column("compatibleCmVersion", String(25)),
+ UniqueConstraint("user", "version", name="chuni_profile_data_ex_uk"),
+ mysql_charset="utf8mb4",
+)
+
+option = Table(
+ "chuni_profile_option",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("speed", Integer),
+ Column("bgInfo", Integer),
+ Column("rating", Integer),
+ Column("privacy", Integer),
+ Column("judgePos", Integer),
+ Column("matching", Integer),
+ Column("guideLine", Integer),
+ Column("headphone", Integer),
+ Column("optionSet", Integer),
+ Column("fieldColor", Integer),
+ Column("guideSound", Integer),
+ Column("successAir", Integer),
+ Column("successTap", Integer),
+ Column("judgeAttack", Integer),
+ Column("playerLevel", Integer),
+ Column("soundEffect", Integer),
+ Column("judgeJustice", Integer),
+ Column("successExTap", Integer),
+ Column("successFlick", Integer),
+ Column("successSkill", Integer),
+ Column("successSlideHold", Integer),
+ Column("successTapTimbre", Integer),
+ Column("ext1", Integer), # Added in chunew
+ Column("ext2", Integer),
+ Column("ext3", Integer),
+ Column("ext4", Integer),
+ Column("ext5", Integer),
+ Column("ext6", Integer),
+ Column("ext7", Integer),
+ Column("ext8", Integer),
+ Column("ext9", Integer),
+ Column("ext10", Integer),
+ Column("categoryDetail", Integer, server_default="0"),
+ Column("judgeTimingOffset_120", Integer, server_default="0"),
+ Column("resultVoiceShort", Integer, server_default="0"),
+ Column("judgeAppendSe", Integer, server_default="0"),
+ Column("judgeCritical", Integer, server_default="0"),
+ Column("trackSkip", Integer, server_default="0"),
+ Column("selectMusicFilterLv", Integer, server_default="0"),
+ Column("sortMusicFilterLv", Integer, server_default="0"),
+ Column("sortMusicGenre", Integer, server_default="0"),
+ Column("speed_120", Integer, server_default="0"),
+ Column("judgeTimingOffset", Integer, server_default="0"),
+ Column("mirrorFumen", Integer, server_default="0"),
+ Column("playTimingOffset_120", Integer, server_default="0"),
+ Column("hardJudge", Integer, server_default="0"),
+ Column("notesThickness", Integer, server_default="0"),
+ Column("fieldWallPosition", Integer, server_default="0"),
+ Column("playTimingOffset", Integer, server_default="0"),
+ Column("fieldWallPosition_120", Integer, server_default="0"),
+ UniqueConstraint("user", name="chuni_profile_option_uk"),
+ mysql_charset="utf8mb4",
+)
+
+option_ex = Table(
+ "chuni_profile_option_ex",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("ext1", Integer),
+ Column("ext2", Integer),
+ Column("ext3", Integer),
+ Column("ext4", Integer),
+ Column("ext5", Integer),
+ Column("ext6", Integer),
+ Column("ext7", Integer),
+ Column("ext8", Integer),
+ Column("ext9", Integer),
+ Column("ext10", Integer),
+ Column("ext11", Integer),
+ Column("ext12", Integer),
+ Column("ext13", Integer),
+ Column("ext14", Integer),
+ Column("ext15", Integer),
+ Column("ext16", Integer),
+ Column("ext17", Integer),
+ Column("ext18", Integer),
+ Column("ext19", Integer),
+ Column("ext20", Integer),
+ UniqueConstraint("user", name="chuni_profile_option_ex_uk"),
+ mysql_charset="utf8mb4",
+)
+
+recent_rating = Table(
+ "chuni_profile_recent_rating",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("recentRating", JSON),
+ UniqueConstraint("user", name="chuni_profile_recent_rating_uk"),
+ mysql_charset="utf8mb4",
+)
+
+region = Table(
+ "chuni_profile_region",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("regionId", Integer),
+ Column("playCount", Integer),
+ UniqueConstraint("user", "regionId", name="chuni_profile_region_uk"),
+ mysql_charset="utf8mb4",
+)
+
+activity = Table(
+ "chuni_profile_activity",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("kind", Integer),
+ Column(
+ "activityId", Integer
+ ), # Reminder: Change this to ID in base.py or the game will be sad
+ Column("sortNumber", Integer),
+ Column("param1", Integer),
+ Column("param2", Integer),
+ Column("param3", Integer),
+ Column("param4", Integer),
+ UniqueConstraint("user", "kind", "activityId", name="chuni_profile_activity_uk"),
+ mysql_charset="utf8mb4",
+)
+
+charge = Table(
+ "chuni_profile_charge",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("chargeId", Integer),
+ Column("stock", Integer),
+ Column("purchaseDate", String(25)),
+ Column("validDate", String(25)),
+ Column("param1", Integer),
+ Column("param2", Integer),
+ Column("paramDate", String(25)),
+ UniqueConstraint("user", "chargeId", name="chuni_profile_charge_uk"),
+ mysql_charset="utf8mb4",
+)
+
+emoney = Table(
+ "chuni_profile_emoney",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("ext1", Integer),
+ Column("ext2", Integer),
+ Column("ext3", Integer),
+ Column("type", Integer),
+ Column("emoneyBrand", Integer),
+ Column("emoneyCredit", Integer),
+ UniqueConstraint("user", "emoneyBrand", name="chuni_profile_emoney_uk"),
+ mysql_charset="utf8mb4",
+)
+
+overpower = Table(
+ "chuni_profile_overpower",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("genreId", Integer),
+ Column("difficulty", Integer),
+ Column("rate", Integer),
+ Column("point", Integer),
+ UniqueConstraint("user", "genreId", "difficulty", name="chuni_profile_emoney_uk"),
+ mysql_charset="utf8mb4",
+)
+
+team = Table(
+ "chuni_profile_team",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("teamName", String(255)),
+ Column("teamPoint", Integer),
+ mysql_charset="utf8mb4",
+)
+
+
+class ChuniProfileData(BaseData):
+ def put_profile_data(
+ self, aime_id: int, version: int, profile_data: Dict
+ ) -> Optional[int]:
+ profile_data["user"] = aime_id
+ profile_data["version"] = version
+ if "accessCode" in profile_data:
+ profile_data.pop("accessCode")
+
+ profile_data = self.fix_bools(profile_data)
+
+ sql = insert(profile).values(**profile_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**profile_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select([profile, option])
+ .join(option, profile.c.user == option.c.user)
+ .filter(and_(profile.c.user == aime_id, profile.c.version <= version))
+ ).order_by(profile.c.version.desc())
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select(profile)
+ .where(
+ and_(
+ profile.c.user == aime_id,
+ profile.c.version <= version,
+ )
+ )
+ .order_by(profile.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_data_ex(
+ self, aime_id: int, version: int, profile_ex_data: Dict
+ ) -> Optional[int]:
+ profile_ex_data["user"] = aime_id
+ profile_ex_data["version"] = version
+ if "accessCode" in profile_ex_data:
+ profile_ex_data.pop("accessCode")
+
+ sql = insert(profile_ex).values(**profile_ex_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**profile_ex_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_profile_data_ex: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_data_ex(self, aime_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select(profile_ex)
+ .where(
+ and_(
+ profile_ex.c.user == aime_id,
+ profile_ex.c.version <= version,
+ )
+ )
+ .order_by(profile_ex.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_option(self, aime_id: int, option_data: Dict) -> Optional[int]:
+ option_data["user"] = aime_id
+
+ sql = insert(option).values(**option_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**option_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_profile_option: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_option(self, aime_id: int) -> Optional[Row]:
+ sql = select(option).where(option.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_option_ex(
+ self, aime_id: int, option_ex_data: Dict
+ ) -> Optional[int]:
+ option_ex_data["user"] = aime_id
+
+ sql = insert(option_ex).values(**option_ex_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**option_ex_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_profile_option_ex: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_option_ex(self, aime_id: int) -> Optional[Row]:
+ sql = select(option_ex).where(option_ex.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_recent_rating(
+ self, aime_id: int, recent_rating_data: List[Dict]
+ ) -> Optional[int]:
+ sql = insert(recent_rating).values(
+ user=aime_id, recentRating=recent_rating_data
+ )
+ conflict = sql.on_conflict_do_update(set_=dict(recentRating=recent_rating_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_recent_rating: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_recent_rating(self, aime_id: int) -> Optional[Row]:
+ sql = select(recent_rating).where(recent_rating.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_activity(self, aime_id: int, activity_data: Dict) -> Optional[int]:
+ # The game just uses "id" but we need to distinguish that from the db column "id"
+ activity_data["user"] = aime_id
+ activity_data["activityId"] = activity_data["id"]
+ activity_data.pop("id")
+
+ sql = insert(activity).values(**activity_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**activity_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_profile_activity: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_activity(self, aime_id: int, kind: int) -> Optional[List[Row]]:
+ sql = (
+ select(activity)
+ .where(and_(activity.c.user == aime_id, activity.c.kind == kind))
+ .order_by(activity.c.sortNumber.desc()) # to get the last played track
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_profile_charge(self, aime_id: int, charge_data: Dict) -> Optional[int]:
+ charge_data["user"] = aime_id
+
+ sql = insert(charge).values(**charge_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**charge_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_profile_charge: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_charge(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(charge).where(charge.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def add_profile_region(self, aime_id: int, region_id: int) -> Optional[int]:
+ pass
+
+ def get_profile_regions(self, aime_id: int) -> Optional[List[Row]]:
+ pass
+
+ def put_profile_emoney(self, aime_id: int, emoney_data: Dict) -> Optional[int]:
+ emoney_data["user"] = aime_id
+
+ sql = insert(emoney).values(**emoney_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**emoney_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_profile_emoney(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(emoney).where(emoney.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_profile_overpower(
+ self, aime_id: int, overpower_data: Dict
+ ) -> Optional[int]:
+ overpower_data["user"] = aime_id
+
+ sql = insert(overpower).values(**overpower_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**overpower_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_profile_overpower(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(overpower).where(overpower.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
diff --git a/titles/chuni/schema/score.py b/titles/chuni/schema/score.py
index 203aa11..fef6f23 100644
--- a/titles/chuni/schema/score.py
+++ b/titles/chuni/schema/score.py
@@ -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
diff --git a/titles/chuni/schema/static.py b/titles/chuni/schema/static.py
index 85d0397..6777df5 100644
--- a/titles/chuni/schema/static.py
+++ b/titles/chuni/schema/static.py
@@ -1,590 +1,600 @@
-from typing import Dict, List, Optional
-from sqlalchemy import (
- ForeignKeyConstraint,
- 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 datetime import datetime
-
-from core.data.schema import BaseData, metadata
-
-events = Table(
- "chuni_static_events",
- 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", name="chuni_static_events_uk"),
- mysql_charset="utf8mb4",
-)
-
-music = Table(
- "chuni_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("level", Float),
- Column("genre", String(255)),
- Column("jacketPath", String(255)),
- Column("worldsEndTag", String(7)),
- UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"),
- mysql_charset="utf8mb4",
-)
-
-charge = Table(
- "chuni_static_charge",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("version", Integer, nullable=False),
- Column("chargeId", Integer),
- Column("name", String(255)),
- Column("expirationDays", Integer),
- Column("consumeType", Integer),
- Column("sellingAppeal", Boolean),
- Column("enabled", Boolean, server_default="1"),
- UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"),
- mysql_charset="utf8mb4",
-)
-
-avatar = Table(
- "chuni_static_avatar",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("version", Integer, nullable=False),
- Column("avatarAccessoryId", Integer),
- Column("name", String(255)),
- Column("category", Integer),
- Column("iconPath", String(255)),
- Column("texturePath", String(255)),
- UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"),
- mysql_charset="utf8mb4",
-)
-
-gachas = Table(
- "chuni_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("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"),
- UniqueConstraint("version", "gachaId", "gachaName", name="chuni_static_gachas_uk"),
- mysql_charset="utf8mb4",
-)
-
-cards = Table(
- "chuni_static_cards",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("version", Integer, nullable=False),
- Column("cardId", Integer, nullable=False),
- Column("charaName", String(255), nullable=False),
- Column("charaId", Integer, nullable=False),
- Column("presentName", String(255), nullable=False),
- Column("rarity", Integer, server_default="2"),
- Column("labelType", Integer, nullable=False),
- Column("difType", Integer, nullable=False),
- Column("miss", Integer, nullable=False),
- Column("combo", Integer, nullable=False),
- Column("chain", Integer, nullable=False),
- Column("skillName", String(255), nullable=False),
- UniqueConstraint("version", "cardId", name="chuni_static_cards_uk"),
- mysql_charset="utf8mb4",
-)
-
-gacha_cards = Table(
- "chuni_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"),
- UniqueConstraint("gachaId", "cardId", name="chuni_static_gacha_cards_uk"),
- mysql_charset="utf8mb4",
-)
-
-login_bonus_preset = Table(
- "chuni_static_login_bonus_preset",
- metadata,
- Column("presetId", Integer, nullable=False),
- Column("version", Integer, nullable=False),
- Column("presetName", String(255), nullable=False),
- Column("isEnabled", Boolean, server_default="1"),
- PrimaryKeyConstraint(
- "presetId", "version", name="chuni_static_login_bonus_preset_pk"
- ),
- mysql_charset="utf8mb4",
-)
-
-login_bonus = Table(
- "chuni_static_login_bonus",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("version", Integer, nullable=False),
- Column("presetId", Integer, nullable=False),
- Column("loginBonusId", Integer, nullable=False),
- Column("loginBonusName", String(255), nullable=False),
- Column("presentId", Integer, nullable=False),
- Column("presentName", String(255), nullable=False),
- Column("itemNum", Integer, nullable=False),
- Column("needLoginDayCount", Integer, nullable=False),
- Column("loginBonusCategoryType", Integer, nullable=False),
- UniqueConstraint(
- "version", "presetId", "loginBonusId", name="chuni_static_login_bonus_uk"
- ),
- ForeignKeyConstraint(
- ["presetId", "version"],
- [
- "chuni_static_login_bonus_preset.presetId",
- "chuni_static_login_bonus_preset.version",
- ],
- onupdate="CASCADE",
- ondelete="CASCADE",
- name="chuni_static_login_bonus_ibfk_1",
- ),
- mysql_charset="utf8mb4",
-)
-
-
-class ChuniStaticData(BaseData):
- def put_login_bonus(
- self,
- version: int,
- preset_id: int,
- login_bonus_id: int,
- login_bonus_name: str,
- present_id: int,
- present_ame: str,
- item_num: int,
- need_login_day_count: int,
- login_bonus_category_type: int,
- ) -> Optional[int]:
- sql = insert(login_bonus).values(
- version=version,
- presetId=preset_id,
- loginBonusId=login_bonus_id,
- loginBonusName=login_bonus_name,
- presentId=present_id,
- presentName=present_ame,
- itemNum=item_num,
- needLoginDayCount=need_login_day_count,
- loginBonusCategoryType=login_bonus_category_type,
- )
-
- conflict = sql.on_duplicate_key_update(
- loginBonusName=login_bonus_name,
- presentName=present_ame,
- itemNum=item_num,
- needLoginDayCount=need_login_day_count,
- loginBonusCategoryType=login_bonus_category_type,
- )
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_login_bonus(
- self,
- version: int,
- preset_id: int,
- ) -> Optional[List[Row]]:
- sql = login_bonus.select(
- and_(
- login_bonus.c.version == version,
- login_bonus.c.presetId == preset_id,
- )
- ).order_by(login_bonus.c.needLoginDayCount.desc())
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_login_bonus_by_required_days(
- self, version: int, preset_id: int, need_login_day_count: int
- ) -> Optional[Row]:
- sql = login_bonus.select(
- and_(
- login_bonus.c.version == version,
- login_bonus.c.presetId == preset_id,
- login_bonus.c.needLoginDayCount == need_login_day_count,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_login_bonus_preset(
- self, version: int, preset_id: int, preset_name: str, is_enabled: bool
- ) -> Optional[int]:
- sql = insert(login_bonus_preset).values(
- presetId=preset_id,
- version=version,
- presetName=preset_name,
- isEnabled=is_enabled,
- )
-
- conflict = sql.on_duplicate_key_update(
- presetName=preset_name, isEnabled=is_enabled
- )
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_login_bonus_presets(
- self, version: int, is_enabled: bool = True
- ) -> Optional[List[Row]]:
- sql = login_bonus_preset.select(
- and_(
- login_bonus_preset.c.version == version,
- login_bonus_preset.c.isEnabled == is_enabled,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_event(
- self, version: int, event_id: int, type: int, name: str
- ) -> Optional[int]:
- sql = insert(events).values(
- version=version, eventId=event_id, type=type, name=name
- )
-
- conflict = sql.on_duplicate_key_update(name=name)
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def update_event(
- self, version: int, event_id: int, enabled: bool
- ) -> Optional[bool]:
- sql = events.update(
- and_(events.c.version == version, events.c.eventId == event_id)
- ).values(enabled=enabled)
-
- result = self.execute(sql)
- if result is None:
- self.logger.warn(
- f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
- )
- return None
-
- event = self.get_event(version, event_id)
- if event is None:
- self.logger.warn(
- f"update_event: failed to fetch event {event_id} after updating"
- )
- return None
- return event["enabled"]
-
- def get_event(self, version: int, event_id: int) -> Optional[Row]:
- 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.fetchone()
-
- def get_enabled_events(self, version: int) -> Optional[List[Row]]:
- 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 get_events(self, version: int) -> Optional[List[Row]]:
- sql = select(events).where(events.c.version == version)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_music(
- self,
- version: int,
- song_id: int,
- chart_id: int,
- title: int,
- artist: str,
- level: float,
- genre: str,
- jacketPath: str,
- we_tag: str,
- ) -> Optional[int]:
- sql = insert(music).values(
- version=version,
- songId=song_id,
- chartId=chart_id,
- title=title,
- artist=artist,
- level=level,
- genre=genre,
- jacketPath=jacketPath,
- worldsEndTag=we_tag,
- )
-
- conflict = sql.on_duplicate_key_update(
- title=title,
- artist=artist,
- level=level,
- genre=genre,
- jacketPath=jacketPath,
- worldsEndTag=we_tag,
- )
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def put_charge(
- self,
- version: int,
- charge_id: int,
- name: str,
- expiration_days: int,
- consume_type: int,
- selling_appeal: bool,
- ) -> Optional[int]:
- sql = insert(charge).values(
- version=version,
- chargeId=charge_id,
- name=name,
- expirationDays=expiration_days,
- consumeType=consume_type,
- sellingAppeal=selling_appeal,
- )
-
- conflict = sql.on_duplicate_key_update(
- name=name,
- expirationDays=expiration_days,
- consumeType=consume_type,
- sellingAppeal=selling_appeal,
- )
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def get_enabled_charges(self, version: int) -> Optional[List[Row]]:
- sql = select(charge).where(
- and_(charge.c.version == version, charge.c.enabled == True)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_charges(self, version: int) -> Optional[List[Row]]:
- sql = select(charge).where(charge.c.version == version)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_music(self, version: int) -> Optional[List[Row]]:
- sql = music.select(music.c.version <= version)
-
- 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_avatar(
- self,
- version: int,
- avatarAccessoryId: int,
- name: str,
- category: int,
- iconPath: str,
- texturePath: str,
- ) -> Optional[int]:
- sql = insert(avatar).values(
- version=version,
- avatarAccessoryId=avatarAccessoryId,
- name=name,
- category=category,
- iconPath=iconPath,
- texturePath=texturePath,
- )
-
- conflict = sql.on_duplicate_key_update(
- name=name,
- category=category,
- iconPath=iconPath,
- texturePath=texturePath,
- )
-
- result = self.execute(conflict)
- if result is None:
- return None
- return result.lastrowid
-
- def put_gacha(
- self,
- version: int,
- gacha_id: int,
- gacha_name: int,
- **gacha_data,
- ) -> Optional[int]:
- sql = insert(gachas).values(
- version=version,
- gachaId=gacha_id,
- gachaName=gacha_name,
- **gacha_data,
- )
-
- conflict = sql.on_duplicate_key_update(
- version=version,
- gachaId=gacha_id,
- gachaName=gacha_name,
- **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_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 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 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 get_gacha_card_by_character(
- self, gacha_id: int, chara_id: int
- ) -> Optional[Dict]:
- sql_sub = (
- select(cards.c.cardId).filter(cards.c.charaId == chara_id).scalar_subquery()
- )
-
- # Perform the main query, also rename the resulting column to ranking
- sql = gacha_cards.select(
- and_(gacha_cards.c.gachaId == gacha_id, gacha_cards.c.cardId == sql_sub)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- 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()
+from typing import Dict, List, Optional
+from sqlalchemy import (
+ ForeignKeyConstraint,
+ 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 datetime import datetime
+
+from core.data.schema import BaseData, metadata
+
+events = Table(
+ "chuni_static_events",
+ 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", name="chuni_static_events_uk"),
+ mysql_charset="utf8mb4",
+)
+
+music = Table(
+ "chuni_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("level", Float),
+ Column("genre", String(255)),
+ Column("jacketPath", String(255)),
+ Column("worldsEndTag", String(7)),
+ UniqueConstraint("version", "songId", "chartId", name="chuni_static_music_uk"),
+ mysql_charset="utf8mb4",
+)
+
+charge = Table(
+ "chuni_static_charge",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("version", Integer, nullable=False),
+ Column("chargeId", Integer),
+ Column("name", String(255)),
+ Column("expirationDays", Integer),
+ Column("consumeType", Integer),
+ Column("sellingAppeal", Boolean),
+ Column("enabled", Boolean, server_default="1"),
+ UniqueConstraint("version", "chargeId", name="chuni_static_charge_uk"),
+ mysql_charset="utf8mb4",
+)
+
+avatar = Table(
+ "chuni_static_avatar",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("version", Integer, nullable=False),
+ Column("avatarAccessoryId", Integer),
+ Column("name", String(255)),
+ Column("category", Integer),
+ Column("iconPath", String(255)),
+ Column("texturePath", String(255)),
+ UniqueConstraint("version", "avatarAccessoryId", name="chuni_static_avatar_uk"),
+ mysql_charset="utf8mb4",
+)
+
+gachas = Table(
+ "chuni_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("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"),
+ UniqueConstraint("version", "gachaId", "gachaName", name="chuni_static_gachas_uk"),
+ mysql_charset="utf8mb4",
+)
+
+cards = Table(
+ "chuni_static_cards",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("version", Integer, nullable=False),
+ Column("cardId", Integer, nullable=False),
+ Column("charaName", String(255), nullable=False),
+ Column("charaId", Integer, nullable=False),
+ Column("presentName", String(255), nullable=False),
+ Column("rarity", Integer, server_default="2"),
+ Column("labelType", Integer, nullable=False),
+ Column("difType", Integer, nullable=False),
+ Column("miss", Integer, nullable=False),
+ Column("combo", Integer, nullable=False),
+ Column("chain", Integer, nullable=False),
+ Column("skillName", String(255), nullable=False),
+ UniqueConstraint("version", "cardId", name="chuni_static_cards_uk"),
+ mysql_charset="utf8mb4",
+)
+
+gacha_cards = Table(
+ "chuni_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"),
+ UniqueConstraint("gachaId", "cardId", name="chuni_static_gacha_cards_uk"),
+ mysql_charset="utf8mb4",
+)
+
+login_bonus_preset = Table(
+ "chuni_static_login_bonus_preset",
+ metadata,
+ Column("presetId", Integer, nullable=False),
+ Column("version", Integer, nullable=False),
+ Column("presetName", String(255), nullable=False),
+ Column("isEnabled", Boolean, server_default="1"),
+ PrimaryKeyConstraint(
+ "presetId", "version", name="chuni_static_login_bonus_preset_pk"
+ ),
+ mysql_charset="utf8mb4",
+)
+
+login_bonus = Table(
+ "chuni_static_login_bonus",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("version", Integer, nullable=False),
+ Column("presetId", Integer, nullable=False),
+ Column("loginBonusId", Integer, nullable=False),
+ Column("loginBonusName", String(255), nullable=False),
+ Column("presentId", Integer, nullable=False),
+ Column("presentName", String(255), nullable=False),
+ Column("itemNum", Integer, nullable=False),
+ Column("needLoginDayCount", Integer, nullable=False),
+ Column("loginBonusCategoryType", Integer, nullable=False),
+ UniqueConstraint(
+ "version", "presetId", "loginBonusId", name="chuni_static_login_bonus_uk"
+ ),
+ ForeignKeyConstraint(
+ ["presetId", "version"],
+ [
+ "chuni_static_login_bonus_preset.presetId",
+ "chuni_static_login_bonus_preset.version",
+ ],
+ onupdate="CASCADE",
+ ondelete="CASCADE",
+ name="chuni_static_login_bonus_ibfk_1",
+ ),
+ mysql_charset="utf8mb4",
+)
+
+
+class ChuniStaticData(BaseData):
+ def put_login_bonus(
+ self,
+ version: int,
+ preset_id: int,
+ login_bonus_id: int,
+ login_bonus_name: str,
+ present_id: int,
+ present_ame: str,
+ item_num: int,
+ need_login_day_count: int,
+ login_bonus_category_type: int,
+ ) -> Optional[int]:
+ sql = insert(login_bonus).values(
+ version=version,
+ presetId=preset_id,
+ loginBonusId=login_bonus_id,
+ loginBonusName=login_bonus_name,
+ presentId=present_id,
+ presentName=present_ame,
+ itemNum=item_num,
+ needLoginDayCount=need_login_day_count,
+ loginBonusCategoryType=login_bonus_category_type,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ loginBonusName=login_bonus_name,
+ presentName=present_ame,
+ itemNum=item_num,
+ needLoginDayCount=need_login_day_count,
+ loginBonusCategoryType=login_bonus_category_type,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_login_bonus(
+ self,
+ version: int,
+ preset_id: int,
+ ) -> Optional[List[Row]]:
+ sql = login_bonus.select(
+ and_(
+ login_bonus.c.version == version,
+ login_bonus.c.presetId == preset_id,
+ )
+ ).order_by(login_bonus.c.needLoginDayCount.desc())
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_login_bonus_by_required_days(
+ self, version: int, preset_id: int, need_login_day_count: int
+ ) -> Optional[Row]:
+ sql = login_bonus.select(
+ and_(
+ login_bonus.c.version == version,
+ login_bonus.c.presetId == preset_id,
+ login_bonus.c.needLoginDayCount == need_login_day_count,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_login_bonus_preset(
+ self, version: int, preset_id: int, preset_name: str, is_enabled: bool
+ ) -> Optional[int]:
+ sql = insert(login_bonus_preset).values(
+ presetId=preset_id,
+ version=version,
+ presetName=preset_name,
+ isEnabled=is_enabled,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(presetName=preset_name, isEnabled=is_enabled)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_login_bonus_presets(
+ self, version: int, is_enabled: bool = True
+ ) -> Optional[List[Row]]:
+ sql = login_bonus_preset.select(
+ and_(
+ login_bonus_preset.c.version == version,
+ login_bonus_preset.c.isEnabled == is_enabled,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_event(
+ self, version: int, event_id: int, type: int, name: str
+ ) -> Optional[int]:
+ sql = insert(events).values(
+ version=version, eventId=event_id, type=type, name=name
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(name=name))
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def update_event(
+ self, version: int, event_id: int, enabled: bool
+ ) -> Optional[bool]:
+ sql = events.update(
+ and_(events.c.version == version, events.c.eventId == event_id)
+ ).values(enabled=enabled)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.warn(
+ f"update_event: failed to update event! version: {version}, event_id: {event_id}, enabled: {enabled}"
+ )
+ return None
+
+ event = self.get_event(version, event_id)
+ if event is None:
+ self.logger.warn(
+ f"update_event: failed to fetch event {event_id} after updating"
+ )
+ return None
+ return event["enabled"]
+
+ def get_event(self, version: int, event_id: int) -> Optional[Row]:
+ 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.fetchone()
+
+ def get_enabled_events(self, version: int) -> Optional[List[Row]]:
+ 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 get_events(self, version: int) -> Optional[List[Row]]:
+ sql = select(events).where(events.c.version == version)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_music(
+ self,
+ version: int,
+ song_id: int,
+ chart_id: int,
+ title: int,
+ artist: str,
+ level: float,
+ genre: str,
+ jacketPath: str,
+ we_tag: str,
+ ) -> Optional[int]:
+ sql = insert(music).values(
+ version=version,
+ songId=song_id,
+ chartId=chart_id,
+ title=title,
+ artist=artist,
+ level=level,
+ genre=genre,
+ jacketPath=jacketPath,
+ worldsEndTag=we_tag,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ title=title,
+ artist=artist,
+ level=level,
+ genre=genre,
+ jacketPath=jacketPath,
+ worldsEndTag=we_tag,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def put_charge(
+ self,
+ version: int,
+ charge_id: int,
+ name: str,
+ expiration_days: int,
+ consume_type: int,
+ selling_appeal: bool,
+ ) -> Optional[int]:
+ sql = insert(charge).values(
+ version=version,
+ chargeId=charge_id,
+ name=name,
+ expirationDays=expiration_days,
+ consumeType=consume_type,
+ sellingAppeal=selling_appeal,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ name=name,
+ expirationDays=expiration_days,
+ consumeType=consume_type,
+ sellingAppeal=selling_appeal,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def get_enabled_charges(self, version: int) -> Optional[List[Row]]:
+ sql = select(charge).where(
+ and_(charge.c.version == version, charge.c.enabled == True)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_charges(self, version: int) -> Optional[List[Row]]:
+ sql = select(charge).where(charge.c.version == version)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_music(self, version: int) -> Optional[List[Row]]:
+ sql = music.select(music.c.version <= version)
+
+ 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_avatar(
+ self,
+ version: int,
+ avatarAccessoryId: int,
+ name: str,
+ category: int,
+ iconPath: str,
+ texturePath: str,
+ ) -> Optional[int]:
+ sql = insert(avatar).values(
+ version=version,
+ avatarAccessoryId=avatarAccessoryId,
+ name=name,
+ category=category,
+ iconPath=iconPath,
+ texturePath=texturePath,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ name=name,
+ category=category,
+ iconPath=iconPath,
+ texturePath=texturePath,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ return None
+ return result.lastrowid
+
+ def put_gacha(
+ self,
+ version: int,
+ gacha_id: int,
+ gacha_name: int,
+ **gacha_data,
+ ) -> Optional[int]:
+ sql = insert(gachas).values(
+ version=version,
+ gachaId=gacha_id,
+ gachaName=gacha_name,
+ **gacha_data,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ version=version,
+ gachaId=gacha_id,
+ gachaName=gacha_name,
+ **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_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 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 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 get_gacha_card_by_character(
+ self, gacha_id: int, chara_id: int
+ ) -> Optional[Dict]:
+ sql_sub = (
+ select(cards.c.cardId).filter(cards.c.charaId == chara_id).scalar_subquery()
+ )
+
+ # Perform the main query, also rename the resulting column to ranking
+ sql = gacha_cards.select(
+ and_(gacha_cards.c.gachaId == gacha_id, gacha_cards.c.cardId == sql_sub)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ 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()
diff --git a/titles/chuni/sun.py b/titles/chuni/sun.py
index b56fa29..b5c21e0 100644
--- a/titles/chuni/sun.py
+++ b/titles/chuni/sun.py
@@ -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/"
diff --git a/titles/cm/index.py b/titles/cm/index.py
index 348ec4f..d0df73c 100644
--- a/titles/cm/index.py
+++ b/titles/cm/index.py
@@ -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")
diff --git a/titles/cm/read.py b/titles/cm/read.py
index a26f35e..d176198 100644
--- a/titles/cm/read.py
+++ b/titles/cm/read.py
@@ -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}...")
diff --git a/titles/cm/schema/__init__.py b/titles/cm/schema/__init__.py
index a9a2c5b..3ba0f1a 100644
--- a/titles/cm/schema/__init__.py
+++ b/titles/cm/schema/__init__.py
@@ -1 +1 @@
-__all__ = []
+__all__ = []
diff --git a/titles/cxb/base.py b/titles/cxb/base.py
index 89e9cc3..519c151 100644
--- a/titles/cxb/base.py
+++ b/titles/cxb/base.py
@@ -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": ""}
\ No newline at end of file
+ return {"stampreq": ""}
diff --git a/titles/cxb/read.py b/titles/cxb/read.py
index 06a171f..bfdcd13 100644
--- a/titles/cxb/read.py
+++ b/titles/cxb/read.py
@@ -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}")
diff --git a/titles/cxb/schema/__init__.py b/titles/cxb/schema/__init__.py
index ce70412..7fc65eb 100644
--- a/titles/cxb/schema/__init__.py
+++ b/titles/cxb/schema/__init__.py
@@ -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]
diff --git a/titles/cxb/schema/item.py b/titles/cxb/schema/item.py
index 022a036..b982f13 100644
--- a/titles/cxb/schema/item.py
+++ b/titles/cxb/schema/item.py
@@ -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()
diff --git a/titles/cxb/schema/profile.py b/titles/cxb/schema/profile.py
index 5c62f76..8f5e64b 100644
--- a/titles/cxb/schema/profile.py
+++ b/titles/cxb/schema/profile.py
@@ -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()
diff --git a/titles/cxb/schema/score.py b/titles/cxb/schema/score.py
index b6f4f16..11d0bd8 100644
--- a/titles/cxb/schema/score.py
+++ b/titles/cxb/schema/score.py
@@ -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()
diff --git a/titles/cxb/schema/static.py b/titles/cxb/schema/static.py
index 6459e99..df1573a 100644
--- a/titles/cxb/schema/static.py
+++ b/titles/cxb/schema/static.py
@@ -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()
diff --git a/titles/diva/base.py b/titles/diva/base.py
index d7303e7..e8f16e3 100644
--- a/titles/diva/base.py
+++ b/titles/diva/base.py
@@ -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
diff --git a/titles/diva/read.py b/titles/diva/read.py
index 2eeacdc..802273c 100644
--- a/titles/diva/read.py
+++ b/titles/diva/read.py
@@ -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}")
diff --git a/titles/diva/schema/__init__.py b/titles/diva/schema/__init__.py
index e149e6d..6fb45b2 100644
--- a/titles/diva/schema/__init__.py
+++ b/titles/diva/schema/__init__.py
@@ -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,
+]
diff --git a/titles/diva/schema/customize.py b/titles/diva/schema/customize.py
index 91480f5..e08538d 100644
--- a/titles/diva/schema/customize.py
+++ b/titles/diva/schema/customize.py
@@ -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:])
diff --git a/titles/diva/schema/item.py b/titles/diva/schema/item.py
index 4d484ae..d12eec7 100644
--- a/titles/diva/schema/item.py
+++ b/titles/diva/schema/item.py
@@ -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()
diff --git a/titles/diva/schema/module.py b/titles/diva/schema/module.py
index 5872d68..170e870 100644
--- a/titles/diva/schema/module.py
+++ b/titles/diva/schema/module.py
@@ -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:])
diff --git a/titles/diva/schema/profile.py b/titles/diva/schema/profile.py
index 7107068..cd14f59 100644
--- a/titles/diva/schema/profile.py
+++ b/titles/diva/schema/profile.py
@@ -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()
diff --git a/titles/diva/schema/pv_customize.py b/titles/diva/schema/pv_customize.py
index 1ca8909..718dc2e 100644
--- a/titles/diva/schema/pv_customize.py
+++ b/titles/diva/schema/pv_customize.py
@@ -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()
diff --git a/titles/diva/schema/score.py b/titles/diva/schema/score.py
index 2171659..e2b5a51 100644
--- a/titles/diva/schema/score.py
+++ b/titles/diva/schema/score.py
@@ -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()
diff --git a/titles/diva/schema/static.py b/titles/diva/schema/static.py
index 02ee0ec..b224314 100644
--- a/titles/diva/schema/static.py
+++ b/titles/diva/schema/static.py
@@ -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()
diff --git a/titles/idz/userdb.py b/titles/idz/userdb.py
index 2ac765e..09a4103 100644
--- a/titles/idz/userdb.py
+++ b/titles/idz/userdb.py
@@ -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(" 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"}
diff --git a/titles/mai2/config.py b/titles/mai2/config.py
index d63c0b2..2df9421 100644
--- a/titles/mai2/config.py
+++ b/titles/mai2/config.py
@@ -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(
diff --git a/titles/mai2/const.py b/titles/mai2/const.py
index 6e9a070..7f7188c 100644
--- a/titles/mai2/const.py
+++ b/titles/mai2/const.py
@@ -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",
diff --git a/titles/mai2/dx.py b/titles/mai2/dx.py
index 7815e82..821fefa 100644
--- a/titles/mai2/dx.py
+++ b/titles/mai2/dx.py
@@ -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
diff --git a/titles/mai2/finale.py b/titles/mai2/finale.py
index e29196f..194bfdf 100644
--- a/titles/mai2/finale.py
+++ b/titles/mai2/finale.py
@@ -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/"
diff --git a/titles/mai2/index.py b/titles/mai2/index.py
index 9652fc8..eed4d34 100644
--- a/titles/mai2/index.py
+++ b/titles/mai2/index.py
@@ -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"{}")
diff --git a/titles/mai2/read.py b/titles/mai2/read.py
index 4ff401d..1971e5f 100644
--- a/titles/mai2/read.py
+++ b/titles/mai2/read.py
@@ -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
diff --git a/titles/mai2/schema/__init__.py b/titles/mai2/schema/__init__.py
index 7a8c060..0892200 100644
--- a/titles/mai2/schema/__init__.py
+++ b/titles/mai2/schema/__init__.py
@@ -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]
diff --git a/titles/mai2/schema/item.py b/titles/mai2/schema/item.py
index 284365f..e9c46ad 100644
--- a/titles/mai2/schema/item.py
+++ b/titles/mai2/schema/item.py
@@ -1,548 +1,558 @@
-from core.data.schema import BaseData, metadata
-
-from datetime import datetime
-from typing import Optional, Dict, List
-from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
-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 sqlalchemy.engine import Row
-
-character = Table(
- "mai2_item_character",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("characterId", Integer),
- Column("level", Integer),
- Column("awakening", Integer),
- Column("useCount", Integer),
- Column("point", Integer),
- UniqueConstraint("user", "characterId", name="mai2_item_character_uk"),
- mysql_charset="utf8mb4",
-)
-
-card = Table(
- "mai2_item_card",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("cardId", Integer),
- Column("cardTypeId", Integer),
- Column("charaId", Integer),
- Column("mapId", Integer),
- Column("startDate", TIMESTAMP, server_default=func.now()),
- Column("endDate", TIMESTAMP),
- UniqueConstraint("user", "cardId", "cardTypeId", name="mai2_item_card_uk"),
- mysql_charset="utf8mb4",
-)
-
-item = Table(
- "mai2_item_item",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("itemId", Integer),
- Column("itemKind", Integer),
- Column("stock", Integer),
- Column("isValid", Boolean),
- UniqueConstraint("user", "itemId", "itemKind", name="mai2_item_item_uk"),
- mysql_charset="utf8mb4",
-)
-
-map = Table(
- "mai2_item_map",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("mapId", Integer),
- Column("distance", Integer),
- Column("isLock", Boolean),
- Column("isClear", Boolean),
- Column("isComplete", Boolean),
- UniqueConstraint("user", "mapId", name="mai2_item_map_uk"),
- mysql_charset="utf8mb4",
-)
-
-login_bonus = Table(
- "mai2_item_login_bonus",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("bonusId", Integer),
- Column("point", Integer),
- Column("isCurrent", Boolean),
- Column("isComplete", Boolean),
- UniqueConstraint("user", "bonusId", name="mai2_item_login_bonus_uk"),
- mysql_charset="utf8mb4",
-)
-
-friend_season_ranking = Table(
- "mai2_item_friend_season_ranking",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("seasonId", Integer),
- Column("point", Integer),
- Column("rank", Integer),
- Column("rewardGet", Boolean),
- Column("userName", String(8)),
- Column("recordDate", TIMESTAMP),
- UniqueConstraint(
- "user", "seasonId", "userName", name="mai2_item_friend_season_ranking_uk"
- ),
- mysql_charset="utf8mb4",
-)
-
-favorite = Table(
- "mai2_item_favorite",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("itemKind", Integer),
- Column("itemIdList", JSON),
- UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"),
- mysql_charset="utf8mb4",
-)
-
-charge = Table(
- "mai2_item_charge",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("chargeId", Integer),
- Column("stock", Integer),
- Column("purchaseDate", String(255)),
- Column("validDate", String(255)),
- UniqueConstraint("user", "chargeId", name="mai2_item_charge_uk"),
- mysql_charset="utf8mb4",
-)
-
-print_detail = Table(
- "mai2_item_print_detail",
- 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("printNumber", Integer),
- Column("printDate", TIMESTAMP, server_default=func.now()),
- Column("serialId", String(20)),
- Column("placeId", Integer),
- Column("clientId", String(11)),
- Column("printerSerialId", String(20)),
- Column("cardRomVersion", Integer),
- Column("isHolograph", Boolean, server_default="1"),
- Column("printOption1", Boolean, server_default="0"),
- Column("printOption2", Boolean, server_default="0"),
- Column("printOption3", Boolean, server_default="0"),
- Column("printOption4", Boolean, server_default="0"),
- Column("printOption5", Boolean, server_default="0"),
- Column("printOption6", Boolean, server_default="0"),
- Column("printOption7", Boolean, server_default="0"),
- Column("printOption8", Boolean, server_default="0"),
- Column("printOption9", Boolean, server_default="0"),
- Column("printOption10", Boolean, server_default="0"),
- Column("created", String(255), server_default=""),
- UniqueConstraint("user", "serialId", name="mai2_item_print_detail_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-class Mai2ItemData(BaseData):
- def put_item(
- self, user_id: int, item_kind: int, item_id: int, stock: int, is_valid: bool
- ) -> None:
- sql = insert(item).values(
- user=user_id,
- itemKind=item_kind,
- itemId=item_id,
- stock=stock,
- isValid=is_valid,
- )
-
- conflict = sql.on_duplicate_key_update(
- stock=stock,
- isValid=is_valid,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}"
- )
- return None
- return result.lastrowid
-
- def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]:
- if item_kind is None:
- sql = item.select(item.c.user == user_id)
- else:
- sql = item.select(
- and_(item.c.user == user_id, item.c.itemKind == item_kind)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_item(self, user_id: int, item_kind: int, item_id: int) -> Optional[Row]:
- sql = item.select(
- and_(
- item.c.user == user_id,
- item.c.itemKind == item_kind,
- item.c.itemId == item_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_login_bonus(
- self,
- user_id: int,
- bonus_id: int,
- point: int,
- is_current: bool,
- is_complete: bool,
- ) -> None:
- sql = insert(login_bonus).values(
- user=user_id,
- bonusId=bonus_id,
- point=point,
- isCurrent=is_current,
- isComplete=is_complete,
- )
-
- conflict = sql.on_duplicate_key_update(
- point=point,
- isCurrent=is_current,
- isComplete=is_complete,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}"
- )
- return None
- return result.lastrowid
-
- def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]:
- sql = login_bonus.select(login_bonus.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_login_bonus(self, user_id: int, bonus_id: int) -> Optional[Row]:
- sql = login_bonus.select(
- and_(login_bonus.c.user == user_id, login_bonus.c.bonus_id == bonus_id)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_map(
- self,
- user_id: int,
- map_id: int,
- distance: int,
- is_lock: bool,
- is_clear: bool,
- is_complete: bool,
- ) -> None:
- sql = insert(map).values(
- user=user_id,
- mapId=map_id,
- distance=distance,
- isLock=is_lock,
- isClear=is_clear,
- isComplete=is_complete,
- )
-
- conflict = sql.on_duplicate_key_update(
- distance=distance,
- isLock=is_lock,
- isClear=is_clear,
- isComplete=is_complete,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}"
- )
- return None
- return result.lastrowid
-
- def get_maps(self, user_id: int) -> Optional[List[Row]]:
- sql = map.select(map.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_map(self, user_id: int, map_id: int) -> Optional[Row]:
- sql = map.select(and_(map.c.user == user_id, map.c.mapId == map_id))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_character_(self, user_id: int, char_data: Dict) -> Optional[int]:
- char_data["user"] = user_id
- sql = insert(character).values(**char_data)
-
- conflict = sql.on_duplicate_key_update(**char_data)
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_character_: failed to insert item! user_id: {user_id}"
- )
- return None
- return result.lastrowid
-
- def put_character(
- self,
- user_id: int,
- character_id: int,
- level: int,
- awakening: int,
- use_count: int,
- ) -> None:
- sql = insert(character).values(
- user=user_id,
- characterId=character_id,
- level=level,
- awakening=awakening,
- useCount=use_count,
- )
-
- conflict = sql.on_duplicate_key_update(
- level=level,
- awakening=awakening,
- useCount=use_count,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}"
- )
- return None
- return result.lastrowid
-
- def get_characters(self, user_id: int) -> Optional[List[Row]]:
- sql = character.select(character.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_character(self, user_id: int, character_id: int) -> Optional[Row]:
- sql = character.select(
- and_(character.c.user == user_id, character.c.character_id == character_id)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_friend_season_ranking(self, user_id: int) -> Optional[Row]:
- sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_friend_season_ranking(
- self, aime_id: int, friend_season_ranking_data: Dict
- ) -> Optional[int]:
- sql = insert(friend_season_ranking).values(
- user=aime_id, **friend_season_ranking_data
- )
-
- conflict = sql.on_duplicate_key_update(**friend_season_ranking_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_friend_season_ranking: failed to insert",
- f"friend_season_ranking! aime_id: {aime_id}",
- )
- return None
- return result.lastrowid
-
- def put_favorite(
- self, user_id: int, kind: int, item_id_list: List[int]
- ) -> Optional[int]:
- sql = insert(favorite).values(
- user=user_id, kind=kind, item_id_list=item_id_list
- )
-
- conflict = sql.on_duplicate_key_update(item_id_list=item_id_list)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}"
- )
- return None
- return result.lastrowid
-
- def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]:
- if kind is None:
- sql = favorite.select(favorite.c.user == user_id)
- else:
- sql = favorite.select(
- and_(favorite.c.user == user_id, favorite.c.itemKind == kind)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_card(
- self,
- user_id: int,
- card_type_id: int,
- card_kind: int,
- chara_id: int,
- map_id: int,
- start_date: datetime,
- end_date: datetime,
- ) -> Optional[Row]:
- sql = insert(card).values(
- user=user_id,
- cardId=card_type_id,
- cardTypeId=card_kind,
- charaId=chara_id,
- mapId=map_id,
- startDate=start_date,
- endDate=end_date,
- )
-
- conflict = sql.on_duplicate_key_update(
- charaId=chara_id, mapId=map_id, startDate=start_date, endDate=end_date
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_card: failed to insert card! user_id: {user_id}, kind: {card_kind}"
- )
- return None
- return result.lastrowid
-
- def get_cards(self, user_id: int, kind: int = None) -> Optional[Row]:
- if kind is None:
- sql = card.select(card.c.user == user_id)
- else:
- sql = card.select(and_(card.c.user == user_id, card.c.cardKind == kind))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_charge(
- self,
- user_id: int,
- charge_id: int,
- stock: int,
- purchase_date: datetime,
- valid_date: datetime,
- ) -> Optional[Row]:
- sql = insert(charge).values(
- user=user_id,
- chargeId=charge_id,
- stock=stock,
- purchaseDate=purchase_date,
- validDate=valid_date,
- )
-
- conflict = sql.on_duplicate_key_update(
- stock=stock, purchaseDate=purchase_date, validDate=valid_date
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_card: failed to insert charge! user_id: {user_id}, chargeId: {charge_id}"
- )
- return None
- return result.lastrowid
-
- def get_charges(self, user_id: int) -> Optional[Row]:
- sql = charge.select(charge.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_user_print_detail(
- self, aime_id: int, serial_id: str, user_print_data: Dict
- ) -> Optional[int]:
- sql = insert(print_detail).values(
- user=aime_id, serialId=serial_id, **user_print_data
- )
-
- conflict = sql.on_duplicate_key_update(**user_print_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
+from core.data.schema import BaseData, metadata
+
+from datetime import datetime
+from typing import Optional, Dict, List
+from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
+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 sqlalchemy.engine import Row
+
+character = Table(
+ "mai2_item_character",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("characterId", Integer),
+ Column("level", Integer),
+ Column("awakening", Integer),
+ Column("useCount", Integer),
+ Column("point", Integer),
+ UniqueConstraint("user", "characterId", name="mai2_item_character_uk"),
+ mysql_charset="utf8mb4",
+)
+
+card = Table(
+ "mai2_item_card",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("cardId", Integer),
+ Column("cardTypeId", Integer),
+ Column("charaId", Integer),
+ Column("mapId", Integer),
+ Column("startDate", TIMESTAMP, server_default=func.now()),
+ Column("endDate", TIMESTAMP),
+ UniqueConstraint("user", "cardId", "cardTypeId", name="mai2_item_card_uk"),
+ mysql_charset="utf8mb4",
+)
+
+item = Table(
+ "mai2_item_item",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("itemId", Integer),
+ Column("itemKind", Integer),
+ Column("stock", Integer),
+ Column("isValid", Boolean),
+ UniqueConstraint("user", "itemId", "itemKind", name="mai2_item_item_uk"),
+ mysql_charset="utf8mb4",
+)
+
+map = Table(
+ "mai2_item_map",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("mapId", Integer),
+ Column("distance", Integer),
+ Column("isLock", Boolean),
+ Column("isClear", Boolean),
+ Column("isComplete", Boolean),
+ UniqueConstraint("user", "mapId", name="mai2_item_map_uk"),
+ mysql_charset="utf8mb4",
+)
+
+login_bonus = Table(
+ "mai2_item_login_bonus",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("bonusId", Integer),
+ Column("point", Integer),
+ Column("isCurrent", Boolean),
+ Column("isComplete", Boolean),
+ UniqueConstraint("user", "bonusId", name="mai2_item_login_bonus_uk"),
+ mysql_charset="utf8mb4",
+)
+
+friend_season_ranking = Table(
+ "mai2_item_friend_season_ranking",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("seasonId", Integer),
+ Column("point", Integer),
+ Column("rank", Integer),
+ Column("rewardGet", Boolean),
+ Column("userName", String(8)),
+ Column("recordDate", TIMESTAMP),
+ UniqueConstraint(
+ "user", "seasonId", "userName", name="mai2_item_friend_season_ranking_uk"
+ ),
+ mysql_charset="utf8mb4",
+)
+
+favorite = Table(
+ "mai2_item_favorite",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("itemKind", Integer),
+ Column("itemIdList", JSON),
+ UniqueConstraint("user", "itemKind", name="mai2_item_favorite_uk"),
+ mysql_charset="utf8mb4",
+)
+
+charge = Table(
+ "mai2_item_charge",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("chargeId", Integer),
+ Column("stock", Integer),
+ Column("purchaseDate", String(255)),
+ Column("validDate", String(255)),
+ UniqueConstraint("user", "chargeId", name="mai2_item_charge_uk"),
+ mysql_charset="utf8mb4",
+)
+
+print_detail = Table(
+ "mai2_item_print_detail",
+ 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("printNumber", Integer),
+ Column("printDate", TIMESTAMP, server_default=func.now()),
+ Column("serialId", String(20)),
+ Column("placeId", Integer),
+ Column("clientId", String(11)),
+ Column("printerSerialId", String(20)),
+ Column("cardRomVersion", Integer),
+ Column("isHolograph", Boolean, server_default="1"),
+ Column("printOption1", Boolean, server_default="0"),
+ Column("printOption2", Boolean, server_default="0"),
+ Column("printOption3", Boolean, server_default="0"),
+ Column("printOption4", Boolean, server_default="0"),
+ Column("printOption5", Boolean, server_default="0"),
+ Column("printOption6", Boolean, server_default="0"),
+ Column("printOption7", Boolean, server_default="0"),
+ Column("printOption8", Boolean, server_default="0"),
+ Column("printOption9", Boolean, server_default="0"),
+ Column("printOption10", Boolean, server_default="0"),
+ Column("created", String(255), server_default=""),
+ UniqueConstraint("user", "serialId", name="mai2_item_print_detail_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class Mai2ItemData(BaseData):
+ def put_item(
+ self, user_id: int, item_kind: int, item_id: int, stock: int, is_valid: bool
+ ) -> None:
+ sql = insert(item).values(
+ user=user_id,
+ itemKind=item_kind,
+ itemId=item_id,
+ stock=stock,
+ isValid=is_valid,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ stock=stock,
+ isValid=is_valid,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_item: failed to insert item! user_id: {user_id}, item_kind: {item_kind}, item_id: {item_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_items(self, user_id: int, item_kind: int = None) -> Optional[List[Row]]:
+ if item_kind is None:
+ sql = item.select(item.c.user == user_id)
+ else:
+ sql = item.select(
+ and_(item.c.user == user_id, item.c.itemKind == item_kind)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_item(self, user_id: int, item_kind: int, item_id: int) -> Optional[Row]:
+ sql = item.select(
+ and_(
+ item.c.user == user_id,
+ item.c.itemKind == item_kind,
+ item.c.itemId == item_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_login_bonus(
+ self,
+ user_id: int,
+ bonus_id: int,
+ point: int,
+ is_current: bool,
+ is_complete: bool,
+ ) -> None:
+ sql = insert(login_bonus).values(
+ user=user_id,
+ bonusId=bonus_id,
+ point=point,
+ isCurrent=is_current,
+ isComplete=is_complete,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ point=point,
+ isCurrent=is_current,
+ isComplete=is_complete,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_login_bonus: failed to insert item! user_id: {user_id}, bonus_id: {bonus_id}, point: {point}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_login_bonuses(self, user_id: int) -> Optional[List[Row]]:
+ sql = login_bonus.select(login_bonus.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_login_bonus(self, user_id: int, bonus_id: int) -> Optional[Row]:
+ sql = login_bonus.select(
+ and_(login_bonus.c.user == user_id, login_bonus.c.bonus_id == bonus_id)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_map(
+ self,
+ user_id: int,
+ map_id: int,
+ distance: int,
+ is_lock: bool,
+ is_clear: bool,
+ is_complete: bool,
+ ) -> None:
+ sql = insert(map).values(
+ user=user_id,
+ mapId=map_id,
+ distance=distance,
+ isLock=is_lock,
+ isClear=is_clear,
+ isComplete=is_complete,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ distance=distance,
+ isLock=is_lock,
+ isClear=is_clear,
+ isComplete=is_complete,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_map: failed to insert item! user_id: {user_id}, map_id: {map_id}, distance: {distance}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_maps(self, user_id: int) -> Optional[List[Row]]:
+ sql = map.select(map.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_map(self, user_id: int, map_id: int) -> Optional[Row]:
+ sql = map.select(and_(map.c.user == user_id, map.c.mapId == map_id))
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_character_(self, user_id: int, char_data: Dict) -> Optional[int]:
+ char_data["user"] = user_id
+ sql = insert(character).values(**char_data)
+
+ conflict = sql.on_conflict_do_update(set_=dict(**char_data))
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_character_: failed to insert item! user_id: {user_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_character(
+ self,
+ user_id: int,
+ character_id: int,
+ level: int,
+ awakening: int,
+ use_count: int,
+ ) -> None:
+ sql = insert(character).values(
+ user=user_id,
+ characterId=character_id,
+ level=level,
+ awakening=awakening,
+ useCount=use_count,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ level=level,
+ awakening=awakening,
+ useCount=use_count,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_character: failed to insert item! user_id: {user_id}, character_id: {character_id}, level: {level}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_characters(self, user_id: int) -> Optional[List[Row]]:
+ sql = character.select(character.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_character(self, user_id: int, character_id: int) -> Optional[Row]:
+ sql = character.select(
+ and_(character.c.user == user_id, character.c.character_id == character_id)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_friend_season_ranking(self, user_id: int) -> Optional[Row]:
+ sql = friend_season_ranking.select(friend_season_ranking.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_friend_season_ranking(
+ self, aime_id: int, friend_season_ranking_data: Dict
+ ) -> Optional[int]:
+ sql = insert(friend_season_ranking).values(
+ user=aime_id, **friend_season_ranking_data
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(**friend_season_ranking_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_friend_season_ranking: failed to insert",
+ f"friend_season_ranking! aime_id: {aime_id}",
+ )
+ return None
+ return result.lastrowid
+
+ def put_favorite(
+ self, user_id: int, kind: int, item_id_list: List[int]
+ ) -> Optional[int]:
+ sql = insert(favorite).values(
+ user=user_id, kind=kind, item_id_list=item_id_list
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(item_id_list=item_id_list))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_favorite: failed to insert item! user_id: {user_id}, kind: {kind}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_favorites(self, user_id: int, kind: int = None) -> Optional[Row]:
+ if kind is None:
+ sql = favorite.select(favorite.c.user == user_id)
+ else:
+ sql = favorite.select(
+ and_(favorite.c.user == user_id, favorite.c.itemKind == kind)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_card(
+ self,
+ user_id: int,
+ card_type_id: int,
+ card_kind: int,
+ chara_id: int,
+ map_id: int,
+ start_date: datetime,
+ end_date: datetime,
+ ) -> Optional[Row]:
+ sql = insert(card).values(
+ user=user_id,
+ cardId=card_type_id,
+ cardTypeId=card_kind,
+ charaId=chara_id,
+ mapId=map_id,
+ startDate=start_date,
+ endDate=end_date,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ charaId=chara_id, mapId=map_id, startDate=start_date, endDate=end_date
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_card: failed to insert card! user_id: {user_id}, kind: {card_kind}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_cards(self, user_id: int, kind: int = None) -> Optional[Row]:
+ if kind is None:
+ sql = card.select(card.c.user == user_id)
+ else:
+ sql = card.select(and_(card.c.user == user_id, card.c.cardKind == kind))
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_charge(
+ self,
+ user_id: int,
+ charge_id: int,
+ stock: int,
+ purchase_date: datetime,
+ valid_date: datetime,
+ ) -> Optional[Row]:
+ sql = insert(charge).values(
+ user=user_id,
+ chargeId=charge_id,
+ stock=stock,
+ purchaseDate=purchase_date,
+ validDate=valid_date,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(stock=stock, purchaseDate=purchase_date, validDate=valid_date)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_card: failed to insert charge! user_id: {user_id}, chargeId: {charge_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_charges(self, user_id: int) -> Optional[Row]:
+ sql = charge.select(charge.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_user_print_detail(
+ self, aime_id: int, serial_id: str, user_print_data: Dict
+ ) -> Optional[int]:
+ sql = insert(print_detail).values(
+ user=aime_id, serialId=serial_id, **user_print_data
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(**user_print_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
diff --git a/titles/mai2/schema/profile.py b/titles/mai2/schema/profile.py
index 916be20..c72890b 100644
--- a/titles/mai2/schema/profile.py
+++ b/titles/mai2/schema/profile.py
@@ -1,817 +1,838 @@
-from core.data.schema import BaseData, metadata
-from titles.mai2.const import Mai2Constants
-
-from typing import Optional, Dict, List
-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 datetime import datetime
-
-detail = Table(
- "mai2_profile_detail",
- 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("userName", String(25)),
- Column("isNetMember", Integer),
- Column("iconId", Integer),
- Column("plateId", Integer),
- Column("titleId", Integer),
- Column("partnerId", Integer),
- Column("frameId", Integer),
- Column("selectMapId", Integer),
- Column("totalAwake", Integer),
- Column("gradeRating", Integer),
- Column("musicRating", Integer),
- Column("playerRating", Integer),
- Column("highestRating", Integer),
- Column("gradeRank", Integer),
- Column("classRank", Integer),
- Column("courseRank", Integer),
- Column("charaSlot", JSON),
- Column("charaLockSlot", JSON),
- Column("contentBit", BigInteger),
- Column("playCount", Integer),
- Column("eventWatchedDate", String(25)),
- Column("lastGameId", String(25)),
- Column("lastRomVersion", String(25)),
- Column("lastDataVersion", String(25)),
- Column("lastLoginDate", String(25)),
- Column("lastPairLoginDate", String(25)), # new with uni+
- Column("lastPlayDate", String(25)),
- Column("lastTrialPlayDate", String(25)), # new with uni+
- Column("lastPlayCredit", Integer),
- Column("lastPlayMode", Integer),
- Column("lastPlaceId", Integer),
- Column("lastPlaceName", String(25)),
- Column("lastAllNetId", Integer),
- Column("lastRegionId", Integer),
- Column("lastRegionName", String(25)),
- Column("lastClientId", String(25)),
- Column("lastCountryCode", String(25)),
- Column("lastSelectEMoney", Integer),
- Column("lastSelectTicket", Integer),
- Column("lastSelectCourse", Integer),
- Column("lastCountCourse", Integer),
- Column("firstGameId", String(25)),
- Column("firstRomVersion", String(25)),
- Column("firstDataVersion", String(25)),
- Column("firstPlayDate", String(25)),
- Column("compatibleCmVersion", String(25)),
- Column("dailyBonusDate", String(25)),
- Column("dailyCourseBonusDate", String(25)),
- Column("playVsCount", Integer),
- Column("playSyncCount", Integer),
- Column("winCount", Integer),
- Column("helpCount", Integer),
- Column("comboCount", Integer),
- Column("totalDeluxscore", BigInteger),
- Column("totalBasicDeluxscore", BigInteger),
- Column("totalAdvancedDeluxscore", BigInteger),
- Column("totalExpertDeluxscore", BigInteger),
- Column("totalMasterDeluxscore", BigInteger),
- Column("totalReMasterDeluxscore", BigInteger),
- Column("totalSync", Integer),
- Column("totalBasicSync", Integer),
- Column("totalAdvancedSync", Integer),
- Column("totalExpertSync", Integer),
- Column("totalMasterSync", Integer),
- Column("totalReMasterSync", Integer),
- Column("totalAchievement", BigInteger),
- Column("totalBasicAchievement", BigInteger),
- Column("totalAdvancedAchievement", BigInteger),
- Column("totalExpertAchievement", BigInteger),
- Column("totalMasterAchievement", BigInteger),
- Column("totalReMasterAchievement", BigInteger),
- Column("playerOldRating", BigInteger),
- Column("playerNewRating", BigInteger),
- Column("dateTime", BigInteger),
- Column("banState", Integer), # new with uni+
- UniqueConstraint("user", "version", name="mai2_profile_detail_uk"),
- mysql_charset="utf8mb4",
-)
-
-detail_old = Table(
- "maimai_profile_detail",
- 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("lastDataVersion", Integer),
- Column("userName", String(8)),
- Column("point", Integer),
- Column("totalPoint", Integer),
- Column("iconId", Integer),
- Column("nameplateId", Integer),
- Column("frameId", Integer),
- Column("trophyId", Integer),
- Column("playCount", Integer),
- Column("playVsCount", Integer),
- Column("playSyncCount", Integer),
- Column("winCount", Integer),
- Column("helpCount", Integer),
- Column("comboCount", Integer),
- Column("feverCount", Integer),
- Column("totalHiScore", Integer),
- Column("totalEasyHighScore", Integer),
- Column("totalBasicHighScore", Integer),
- Column("totalAdvancedHighScore", Integer),
- Column("totalExpertHighScore", Integer),
- Column("totalMasterHighScore", Integer),
- Column("totalReMasterHighScore", Integer),
- Column("totalHighSync", Integer),
- Column("totalEasySync", Integer),
- Column("totalBasicSync", Integer),
- Column("totalAdvancedSync", Integer),
- Column("totalExpertSync", Integer),
- Column("totalMasterSync", Integer),
- Column("totalReMasterSync", Integer),
- Column("playerRating", Integer),
- Column("highestRating", Integer),
- Column("rankAuthTailId", Integer),
- Column("eventWatchedDate", String(255)),
- Column("webLimitDate", String(255)),
- Column("challengeTrackPhase", Integer),
- Column("firstPlayBits", Integer),
- Column("lastPlayDate", String(255)),
- Column("lastPlaceId", Integer),
- Column("lastPlaceName", String(255)),
- Column("lastRegionId", Integer),
- Column("lastRegionName", String(255)),
- Column("lastClientId", String(255)),
- Column("lastCountryCode", String(255)),
- Column("eventPoint", Integer),
- Column("totalLv", Integer),
- Column("lastLoginBonusDay", Integer),
- Column("lastSurvivalBonusDay", Integer),
- Column("loginBonusLv", Integer),
- UniqueConstraint("user", "version", name="maimai_profile_detail_uk"),
- mysql_charset="utf8mb4",
-)
-
-ghost = Table(
- "mai2_profile_ghost",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("version_int", Integer, nullable=False),
- Column("name", String(25)),
- Column("iconId", Integer),
- Column("plateId", Integer),
- Column("titleId", Integer),
- Column("rate", Integer),
- Column("udemaeRate", Integer),
- Column("courseRank", Integer),
- Column("classRank", Integer),
- Column("classValue", Integer),
- Column("playDatetime", String(25)),
- Column("shopId", Integer),
- Column("regionCode", Integer),
- Column("typeId", Integer),
- Column("musicId", Integer),
- Column("difficulty", Integer),
- Column("version", Integer),
- Column("resultBitList", JSON),
- Column("resultNum", Integer),
- Column("achievement", Integer),
- UniqueConstraint(
- "user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"
- ),
- mysql_charset="utf8mb4",
-)
-
-extend = Table(
- "mai2_profile_extend",
- 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("selectMusicId", Integer),
- Column("selectDifficultyId", Integer),
- Column("categoryIndex", Integer),
- Column("musicIndex", Integer),
- Column("extraFlag", Integer),
- Column("selectScoreType", Integer),
- Column("extendContentBit", BigInteger),
- Column("isPhotoAgree", Boolean),
- Column("isGotoCodeRead", Boolean),
- Column("selectResultDetails", Boolean),
- Column("sortCategorySetting", Integer),
- Column("sortMusicSetting", Integer),
- Column("selectedCardList", JSON),
- Column("encountMapNpcList", JSON),
- Column("playStatusSetting", Integer, server_default="0"),
- UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
- mysql_charset="utf8mb4",
-)
-
-option = Table(
- "mai2_profile_option",
- 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("selectMusicId", Integer),
- Column("optionKind", Integer),
- Column("noteSpeed", Integer),
- Column("slideSpeed", Integer),
- Column("touchSpeed", Integer),
- Column("tapDesign", Integer),
- Column("tapSe", Integer, server_default="0"),
- Column("holdDesign", Integer),
- Column("slideDesign", Integer),
- Column("starType", Integer),
- Column("outlineDesign", Integer),
- Column("noteSize", Integer),
- Column("slideSize", Integer),
- Column("touchSize", Integer),
- Column("starRotate", Integer),
- Column("dispCenter", Integer),
- Column("dispChain", Integer),
- Column("dispRate", Integer),
- Column("dispBar", Integer),
- Column("touchEffect", Integer),
- Column("submonitorAnimation", Integer),
- Column("submonitorAchive", Integer),
- Column("submonitorAppeal", Integer),
- Column("matching", Integer),
- Column("trackSkip", Integer),
- Column("brightness", Integer),
- Column("mirrorMode", Integer),
- Column("dispJudge", Integer),
- Column("dispJudgePos", Integer),
- Column("dispJudgeTouchPos", Integer),
- Column("adjustTiming", Integer),
- Column("judgeTiming", Integer),
- Column("ansVolume", Integer),
- Column("tapHoldVolume", Integer),
- Column("criticalSe", Integer),
- Column("breakSe", Integer),
- Column("breakVolume", Integer),
- Column("exSe", Integer),
- Column("exVolume", Integer),
- Column("slideSe", Integer),
- Column("slideVolume", Integer),
- Column("touchHoldVolume", Integer),
- Column("damageSeVolume", Integer),
- Column("headPhoneVolume", Integer),
- Column("sortTab", Integer),
- Column("sortMusic", Integer),
- UniqueConstraint("user", "version", name="mai2_profile_option_uk"),
- mysql_charset="utf8mb4",
-)
-
-option_old = Table(
- "maimai_profile_option",
- 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("soudEffect", Integer),
- Column("mirrorMode", Integer),
- Column("guideSpeed", Integer),
- Column("bgInfo", Integer),
- Column("brightness", Integer),
- Column("isStarRot", Integer),
- Column("breakSe", Integer),
- Column("slideSe", Integer),
- Column("hardJudge", Integer),
- Column("isTagJump", Integer),
- Column("breakSeVol", Integer),
- Column("slideSeVol", Integer),
- Column("isUpperDisp", Integer),
- Column("trackSkip", Integer),
- Column("optionMode", Integer),
- Column("simpleOptionParam", Integer),
- Column("adjustTiming", Integer),
- Column("dispTiming", Integer),
- Column("timingPos", Integer),
- Column("ansVol", Integer),
- Column("noteVol", Integer),
- Column("dmgVol", Integer),
- Column("appealFlame", Integer),
- Column("isFeverDisp", Integer),
- Column("dispJudge", Integer),
- Column("judgePos", Integer),
- Column("ratingGuard", Integer),
- Column("selectChara", Integer),
- Column("sortType", Integer),
- Column("filterGenre", Integer),
- Column("filterLevel", Integer),
- Column("filterRank", Integer),
- Column("filterVersion", Integer),
- Column("filterRec", Integer),
- Column("filterFullCombo", Integer),
- Column("filterAllPerfect", Integer),
- Column("filterDifficulty", Integer),
- Column("filterFullSync", Integer),
- Column("filterReMaster", Integer),
- Column("filterMaxFever", Integer),
- Column("finalSelectId", Integer),
- Column("finalSelectCategory", Integer),
- UniqueConstraint("user", "version", name="maimai_profile_option_uk"),
- mysql_charset="utf8mb4",
-)
-
-web_opt = Table(
- "maimai_profile_web_option",
- 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("isNetMember", Boolean),
- Column("dispRate", Integer),
- Column("dispJudgeStyle", Integer),
- Column("dispRank", Integer),
- Column("dispHomeRanker", Integer),
- Column("dispTotalLv", Integer),
- UniqueConstraint("user", "version", name="maimai_profile_web_option_uk"),
- mysql_charset="utf8mb4",
-)
-
-grade_status = Table(
- "maimai_profile_grade_status",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("gradeVersion", Integer),
- Column("gradeLevel", Integer),
- Column("gradeSubLevel", Integer),
- Column("gradeMaxId", Integer),
- UniqueConstraint("user", "gradeVersion", name="maimai_profile_grade_status_uk"),
- mysql_charset="utf8mb4",
-)
-
-rating = Table(
- "mai2_profile_rating",
- 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("rating", Integer),
- Column("ratingList", JSON),
- Column("newRatingList", JSON),
- Column("nextRatingList", JSON),
- Column("nextNewRatingList", JSON),
- Column("udemae", JSON),
- UniqueConstraint("user", "version", name="mai2_profile_rating_uk"),
- mysql_charset="utf8mb4",
-)
-
-region = Table(
- "mai2_profile_region",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("regionId", Integer),
- Column("playCount", Integer, server_default="1"),
- Column("created", String(25)),
- UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"),
- mysql_charset="utf8mb4",
-)
-
-activity = Table(
- "mai2_profile_activity",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("kind", Integer),
- Column("activityId", Integer),
- Column("param1", Integer),
- Column("param2", Integer),
- Column("param3", Integer),
- Column("param4", Integer),
- Column("sortNumber", Integer),
- UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"),
- mysql_charset="utf8mb4",
-)
-
-boss = Table(
- "maimai_profile_boss",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
- Column("pandoraFlagList0", Integer),
- Column("pandoraFlagList1", Integer),
- Column("pandoraFlagList2", Integer),
- Column("pandoraFlagList3", Integer),
- Column("pandoraFlagList4", Integer),
- Column("pandoraFlagList5", Integer),
- Column("pandoraFlagList6", Integer),
- Column("emblemFlagList", Integer),
- UniqueConstraint("user", name="mai2_profile_boss_uk"),
- mysql_charset="utf8mb4",
-)
-
-recent_rating = Table(
- "maimai_profile_recent_rating",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"), nullable=False),
- Column("userRecentRatingList", JSON),
- UniqueConstraint("user", name="mai2_profile_recent_rating_uk"),
- mysql_charset="utf8mb4",
-)
-
-consec_logins = Table(
- "mai2_profile_consec_logins",
- 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("logins", Integer),
- UniqueConstraint("user", "version", name="mai2_profile_consec_logins_uk"),
- mysql_charset="utf8mb4",
-)
-
-class Mai2ProfileData(BaseData):
- def put_profile_detail(
- self, user_id: int, version: int, detail_data: Dict, is_dx: bool = True
- ) -> Optional[Row]:
- detail_data["user"] = user_id
- detail_data["version"] = version
-
- if is_dx:
- sql = insert(detail).values(**detail_data)
- else:
- sql = insert(detail_old).values(**detail_data)
-
- conflict = sql.on_duplicate_key_update(**detail_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile: Failed to create profile! user_id {user_id} is_dx {is_dx}"
- )
- return None
- return result.lastrowid
-
- def get_profile_detail(self, user_id: int, version: int, is_dx: bool = True) -> Optional[Row]:
- if is_dx:
- sql = (
- select(detail)
- .where(and_(detail.c.user == user_id, detail.c.version <= version))
- .order_by(detail.c.version.desc())
- )
-
- else:
- sql = (
- select(detail_old)
- .where(and_(detail_old.c.user == user_id, detail_old.c.version <= version))
- .order_by(detail_old.c.version.desc())
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_ghost(
- self, user_id: int, version: int, ghost_data: Dict
- ) -> Optional[int]:
- ghost_data["user"] = user_id
- ghost_data["version_int"] = version
-
- sql = insert(ghost).values(**ghost_data)
- conflict = sql.on_duplicate_key_update(**ghost_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(f"put_profile_ghost: failed to update! {user_id}")
- return None
- return result.lastrowid
-
- def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]:
- sql = (
- select(ghost)
- .where(and_(ghost.c.user == user_id, ghost.c.version_int <= version))
- .order_by(ghost.c.version.desc())
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_extend(
- self, user_id: int, version: int, extend_data: Dict
- ) -> Optional[int]:
- extend_data["user"] = user_id
- extend_data["version"] = version
-
- sql = insert(extend).values(**extend_data)
- conflict = sql.on_duplicate_key_update(**extend_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(f"put_profile_extend: failed to update! {user_id}")
- return None
- return result.lastrowid
-
- def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]:
- sql = (
- select(extend)
- .where(and_(extend.c.user == user_id, extend.c.version <= version))
- .order_by(extend.c.version.desc())
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_option(
- self, user_id: int, version: int, option_data: Dict, is_dx: bool = True
- ) -> Optional[int]:
- option_data["user"] = user_id
- option_data["version"] = version
-
- if is_dx:
- sql = insert(option).values(**option_data)
- else:
- sql = insert(option_old).values(**option_data)
- conflict = sql.on_duplicate_key_update(**option_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(f"put_profile_option: failed to update! {user_id} is_dx {is_dx}")
- return None
- return result.lastrowid
-
- def get_profile_option(self, user_id: int, version: int, is_dx: bool = True) -> Optional[Row]:
- if is_dx:
- sql = (
- select(option)
- .where(and_(option.c.user == user_id, option.c.version <= version))
- .order_by(option.c.version.desc())
- )
- else:
- sql = (
- select(option_old)
- .where(and_(option_old.c.user == user_id, option_old.c.version <= version))
- .order_by(option_old.c.version.desc())
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_rating(
- self, user_id: int, version: int, rating_data: Dict
- ) -> Optional[int]:
- rating_data["user"] = user_id
- rating_data["version"] = version
-
- sql = insert(rating).values(**rating_data)
- conflict = sql.on_duplicate_key_update(**rating_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(f"put_profile_rating: failed to update! {user_id}")
- return None
- return result.lastrowid
-
- def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]:
- sql = (
- select(rating)
- .where(and_(rating.c.user == user_id, rating.c.version <= version))
- .order_by(rating.c.version.desc())
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_profile_region(self, user_id: int, region_id: int) -> Optional[int]:
- sql = insert(region).values(
- user=user_id,
- regionId=region_id,
- created=datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT),
- )
-
- conflict = sql.on_duplicate_key_update(playCount=region.c.playCount + 1)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(f"put_region: failed to update! {user_id}")
- return None
- return result.lastrowid
-
- def get_regions(self, user_id: int) -> Optional[List[Dict]]:
- sql = select(region).where(region.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]:
- if "id" in activity_data:
- activity_data["activityId"] = activity_data["id"]
- activity_data.pop("id")
-
- activity_data["user"] = user_id
-
- sql = insert(activity).values(**activity_data)
-
- conflict = sql.on_duplicate_key_update(**activity_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile_activity: failed to update! user_id: {user_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile_activity(
- self, user_id: int, kind: int = None
- ) -> Optional[List[Row]]:
- sql = activity.select(
- and_(
- activity.c.user == user_id,
- (activity.c.kind == kind) if kind is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_web_option(self, user_id: int, version: int, web_opts: Dict) -> Optional[int]:
- web_opts["user"] = user_id
- web_opts["version"] = version
- sql = insert(web_opt).values(**web_opts)
-
- conflict = sql.on_duplicate_key_update(**web_opts)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_web_option: failed to update! user_id: {user_id}"
- )
- return None
- return result.lastrowid
-
- def get_web_option(self, user_id: int, version: int) -> Optional[Row]:
- sql = web_opt.select(and_(web_opt.c.user == user_id, web_opt.c.version == version))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_grade_status(self, user_id: int, grade_stat: Dict) -> Optional[int]:
- grade_stat["user"] = user_id
- sql = insert(grade_status).values(**grade_stat)
-
- conflict = sql.on_duplicate_key_update(**grade_stat)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_grade_status: failed to update! user_id: {user_id}"
- )
- return None
- return result.lastrowid
-
- def get_grade_status(self, user_id: int) -> Optional[Row]:
- sql = grade_status.select(grade_status.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_boss_list(self, user_id: int, boss_stat: Dict) -> Optional[int]:
- boss_stat["user"] = user_id
- sql = insert(boss).values(**boss_stat)
-
- conflict = sql.on_duplicate_key_update(**boss_stat)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_boss_list: failed to update! user_id: {user_id}"
- )
- return None
- return result.lastrowid
-
- def get_boss_list(self, user_id: int) -> Optional[Row]:
- sql = boss.select(boss.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def put_recent_rating(self, user_id: int, rr: Dict) -> Optional[int]:
- sql = insert(recent_rating).values(user=user_id, userRecentRatingList=rr)
-
- conflict = sql.on_duplicate_key_update({'userRecentRatingList': rr})
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_recent_rating: failed to update! user_id: {user_id}"
- )
- return None
- return result.lastrowid
-
- def get_recent_rating(self, user_id: int) -> Optional[Row]:
- sql = recent_rating.select(recent_rating.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def add_consec_login(self, user_id: int, version: int) -> None:
- sql = insert(consec_logins).values(
- user=user_id,
- version=version,
- logins=1
- )
-
- conflict = sql.on_duplicate_key_update(
- logins=consec_logins.c.logins + 1
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(f"Failed to update consecutive login count for user {user_id} version {version}")
-
- def get_consec_login(self, user_id: int, version: int) -> Optional[Row]:
- sql = select(consec_logins).where(and_(
- consec_logins.c.user==user_id,
- consec_logins.c.version==version,
- ))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def reset_consec_login(self, user_id: int, version: int) -> Optional[Row]:
- sql = consec_logins.update(and_(
- consec_logins.c.user==user_id,
- consec_logins.c.version==version,
- )).values(
- logins=1
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
+from core.data.schema import BaseData, metadata
+from titles.mai2.const import Mai2Constants
+
+from typing import Optional, Dict, List
+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 datetime import datetime
+
+detail = Table(
+ "mai2_profile_detail",
+ 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("userName", String(25)),
+ Column("isNetMember", Integer),
+ Column("iconId", Integer),
+ Column("plateId", Integer),
+ Column("titleId", Integer),
+ Column("partnerId", Integer),
+ Column("frameId", Integer),
+ Column("selectMapId", Integer),
+ Column("totalAwake", Integer),
+ Column("gradeRating", Integer),
+ Column("musicRating", Integer),
+ Column("playerRating", Integer),
+ Column("highestRating", Integer),
+ Column("gradeRank", Integer),
+ Column("classRank", Integer),
+ Column("courseRank", Integer),
+ Column("charaSlot", JSON),
+ Column("charaLockSlot", JSON),
+ Column("contentBit", BigInteger),
+ Column("playCount", Integer),
+ Column("eventWatchedDate", String(25)),
+ Column("lastGameId", String(25)),
+ Column("lastRomVersion", String(25)),
+ Column("lastDataVersion", String(25)),
+ Column("lastLoginDate", String(25)),
+ Column("lastPairLoginDate", String(25)), # new with uni+
+ Column("lastPlayDate", String(25)),
+ Column("lastTrialPlayDate", String(25)), # new with uni+
+ Column("lastPlayCredit", Integer),
+ Column("lastPlayMode", Integer),
+ Column("lastPlaceId", Integer),
+ Column("lastPlaceName", String(25)),
+ Column("lastAllNetId", Integer),
+ Column("lastRegionId", Integer),
+ Column("lastRegionName", String(25)),
+ Column("lastClientId", String(25)),
+ Column("lastCountryCode", String(25)),
+ Column("lastSelectEMoney", Integer),
+ Column("lastSelectTicket", Integer),
+ Column("lastSelectCourse", Integer),
+ Column("lastCountCourse", Integer),
+ Column("firstGameId", String(25)),
+ Column("firstRomVersion", String(25)),
+ Column("firstDataVersion", String(25)),
+ Column("firstPlayDate", String(25)),
+ Column("compatibleCmVersion", String(25)),
+ Column("dailyBonusDate", String(25)),
+ Column("dailyCourseBonusDate", String(25)),
+ Column("playVsCount", Integer),
+ Column("playSyncCount", Integer),
+ Column("winCount", Integer),
+ Column("helpCount", Integer),
+ Column("comboCount", Integer),
+ Column("totalDeluxscore", BigInteger),
+ Column("totalBasicDeluxscore", BigInteger),
+ Column("totalAdvancedDeluxscore", BigInteger),
+ Column("totalExpertDeluxscore", BigInteger),
+ Column("totalMasterDeluxscore", BigInteger),
+ Column("totalReMasterDeluxscore", BigInteger),
+ Column("totalSync", Integer),
+ Column("totalBasicSync", Integer),
+ Column("totalAdvancedSync", Integer),
+ Column("totalExpertSync", Integer),
+ Column("totalMasterSync", Integer),
+ Column("totalReMasterSync", Integer),
+ Column("totalAchievement", BigInteger),
+ Column("totalBasicAchievement", BigInteger),
+ Column("totalAdvancedAchievement", BigInteger),
+ Column("totalExpertAchievement", BigInteger),
+ Column("totalMasterAchievement", BigInteger),
+ Column("totalReMasterAchievement", BigInteger),
+ Column("playerOldRating", BigInteger),
+ Column("playerNewRating", BigInteger),
+ Column("dateTime", BigInteger),
+ Column("banState", Integer), # new with uni+
+ UniqueConstraint("user", "version", name="mai2_profile_detail_uk"),
+ mysql_charset="utf8mb4",
+)
+
+detail_old = Table(
+ "maimai_profile_detail",
+ 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("lastDataVersion", Integer),
+ Column("userName", String(8)),
+ Column("point", Integer),
+ Column("totalPoint", Integer),
+ Column("iconId", Integer),
+ Column("nameplateId", Integer),
+ Column("frameId", Integer),
+ Column("trophyId", Integer),
+ Column("playCount", Integer),
+ Column("playVsCount", Integer),
+ Column("playSyncCount", Integer),
+ Column("winCount", Integer),
+ Column("helpCount", Integer),
+ Column("comboCount", Integer),
+ Column("feverCount", Integer),
+ Column("totalHiScore", Integer),
+ Column("totalEasyHighScore", Integer),
+ Column("totalBasicHighScore", Integer),
+ Column("totalAdvancedHighScore", Integer),
+ Column("totalExpertHighScore", Integer),
+ Column("totalMasterHighScore", Integer),
+ Column("totalReMasterHighScore", Integer),
+ Column("totalHighSync", Integer),
+ Column("totalEasySync", Integer),
+ Column("totalBasicSync", Integer),
+ Column("totalAdvancedSync", Integer),
+ Column("totalExpertSync", Integer),
+ Column("totalMasterSync", Integer),
+ Column("totalReMasterSync", Integer),
+ Column("playerRating", Integer),
+ Column("highestRating", Integer),
+ Column("rankAuthTailId", Integer),
+ Column("eventWatchedDate", String(255)),
+ Column("webLimitDate", String(255)),
+ Column("challengeTrackPhase", Integer),
+ Column("firstPlayBits", Integer),
+ Column("lastPlayDate", String(255)),
+ Column("lastPlaceId", Integer),
+ Column("lastPlaceName", String(255)),
+ Column("lastRegionId", Integer),
+ Column("lastRegionName", String(255)),
+ Column("lastClientId", String(255)),
+ Column("lastCountryCode", String(255)),
+ Column("eventPoint", Integer),
+ Column("totalLv", Integer),
+ Column("lastLoginBonusDay", Integer),
+ Column("lastSurvivalBonusDay", Integer),
+ Column("loginBonusLv", Integer),
+ UniqueConstraint("user", "version", name="maimai_profile_detail_uk"),
+ mysql_charset="utf8mb4",
+)
+
+ghost = Table(
+ "mai2_profile_ghost",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("version_int", Integer, nullable=False),
+ Column("name", String(25)),
+ Column("iconId", Integer),
+ Column("plateId", Integer),
+ Column("titleId", Integer),
+ Column("rate", Integer),
+ Column("udemaeRate", Integer),
+ Column("courseRank", Integer),
+ Column("classRank", Integer),
+ Column("classValue", Integer),
+ Column("playDatetime", String(25)),
+ Column("shopId", Integer),
+ Column("regionCode", Integer),
+ Column("typeId", Integer),
+ Column("musicId", Integer),
+ Column("difficulty", Integer),
+ Column("version", Integer),
+ Column("resultBitList", JSON),
+ Column("resultNum", Integer),
+ Column("achievement", Integer),
+ UniqueConstraint(
+ "user", "version", "musicId", "difficulty", name="mai2_profile_ghost_uk"
+ ),
+ mysql_charset="utf8mb4",
+)
+
+extend = Table(
+ "mai2_profile_extend",
+ 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("selectMusicId", Integer),
+ Column("selectDifficultyId", Integer),
+ Column("categoryIndex", Integer),
+ Column("musicIndex", Integer),
+ Column("extraFlag", Integer),
+ Column("selectScoreType", Integer),
+ Column("extendContentBit", BigInteger),
+ Column("isPhotoAgree", Boolean),
+ Column("isGotoCodeRead", Boolean),
+ Column("selectResultDetails", Boolean),
+ Column("sortCategorySetting", Integer),
+ Column("sortMusicSetting", Integer),
+ Column("selectedCardList", JSON),
+ Column("encountMapNpcList", JSON),
+ Column("playStatusSetting", Integer, server_default="0"),
+ UniqueConstraint("user", "version", name="mai2_profile_extend_uk"),
+ mysql_charset="utf8mb4",
+)
+
+option = Table(
+ "mai2_profile_option",
+ 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("selectMusicId", Integer),
+ Column("optionKind", Integer),
+ Column("noteSpeed", Integer),
+ Column("slideSpeed", Integer),
+ Column("touchSpeed", Integer),
+ Column("tapDesign", Integer),
+ Column("tapSe", Integer, server_default="0"),
+ Column("holdDesign", Integer),
+ Column("slideDesign", Integer),
+ Column("starType", Integer),
+ Column("outlineDesign", Integer),
+ Column("noteSize", Integer),
+ Column("slideSize", Integer),
+ Column("touchSize", Integer),
+ Column("starRotate", Integer),
+ Column("dispCenter", Integer),
+ Column("dispChain", Integer),
+ Column("dispRate", Integer),
+ Column("dispBar", Integer),
+ Column("touchEffect", Integer),
+ Column("submonitorAnimation", Integer),
+ Column("submonitorAchive", Integer),
+ Column("submonitorAppeal", Integer),
+ Column("matching", Integer),
+ Column("trackSkip", Integer),
+ Column("brightness", Integer),
+ Column("mirrorMode", Integer),
+ Column("dispJudge", Integer),
+ Column("dispJudgePos", Integer),
+ Column("dispJudgeTouchPos", Integer),
+ Column("adjustTiming", Integer),
+ Column("judgeTiming", Integer),
+ Column("ansVolume", Integer),
+ Column("tapHoldVolume", Integer),
+ Column("criticalSe", Integer),
+ Column("breakSe", Integer),
+ Column("breakVolume", Integer),
+ Column("exSe", Integer),
+ Column("exVolume", Integer),
+ Column("slideSe", Integer),
+ Column("slideVolume", Integer),
+ Column("touchHoldVolume", Integer),
+ Column("damageSeVolume", Integer),
+ Column("headPhoneVolume", Integer),
+ Column("sortTab", Integer),
+ Column("sortMusic", Integer),
+ UniqueConstraint("user", "version", name="mai2_profile_option_uk"),
+ mysql_charset="utf8mb4",
+)
+
+option_old = Table(
+ "maimai_profile_option",
+ 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("soudEffect", Integer),
+ Column("mirrorMode", Integer),
+ Column("guideSpeed", Integer),
+ Column("bgInfo", Integer),
+ Column("brightness", Integer),
+ Column("isStarRot", Integer),
+ Column("breakSe", Integer),
+ Column("slideSe", Integer),
+ Column("hardJudge", Integer),
+ Column("isTagJump", Integer),
+ Column("breakSeVol", Integer),
+ Column("slideSeVol", Integer),
+ Column("isUpperDisp", Integer),
+ Column("trackSkip", Integer),
+ Column("optionMode", Integer),
+ Column("simpleOptionParam", Integer),
+ Column("adjustTiming", Integer),
+ Column("dispTiming", Integer),
+ Column("timingPos", Integer),
+ Column("ansVol", Integer),
+ Column("noteVol", Integer),
+ Column("dmgVol", Integer),
+ Column("appealFlame", Integer),
+ Column("isFeverDisp", Integer),
+ Column("dispJudge", Integer),
+ Column("judgePos", Integer),
+ Column("ratingGuard", Integer),
+ Column("selectChara", Integer),
+ Column("sortType", Integer),
+ Column("filterGenre", Integer),
+ Column("filterLevel", Integer),
+ Column("filterRank", Integer),
+ Column("filterVersion", Integer),
+ Column("filterRec", Integer),
+ Column("filterFullCombo", Integer),
+ Column("filterAllPerfect", Integer),
+ Column("filterDifficulty", Integer),
+ Column("filterFullSync", Integer),
+ Column("filterReMaster", Integer),
+ Column("filterMaxFever", Integer),
+ Column("finalSelectId", Integer),
+ Column("finalSelectCategory", Integer),
+ UniqueConstraint("user", "version", name="maimai_profile_option_uk"),
+ mysql_charset="utf8mb4",
+)
+
+web_opt = Table(
+ "maimai_profile_web_option",
+ 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("isNetMember", Boolean),
+ Column("dispRate", Integer),
+ Column("dispJudgeStyle", Integer),
+ Column("dispRank", Integer),
+ Column("dispHomeRanker", Integer),
+ Column("dispTotalLv", Integer),
+ UniqueConstraint("user", "version", name="maimai_profile_web_option_uk"),
+ mysql_charset="utf8mb4",
+)
+
+grade_status = Table(
+ "maimai_profile_grade_status",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("gradeVersion", Integer),
+ Column("gradeLevel", Integer),
+ Column("gradeSubLevel", Integer),
+ Column("gradeMaxId", Integer),
+ UniqueConstraint("user", "gradeVersion", name="maimai_profile_grade_status_uk"),
+ mysql_charset="utf8mb4",
+)
+
+rating = Table(
+ "mai2_profile_rating",
+ 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("rating", Integer),
+ Column("ratingList", JSON),
+ Column("newRatingList", JSON),
+ Column("nextRatingList", JSON),
+ Column("nextNewRatingList", JSON),
+ Column("udemae", JSON),
+ UniqueConstraint("user", "version", name="mai2_profile_rating_uk"),
+ mysql_charset="utf8mb4",
+)
+
+region = Table(
+ "mai2_profile_region",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("regionId", Integer),
+ Column("playCount", Integer, server_default="1"),
+ Column("created", String(25)),
+ UniqueConstraint("user", "regionId", name="mai2_profile_region_uk"),
+ mysql_charset="utf8mb4",
+)
+
+activity = Table(
+ "mai2_profile_activity",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("kind", Integer),
+ Column("activityId", Integer),
+ Column("param1", Integer),
+ Column("param2", Integer),
+ Column("param3", Integer),
+ Column("param4", Integer),
+ Column("sortNumber", Integer),
+ UniqueConstraint("user", "kind", "activityId", name="mai2_profile_activity_uk"),
+ mysql_charset="utf8mb4",
+)
+
+boss = Table(
+ "maimai_profile_boss",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("pandoraFlagList0", Integer),
+ Column("pandoraFlagList1", Integer),
+ Column("pandoraFlagList2", Integer),
+ Column("pandoraFlagList3", Integer),
+ Column("pandoraFlagList4", Integer),
+ Column("pandoraFlagList5", Integer),
+ Column("pandoraFlagList6", Integer),
+ Column("emblemFlagList", Integer),
+ UniqueConstraint("user", name="mai2_profile_boss_uk"),
+ mysql_charset="utf8mb4",
+)
+
+recent_rating = Table(
+ "maimai_profile_recent_rating",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("userRecentRatingList", JSON),
+ UniqueConstraint("user", name="mai2_profile_recent_rating_uk"),
+ mysql_charset="utf8mb4",
+)
+
+consec_logins = Table(
+ "mai2_profile_consec_logins",
+ 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("logins", Integer),
+ UniqueConstraint("user", "version", name="mai2_profile_consec_logins_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class Mai2ProfileData(BaseData):
+ def put_profile_detail(
+ self, user_id: int, version: int, detail_data: Dict, is_dx: bool = True
+ ) -> Optional[Row]:
+ detail_data["user"] = user_id
+ detail_data["version"] = version
+
+ if is_dx:
+ sql = insert(detail).values(**detail_data)
+ else:
+ sql = insert(detail_old).values(**detail_data)
+
+ conflict = sql.on_conflict_do_update(set_=dict(**detail_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile: Failed to create profile! user_id {user_id} is_dx {is_dx}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_detail(
+ self, user_id: int, version: int, is_dx: bool = True
+ ) -> Optional[Row]:
+ if is_dx:
+ sql = (
+ select(detail)
+ .where(and_(detail.c.user == user_id, detail.c.version <= version))
+ .order_by(detail.c.version.desc())
+ )
+
+ else:
+ sql = (
+ select(detail_old)
+ .where(
+ and_(detail_old.c.user == user_id, detail_old.c.version <= version)
+ )
+ .order_by(detail_old.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_ghost(
+ self, user_id: int, version: int, ghost_data: Dict
+ ) -> Optional[int]:
+ ghost_data["user"] = user_id
+ ghost_data["version_int"] = version
+
+ sql = insert(ghost).values(**ghost_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**ghost_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_profile_ghost: failed to update! {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_profile_ghost(self, user_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select(ghost)
+ .where(and_(ghost.c.user == user_id, ghost.c.version_int <= version))
+ .order_by(ghost.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_extend(
+ self, user_id: int, version: int, extend_data: Dict
+ ) -> Optional[int]:
+ extend_data["user"] = user_id
+ extend_data["version"] = version
+
+ sql = insert(extend).values(**extend_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**extend_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_profile_extend: failed to update! {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_profile_extend(self, user_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select(extend)
+ .where(and_(extend.c.user == user_id, extend.c.version <= version))
+ .order_by(extend.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_option(
+ self, user_id: int, version: int, option_data: Dict, is_dx: bool = True
+ ) -> Optional[int]:
+ option_data["user"] = user_id
+ option_data["version"] = version
+
+ if is_dx:
+ sql = insert(option).values(**option_data)
+ else:
+ sql = insert(option_old).values(**option_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**option_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_option: failed to update! {user_id} is_dx {is_dx}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_option(
+ self, user_id: int, version: int, is_dx: bool = True
+ ) -> Optional[Row]:
+ if is_dx:
+ sql = (
+ select(option)
+ .where(and_(option.c.user == user_id, option.c.version <= version))
+ .order_by(option.c.version.desc())
+ )
+ else:
+ sql = (
+ select(option_old)
+ .where(
+ and_(option_old.c.user == user_id, option_old.c.version <= version)
+ )
+ .order_by(option_old.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_rating(
+ self, user_id: int, version: int, rating_data: Dict
+ ) -> Optional[int]:
+ rating_data["user"] = user_id
+ rating_data["version"] = version
+
+ sql = insert(rating).values(**rating_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**rating_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_profile_rating: failed to update! {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_profile_rating(self, user_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select(rating)
+ .where(and_(rating.c.user == user_id, rating.c.version <= version))
+ .order_by(rating.c.version.desc())
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_profile_region(self, user_id: int, region_id: int) -> Optional[int]:
+ sql = insert(region).values(
+ user=user_id,
+ regionId=region_id,
+ created=datetime.strftime(datetime.now(), Mai2Constants.DATE_TIME_FORMAT),
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(playCount=region.c.playCount + 1)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_region: failed to update! {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_regions(self, user_id: int) -> Optional[List[Dict]]:
+ sql = select(region).where(region.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_profile_activity(self, user_id: int, activity_data: Dict) -> Optional[int]:
+ if "id" in activity_data:
+ activity_data["activityId"] = activity_data["id"]
+ activity_data.pop("id")
+
+ activity_data["user"] = user_id
+
+ sql = insert(activity).values(**activity_data)
+
+ conflict = sql.on_conflict_do_update(set_=dict(**activity_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_activity: failed to update! user_id: {user_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile_activity(
+ self, user_id: int, kind: int = None
+ ) -> Optional[List[Row]]:
+ sql = activity.select(
+ and_(
+ activity.c.user == user_id,
+ (activity.c.kind == kind) if kind is not None else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_web_option(
+ self, user_id: int, version: int, web_opts: Dict
+ ) -> Optional[int]:
+ web_opts["user"] = user_id
+ web_opts["version"] = version
+ sql = insert(web_opt).values(**web_opts)
+
+ conflict = sql.on_conflict_do_update(set_=dict(**web_opts))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_web_option: failed to update! user_id: {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_web_option(self, user_id: int, version: int) -> Optional[Row]:
+ sql = web_opt.select(
+ and_(web_opt.c.user == user_id, web_opt.c.version == version)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_grade_status(self, user_id: int, grade_stat: Dict) -> Optional[int]:
+ grade_stat["user"] = user_id
+ sql = insert(grade_status).values(**grade_stat)
+
+ conflict = sql.on_conflict_do_update(set_=dict(**grade_stat))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_grade_status: failed to update! user_id: {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_grade_status(self, user_id: int) -> Optional[Row]:
+ sql = grade_status.select(grade_status.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_boss_list(self, user_id: int, boss_stat: Dict) -> Optional[int]:
+ boss_stat["user"] = user_id
+ sql = insert(boss).values(**boss_stat)
+
+ conflict = sql.on_conflict_do_update(set_=dict(**boss_stat))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_boss_list: failed to update! user_id: {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_boss_list(self, user_id: int) -> Optional[Row]:
+ sql = boss.select(boss.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def put_recent_rating(self, user_id: int, rr: Dict) -> Optional[int]:
+ sql = insert(recent_rating).values(user=user_id, userRecentRatingList=rr)
+
+ conflict = sql.on_conflict_do_update(set_=dict({"userRecentRatingList": rr}))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"put_recent_rating: failed to update! user_id: {user_id}")
+ return None
+ return result.lastrowid
+
+ def get_recent_rating(self, user_id: int) -> Optional[Row]:
+ sql = recent_rating.select(recent_rating.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def add_consec_login(self, user_id: int, version: int) -> None:
+ sql = insert(consec_logins).values(user=user_id, version=version, logins=1)
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(logins=consec_logins.c.logins + 1)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"Failed to update consecutive login count for user {user_id} version {version}"
+ )
+
+ def get_consec_login(self, user_id: int, version: int) -> Optional[Row]:
+ sql = select(consec_logins).where(
+ and_(
+ consec_logins.c.user == user_id,
+ consec_logins.c.version == version,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def reset_consec_login(self, user_id: int, version: int) -> Optional[Row]:
+ sql = consec_logins.update(
+ and_(
+ consec_logins.c.user == user_id,
+ consec_logins.c.version == version,
+ )
+ ).values(logins=1)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
diff --git a/titles/mai2/schema/score.py b/titles/mai2/schema/score.py
index 181a895..193bbbc 100644
--- a/titles/mai2/schema/score.py
+++ b/titles/mai2/schema/score.py
@@ -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()
diff --git a/titles/mai2/schema/static.py b/titles/mai2/schema/static.py
index 76b163c..d96d1f7 100644
--- a/titles/mai2/schema/static.py
+++ b/titles/mai2/schema/static.py
@@ -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()
diff --git a/titles/mai2/universe.py b/titles/mai2/universe.py
index c17f899..47dc5ee 100644
--- a/titles/mai2/universe.py
+++ b/titles/mai2/universe.py
@@ -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,
diff --git a/titles/ongeki/base.py b/titles/ongeki/base.py
index ace1d12..95f6b02 100644
--- a/titles/ongeki/base.py
+++ b/titles/ongeki/base.py
@@ -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"}
diff --git a/titles/ongeki/bright.py b/titles/ongeki/bright.py
index 49d6216..6d87dac 100644
--- a/titles/ongeki/bright.py
+++ b/titles/ongeki/bright.py
@@ -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"}
diff --git a/titles/ongeki/index.py b/titles/ongeki/index.py
index af206e9..9f84ec2 100644
--- a/titles/ongeki/index.py
+++ b/titles/ongeki/index.py
@@ -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"
diff --git a/titles/ongeki/read.py b/titles/ongeki/read.py
index b64194f..9149492 100644
--- a/titles/ongeki/read.py
+++ b/titles/ongeki/read.py
@@ -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}...")
diff --git a/titles/ongeki/schema/__init__.py b/titles/ongeki/schema/__init__.py
index b93a16c..2b4d7fc 100644
--- a/titles/ongeki/schema/__init__.py
+++ b/titles/ongeki/schema/__init__.py
@@ -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,
+]
diff --git a/titles/ongeki/schema/item.py b/titles/ongeki/schema/item.py
index 27d90f8..ac2ae8a 100644
--- a/titles/ongeki/schema/item.py
+++ b/titles/ongeki/schema/item.py
@@ -1,716 +1,718 @@
-from typing import Dict, Optional, List
-from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
-from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
-from sqlalchemy.schema import ForeignKey
-from sqlalchemy.engine import Row
-from sqlalchemy.sql import func, select
-from sqlalchemy.dialects.mysql import insert
-
-from core.data.schema import BaseData, metadata
-
-card = Table(
- "ongeki_user_card",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("cardId", Integer),
- Column("digitalStock", Integer),
- Column("analogStock", Integer),
- Column("level", Integer),
- Column("maxLevel", Integer),
- Column("exp", Integer),
- Column("printCount", Integer),
- Column("useCount", Integer),
- Column("isNew", Boolean),
- Column("kaikaDate", String(25)),
- Column("choKaikaDate", String(25)),
- Column("skillId", Integer),
- Column("isAcquired", Boolean),
- Column("created", String(25)),
- UniqueConstraint("user", "cardId", name="ongeki_user_card_uk"),
- mysql_charset="utf8mb4",
-)
-
-deck = Table(
- "ongeki_user_deck",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("deckId", Integer),
- Column("cardId1", Integer),
- Column("cardId2", Integer),
- Column("cardId3", Integer),
- UniqueConstraint("user", "deckId", name="ongeki_user_deck_uk"),
- mysql_charset="utf8mb4",
-)
-
-character = Table(
- "ongeki_user_character",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("characterId", Integer),
- Column("costumeId", Integer),
- Column("attachmentId", Integer),
- Column("playCount", Integer),
- Column("intimateLevel", Integer),
- Column("intimateCount", Integer),
- Column("intimateCountRewarded", Integer),
- Column("intimateCountDate", String(25)),
- Column("isNew", Boolean),
- UniqueConstraint("user", "characterId", name="ongeki_user_character_uk"),
- mysql_charset="utf8mb4",
-)
-
-boss = Table(
- "ongeki_user_boss",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("musicId", Integer),
- Column("damage", Integer),
- Column("isClear", Boolean),
- Column("eventId", Integer),
- UniqueConstraint("user", "musicId", "eventId", name="ongeki_user_boss_uk"),
- mysql_charset="utf8mb4",
-)
-
-story = Table(
- "ongeki_user_story",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("storyId", Integer),
- Column("jewelCount", Integer),
- Column("lastChapterId", Integer),
- Column("lastPlayMusicId", Integer),
- Column("lastPlayMusicCategory", Integer),
- Column("lastPlayMusicLevel", Integer),
- UniqueConstraint("user", "storyId", name="ongeki_user_story_uk"),
- mysql_charset="utf8mb4",
-)
-
-chapter = Table(
- "ongeki_user_chapter",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("chapterId", Integer),
- Column("jewelCount", Integer),
- Column("isStoryWatched", Boolean),
- Column("isClear", Boolean),
- Column("lastPlayMusicId", Integer),
- Column("lastPlayMusicCategory", Integer),
- Column("lastPlayMusicLevel", Integer),
- Column("skipTiming1", Integer),
- Column("skipTiming2", Integer),
- UniqueConstraint("user", "chapterId", name="ongeki_user_chapter_uk"),
- mysql_charset="utf8mb4",
-)
-
-memorychapter = Table(
- "ongeki_user_memorychapter",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("chapterId", Integer),
- Column("gaugeId", Integer),
- Column("gaugeNum", Integer),
- Column("jewelCount", Integer),
- Column("isStoryWatched", Boolean),
- Column("isBossWatched", Boolean),
- Column("isDialogWatched", Boolean),
- Column("isEndingWatched", Boolean),
- Column("isClear", Boolean),
- Column("lastPlayMusicId", Integer),
- Column("lastPlayMusicLevel", Integer),
- Column("lastPlayMusicCategory", Integer),
- UniqueConstraint("user", "chapterId", name="ongeki_user_memorychapter_uk"),
- mysql_charset="utf8mb4",
-)
-
-item = Table(
- "ongeki_user_item",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("itemKind", Integer),
- Column("itemId", Integer),
- Column("stock", Integer),
- Column("isValid", Boolean),
- UniqueConstraint("user", "itemKind", "itemId", name="ongeki_user_item_uk"),
- mysql_charset="utf8mb4",
-)
-
-music_item = Table(
- "ongeki_user_music_item",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("musicId", Integer),
- Column("status", Integer),
- UniqueConstraint("user", "musicId", name="ongeki_user_music_item_uk"),
- mysql_charset="utf8mb4",
-)
-
-login_bonus = Table(
- "ongeki_user_login_bonus",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("bonusId", Integer),
- Column("bonusCount", Integer),
- Column("lastUpdateDate", String(25)),
- UniqueConstraint("user", "bonusId", name="ongeki_user_login_bonus_uk"),
- mysql_charset="utf8mb4",
-)
-
-event_point = Table(
- "ongeki_user_event_point",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("eventId", Integer),
- Column("point", Integer),
- Column("isRankingRewarded", Boolean),
- UniqueConstraint("user", "eventId", name="ongeki_user_event_point_uk"),
- mysql_charset="utf8mb4",
-)
-
-mission_point = Table(
- "ongeki_user_mission_point",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("eventId", Integer),
- Column("point", Integer),
- UniqueConstraint("user", "eventId", name="ongeki_user_mission_point_uk"),
- mysql_charset="utf8mb4",
-)
-
-scenerio = Table(
- "ongeki_user_scenerio",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("scenarioId", Integer),
- Column("playCount", Integer),
- UniqueConstraint("user", "scenarioId", name="ongeki_user_scenerio_uk"),
- mysql_charset="utf8mb4",
-)
-
-trade_item = Table(
- "ongeki_user_trade_item",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("chapterId", Integer),
- Column("tradeItemId", Integer),
- Column("tradeCount", Integer),
- UniqueConstraint(
- "user", "chapterId", "tradeItemId", name="ongeki_user_trade_item_uk"
- ),
- mysql_charset="utf8mb4",
-)
-
-event_music = Table(
- "ongeki_user_event_music",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("eventId", Integer),
- Column("type", Integer),
- Column("musicId", Integer),
- Column("level", Integer),
- Column("techScoreMax", Integer),
- Column("platinumScoreMax", Integer),
- Column("techRecordDate", String(25)),
- Column("isTechNewRecord", Boolean),
- UniqueConstraint(
- "user", "eventId", "type", "musicId", "level", name="ongeki_user_event_music"
- ),
- mysql_charset="utf8mb4",
-)
-
-tech_event = Table(
- "ongeki_user_tech_event",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("eventId", Integer),
- Column("totalTechScore", Integer),
- Column("totalPlatinumScore", Integer),
- Column("techRecordDate", String(25)),
- Column("isRankingRewarded", Boolean),
- Column("isTotalTechNewRecord", Boolean),
- UniqueConstraint("user", "eventId", name="ongeki_user_tech_event_uk"),
- mysql_charset="utf8mb4",
-)
-
-gacha = Table(
- "ongeki_user_gacha",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("gachaId", Integer, nullable=False),
- Column("totalGachaCnt", Integer, server_default="0"),
- Column("ceilingGachaCnt", Integer, server_default="0"),
- Column("selectPoint", Integer, server_default="0"),
- Column("useSelectPoint", Integer, server_default="0"),
- Column("dailyGachaCnt", Integer, server_default="0"),
- Column("fiveGachaCnt", Integer, server_default="0"),
- Column("elevenGachaCnt", Integer, server_default="0"),
- Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "gachaId", name="ongeki_user_gacha_uk"),
- mysql_charset="utf8mb4",
-)
-
-gacha_supply = Table(
- "ongeki_user_gacha_supply",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("cardId", Integer, nullable=False),
- UniqueConstraint("user", "cardId", name="ongeki_user_gacha_supply_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-print_detail = Table(
- "ongeki_user_print_detail",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("cardId", Integer, nullable=False),
- Column("cardType", Integer, server_default="0"),
- Column("printDate", TIMESTAMP, nullable=False),
- Column("serialId", String(20), nullable=False),
- Column("placeId", Integer, nullable=False),
- Column("clientId", String(11), nullable=False),
- Column("printerSerialId", String(20), nullable=False),
- Column("isHolograph", Boolean, server_default="0"),
- Column("isAutographed", Boolean, server_default="0"),
- Column("printOption1", Boolean, server_default="1"),
- Column("printOption2", Boolean, server_default="1"),
- Column("printOption3", Boolean, server_default="1"),
- Column("printOption4", Boolean, server_default="1"),
- Column("printOption5", Boolean, server_default="1"),
- Column("printOption6", Boolean, server_default="1"),
- Column("printOption7", Boolean, server_default="1"),
- Column("printOption8", Boolean, server_default="1"),
- Column("printOption9", Boolean, server_default="1"),
- Column("printOption10", Boolean, server_default="0"),
- UniqueConstraint("serialId", name="ongeki_user_print_detail_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-class OngekiItemData(BaseData):
- def put_card(self, aime_id: int, card_data: Dict) -> Optional[int]:
- card_data["user"] = aime_id
-
- sql = insert(card).values(**card_data)
- conflict = sql.on_duplicate_key_update(**card_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_card: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_cards(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(card).where(card.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_character(self, aime_id: int, character_data: Dict) -> Optional[int]:
- character_data["user"] = aime_id
-
- sql = insert(character).values(**character_data)
- conflict = sql.on_duplicate_key_update(**character_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_character: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_characters(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(character).where(character.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_deck(self, aime_id: int, deck_data: Dict) -> Optional[int]:
- deck_data["user"] = aime_id
-
- sql = insert(deck).values(**deck_data)
- conflict = sql.on_duplicate_key_update(**deck_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_deck: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_deck(self, aime_id: int, deck_id: int) -> Optional[Dict]:
- sql = select(deck).where(and_(deck.c.user == aime_id, deck.c.deckId == deck_id))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_decks(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(deck).where(deck.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_boss(self, aime_id: int, boss_data: Dict) -> Optional[int]:
- boss_data["user"] = aime_id
-
- sql = insert(boss).values(**boss_data)
- conflict = sql.on_duplicate_key_update(**boss_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_boss: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def put_story(self, aime_id: int, story_data: Dict) -> Optional[int]:
- story_data["user"] = aime_id
-
- sql = insert(story).values(**story_data)
- conflict = sql.on_duplicate_key_update(**story_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_story: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_stories(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(story).where(story.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_chapter(self, aime_id: int, chapter_data: Dict) -> Optional[int]:
- chapter_data["user"] = aime_id
-
- sql = insert(chapter).values(**chapter_data)
- conflict = sql.on_duplicate_key_update(**chapter_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_chapter: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_chapters(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(chapter).where(chapter.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_item(self, aime_id: int, item_data: Dict) -> Optional[int]:
- item_data["user"] = aime_id
-
- sql = insert(item).values(**item_data)
- conflict = sql.on_duplicate_key_update(**item_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_item: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_item(self, aime_id: int, item_id: int, item_kind: int) -> Optional[Dict]:
- sql = select(item).where(and_(item.c.user == aime_id, item.c.itemId == item_id))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_items(self, aime_id: int, item_kind: int = None) -> Optional[List[Dict]]:
- if item_kind is None:
- sql = select(item).where(item.c.user == aime_id)
- else:
- sql = select(item).where(
- and_(item.c.user == aime_id, item.c.itemKind == item_kind)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_music_item(self, aime_id: int, music_item_data: Dict) -> Optional[int]:
- music_item_data["user"] = aime_id
-
- sql = insert(music_item).values(**music_item_data)
- conflict = sql.on_duplicate_key_update(**music_item_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_music_item: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_music_items(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(music_item).where(music_item.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_login_bonus(self, aime_id: int, login_bonus_data: Dict) -> Optional[int]:
- login_bonus_data["user"] = aime_id
-
- sql = insert(login_bonus).values(**login_bonus_data)
- conflict = sql.on_duplicate_key_update(**login_bonus_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_login_bonus: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_login_bonuses(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(login_bonus).where(login_bonus.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_mission_point(
- self, aime_id: int, mission_point_data: Dict
- ) -> Optional[int]:
- mission_point_data["user"] = aime_id
-
- sql = insert(mission_point).values(**mission_point_data)
- conflict = sql.on_duplicate_key_update(**mission_point_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_mission_point: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_mission_points(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(mission_point).where(mission_point.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_event_point(self, aime_id: int, event_point_data: Dict) -> Optional[int]:
- event_point_data["user"] = aime_id
-
- sql = insert(event_point).values(**event_point_data)
- conflict = sql.on_duplicate_key_update(**event_point_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_event_point: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_event_points(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(event_point).where(event_point.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_scenerio(self, aime_id: int, scenerio_data: Dict) -> Optional[int]:
- scenerio_data["user"] = aime_id
-
- sql = insert(scenerio).values(**scenerio_data)
- conflict = sql.on_duplicate_key_update(**scenerio_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_scenerio: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_scenerios(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(scenerio).where(scenerio.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_trade_item(self, aime_id: int, trade_item_data: Dict) -> Optional[int]:
- trade_item_data["user"] = aime_id
-
- sql = insert(trade_item).values(**trade_item_data)
- conflict = sql.on_duplicate_key_update(**trade_item_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_trade_item: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_trade_items(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(trade_item).where(trade_item.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_event_music(self, aime_id: int, event_music_data: Dict) -> Optional[int]:
- event_music_data["user"] = aime_id
-
- sql = insert(event_music).values(**event_music_data)
- conflict = sql.on_duplicate_key_update(**event_music_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_event_music: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_event_music(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(event_music).where(event_music.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_tech_event(self, aime_id: int, tech_event_data: Dict) -> Optional[int]:
- tech_event_data["user"] = aime_id
-
- sql = insert(tech_event).values(**tech_event_data)
- conflict = sql.on_duplicate_key_update(**tech_event_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_tech_event: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_tech_event(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(tech_event).where(tech_event.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def get_bosses(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(boss).where(boss.c.user == aime_id)
- result = self.execute(sql)
-
- if result is None:
- return None
- return result.fetchall()
-
- def put_memorychapter(
- self, aime_id: int, memorychapter_data: Dict
- ) -> Optional[int]:
- memorychapter_data["user"] = aime_id
-
- sql = insert(memorychapter).values(**memorychapter_data)
- conflict = sql.on_duplicate_key_update(**memorychapter_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]:
- sql = select(memorychapter).where(memorychapter.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_user_gacha(self, aime_id: int, gacha_id: int) -> Optional[Row]:
- sql = gacha.select(and_(gacha.c.user == aime_id, gacha.c.gachaId == gacha_id))
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
- sql = gacha.select(gacha.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_user_gacha_supplies(self, aime_id: int) -> Optional[List[Row]]:
- sql = gacha_supply.select(gacha_supply.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_user_gacha(self, aime_id: int, gacha_id: int, **data) -> Optional[int]:
- sql = insert(gacha).values(user=aime_id, gachaId=gacha_id, **data)
-
- conflict = sql.on_duplicate_key_update(user=aime_id, gachaId=gacha_id, **data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def put_user_print_detail(
- self, aime_id: int, serial_id: str, user_print_data: Dict
- ) -> Optional[int]:
- sql = insert(print_detail).values(
- user=aime_id, serialId=serial_id, **user_print_data
- )
-
- conflict = sql.on_duplicate_key_update(user=aime_id, **user_print_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
+from typing import Dict, Optional, List
+from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
+from sqlalchemy.types import Integer, String, TIMESTAMP, Boolean, JSON
+from sqlalchemy.schema import ForeignKey
+from sqlalchemy.engine import Row
+from sqlalchemy.sql import func, select
+from sqlalchemy.dialects.sqlite import insert
+
+from core.data.schema import BaseData, metadata
+
+card = Table(
+ "ongeki_user_card",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("cardId", Integer),
+ Column("digitalStock", Integer),
+ Column("analogStock", Integer),
+ Column("level", Integer),
+ Column("maxLevel", Integer),
+ Column("exp", Integer),
+ Column("printCount", Integer),
+ Column("useCount", Integer),
+ Column("isNew", Boolean),
+ Column("kaikaDate", String(25)),
+ Column("choKaikaDate", String(25)),
+ Column("skillId", Integer),
+ Column("isAcquired", Boolean),
+ Column("created", String(25)),
+ UniqueConstraint("user", "cardId", name="ongeki_user_card_uk"),
+ mysql_charset="utf8mb4",
+)
+
+deck = Table(
+ "ongeki_user_deck",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("deckId", Integer),
+ Column("cardId1", Integer),
+ Column("cardId2", Integer),
+ Column("cardId3", Integer),
+ UniqueConstraint("user", "deckId", name="ongeki_user_deck_uk"),
+ mysql_charset="utf8mb4",
+)
+
+character = Table(
+ "ongeki_user_character",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("characterId", Integer),
+ Column("costumeId", Integer),
+ Column("attachmentId", Integer),
+ Column("playCount", Integer),
+ Column("intimateLevel", Integer),
+ Column("intimateCount", Integer),
+ Column("intimateCountRewarded", Integer),
+ Column("intimateCountDate", String(25)),
+ Column("isNew", Boolean),
+ UniqueConstraint("user", "characterId", name="ongeki_user_character_uk"),
+ mysql_charset="utf8mb4",
+)
+
+boss = Table(
+ "ongeki_user_boss",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("musicId", Integer),
+ Column("damage", Integer),
+ Column("isClear", Boolean),
+ Column("eventId", Integer),
+ UniqueConstraint("user", "musicId", "eventId", name="ongeki_user_boss_uk"),
+ mysql_charset="utf8mb4",
+)
+
+story = Table(
+ "ongeki_user_story",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("storyId", Integer),
+ Column("jewelCount", Integer),
+ Column("lastChapterId", Integer),
+ Column("lastPlayMusicId", Integer),
+ Column("lastPlayMusicCategory", Integer),
+ Column("lastPlayMusicLevel", Integer),
+ UniqueConstraint("user", "storyId", name="ongeki_user_story_uk"),
+ mysql_charset="utf8mb4",
+)
+
+chapter = Table(
+ "ongeki_user_chapter",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("chapterId", Integer),
+ Column("jewelCount", Integer),
+ Column("isStoryWatched", Boolean),
+ Column("isClear", Boolean),
+ Column("lastPlayMusicId", Integer),
+ Column("lastPlayMusicCategory", Integer),
+ Column("lastPlayMusicLevel", Integer),
+ Column("skipTiming1", Integer),
+ Column("skipTiming2", Integer),
+ UniqueConstraint("user", "chapterId", name="ongeki_user_chapter_uk"),
+ mysql_charset="utf8mb4",
+)
+
+memorychapter = Table(
+ "ongeki_user_memorychapter",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("chapterId", Integer),
+ Column("gaugeId", Integer),
+ Column("gaugeNum", Integer),
+ Column("jewelCount", Integer),
+ Column("isStoryWatched", Boolean),
+ Column("isBossWatched", Boolean),
+ Column("isDialogWatched", Boolean),
+ Column("isEndingWatched", Boolean),
+ Column("isClear", Boolean),
+ Column("lastPlayMusicId", Integer),
+ Column("lastPlayMusicLevel", Integer),
+ Column("lastPlayMusicCategory", Integer),
+ UniqueConstraint("user", "chapterId", name="ongeki_user_memorychapter_uk"),
+ mysql_charset="utf8mb4",
+)
+
+item = Table(
+ "ongeki_user_item",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("itemKind", Integer),
+ Column("itemId", Integer),
+ Column("stock", Integer),
+ Column("isValid", Boolean),
+ UniqueConstraint("user", "itemKind", "itemId", name="ongeki_user_item_uk"),
+ mysql_charset="utf8mb4",
+)
+
+music_item = Table(
+ "ongeki_user_music_item",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("musicId", Integer),
+ Column("status", Integer),
+ UniqueConstraint("user", "musicId", name="ongeki_user_music_item_uk"),
+ mysql_charset="utf8mb4",
+)
+
+login_bonus = Table(
+ "ongeki_user_login_bonus",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("bonusId", Integer),
+ Column("bonusCount", Integer),
+ Column("lastUpdateDate", String(25)),
+ UniqueConstraint("user", "bonusId", name="ongeki_user_login_bonus_uk"),
+ mysql_charset="utf8mb4",
+)
+
+event_point = Table(
+ "ongeki_user_event_point",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("eventId", Integer),
+ Column("point", Integer),
+ Column("isRankingRewarded", Boolean),
+ UniqueConstraint("user", "eventId", name="ongeki_user_event_point_uk"),
+ mysql_charset="utf8mb4",
+)
+
+mission_point = Table(
+ "ongeki_user_mission_point",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("eventId", Integer),
+ Column("point", Integer),
+ UniqueConstraint("user", "eventId", name="ongeki_user_mission_point_uk"),
+ mysql_charset="utf8mb4",
+)
+
+scenerio = Table(
+ "ongeki_user_scenerio",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("scenarioId", Integer),
+ Column("playCount", Integer),
+ UniqueConstraint("user", "scenarioId", name="ongeki_user_scenerio_uk"),
+ mysql_charset="utf8mb4",
+)
+
+trade_item = Table(
+ "ongeki_user_trade_item",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("chapterId", Integer),
+ Column("tradeItemId", Integer),
+ Column("tradeCount", Integer),
+ UniqueConstraint(
+ "user", "chapterId", "tradeItemId", name="ongeki_user_trade_item_uk"
+ ),
+ mysql_charset="utf8mb4",
+)
+
+event_music = Table(
+ "ongeki_user_event_music",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("eventId", Integer),
+ Column("type", Integer),
+ Column("musicId", Integer),
+ Column("level", Integer),
+ Column("techScoreMax", Integer),
+ Column("platinumScoreMax", Integer),
+ Column("techRecordDate", String(25)),
+ Column("isTechNewRecord", Boolean),
+ UniqueConstraint(
+ "user", "eventId", "type", "musicId", "level", name="ongeki_user_event_music"
+ ),
+ mysql_charset="utf8mb4",
+)
+
+tech_event = Table(
+ "ongeki_user_tech_event",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("eventId", Integer),
+ Column("totalTechScore", Integer),
+ Column("totalPlatinumScore", Integer),
+ Column("techRecordDate", String(25)),
+ Column("isRankingRewarded", Boolean),
+ Column("isTotalTechNewRecord", Boolean),
+ UniqueConstraint("user", "eventId", name="ongeki_user_tech_event_uk"),
+ mysql_charset="utf8mb4",
+)
+
+gacha = Table(
+ "ongeki_user_gacha",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("gachaId", Integer, nullable=False),
+ Column("totalGachaCnt", Integer, server_default="0"),
+ Column("ceilingGachaCnt", Integer, server_default="0"),
+ Column("selectPoint", Integer, server_default="0"),
+ Column("useSelectPoint", Integer, server_default="0"),
+ Column("dailyGachaCnt", Integer, server_default="0"),
+ Column("fiveGachaCnt", Integer, server_default="0"),
+ Column("elevenGachaCnt", Integer, server_default="0"),
+ Column("dailyGachaDate", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "gachaId", name="ongeki_user_gacha_uk"),
+ mysql_charset="utf8mb4",
+)
+
+gacha_supply = Table(
+ "ongeki_user_gacha_supply",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("cardId", Integer, nullable=False),
+ UniqueConstraint("user", "cardId", name="ongeki_user_gacha_supply_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+print_detail = Table(
+ "ongeki_user_print_detail",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("cardId", Integer, nullable=False),
+ Column("cardType", Integer, server_default="0"),
+ Column("printDate", TIMESTAMP, nullable=False),
+ Column("serialId", String(20), nullable=False),
+ Column("placeId", Integer, nullable=False),
+ Column("clientId", String(11), nullable=False),
+ Column("printerSerialId", String(20), nullable=False),
+ Column("isHolograph", Boolean, server_default="0"),
+ Column("isAutographed", Boolean, server_default="0"),
+ Column("printOption1", Boolean, server_default="1"),
+ Column("printOption2", Boolean, server_default="1"),
+ Column("printOption3", Boolean, server_default="1"),
+ Column("printOption4", Boolean, server_default="1"),
+ Column("printOption5", Boolean, server_default="1"),
+ Column("printOption6", Boolean, server_default="1"),
+ Column("printOption7", Boolean, server_default="1"),
+ Column("printOption8", Boolean, server_default="1"),
+ Column("printOption9", Boolean, server_default="1"),
+ Column("printOption10", Boolean, server_default="0"),
+ UniqueConstraint("serialId", name="ongeki_user_print_detail_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class OngekiItemData(BaseData):
+ def put_card(self, aime_id: int, card_data: Dict) -> Optional[int]:
+ card_data["user"] = aime_id
+
+ sql = insert(card).values(**card_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**card_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_card: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_cards(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(card).where(card.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_character(self, aime_id: int, character_data: Dict) -> Optional[int]:
+ character_data["user"] = aime_id
+
+ sql = insert(character).values(**character_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**character_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_character: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_characters(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(character).where(character.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_deck(self, aime_id: int, deck_data: Dict) -> Optional[int]:
+ deck_data["user"] = aime_id
+
+ sql = insert(deck).values(**deck_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**deck_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_deck: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_deck(self, aime_id: int, deck_id: int) -> Optional[Dict]:
+ sql = select(deck).where(and_(deck.c.user == aime_id, deck.c.deckId == deck_id))
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_decks(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(deck).where(deck.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_boss(self, aime_id: int, boss_data: Dict) -> Optional[int]:
+ boss_data["user"] = aime_id
+
+ sql = insert(boss).values(**boss_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**boss_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_boss: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def put_story(self, aime_id: int, story_data: Dict) -> Optional[int]:
+ story_data["user"] = aime_id
+
+ sql = insert(story).values(**story_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**story_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_story: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_stories(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(story).where(story.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_chapter(self, aime_id: int, chapter_data: Dict) -> Optional[int]:
+ chapter_data["user"] = aime_id
+
+ sql = insert(chapter).values(**chapter_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**chapter_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_chapter: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_chapters(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(chapter).where(chapter.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_item(self, aime_id: int, item_data: Dict) -> Optional[int]:
+ item_data["user"] = aime_id
+
+ sql = insert(item).values(**item_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**item_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_item: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_item(self, aime_id: int, item_id: int, item_kind: int) -> Optional[Dict]:
+ sql = select(item).where(and_(item.c.user == aime_id, item.c.itemId == item_id))
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_items(self, aime_id: int, item_kind: int = None) -> Optional[List[Dict]]:
+ if item_kind is None:
+ sql = select(item).where(item.c.user == aime_id)
+ else:
+ sql = select(item).where(
+ and_(item.c.user == aime_id, item.c.itemKind == item_kind)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_music_item(self, aime_id: int, music_item_data: Dict) -> Optional[int]:
+ music_item_data["user"] = aime_id
+
+ sql = insert(music_item).values(**music_item_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**music_item_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_music_item: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_music_items(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(music_item).where(music_item.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_login_bonus(self, aime_id: int, login_bonus_data: Dict) -> Optional[int]:
+ login_bonus_data["user"] = aime_id
+
+ sql = insert(login_bonus).values(**login_bonus_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**login_bonus_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_login_bonus: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_login_bonuses(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(login_bonus).where(login_bonus.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_mission_point(
+ self, aime_id: int, mission_point_data: Dict
+ ) -> Optional[int]:
+ mission_point_data["user"] = aime_id
+
+ sql = insert(mission_point).values(**mission_point_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**mission_point_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_mission_point: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_mission_points(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(mission_point).where(mission_point.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_event_point(self, aime_id: int, event_point_data: Dict) -> Optional[int]:
+ event_point_data["user"] = aime_id
+
+ sql = insert(event_point).values(**event_point_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**event_point_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_event_point: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_event_points(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(event_point).where(event_point.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_scenerio(self, aime_id: int, scenerio_data: Dict) -> Optional[int]:
+ scenerio_data["user"] = aime_id
+
+ sql = insert(scenerio).values(**scenerio_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**scenerio_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_scenerio: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_scenerios(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(scenerio).where(scenerio.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_trade_item(self, aime_id: int, trade_item_data: Dict) -> Optional[int]:
+ trade_item_data["user"] = aime_id
+
+ sql = insert(trade_item).values(**trade_item_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**trade_item_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_trade_item: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_trade_items(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(trade_item).where(trade_item.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_event_music(self, aime_id: int, event_music_data: Dict) -> Optional[int]:
+ event_music_data["user"] = aime_id
+
+ sql = insert(event_music).values(**event_music_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**event_music_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_event_music: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_event_music(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(event_music).where(event_music.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_tech_event(self, aime_id: int, tech_event_data: Dict) -> Optional[int]:
+ tech_event_data["user"] = aime_id
+
+ sql = insert(tech_event).values(**tech_event_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**tech_event_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_tech_event: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_tech_event(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(tech_event).where(tech_event.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_bosses(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(boss).where(boss.c.user == aime_id)
+ result = self.execute(sql)
+
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_memorychapter(
+ self, aime_id: int, memorychapter_data: Dict
+ ) -> Optional[int]:
+ memorychapter_data["user"] = aime_id
+
+ sql = insert(memorychapter).values(**memorychapter_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**memorychapter_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]:
+ sql = select(memorychapter).where(memorychapter.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_user_gacha(self, aime_id: int, gacha_id: int) -> Optional[Row]:
+ sql = gacha.select(and_(gacha.c.user == aime_id, gacha.c.gachaId == gacha_id))
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_user_gachas(self, aime_id: int) -> Optional[List[Row]]:
+ sql = gacha.select(gacha.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_user_gacha_supplies(self, aime_id: int) -> Optional[List[Row]]:
+ sql = gacha_supply.select(gacha_supply.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_user_gacha(self, aime_id: int, gacha_id: int, **data) -> Optional[int]:
+ sql = insert(gacha).values(user=aime_id, gachaId=gacha_id, **data)
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(user=aime_id, gachaId=gacha_id, **data)
+ )
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_user_gacha: Failed to insert! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def put_user_print_detail(
+ self, aime_id: int, serial_id: str, user_print_data: Dict
+ ) -> Optional[int]:
+ sql = insert(print_detail).values(
+ user=aime_id, serialId=serial_id, **user_print_data
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(user=aime_id, **user_print_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_user_print_detail: Failed to insert! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
diff --git a/titles/ongeki/schema/log.py b/titles/ongeki/schema/log.py
index 701e8e0..a588a55 100644
--- a/titles/ongeki/schema/log.py
+++ b/titles/ongeki/schema/log.py
@@ -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
diff --git a/titles/ongeki/schema/profile.py b/titles/ongeki/schema/profile.py
index 374503e..61c2f3a 100644
--- a/titles/ongeki/schema/profile.py
+++ b/titles/ongeki/schema/profile.py
@@ -1,510 +1,512 @@
-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.sql import func, select
-from sqlalchemy.engine import Row
-from sqlalchemy.dialects.mysql import insert
-
-from core.data.schema import BaseData, metadata
-from core.config import CoreConfig
-
-# Cammel case column names technically don't follow the other games but
-# it makes it way easier on me to not fuck with what the games has
-profile = Table(
- "ongeki_profile_data",
- 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("userName", String(8)),
- Column("level", Integer),
- Column("reincarnationNum", Integer),
- Column("exp", Integer),
- Column("point", Integer),
- Column("totalPoint", Integer),
- Column("playCount", Integer),
- Column("jewelCount", Integer),
- Column("totalJewelCount", Integer),
- Column("medalCount", Integer),
- Column("playerRating", Integer),
- Column("highestRating", Integer),
- Column("battlePoint", Integer),
- Column("nameplateId", Integer),
- Column("trophyId", Integer),
- Column("cardId", Integer),
- Column("characterId", Integer),
- Column("characterVoiceNo", Integer),
- Column("tabSetting", Integer),
- Column("tabSortSetting", Integer),
- Column("cardCategorySetting", Integer),
- Column("cardSortSetting", Integer),
- Column("playedTutorialBit", Integer),
- Column("firstTutorialCancelNum", Integer),
- Column("sumTechHighScore", BigInteger),
- Column("sumTechBasicHighScore", BigInteger),
- Column("sumTechAdvancedHighScore", BigInteger),
- Column("sumTechExpertHighScore", BigInteger),
- Column("sumTechMasterHighScore", BigInteger),
- Column("sumTechLunaticHighScore", BigInteger),
- Column("sumBattleHighScore", BigInteger),
- Column("sumBattleBasicHighScore", BigInteger),
- Column("sumBattleAdvancedHighScore", BigInteger),
- Column("sumBattleExpertHighScore", BigInteger),
- Column("sumBattleMasterHighScore", BigInteger),
- Column("sumBattleLunaticHighScore", BigInteger),
- Column("eventWatchedDate", String(255)),
- Column("cmEventWatchedDate", String(255)),
- Column("firstGameId", String(8)),
- Column("firstRomVersion", String(8)),
- Column("firstDataVersion", String(8)),
- Column("firstPlayDate", String(255)),
- Column("lastGameId", String(8)),
- Column("lastRomVersion", String(8)),
- Column("lastDataVersion", String(8)),
- Column("compatibleCmVersion", String(8)),
- Column("lastPlayDate", String(255)),
- Column("lastPlaceId", Integer),
- Column("lastPlaceName", String(255)),
- Column("lastRegionId", Integer),
- Column("lastRegionName", String(255)),
- Column("lastAllNetId", Integer),
- Column("lastClientId", String(16)),
- Column("lastUsedDeckId", Integer),
- Column("lastPlayMusicLevel", Integer),
- Column("banStatus", Integer, server_default="0"),
- Column("rivalScoreCategorySetting", Integer, server_default="0"),
- Column("overDamageBattlePoint", Integer, server_default="0"),
- Column("bestBattlePoint", Integer, server_default="0"),
- Column("lastEmoneyBrand", Integer, server_default="0"),
- Column("lastEmoneyCredit", Integer, server_default="0"),
- Column("isDialogWatchedSuggestMemory", Boolean, server_default="0"),
- UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"),
- mysql_charset="utf8mb4",
-)
-
-# No point setting defaults since the game sends everything on profile creation anyway
-option = Table(
- "ongeki_profile_option",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("optionSet", Integer),
- Column("speed", Integer),
- Column("mirror", Integer),
- Column("judgeTiming", Integer),
- Column("judgeAdjustment", Integer),
- Column("abort", Integer),
- Column("tapSound", Integer),
- Column("volGuide", Integer),
- Column("volAll", Integer),
- Column("volTap", Integer),
- Column("volCrTap", Integer),
- Column("volHold", Integer),
- Column("volSide", Integer),
- Column("volFlick", Integer),
- Column("volBell", Integer),
- Column("volEnemy", Integer),
- Column("volSkill", Integer),
- Column("volDamage", Integer),
- Column("colorField", Integer),
- Column("colorLaneBright", Integer),
- Column("colorLane", Integer),
- Column("colorSide", Integer),
- Column("effectDamage", Integer),
- Column("effectPos", Integer),
- Column("judgeDisp", Integer),
- Column("judgePos", Integer),
- Column("judgeBreak", Integer),
- Column("judgeHit", Integer),
- Column("platinumBreakDisp", Integer),
- Column("judgeCriticalBreak", Integer),
- Column("matching", Integer),
- Column("dispPlayerLv", Integer),
- Column("dispRating", Integer),
- Column("dispBP", Integer),
- Column("headphone", Integer),
- Column("stealthField", Integer),
- Column("colorWallBright", Integer),
- UniqueConstraint("user", name="ongeki_profile_option_uk"),
- mysql_charset="utf8mb4",
-)
-
-activity = Table(
- "ongeki_profile_activity",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("kind", Integer),
- Column("activityId", Integer),
- Column("sortNumber", Integer),
- Column("param1", Integer),
- Column("param2", Integer),
- Column("param3", Integer),
- Column("param4", Integer),
- UniqueConstraint("user", "kind", "activityId", name="ongeki_profile_activity_uk"),
- mysql_charset="utf8mb4",
-)
-
-recent_rating = Table(
- "ongeki_profile_recent_rating",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("recentRating", JSON),
- UniqueConstraint("user", name="ongeki_profile_recent_rating_uk"),
- mysql_charset="utf8mb4",
-)
-
-rating_log = Table(
- "ongeki_profile_rating_log",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("highestRating", Integer),
- Column("dataVersion", String(10)),
- UniqueConstraint("user", "dataVersion", name="ongeki_profile_rating_log_uk"),
- mysql_charset="utf8mb4",
-)
-
-region = Table(
- "ongeki_profile_region",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("regionId", Integer),
- Column("playCount", Integer),
- Column("created", String(25)),
- UniqueConstraint("user", "regionId", name="ongeki_profile_region_uk"),
- mysql_charset="utf8mb4",
-)
-
-training_room = Table(
- "ongeki_profile_training_room",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("roomId", Integer),
- Column("authKey", Integer),
- Column("cardId", Integer),
- Column("valueDate", String(25)),
- UniqueConstraint("user", "roomId", name="ongeki_profile_training_room_uk"),
- mysql_charset="utf8mb4",
-)
-
-kop = Table(
- "ongeki_profile_kop",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column("authKey", Integer),
- Column("kopId", Integer),
- Column("areaId", Integer),
- Column("totalTechScore", Integer),
- Column("totalPlatinumScore", Integer),
- Column("techRecordDate", String(25)),
- Column("isTotalTechNewRecord", Boolean),
- UniqueConstraint("user", "kopId", name="ongeki_profile_kop_uk"),
- mysql_charset="utf8mb4",
-)
-
-rival = Table(
- "ongeki_profile_rival",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
- Column(
- "rivalUserId",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- ),
- UniqueConstraint("user", "rivalUserId", name="ongeki_profile_rival_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-class OngekiProfileData(BaseData):
- def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
- super().__init__(cfg, conn)
- self.date_time_format_ext = (
- "%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
- )
- self.date_time_format_short = "%Y-%m-%d"
-
- def get_profile_name(self, aime_id: int, version: int) -> Optional[str]:
- sql = select(profile.c.userName).where(
- and_(profile.c.user == aime_id, profile.c.version == version)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
-
- row = result.fetchone()
- if row is None:
- return None
-
- return row["userName"]
-
- def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
- sql = (
- select([profile, option])
- .join(option, profile.c.user == option.c.user)
- .filter(and_(profile.c.user == aime_id, profile.c.version == version))
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
- sql = select(profile).where(
- and_(
- profile.c.user == aime_id,
- profile.c.version == version,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_profile_options(self, aime_id: int) -> Optional[Row]:
- sql = select(option).where(
- and_(
- option.c.user == aime_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_profile_recent_rating(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(recent_rating).where(recent_rating.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_profile_rating_log(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(rating_log).where(rating_log.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_profile_activity(
- self, aime_id: int, kind: int = None
- ) -> Optional[List[Row]]:
- sql = select(activity).where(
- and_(
- activity.c.user == aime_id,
- (activity.c.kind == kind) if kind is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_kop(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(kop).where(kop.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_rivals(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(rival.c.rivalUserId).where(rival.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def put_profile_data(self, aime_id: int, version: int, data: Dict) -> Optional[int]:
- data["user"] = aime_id
- data["version"] = version
- data.pop("accessCode")
-
- sql = insert(profile).values(**data)
- conflict = sql.on_duplicate_key_update(**data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def put_profile_options(self, aime_id: int, options_data: Dict) -> Optional[int]:
- options_data["user"] = aime_id
-
- sql = insert(option).values(**options_data)
- conflict = sql.on_duplicate_key_update(**options_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(
- f"put_profile_options: Failed to update! aime_id: {aime_id}"
- )
- return None
- return result.lastrowid
-
- def put_profile_recent_rating(
- self, aime_id: int, recent_rating_data: List[Dict]
- ) -> Optional[int]:
- sql = insert(recent_rating).values(
- user=aime_id, recentRating=recent_rating_data
- )
-
- conflict = sql.on_duplicate_key_update(recentRating=recent_rating_data)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile_recent_rating: failed to update recent rating! aime_id {aime_id}"
- )
- return None
- return result.lastrowid
-
- def put_profile_bp_list(
- self, aime_id: int, bp_base_list: List[Dict]
- ) -> Optional[int]:
- pass
-
- def put_profile_rating_log(
- self, aime_id: int, data_version: str, highest_rating: int
- ) -> Optional[int]:
- sql = insert(rating_log).values(
- user=aime_id, dataVersion=data_version, highestRating=highest_rating
- )
-
- conflict = sql.on_duplicate_key_update(highestRating=highest_rating)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile_rating_log: failed to update rating log! aime_id {aime_id} data_version {data_version} highest_rating {highest_rating}"
- )
- return None
- return result.lastrowid
-
- def put_profile_activity(
- self,
- aime_id: int,
- kind: int,
- activity_id: int,
- sort_num: int,
- p1: int,
- p2: int,
- p3: int,
- p4: int,
- ) -> Optional[int]:
- sql = insert(activity).values(
- user=aime_id,
- kind=kind,
- activityId=activity_id,
- sortNumber=sort_num,
- param1=p1,
- param2=p2,
- param3=p3,
- param4=p4,
- )
-
- conflict = sql.on_duplicate_key_update(
- sortNumber=sort_num, param1=p1, param2=p2, param3=p3, param4=p4
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile_activity: failed to put activity! aime_id {aime_id} kind {kind} activity_id {activity_id}"
- )
- return None
- return result.lastrowid
-
- def put_profile_region(self, aime_id: int, region: int, date: str) -> Optional[int]:
- sql = insert(activity).values(
- user=aime_id, region=region, playCount=1, created=date
- )
-
- conflict = sql.on_duplicate_key_update(
- playCount=activity.c.playCount + 1,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_profile_region: failed to update! aime_id {aime_id} region {region}"
- )
- return None
- return result.lastrowid
-
- def put_training_room(self, aime_id: int, room_detail: Dict) -> Optional[int]:
- room_detail["user"] = aime_id
-
- sql = insert(training_room).values(**room_detail)
- conflict = sql.on_duplicate_key_update(**room_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_kop(self, aime_id: int, kop_data: Dict) -> Optional[int]:
- kop_data["user"] = aime_id
-
- sql = insert(kop).values(**kop_data)
- conflict = sql.on_duplicate_key_update(**kop_data)
- result = self.execute(conflict)
-
- if result is None:
- self.logger.warn(f"put_kop: Failed to add score! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def put_rival(self, aime_id: int, rival_id: int) -> Optional[int]:
- sql = insert(rival).values(user=aime_id, rivalUserId=rival_id)
-
- conflict = sql.on_duplicate_key_update(rival=rival_id)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_rival: failed to update! aime_id: {aime_id}, rival_id: {rival_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, BigInteger
+from sqlalchemy.engine.base import Connection
+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.config import CoreConfig
+
+# Cammel case column names technically don't follow the other games but
+# it makes it way easier on me to not fuck with what the games has
+profile = Table(
+ "ongeki_profile_data",
+ 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("userName", String(8)),
+ Column("level", Integer),
+ Column("reincarnationNum", Integer),
+ Column("exp", Integer),
+ Column("point", Integer),
+ Column("totalPoint", Integer),
+ Column("playCount", Integer),
+ Column("jewelCount", Integer),
+ Column("totalJewelCount", Integer),
+ Column("medalCount", Integer),
+ Column("playerRating", Integer),
+ Column("highestRating", Integer),
+ Column("battlePoint", Integer),
+ Column("nameplateId", Integer),
+ Column("trophyId", Integer),
+ Column("cardId", Integer),
+ Column("characterId", Integer),
+ Column("characterVoiceNo", Integer),
+ Column("tabSetting", Integer),
+ Column("tabSortSetting", Integer),
+ Column("cardCategorySetting", Integer),
+ Column("cardSortSetting", Integer),
+ Column("playedTutorialBit", Integer),
+ Column("firstTutorialCancelNum", Integer),
+ Column("sumTechHighScore", BigInteger),
+ Column("sumTechBasicHighScore", BigInteger),
+ Column("sumTechAdvancedHighScore", BigInteger),
+ Column("sumTechExpertHighScore", BigInteger),
+ Column("sumTechMasterHighScore", BigInteger),
+ Column("sumTechLunaticHighScore", BigInteger),
+ Column("sumBattleHighScore", BigInteger),
+ Column("sumBattleBasicHighScore", BigInteger),
+ Column("sumBattleAdvancedHighScore", BigInteger),
+ Column("sumBattleExpertHighScore", BigInteger),
+ Column("sumBattleMasterHighScore", BigInteger),
+ Column("sumBattleLunaticHighScore", BigInteger),
+ Column("eventWatchedDate", String(255)),
+ Column("cmEventWatchedDate", String(255)),
+ Column("firstGameId", String(8)),
+ Column("firstRomVersion", String(8)),
+ Column("firstDataVersion", String(8)),
+ Column("firstPlayDate", String(255)),
+ Column("lastGameId", String(8)),
+ Column("lastRomVersion", String(8)),
+ Column("lastDataVersion", String(8)),
+ Column("compatibleCmVersion", String(8)),
+ Column("lastPlayDate", String(255)),
+ Column("lastPlaceId", Integer),
+ Column("lastPlaceName", String(255)),
+ Column("lastRegionId", Integer),
+ Column("lastRegionName", String(255)),
+ Column("lastAllNetId", Integer),
+ Column("lastClientId", String(16)),
+ Column("lastUsedDeckId", Integer),
+ Column("lastPlayMusicLevel", Integer),
+ Column("banStatus", Integer, server_default="0"),
+ Column("rivalScoreCategorySetting", Integer, server_default="0"),
+ Column("overDamageBattlePoint", Integer, server_default="0"),
+ Column("bestBattlePoint", Integer, server_default="0"),
+ Column("lastEmoneyBrand", Integer, server_default="0"),
+ Column("lastEmoneyCredit", Integer, server_default="0"),
+ Column("isDialogWatchedSuggestMemory", Boolean, server_default="0"),
+ UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"),
+ mysql_charset="utf8mb4",
+)
+
+# No point setting defaults since the game sends everything on profile creation anyway
+option = Table(
+ "ongeki_profile_option",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("optionSet", Integer),
+ Column("speed", Integer),
+ Column("mirror", Integer),
+ Column("judgeTiming", Integer),
+ Column("judgeAdjustment", Integer),
+ Column("abort", Integer),
+ Column("tapSound", Integer),
+ Column("volGuide", Integer),
+ Column("volAll", Integer),
+ Column("volTap", Integer),
+ Column("volCrTap", Integer),
+ Column("volHold", Integer),
+ Column("volSide", Integer),
+ Column("volFlick", Integer),
+ Column("volBell", Integer),
+ Column("volEnemy", Integer),
+ Column("volSkill", Integer),
+ Column("volDamage", Integer),
+ Column("colorField", Integer),
+ Column("colorLaneBright", Integer),
+ Column("colorLane", Integer),
+ Column("colorSide", Integer),
+ Column("effectDamage", Integer),
+ Column("effectPos", Integer),
+ Column("judgeDisp", Integer),
+ Column("judgePos", Integer),
+ Column("judgeBreak", Integer),
+ Column("judgeHit", Integer),
+ Column("platinumBreakDisp", Integer),
+ Column("judgeCriticalBreak", Integer),
+ Column("matching", Integer),
+ Column("dispPlayerLv", Integer),
+ Column("dispRating", Integer),
+ Column("dispBP", Integer),
+ Column("headphone", Integer),
+ Column("stealthField", Integer),
+ Column("colorWallBright", Integer),
+ UniqueConstraint("user", name="ongeki_profile_option_uk"),
+ mysql_charset="utf8mb4",
+)
+
+activity = Table(
+ "ongeki_profile_activity",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("kind", Integer),
+ Column("activityId", Integer),
+ Column("sortNumber", Integer),
+ Column("param1", Integer),
+ Column("param2", Integer),
+ Column("param3", Integer),
+ Column("param4", Integer),
+ UniqueConstraint("user", "kind", "activityId", name="ongeki_profile_activity_uk"),
+ mysql_charset="utf8mb4",
+)
+
+recent_rating = Table(
+ "ongeki_profile_recent_rating",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("recentRating", JSON),
+ UniqueConstraint("user", name="ongeki_profile_recent_rating_uk"),
+ mysql_charset="utf8mb4",
+)
+
+rating_log = Table(
+ "ongeki_profile_rating_log",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("highestRating", Integer),
+ Column("dataVersion", String(10)),
+ UniqueConstraint("user", "dataVersion", name="ongeki_profile_rating_log_uk"),
+ mysql_charset="utf8mb4",
+)
+
+region = Table(
+ "ongeki_profile_region",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("regionId", Integer),
+ Column("playCount", Integer),
+ Column("created", String(25)),
+ UniqueConstraint("user", "regionId", name="ongeki_profile_region_uk"),
+ mysql_charset="utf8mb4",
+)
+
+training_room = Table(
+ "ongeki_profile_training_room",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("roomId", Integer),
+ Column("authKey", Integer),
+ Column("cardId", Integer),
+ Column("valueDate", String(25)),
+ UniqueConstraint("user", "roomId", name="ongeki_profile_training_room_uk"),
+ mysql_charset="utf8mb4",
+)
+
+kop = Table(
+ "ongeki_profile_kop",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column("authKey", Integer),
+ Column("kopId", Integer),
+ Column("areaId", Integer),
+ Column("totalTechScore", Integer),
+ Column("totalPlatinumScore", Integer),
+ Column("techRecordDate", String(25)),
+ Column("isTotalTechNewRecord", Boolean),
+ UniqueConstraint("user", "kopId", name="ongeki_profile_kop_uk"),
+ mysql_charset="utf8mb4",
+)
+
+rival = Table(
+ "ongeki_profile_rival",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
+ Column(
+ "rivalUserId",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ ),
+ UniqueConstraint("user", "rivalUserId", name="ongeki_profile_rival_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class OngekiProfileData(BaseData):
+ def __init__(self, cfg: CoreConfig, conn: Connection) -> None:
+ super().__init__(cfg, conn)
+ self.date_time_format_ext = (
+ "%Y-%m-%d %H:%M:%S.%f" # needs to be lopped off at [:-5]
+ )
+ self.date_time_format_short = "%Y-%m-%d"
+
+ def get_profile_name(self, aime_id: int, version: int) -> Optional[str]:
+ sql = select(profile.c.userName).where(
+ and_(profile.c.user == aime_id, profile.c.version == version)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+
+ row = result.fetchone()
+ if row is None:
+ return None
+
+ return row["userName"]
+
+ def get_profile_preview(self, aime_id: int, version: int) -> Optional[Row]:
+ sql = (
+ select([profile, option])
+ .join(option, profile.c.user == option.c.user)
+ .filter(and_(profile.c.user == aime_id, profile.c.version == version))
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_profile_data(self, aime_id: int, version: int) -> Optional[Row]:
+ sql = select(profile).where(
+ and_(
+ profile.c.user == aime_id,
+ profile.c.version == version,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_profile_options(self, aime_id: int) -> Optional[Row]:
+ sql = select(option).where(
+ and_(
+ option.c.user == aime_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_profile_recent_rating(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(recent_rating).where(recent_rating.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_profile_rating_log(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(rating_log).where(rating_log.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_profile_activity(
+ self, aime_id: int, kind: int = None
+ ) -> Optional[List[Row]]:
+ sql = select(activity).where(
+ and_(
+ activity.c.user == aime_id,
+ (activity.c.kind == kind) if kind is not None else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_kop(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(kop).where(kop.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_rivals(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(rival.c.rivalUserId).where(rival.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def put_profile_data(self, aime_id: int, version: int, data: Dict) -> Optional[int]:
+ data["user"] = aime_id
+ data["version"] = version
+ data.pop("accessCode")
+
+ sql = insert(profile).values(**data)
+ conflict = sql.on_conflict_do_update(set_=dict(**data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_profile_data: Failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def put_profile_options(self, aime_id: int, options_data: Dict) -> Optional[int]:
+ options_data["user"] = aime_id
+
+ sql = insert(option).values(**options_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**options_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(
+ f"put_profile_options: Failed to update! aime_id: {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_profile_recent_rating(
+ self, aime_id: int, recent_rating_data: List[Dict]
+ ) -> Optional[int]:
+ sql = insert(recent_rating).values(
+ user=aime_id, recentRating=recent_rating_data
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(recentRating=recent_rating_data))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_recent_rating: failed to update recent rating! aime_id {aime_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_profile_bp_list(
+ self, aime_id: int, bp_base_list: List[Dict]
+ ) -> Optional[int]:
+ pass
+
+ def put_profile_rating_log(
+ self, aime_id: int, data_version: str, highest_rating: int
+ ) -> Optional[int]:
+ sql = insert(rating_log).values(
+ user=aime_id, dataVersion=data_version, highestRating=highest_rating
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(highestRating=highest_rating))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_rating_log: failed to update rating log! aime_id {aime_id} data_version {data_version} highest_rating {highest_rating}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_profile_activity(
+ self,
+ aime_id: int,
+ kind: int,
+ activity_id: int,
+ sort_num: int,
+ p1: int,
+ p2: int,
+ p3: int,
+ p4: int,
+ ) -> Optional[int]:
+ sql = insert(activity).values(
+ user=aime_id,
+ kind=kind,
+ activityId=activity_id,
+ sortNumber=sort_num,
+ param1=p1,
+ param2=p2,
+ param3=p3,
+ param4=p4,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(sortNumber=sort_num, param1=p1, param2=p2, param3=p3, param4=p4)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_activity: failed to put activity! aime_id {aime_id} kind {kind} activity_id {activity_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_profile_region(self, aime_id: int, region: int, date: str) -> Optional[int]:
+ sql = insert(activity).values(
+ user=aime_id, region=region, playCount=1, created=date
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ playCount=activity.c.playCount + 1,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_profile_region: failed to update! aime_id {aime_id} region {region}"
+ )
+ return None
+ return result.lastrowid
+
+ def put_training_room(self, aime_id: int, room_detail: Dict) -> Optional[int]:
+ room_detail["user"] = aime_id
+
+ sql = insert(training_room).values(**room_detail)
+ conflict = sql.on_conflict_do_update(set_=dict(**room_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_kop(self, aime_id: int, kop_data: Dict) -> Optional[int]:
+ kop_data["user"] = aime_id
+
+ sql = insert(kop).values(**kop_data)
+ conflict = sql.on_conflict_do_update(set_=dict(**kop_data))
+ result = self.execute(conflict)
+
+ if result is None:
+ self.logger.warn(f"put_kop: Failed to add score! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def put_rival(self, aime_id: int, rival_id: int) -> Optional[int]:
+ sql = insert(rival).values(user=aime_id, rivalUserId=rival_id)
+
+ conflict = sql.on_conflict_do_update(set_=dict(rival=rival_id))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_rival: failed to update! aime_id: {aime_id}, rival_id: {rival_id}"
+ )
+ return None
+ return result.lastrowid
diff --git a/titles/ongeki/schema/score.py b/titles/ongeki/schema/score.py
index 8bb9fc9..2783ee5 100644
--- a/titles/ongeki/schema/score.py
+++ b/titles/ongeki/schema/score.py
@@ -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
diff --git a/titles/ongeki/schema/static.py b/titles/ongeki/schema/static.py
index b34802d..8e9304e 100644
--- a/titles/ongeki/schema/static.py
+++ b/titles/ongeki/schema/static.py
@@ -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()
diff --git a/titles/pokken/base.py b/titles/pokken/base.py
index 0b849b3..9d03613 100644
--- a/titles/pokken/base.py
+++ b/titles/pokken/base.py
@@ -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}}
diff --git a/titles/pokken/config.py b/titles/pokken/config.py
index d3741af..45c5956 100644
--- a/titles/pokken/config.py
+++ b/titles/pokken/config.py
@@ -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:
diff --git a/titles/pokken/frontend.py b/titles/pokken/frontend.py
index af344dc..08eb32e 100644
--- a/titles/pokken/frontend.py
+++ b/titles/pokken/frontend.py
@@ -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")
diff --git a/titles/pokken/index.py b/titles/pokken/index.py
index 3ccb3fc..6ba292f 100644
--- a/titles/pokken/index.py
+++ b/titles/pokken/index.py
@@ -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)
diff --git a/titles/pokken/schema/__init__.py b/titles/pokken/schema/__init__.py
index 81b8132..17a5806 100644
--- a/titles/pokken/schema/__init__.py
+++ b/titles/pokken/schema/__init__.py
@@ -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
diff --git a/titles/pokken/schema/item.py b/titles/pokken/schema/item.py
index 32bff2a..10d84c3 100644
--- a/titles/pokken/schema/item.py
+++ b/titles/pokken/schema/item.py
@@ -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
diff --git a/titles/pokken/schema/match.py b/titles/pokken/schema/match.py
index c84ec63..dec99f7 100644
--- a/titles/pokken/schema/match.py
+++ b/titles/pokken/schema/match.py
@@ -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
diff --git a/titles/pokken/schema/profile.py b/titles/pokken/schema/profile.py
index 812964d..438c56b 100644
--- a/titles/pokken/schema/profile.py
+++ b/titles/pokken/schema/profile.py
@@ -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}"
+ )
diff --git a/titles/pokken/schema/static.py b/titles/pokken/schema/static.py
index 121ebc4..976c3b6 100644
--- a/titles/pokken/schema/static.py
+++ b/titles/pokken/schema/static.py
@@ -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
diff --git a/titles/pokken/services.py b/titles/pokken/services.py
index 952c232..9f915c9 100644
--- a/titles/pokken/services.py
+++ b/titles/pokken/services.py
@@ -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
diff --git a/titles/sao/base.py b/titles/sao/base.py
index 3c4aade..ade6599 100644
--- a/titles/sao/base.py
+++ b/titles/sao/base.py
@@ -6,6 +6,7 @@ import struct
from csv import *
from random import choice
import random as rand
+from contextlib import nullcontext
from core.data import Data
from core import CoreConfig
@@ -13,6 +14,7 @@ from .config import SaoConfig
from .database import SaoData
from titles.sao.handlers.base import *
+
class SaoBase:
def __init__(self, core_cfg: CoreConfig, game_cfg: SaoConfig) -> None:
self.core_cfg = core_cfg
@@ -25,67 +27,103 @@ class SaoBase:
def handle_noop(self, request: Any) -> bytes:
sao_request = request
- sao_id = int(sao_request[:4],16) + 1
+ sao_id = int(sao_request[:4], 16) + 1
ret = struct.pack("!HHIIIIIIb", sao_id, 0, 0, 5, 1, 1, 5, 0x01000000, 0).hex()
return bytes.fromhex(ret)
def handle_c122(self, request: Any) -> bytes:
- #common/get_maintenance_info
- resp = SaoGetMaintResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # common/get_maintenance_info
+ resp = SaoGetMaintResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c12e(self, request: Any) -> bytes:
- #common/ac_cabinet_boot_notification
- resp = SaoCommonAcCabinetBootNotificationResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # common/ac_cabinet_boot_notification
+ resp = SaoCommonAcCabinetBootNotificationResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c100(self, request: Any) -> bytes:
- #common/get_app_versions
- resp = SaoCommonGetAppVersionsRequest(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # common/get_app_versions
+ resp = SaoCommonGetAppVersionsRequest(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c102(self, request: Any) -> bytes:
- #common/master_data_version_check
- resp = SaoMasterDataVersionCheckResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # common/master_data_version_check
+ resp = SaoMasterDataVersionCheckResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c10a(self, request: Any) -> bytes:
- #common/paying_play_start
- resp = SaoCommonPayingPlayStartRequest(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # common/paying_play_start
+ resp = SaoCommonPayingPlayStartRequest(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_ca02(self, request: Any) -> bytes:
- #quest_multi_play_room/get_quest_scene_multi_play_photon_server
- resp = SaoGetQuestSceneMultiPlayPhotonServerResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # quest_multi_play_room/get_quest_scene_multi_play_photon_server
+ resp = SaoGetQuestSceneMultiPlayPhotonServerResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c11e(self, request: Any) -> bytes:
- #common/get_auth_card_data
+ # common/get_auth_card_data
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
"cabinet_type" / Int8ub, # cabinet_type is a byte
"auth_type" / Int8ub, # auth_type is a byte
- "store_id_size" / Rebuild(Int32ub, len_(this.store_id) * 2), # calculates the length of the store_id
- "store_id" / PaddedString(this.store_id_size, "utf_16_le"), # store_id is a (zero) padded string
- "serial_no_size" / Rebuild(Int32ub, len_(this.serial_no) * 2), # calculates the length of the serial_no
- "serial_no" / PaddedString(this.serial_no_size, "utf_16_le"), # serial_no is a (zero) padded string
- "access_code_size" / Rebuild(Int32ub, len_(this.access_code) * 2), # calculates the length of the access_code
- "access_code" / PaddedString(this.access_code_size, "utf_16_le"), # access_code is a (zero) padded string
- "chip_id_size" / Rebuild(Int32ub, len_(this.chip_id) * 2), # calculates the length of the chip_id
- "chip_id" / PaddedString(this.chip_id_size, "utf_16_le"), # chip_id is a (zero) padded string
+ "store_id_size"
+ / Rebuild(
+ Int32ub, len_(this.store_id) * 2
+ ), # calculates the length of the store_id
+ "store_id"
+ / PaddedString(
+ this.store_id_size, "utf_16_le"
+ ), # store_id is a (zero) padded string
+ "serial_no_size"
+ / Rebuild(
+ Int32ub, len_(this.serial_no) * 2
+ ), # calculates the length of the serial_no
+ "serial_no"
+ / PaddedString(
+ this.serial_no_size, "utf_16_le"
+ ), # serial_no is a (zero) padded string
+ "access_code_size"
+ / Rebuild(
+ Int32ub, len_(this.access_code) * 2
+ ), # calculates the length of the access_code
+ "access_code"
+ / PaddedString(
+ this.access_code_size, "utf_16_le"
+ ), # access_code is a (zero) padded string
+ "chip_id_size"
+ / Rebuild(
+ Int32ub, len_(this.chip_id) * 2
+ ), # calculates the length of the chip_id
+ "chip_id"
+ / PaddedString(
+ this.chip_id_size, "utf_16_le"
+ ), # chip_id is a (zero) padded string
)
req_data = req_struct.parse(req)
access_code = req_data.access_code
- #Check authentication
- user_id = self.core_data.card.get_user_id_from_card( access_code )
+ # Check authentication
+ user_id = self.core_data.card.get_user_id_from_card(access_code)
if not user_id:
- user_id = self.core_data.user.create_user() #works
+ user_id = self.core_data.user.create_user() # works
card_id = self.core_data.card.create_card(user_id, access_code)
if card_id is None:
@@ -94,95 +132,157 @@ class SaoBase:
# Create profile with 3 basic heroes
profile_id = self.game_data.profile.create_profile(user_id)
- self.game_data.item.put_hero_log(user_id, 101000010, 1, 0, 101000016, 0, 30086, 1001, 1002, 1003, 1005)
- self.game_data.item.put_hero_log(user_id, 102000010, 1, 0, 103000006, 0, 30086, 1001, 1002, 1003, 1005)
- self.game_data.item.put_hero_log(user_id, 103000010, 1, 0, 112000009, 0, 30086, 1001, 1002, 1003, 1005)
- self.game_data.item.put_hero_party(user_id, 0, 101000010, 102000010, 103000010)
- self.game_data.item.put_equipment_data(user_id, 101000016, 1, 200, 0, 0, 0)
- self.game_data.item.put_equipment_data(user_id, 103000006, 1, 200, 0, 0, 0)
- self.game_data.item.put_equipment_data(user_id, 112000009, 1, 200, 0, 0, 0)
- self.game_data.item.put_player_quest(user_id, 1001, True, 300, 0, 0, 1)
+
+ with self.game_data.item.conn.begin() or nullcontext():
+ self.game_data.item.put_hero_log(
+ user_id, 101000010, 1, 0, 101000016, 0, 30086, 1001, 1002, 1003, 1005
+ )
+ self.game_data.item.put_hero_log(
+ user_id, 102000010, 1, 0, 103000006, 0, 30086, 1001, 1002, 1003, 1005
+ )
+ self.game_data.item.put_hero_log(
+ user_id, 103000010, 1, 0, 112000009, 0, 30086, 1001, 1002, 1003, 1005
+ )
+ self.game_data.item.put_hero_party(
+ user_id, 0, 101000010, 102000010, 103000010
+ )
+ self.game_data.item.put_equipment_data(user_id, 101000016, 1, 200, 0, 0, 0)
+ self.game_data.item.put_equipment_data(user_id, 103000006, 1, 200, 0, 0, 0)
+ self.game_data.item.put_equipment_data(user_id, 112000009, 1, 200, 0, 0, 0)
+ self.game_data.item.put_player_quest(user_id, 1001, True, 300, 0, 0, 1)
# Force the tutorial stage to be completed due to potential crash in-game
-
self.logger.info(f"User Authenticated: { access_code } | { user_id }")
- #Grab values from profile
+ # Grab values from profile
profile_data = self.game_data.profile.get_profile(user_id)
if user_id and not profile_data:
profile_id = self.game_data.profile.create_profile(user_id)
- self.game_data.item.put_hero_log(user_id, 101000010, 1, 0, 101000016, 0, 30086, 1001, 1002, 1003, 1005)
- self.game_data.item.put_hero_log(user_id, 102000010, 1, 0, 103000006, 0, 30086, 1001, 1002, 1003, 1005)
- self.game_data.item.put_hero_log(user_id, 103000010, 1, 0, 112000009, 0, 30086, 1001, 1002, 1003, 1005)
- self.game_data.item.put_hero_party(user_id, 0, 101000010, 102000010, 103000010)
- self.game_data.item.put_equipment_data(user_id, 101000016, 1, 200, 0, 0, 0)
- self.game_data.item.put_equipment_data(user_id, 103000006, 1, 200, 0, 0, 0)
- self.game_data.item.put_equipment_data(user_id, 112000009, 1, 200, 0, 0, 0)
- self.game_data.item.put_player_quest(user_id, 1001, True, 300, 0, 0, 1)
+
+ with self.game_data.item.conn.begin() or nullcontext():
+ self.game_data.item.put_hero_log(
+ user_id, 101000010, 1, 0, 101000016, 0, 30086, 1001, 1002, 1003, 1005
+ )
+ self.game_data.item.put_hero_log(
+ user_id, 102000010, 1, 0, 103000006, 0, 30086, 1001, 1002, 1003, 1005
+ )
+ self.game_data.item.put_hero_log(
+ user_id, 103000010, 1, 0, 112000009, 0, 30086, 1001, 1002, 1003, 1005
+ )
+ self.game_data.item.put_hero_party(
+ user_id, 0, 101000010, 102000010, 103000010
+ )
+ self.game_data.item.put_equipment_data(user_id, 101000016, 1, 200, 0, 0, 0)
+ self.game_data.item.put_equipment_data(user_id, 103000006, 1, 200, 0, 0, 0)
+ self.game_data.item.put_equipment_data(user_id, 112000009, 1, 200, 0, 0, 0)
+ self.game_data.item.put_player_quest(user_id, 1001, True, 300, 0, 0, 1)
# Force the tutorial stage to be completed due to potential crash in-game
-
-
profile_data = self.game_data.profile.get_profile(user_id)
- resp = SaoGetAuthCardDataResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, profile_data)
+ resp = SaoGetAuthCardDataResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, profile_data
+ )
return resp.make()
def handle_c40c(self, request: Any) -> bytes:
- #home/check_ac_login_bonus
- resp = SaoHomeCheckAcLoginBonusResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # home/check_ac_login_bonus
+ resp = SaoHomeCheckAcLoginBonusResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c104(self, request: Any) -> bytes:
- #common/login
+ # common/login
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
"cabinet_type" / Int8ub, # cabinet_type is a byte
"auth_type" / Int8ub, # auth_type is a byte
- "store_id_size" / Rebuild(Int32ub, len_(this.store_id) * 2), # calculates the length of the store_id
- "store_id" / PaddedString(this.store_id_size, "utf_16_le"), # store_id is a (zero) padded string
- "store_name_size" / Rebuild(Int32ub, len_(this.store_name) * 2), # calculates the length of the store_name
- "store_name" / PaddedString(this.store_name_size, "utf_16_le"), # store_name is a (zero) padded string
- "serial_no_size" / Rebuild(Int32ub, len_(this.serial_no) * 2), # calculates the length of the serial_no
- "serial_no" / PaddedString(this.serial_no_size, "utf_16_le"), # serial_no is a (zero) padded string
- "access_code_size" / Rebuild(Int32ub, len_(this.access_code) * 2), # calculates the length of the access_code
- "access_code" / PaddedString(this.access_code_size, "utf_16_le"), # access_code is a (zero) padded string
- "chip_id_size" / Rebuild(Int32ub, len_(this.chip_id) * 2), # calculates the length of the chip_id
- "chip_id" / PaddedString(this.chip_id_size, "utf_16_le"), # chip_id is a (zero) padded string
- "free_ticket_distribution_target_flag" / Int8ub, # free_ticket_distribution_target_flag is a byte
+ "store_id_size"
+ / Rebuild(
+ Int32ub, len_(this.store_id) * 2
+ ), # calculates the length of the store_id
+ "store_id"
+ / PaddedString(
+ this.store_id_size, "utf_16_le"
+ ), # store_id is a (zero) padded string
+ "store_name_size"
+ / Rebuild(
+ Int32ub, len_(this.store_name) * 2
+ ), # calculates the length of the store_name
+ "store_name"
+ / PaddedString(
+ this.store_name_size, "utf_16_le"
+ ), # store_name is a (zero) padded string
+ "serial_no_size"
+ / Rebuild(
+ Int32ub, len_(this.serial_no) * 2
+ ), # calculates the length of the serial_no
+ "serial_no"
+ / PaddedString(
+ this.serial_no_size, "utf_16_le"
+ ), # serial_no is a (zero) padded string
+ "access_code_size"
+ / Rebuild(
+ Int32ub, len_(this.access_code) * 2
+ ), # calculates the length of the access_code
+ "access_code"
+ / PaddedString(
+ this.access_code_size, "utf_16_le"
+ ), # access_code is a (zero) padded string
+ "chip_id_size"
+ / Rebuild(
+ Int32ub, len_(this.chip_id) * 2
+ ), # calculates the length of the chip_id
+ "chip_id"
+ / PaddedString(
+ this.chip_id_size, "utf_16_le"
+ ), # chip_id is a (zero) padded string
+ "free_ticket_distribution_target_flag"
+ / Int8ub, # free_ticket_distribution_target_flag is a byte
)
req_data = req_struct.parse(req)
access_code = req_data.access_code
- user_id = self.core_data.card.get_user_id_from_card( access_code )
+ user_id = self.core_data.card.get_user_id_from_card(access_code)
profile_data = self.game_data.profile.get_profile(user_id)
- resp = SaoCommonLoginResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, profile_data)
+ resp = SaoCommonLoginResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, profile_data
+ )
return resp.make()
def handle_c404(self, request: Any) -> bytes:
- #home/check_comeback_event
- resp = SaoCheckComebackEventRequest(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # home/check_comeback_event
+ resp = SaoCheckComebackEventRequest(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c000(self, request: Any) -> bytes:
- #ticket/ticket
- resp = SaoTicketResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # ticket/ticket
+ resp = SaoTicketResponse(int.from_bytes(bytes.fromhex(request[:4]), "big") + 1)
return resp.make()
def handle_c500(self, request: Any) -> bytes:
- #user_info/get_user_basic_data
+ # user_info/get_user_basic_data
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
@@ -190,85 +290,118 @@ class SaoBase:
profile_data = self.game_data.profile.get_profile(user_id)
- resp = SaoGetUserBasicDataResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, profile_data)
+ resp = SaoGetUserBasicDataResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, profile_data
+ )
return resp.make()
-
+
def handle_c600(self, request: Any) -> bytes:
- #have_object/get_hero_log_user_data_list
+ # have_object/get_hero_log_user_data_list
req = bytes.fromhex(request)[24:]
-
+
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
-
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
hero_data = self.game_data.item.get_hero_logs(user_id)
-
- resp = SaoGetHeroLogUserDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, hero_data)
+
+ resp = SaoGetHeroLogUserDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, hero_data
+ )
return resp.make()
-
+
def handle_c602(self, request: Any) -> bytes:
- #have_object/get_equipment_user_data_list
+ # have_object/get_equipment_user_data_list
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
-
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
-
+
equipment_data = self.game_data.item.get_user_equipments(user_id)
- resp = SaoGetEquipmentUserDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, equipment_data)
+ resp = SaoGetEquipmentUserDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, equipment_data
+ )
return resp.make()
-
+
def handle_c604(self, request: Any) -> bytes:
- #have_object/get_item_user_data_list
+ # have_object/get_item_user_data_list
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
-
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
item_data = self.game_data.item.get_user_items(user_id)
- resp = SaoGetItemUserDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, item_data)
+ resp = SaoGetItemUserDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, item_data
+ )
return resp.make()
-
+
def handle_c606(self, request: Any) -> bytes:
- #have_object/get_support_log_user_data_list
+ # have_object/get_support_log_user_data_list
supportIdsData = self.game_data.static.get_support_log_ids(0, True)
-
- resp = SaoGetSupportLogUserDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, supportIdsData)
+
+ resp = SaoGetSupportLogUserDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, supportIdsData
+ )
return resp.make()
-
+
def handle_c800(self, request: Any) -> bytes:
- #custom/get_title_user_data_list
+ # custom/get_title_user_data_list
titleIdsData = self.game_data.static.get_title_ids(0, True)
-
- resp = SaoGetTitleUserDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, titleIdsData)
+
+ resp = SaoGetTitleUserDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, titleIdsData
+ )
return resp.make()
-
+
def handle_c608(self, request: Any) -> bytes:
- #have_object/get_episode_append_data_list
+ # have_object/get_episode_append_data_list
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
@@ -276,18 +409,25 @@ class SaoBase:
profile_data = self.game_data.profile.get_profile(user_id)
- resp = SaoGetEpisodeAppendDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, profile_data)
+ resp = SaoGetEpisodeAppendDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, profile_data
+ )
return resp.make()
def handle_c804(self, request: Any) -> bytes:
- #custom/get_party_data_list
+ # custom/get_party_data_list
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
-
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
@@ -297,302 +437,568 @@ class SaoBase:
hero2_data = self.game_data.item.get_hero_log(user_id, hero_party[4])
hero3_data = self.game_data.item.get_hero_log(user_id, hero_party[5])
- resp = SaoGetPartyDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, hero1_data, hero2_data, hero3_data)
+ resp = SaoGetPartyDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1,
+ hero1_data,
+ hero2_data,
+ hero3_data,
+ )
return resp.make()
- def handle_c902(self, request: Any) -> bytes: # for whatever reason, having all entries empty or filled changes nothing
- #quest/get_quest_scene_prev_scan_profile_card
- resp = SaoGetQuestScenePrevScanProfileCardResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ def handle_c902(
+ self, request: Any
+ ) -> bytes: # for whatever reason, having all entries empty or filled changes nothing
+ # quest/get_quest_scene_prev_scan_profile_card
+ resp = SaoGetQuestScenePrevScanProfileCardResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c124(self, request: Any) -> bytes:
- #common/get_resource_path_info
- resp = SaoGetResourcePathInfoResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # common/get_resource_path_info
+ resp = SaoGetResourcePathInfoResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c900(self, request: Any) -> bytes:
- #quest/get_quest_scene_user_data_list // QuestScene.csv
+ # quest/get_quest_scene_user_data_list // QuestScene.csv
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
-
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
quest_data = self.game_data.item.get_quest_logs(user_id)
- resp = SaoGetQuestSceneUserDataListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, quest_data)
+ resp = SaoGetQuestSceneUserDataListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, quest_data
+ )
return resp.make()
def handle_c400(self, request: Any) -> bytes:
- #home/check_yui_medal_get_condition
- resp = SaoCheckYuiMedalGetConditionResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # home/check_yui_medal_get_condition
+ resp = SaoCheckYuiMedalGetConditionResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c402(self, request: Any) -> bytes:
- #home/get_yui_medal_bonus_user_data
- resp = SaoGetYuiMedalBonusUserDataResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # home/get_yui_medal_bonus_user_data
+ resp = SaoGetYuiMedalBonusUserDataResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c40a(self, request: Any) -> bytes:
- #home/check_profile_card_used_reward
- resp = SaoCheckProfileCardUsedRewardResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # home/check_profile_card_used_reward
+ resp = SaoCheckProfileCardUsedRewardResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c814(self, request: Any) -> bytes:
- #custom/synthesize_enhancement_hero_log
+ # custom/synthesize_enhancement_hero_log
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(20),
"ticket_id" / Bytes(1), # needs to be parsed as an int
Padding(1),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
- "origin_user_hero_log_id_size" / Rebuild(Int32ub, len_(this.origin_user_hero_log_id) * 2), # calculates the length of the origin_user_hero_log_id
- "origin_user_hero_log_id" / PaddedString(this.origin_user_hero_log_id_size, "utf_16_le"), # origin_user_hero_log_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
+ "origin_user_hero_log_id_size"
+ / Rebuild(
+ Int32ub, len_(this.origin_user_hero_log_id) * 2
+ ), # calculates the length of the origin_user_hero_log_id
+ "origin_user_hero_log_id"
+ / PaddedString(
+ this.origin_user_hero_log_id_size, "utf_16_le"
+ ), # origin_user_hero_log_id is a (zero) padded string
Padding(3),
- "material_common_reward_user_data_list_length" / Rebuild(Int8ub, len_(this.material_common_reward_user_data_list)), # material_common_reward_user_data_list is a byte,
- "material_common_reward_user_data_list" / Array(this.material_common_reward_user_data_list_length, Struct(
- "common_reward_type" / Int16ub, # team_no is a byte
- "user_common_reward_id_size" / Rebuild(Int32ub, len_(this.user_common_reward_id) * 2), # calculates the length of the user_common_reward_id
- "user_common_reward_id" / PaddedString(this.user_common_reward_id_size, "utf_16_le"), # user_common_reward_id is a (zero) padded string
- )),
+ "material_common_reward_user_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.material_common_reward_user_data_list)
+ ), # material_common_reward_user_data_list is a byte,
+ "material_common_reward_user_data_list"
+ / Array(
+ this.material_common_reward_user_data_list_length,
+ Struct(
+ "common_reward_type" / Int16ub, # team_no is a byte
+ "user_common_reward_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_common_reward_id) * 2
+ ), # calculates the length of the user_common_reward_id
+ "user_common_reward_id"
+ / PaddedString(
+ this.user_common_reward_id_size, "utf_16_le"
+ ), # user_common_reward_id is a (zero) padded string
+ ),
+ ),
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
- synthesize_hero_log_data = self.game_data.item.get_hero_log(req_data.user_id, req_data.origin_user_hero_log_id)
+ synthesize_hero_log_data = self.game_data.item.get_hero_log(
+ req_data.user_id, req_data.origin_user_hero_log_id
+ )
- for i in range(0,req_data.material_common_reward_user_data_list_length):
+ with (
+ self.game_data.item.conn.begin() or nullcontext(),
+ self.game_data.profile.conn.begin() or nullcontext(),
+ ):
+ for i in range(0, req_data.material_common_reward_user_data_list_length):
- itemList = self.game_data.static.get_item_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- heroList = self.game_data.static.get_hero_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- equipmentList = self.game_data.static.get_equipment_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id)
+ itemList = self.game_data.static.get_item_id(
+ req_data.material_common_reward_user_data_list[i].user_common_reward_id
+ )
+ heroList = self.game_data.static.get_hero_id(
+ req_data.material_common_reward_user_data_list[i].user_common_reward_id
+ )
+ equipmentList = self.game_data.static.get_equipment_id(
+ req_data.material_common_reward_user_data_list[i].user_common_reward_id
+ )
- if itemList:
- hero_exp = 2000 + int(synthesize_hero_log_data["log_exp"])
- self.game_data.item.remove_item(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
+ if itemList:
+ hero_exp = 2000 + int(synthesize_hero_log_data["log_exp"])
+ self.game_data.item.remove_item(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
- if equipmentList:
- equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- hero_exp = int(equipment_data["enhancement_exp"]) + int(synthesize_hero_log_data["log_exp"])
- self.game_data.item.remove_equipment(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
+ if equipmentList:
+ equipment_data = self.game_data.item.get_user_equipment(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
+ hero_exp = int(equipment_data["enhancement_exp"]) + int(
+ synthesize_hero_log_data["log_exp"]
+ )
+ self.game_data.item.remove_equipment(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
- if heroList:
- hero_data = self.game_data.item.get_hero_log(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- hero_exp = int(hero_data["log_exp"]) + int(synthesize_hero_log_data["log_exp"])
- self.game_data.item.remove_hero_log(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
+ if heroList:
+ hero_data = self.game_data.item.get_hero_log(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
+ hero_exp = int(hero_data["log_exp"]) + int(
+ synthesize_hero_log_data["log_exp"]
+ )
+ self.game_data.item.remove_hero_log(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
- self.game_data.item.put_hero_log(
- user_id,
- int(req_data.origin_user_hero_log_id),
- synthesize_hero_log_data["log_level"],
- hero_exp,
- synthesize_hero_log_data["main_weapon"],
- synthesize_hero_log_data["sub_equipment"],
- synthesize_hero_log_data["skill_slot1_skill_id"],
- synthesize_hero_log_data["skill_slot2_skill_id"],
- synthesize_hero_log_data["skill_slot3_skill_id"],
- synthesize_hero_log_data["skill_slot4_skill_id"],
- synthesize_hero_log_data["skill_slot5_skill_id"]
- )
+ self.game_data.item.put_hero_log(
+ user_id,
+ int(req_data.origin_user_hero_log_id),
+ synthesize_hero_log_data["log_level"],
+ hero_exp,
+ synthesize_hero_log_data["main_weapon"],
+ synthesize_hero_log_data["sub_equipment"],
+ synthesize_hero_log_data["skill_slot1_skill_id"],
+ synthesize_hero_log_data["skill_slot2_skill_id"],
+ synthesize_hero_log_data["skill_slot3_skill_id"],
+ synthesize_hero_log_data["skill_slot4_skill_id"],
+ synthesize_hero_log_data["skill_slot5_skill_id"],
+ )
- profile = self.game_data.profile.get_profile(req_data.user_id)
- new_col = int(profile["own_col"]) - 100
+ profile = self.game_data.profile.get_profile(req_data.user_id)
+ new_col = int(profile["own_col"]) - 100
- # Update profile
-
- self.game_data.profile.put_profile(
- req_data.user_id,
- profile["user_type"],
- profile["nick_name"],
- profile["rank_num"],
- profile["rank_exp"],
- new_col,
- profile["own_vp"],
- profile["own_yui_medal"],
- profile["setting_title_id"]
- )
+ # Update profile
- # Load the item again to push to the response handler
- synthesize_hero_log_data = self.game_data.item.get_hero_log(req_data.user_id, req_data.origin_user_hero_log_id)
+ self.game_data.profile.put_profile(
+ req_data.user_id,
+ profile["user_type"],
+ profile["nick_name"],
+ profile["rank_num"],
+ profile["rank_exp"],
+ new_col,
+ profile["own_vp"],
+ profile["own_yui_medal"],
+ profile["setting_title_id"],
+ )
- resp = SaoSynthesizeEnhancementHeroLogResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, synthesize_hero_log_data)
+ # Load the item again to push to the response handler
+ synthesize_hero_log_data = self.game_data.item.get_hero_log(
+ req_data.user_id, req_data.origin_user_hero_log_id
+ )
+
+ resp = SaoSynthesizeEnhancementHeroLogResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1,
+ synthesize_hero_log_data,
+ )
return resp.make()
def handle_c816(self, request: Any) -> bytes:
- #custom/synthesize_enhancement_equipment
+ # custom/synthesize_enhancement_equipment
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(20),
"ticket_id" / Bytes(1), # needs to be parsed as an int
Padding(1),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
- "origin_user_equipment_id_size" / Rebuild(Int32ub, len_(this.origin_user_equipment_id) * 2), # calculates the length of the origin_user_equipment_id
- "origin_user_equipment_id" / PaddedString(this.origin_user_equipment_id_size, "utf_16_le"), # origin_user_equipment_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
+ "origin_user_equipment_id_size"
+ / Rebuild(
+ Int32ub, len_(this.origin_user_equipment_id) * 2
+ ), # calculates the length of the origin_user_equipment_id
+ "origin_user_equipment_id"
+ / PaddedString(
+ this.origin_user_equipment_id_size, "utf_16_le"
+ ), # origin_user_equipment_id is a (zero) padded string
Padding(3),
- "material_common_reward_user_data_list_length" / Rebuild(Int8ub, len_(this.material_common_reward_user_data_list)), # material_common_reward_user_data_list is a byte,
- "material_common_reward_user_data_list" / Array(this.material_common_reward_user_data_list_length, Struct(
- "common_reward_type" / Int16ub, # team_no is a byte
- "user_common_reward_id_size" / Rebuild(Int32ub, len_(this.user_common_reward_id) * 2), # calculates the length of the user_common_reward_id
- "user_common_reward_id" / PaddedString(this.user_common_reward_id_size, "utf_16_le"), # user_common_reward_id is a (zero) padded string
- )),
+ "material_common_reward_user_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.material_common_reward_user_data_list)
+ ), # material_common_reward_user_data_list is a byte,
+ "material_common_reward_user_data_list"
+ / Array(
+ this.material_common_reward_user_data_list_length,
+ Struct(
+ "common_reward_type" / Int16ub, # team_no is a byte
+ "user_common_reward_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_common_reward_id) * 2
+ ), # calculates the length of the user_common_reward_id
+ "user_common_reward_id"
+ / PaddedString(
+ this.user_common_reward_id_size, "utf_16_le"
+ ), # user_common_reward_id is a (zero) padded string
+ ),
+ ),
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
- synthesize_equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id)
+ synthesize_equipment_data = self.game_data.item.get_user_equipment(
+ req_data.user_id, req_data.origin_user_equipment_id
+ )
- for i in range(0,req_data.material_common_reward_user_data_list_length):
+ with (
+ self.game_data.item.conn.begin() or nullcontext(),
+ self.game_data.profile.conn.begin() or nullcontext(),
+ ):
+ for i in range(0, req_data.material_common_reward_user_data_list_length):
- itemList = self.game_data.static.get_item_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- heroList = self.game_data.static.get_hero_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- equipmentList = self.game_data.static.get_equipment_id(req_data.material_common_reward_user_data_list[i].user_common_reward_id)
-
- if itemList:
- equipment_exp = 2000 + int(synthesize_equipment_data["enhancement_exp"])
- self.game_data.item.remove_item(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
-
- if equipmentList:
- equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- equipment_exp = int(equipment_data["enhancement_exp"]) + int(synthesize_equipment_data["enhancement_exp"])
- self.game_data.item.remove_equipment(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
-
- if heroList:
- hero_data = self.game_data.item.get_hero_log(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
- equipment_exp = int(hero_data["log_exp"]) + int(synthesize_equipment_data["enhancement_exp"])
- self.game_data.item.remove_hero_log(req_data.user_id, req_data.material_common_reward_user_data_list[i].user_common_reward_id)
-
- self.game_data.item.put_equipment_data(req_data.user_id, int(req_data.origin_user_equipment_id), synthesize_equipment_data["enhancement_value"], equipment_exp, 0, 0, 0)
-
- profile = self.game_data.profile.get_profile(req_data.user_id)
- new_col = int(profile["own_col"]) - 100
-
- # Update profile
-
- self.game_data.profile.put_profile(
- req_data.user_id,
- profile["user_type"],
- profile["nick_name"],
- profile["rank_num"],
- profile["rank_exp"],
- new_col,
- profile["own_vp"],
- profile["own_yui_medal"],
- profile["setting_title_id"]
+ itemList = self.game_data.static.get_item_id(
+ req_data.material_common_reward_user_data_list[i].user_common_reward_id
+ )
+ heroList = self.game_data.static.get_hero_id(
+ req_data.material_common_reward_user_data_list[i].user_common_reward_id
+ )
+ equipmentList = self.game_data.static.get_equipment_id(
+ req_data.material_common_reward_user_data_list[i].user_common_reward_id
)
- # Load the item again to push to the response handler
- synthesize_equipment_data = self.game_data.item.get_user_equipment(req_data.user_id, req_data.origin_user_equipment_id)
+ if itemList:
+ equipment_exp = 2000 + int(synthesize_equipment_data["enhancement_exp"])
+ self.game_data.item.remove_item(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
- resp = SaoSynthesizeEnhancementEquipmentResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, synthesize_equipment_data)
+ if equipmentList:
+ equipment_data = self.game_data.item.get_user_equipment(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
+ equipment_exp = int(equipment_data["enhancement_exp"]) + int(
+ synthesize_equipment_data["enhancement_exp"]
+ )
+ self.game_data.item.remove_equipment(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
+
+ if heroList:
+ hero_data = self.game_data.item.get_hero_log(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
+ equipment_exp = int(hero_data["log_exp"]) + int(
+ synthesize_equipment_data["enhancement_exp"]
+ )
+ self.game_data.item.remove_hero_log(
+ req_data.user_id,
+ req_data.material_common_reward_user_data_list[
+ i
+ ].user_common_reward_id,
+ )
+
+ self.game_data.item.put_equipment_data(
+ req_data.user_id,
+ int(req_data.origin_user_equipment_id),
+ synthesize_equipment_data["enhancement_value"],
+ equipment_exp,
+ 0,
+ 0,
+ 0,
+ )
+
+ profile = self.game_data.profile.get_profile(req_data.user_id)
+ new_col = int(profile["own_col"]) - 100
+
+ # Update profile
+
+ self.game_data.profile.put_profile(
+ req_data.user_id,
+ profile["user_type"],
+ profile["nick_name"],
+ profile["rank_num"],
+ profile["rank_exp"],
+ new_col,
+ profile["own_vp"],
+ profile["own_yui_medal"],
+ profile["setting_title_id"],
+ )
+
+ # Load the item again to push to the response handler
+ synthesize_equipment_data = self.game_data.item.get_user_equipment(
+ req_data.user_id, req_data.origin_user_equipment_id
+ )
+
+ resp = SaoSynthesizeEnhancementEquipmentResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1,
+ synthesize_equipment_data,
+ )
return resp.make()
def handle_c806(self, request: Any) -> bytes:
- #custom/change_party
+ # custom/change_party
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(20),
"ticket_id" / Bytes(1), # needs to be parsed as an int
Padding(1),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
"act_type" / Int8ub, # play_mode is a byte
Padding(3),
- "party_data_list_length" / Rebuild(Int8ub, len_(this.party_data_list)), # party_data_list is a byte,
- "party_data_list" / Array(this.party_data_list_length, Struct(
- "user_party_id_size" / Rebuild(Int32ub, len_(this.user_party_id) * 2), # calculates the length of the user_party_id
- "user_party_id" / PaddedString(this.user_party_id_size, "utf_16_le"), # user_party_id is a (zero) padded string
- "team_no" / Int8ub, # team_no is a byte
- Padding(3),
- "party_team_data_list_length" / Rebuild(Int8ub, len_(this.party_team_data_list)), # party_team_data_list is a byte
- "party_team_data_list" / Array(this.party_team_data_list_length, Struct(
- "user_party_team_id_size" / Rebuild(Int32ub, len_(this.user_party_team_id) * 2), # calculates the length of the user_party_team_id
- "user_party_team_id" / PaddedString(this.user_party_team_id_size, "utf_16_le"), # user_party_team_id is a (zero) padded string
- "arrangement_num" / Int8ub, # arrangement_num is a byte
- "user_hero_log_id_size" / Rebuild(Int32ub, len_(this.user_hero_log_id) * 2), # calculates the length of the user_hero_log_id
- "user_hero_log_id" / PaddedString(this.user_hero_log_id_size, "utf_16_le"), # user_hero_log_id is a (zero) padded string
- "main_weapon_user_equipment_id_size" / Rebuild(Int32ub, len_(this.main_weapon_user_equipment_id) * 2), # calculates the length of the main_weapon_user_equipment_id
- "main_weapon_user_equipment_id" / PaddedString(this.main_weapon_user_equipment_id_size, "utf_16_le"), # main_weapon_user_equipment_id is a (zero) padded string
- "sub_equipment_user_equipment_id_size" / Rebuild(Int32ub, len_(this.sub_equipment_user_equipment_id) * 2), # calculates the length of the sub_equipment_user_equipment_id
- "sub_equipment_user_equipment_id" / PaddedString(this.sub_equipment_user_equipment_id_size, "utf_16_le"), # sub_equipment_user_equipment_id is a (zero) padded string
- "skill_slot1_skill_id" / Int32ub, # skill_slot1_skill_id is a int,
- "skill_slot2_skill_id" / Int32ub, # skill_slot1_skill_id is a int,
- "skill_slot3_skill_id" / Int32ub, # skill_slot1_skill_id is a int,
- "skill_slot4_skill_id" / Int32ub, # skill_slot1_skill_id is a int,
- "skill_slot5_skill_id" / Int32ub, # skill_slot1_skill_id is a int,
- )),
- )),
-
+ "party_data_list_length"
+ / Rebuild(Int8ub, len_(this.party_data_list)), # party_data_list is a byte,
+ "party_data_list"
+ / Array(
+ this.party_data_list_length,
+ Struct(
+ "user_party_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_party_id) * 2
+ ), # calculates the length of the user_party_id
+ "user_party_id"
+ / PaddedString(
+ this.user_party_id_size, "utf_16_le"
+ ), # user_party_id is a (zero) padded string
+ "team_no" / Int8ub, # team_no is a byte
+ Padding(3),
+ "party_team_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.party_team_data_list)
+ ), # party_team_data_list is a byte
+ "party_team_data_list"
+ / Array(
+ this.party_team_data_list_length,
+ Struct(
+ "user_party_team_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_party_team_id) * 2
+ ), # calculates the length of the user_party_team_id
+ "user_party_team_id"
+ / PaddedString(
+ this.user_party_team_id_size, "utf_16_le"
+ ), # user_party_team_id is a (zero) padded string
+ "arrangement_num" / Int8ub, # arrangement_num is a byte
+ "user_hero_log_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_hero_log_id) * 2
+ ), # calculates the length of the user_hero_log_id
+ "user_hero_log_id"
+ / PaddedString(
+ this.user_hero_log_id_size, "utf_16_le"
+ ), # user_hero_log_id is a (zero) padded string
+ "main_weapon_user_equipment_id_size"
+ / Rebuild(
+ Int32ub, len_(this.main_weapon_user_equipment_id) * 2
+ ), # calculates the length of the main_weapon_user_equipment_id
+ "main_weapon_user_equipment_id"
+ / PaddedString(
+ this.main_weapon_user_equipment_id_size, "utf_16_le"
+ ), # main_weapon_user_equipment_id is a (zero) padded string
+ "sub_equipment_user_equipment_id_size"
+ / Rebuild(
+ Int32ub, len_(this.sub_equipment_user_equipment_id) * 2
+ ), # calculates the length of the sub_equipment_user_equipment_id
+ "sub_equipment_user_equipment_id"
+ / PaddedString(
+ this.sub_equipment_user_equipment_id_size, "utf_16_le"
+ ), # sub_equipment_user_equipment_id is a (zero) padded string
+ "skill_slot1_skill_id"
+ / Int32ub, # skill_slot1_skill_id is a int,
+ "skill_slot2_skill_id"
+ / Int32ub, # skill_slot1_skill_id is a int,
+ "skill_slot3_skill_id"
+ / Int32ub, # skill_slot1_skill_id is a int,
+ "skill_slot4_skill_id"
+ / Int32ub, # skill_slot1_skill_id is a int,
+ "skill_slot5_skill_id"
+ / Int32ub, # skill_slot1_skill_id is a int,
+ ),
+ ),
+ ),
+ ),
)
req_data = req_struct.parse(req)
user_id = req_data.user_id
party_hero_list = []
- for party_team in req_data.party_data_list[0].party_team_data_list:
- hero_data = self.game_data.item.get_hero_log(user_id, party_team["user_hero_log_id"])
- hero_level = 1
- hero_exp = 0
+ with self.game_data.item.conn.begin() or nullcontext():
+ for party_team in req_data.party_data_list[0].party_team_data_list:
+ hero_data = self.game_data.item.get_hero_log(
+ user_id, party_team["user_hero_log_id"]
+ )
+ hero_level = 1
+ hero_exp = 0
- if hero_data:
- hero_level = hero_data["log_level"]
- hero_exp = hero_data["log_exp"]
+ if hero_data:
+ hero_level = hero_data["log_level"]
+ hero_exp = hero_data["log_exp"]
- self.game_data.item.put_hero_log(
+ self.game_data.item.put_hero_log(
+ user_id,
+ party_team["user_hero_log_id"],
+ hero_level,
+ hero_exp,
+ party_team["main_weapon_user_equipment_id"],
+ party_team["sub_equipment_user_equipment_id"],
+ party_team["skill_slot1_skill_id"],
+ party_team["skill_slot2_skill_id"],
+ party_team["skill_slot3_skill_id"],
+ party_team["skill_slot4_skill_id"],
+ party_team["skill_slot5_skill_id"],
+ )
+
+ party_hero_list.append(party_team["user_hero_log_id"])
+
+ self.game_data.item.put_hero_party(
user_id,
- party_team["user_hero_log_id"],
- hero_level,
- hero_exp,
- party_team["main_weapon_user_equipment_id"],
- party_team["sub_equipment_user_equipment_id"],
- party_team["skill_slot1_skill_id"],
- party_team["skill_slot2_skill_id"],
- party_team["skill_slot3_skill_id"],
- party_team["skill_slot4_skill_id"],
- party_team["skill_slot5_skill_id"]
+ req_data.party_data_list[0].party_team_data_list[0].user_party_team_id,
+ party_hero_list[0],
+ party_hero_list[1],
+ party_hero_list[2],
)
- party_hero_list.append(party_team["user_hero_log_id"])
-
- self.game_data.item.put_hero_party(user_id, req_data.party_data_list[0].party_team_data_list[0].user_party_team_id, party_hero_list[0], party_hero_list[1], party_hero_list[2])
-
- resp = SaoNoopResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ resp = SaoNoopResponse(int.from_bytes(bytes.fromhex(request[:4]), "big") + 1)
return resp.make()
def handle_c904(self, request: Any) -> bytes:
- #quest/episode_play_start
+ # quest/episode_play_start
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(20),
"ticket_id" / Bytes(1), # needs to be parsed as an int
Padding(1),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
"episode_id" / Int32ub, # episode_id is a int,
"play_mode" / Int8ub, # play_mode is a byte
Padding(3),
- "play_start_request_data_length" / Rebuild(Int8ub, len_(this.play_start_request_data)), # play_start_request_data_length is a byte,
- "play_start_request_data" / Array(this.play_start_request_data_length, Struct(
- "user_party_id_size" / Rebuild(Int32ub, len_(this.user_party_id) * 2), # calculates the length of the user_party_id
- "user_party_id" / PaddedString(this.user_party_id_size, "utf_16_le"), # user_party_id is a (zero) padded string
- "appoint_leader_resource_card_code_size" / Rebuild(Int32ub, len_(this.appoint_leader_resource_card_code) * 2), # calculates the length of the total_damage
- "appoint_leader_resource_card_code" / PaddedString(this.appoint_leader_resource_card_code_size, "utf_16_le"), # total_damage is a (zero) padded string
- "use_profile_card_code_size" / Rebuild(Int32ub, len_(this.use_profile_card_code) * 2), # calculates the length of the total_damage
- "use_profile_card_code" / PaddedString(this.use_profile_card_code_size, "utf_16_le"), # use_profile_card_code is a (zero) padded string
- "quest_drop_boost_apply_flag" / Int8ub, # quest_drop_boost_apply_flag is a byte
- )),
-
+ "play_start_request_data_length"
+ / Rebuild(
+ Int8ub, len_(this.play_start_request_data)
+ ), # play_start_request_data_length is a byte,
+ "play_start_request_data"
+ / Array(
+ this.play_start_request_data_length,
+ Struct(
+ "user_party_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_party_id) * 2
+ ), # calculates the length of the user_party_id
+ "user_party_id"
+ / PaddedString(
+ this.user_party_id_size, "utf_16_le"
+ ), # user_party_id is a (zero) padded string
+ "appoint_leader_resource_card_code_size"
+ / Rebuild(
+ Int32ub, len_(this.appoint_leader_resource_card_code) * 2
+ ), # calculates the length of the total_damage
+ "appoint_leader_resource_card_code"
+ / PaddedString(
+ this.appoint_leader_resource_card_code_size, "utf_16_le"
+ ), # total_damage is a (zero) padded string
+ "use_profile_card_code_size"
+ / Rebuild(
+ Int32ub, len_(this.use_profile_card_code) * 2
+ ), # calculates the length of the total_damage
+ "use_profile_card_code"
+ / PaddedString(
+ this.use_profile_card_code_size, "utf_16_le"
+ ), # use_profile_card_code is a (zero) padded string
+ "quest_drop_boost_apply_flag"
+ / Int8ub, # quest_drop_boost_apply_flag is a byte
+ ),
+ ),
)
req_data = req_struct.parse(req)
@@ -601,18 +1007,22 @@ class SaoBase:
profile_data = self.game_data.profile.get_profile(user_id)
self.game_data.item.create_session(
- user_id,
- int(req_data.play_start_request_data[0].user_party_id),
- req_data.episode_id,
- req_data.play_mode,
- req_data.play_start_request_data[0].quest_drop_boost_apply_flag
- )
+ user_id,
+ int(req_data.play_start_request_data[0].user_party_id),
+ req_data.episode_id,
+ req_data.play_mode,
+ req_data.play_start_request_data[0].quest_drop_boost_apply_flag,
+ )
- resp = SaoEpisodePlayStartResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, profile_data)
+ resp = SaoEpisodePlayStartResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, profile_data
+ )
return resp.make()
- def handle_c908(self, request: Any) -> bytes: # Level calculation missing for the profile and heroes
- #quest/episode_play_end
+ def handle_c908(
+ self, request: Any
+ ) -> bytes: # Level calculation missing for the profile and heroes
+ # quest/episode_play_end
req = bytes.fromhex(request)[24:]
@@ -620,8 +1030,14 @@ class SaoBase:
Padding(20),
"ticket_id" / Bytes(1), # needs to be parsed as an int
Padding(1),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
Padding(2),
"episode_id" / Int16ub, # episode_id is a short,
Padding(3),
@@ -629,76 +1045,156 @@ class SaoBase:
Padding(1),
"play_result_flag" / Int8ub, # play_result_flag is a byte
Padding(2),
- "base_get_data_length" / Rebuild(Int8ub, len_(this.base_get_data)), # base_get_data_length is a byte,
- "base_get_data" / Array(this.base_get_data_length, Struct(
- "get_hero_log_exp" / Int32ub, # get_hero_log_exp is an int
- "get_col" / Int32ub, # get_num is a short
- )),
+ "base_get_data_length"
+ / Rebuild(
+ Int8ub, len_(this.base_get_data)
+ ), # base_get_data_length is a byte,
+ "base_get_data"
+ / Array(
+ this.base_get_data_length,
+ Struct(
+ "get_hero_log_exp" / Int32ub, # get_hero_log_exp is an int
+ "get_col" / Int32ub, # get_num is a short
+ ),
+ ),
Padding(3),
- "get_player_trace_data_list_length" / Rebuild(Int8ub, len_(this.get_player_trace_data_list)), # get_player_trace_data_list_length is a byte
- "get_player_trace_data_list" / Array(this.get_player_trace_data_list_length, Struct(
- "user_quest_scene_player_trace_id" / Int32ub, # user_quest_scene_player_trace_id is an int
- )),
+ "get_player_trace_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_player_trace_data_list)
+ ), # get_player_trace_data_list_length is a byte
+ "get_player_trace_data_list"
+ / Array(
+ this.get_player_trace_data_list_length,
+ Struct(
+ "user_quest_scene_player_trace_id"
+ / Int32ub, # user_quest_scene_player_trace_id is an int
+ ),
+ ),
Padding(3),
- "get_rare_drop_data_list_length" / Rebuild(Int8ub, len_(this.get_rare_drop_data_list)), # get_rare_drop_data_list_length is a byte
- "get_rare_drop_data_list" / Array(this.get_rare_drop_data_list_length, Struct(
- "quest_rare_drop_id" / Int32ub, # quest_rare_drop_id is an int
- )),
+ "get_rare_drop_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_rare_drop_data_list)
+ ), # get_rare_drop_data_list_length is a byte
+ "get_rare_drop_data_list"
+ / Array(
+ this.get_rare_drop_data_list_length,
+ Struct(
+ "quest_rare_drop_id" / Int32ub, # quest_rare_drop_id is an int
+ ),
+ ),
Padding(3),
- "get_special_rare_drop_data_list_length" / Rebuild(Int8ub, len_(this.get_special_rare_drop_data_list)), # get_special_rare_drop_data_list_length is a byte
- "get_special_rare_drop_data_list" / Array(this.get_special_rare_drop_data_list_length, Struct(
- "quest_special_rare_drop_id" / Int32ub, # quest_special_rare_drop_id is an int
- )),
+ "get_special_rare_drop_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_special_rare_drop_data_list)
+ ), # get_special_rare_drop_data_list_length is a byte
+ "get_special_rare_drop_data_list"
+ / Array(
+ this.get_special_rare_drop_data_list_length,
+ Struct(
+ "quest_special_rare_drop_id"
+ / Int32ub, # quest_special_rare_drop_id is an int
+ ),
+ ),
Padding(3),
- "get_unanalyzed_log_tmp_reward_data_list_length" / Rebuild(Int8ub, len_(this.get_unanalyzed_log_tmp_reward_data_list)), # get_unanalyzed_log_tmp_reward_data_list_length is a byte
- "get_unanalyzed_log_tmp_reward_data_list" / Array(this.get_unanalyzed_log_tmp_reward_data_list_length, Struct(
- "unanalyzed_log_grade_id" / Int32ub, # unanalyzed_log_grade_id is an int,
- )),
+ "get_unanalyzed_log_tmp_reward_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_unanalyzed_log_tmp_reward_data_list)
+ ), # get_unanalyzed_log_tmp_reward_data_list_length is a byte
+ "get_unanalyzed_log_tmp_reward_data_list"
+ / Array(
+ this.get_unanalyzed_log_tmp_reward_data_list_length,
+ Struct(
+ "unanalyzed_log_grade_id"
+ / Int32ub, # unanalyzed_log_grade_id is an int,
+ ),
+ ),
Padding(3),
- "get_event_item_data_list_length" / Rebuild(Int8ub, len_(this.get_event_item_data_list)), # get_event_item_data_list_length is a byte,
- "get_event_item_data_list" / Array(this.get_event_item_data_list_length, Struct(
- "event_item_id" / Int32ub, # event_item_id is an int
- "get_num" / Int16ub, # get_num is a short
- )),
+ "get_event_item_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_event_item_data_list)
+ ), # get_event_item_data_list_length is a byte,
+ "get_event_item_data_list"
+ / Array(
+ this.get_event_item_data_list_length,
+ Struct(
+ "event_item_id" / Int32ub, # event_item_id is an int
+ "get_num" / Int16ub, # get_num is a short
+ ),
+ ),
Padding(3),
- "discovery_enemy_data_list_length" / Rebuild(Int8ub, len_(this.discovery_enemy_data_list)), # discovery_enemy_data_list_length is a byte
- "discovery_enemy_data_list" / Array(this.discovery_enemy_data_list_length, Struct(
- "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
- "destroy_num" / Int16ub, # destroy_num is a short
- )),
+ "discovery_enemy_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.discovery_enemy_data_list)
+ ), # discovery_enemy_data_list_length is a byte
+ "discovery_enemy_data_list"
+ / Array(
+ this.discovery_enemy_data_list_length,
+ Struct(
+ "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
+ "destroy_num" / Int16ub, # destroy_num is a short
+ ),
+ ),
Padding(3),
- "destroy_boss_data_list_length" / Rebuild(Int8ub, len_(this.destroy_boss_data_list)), # destroy_boss_data_list_length is a byte
- "destroy_boss_data_list" / Array(this.destroy_boss_data_list_length, Struct(
- "boss_type" / Int8ub, # boss_type is a byte
- "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
- "destroy_num" / Int16ub, # destroy_num is a short
- )),
+ "destroy_boss_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.destroy_boss_data_list)
+ ), # destroy_boss_data_list_length is a byte
+ "destroy_boss_data_list"
+ / Array(
+ this.destroy_boss_data_list_length,
+ Struct(
+ "boss_type" / Int8ub, # boss_type is a byte
+ "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
+ "destroy_num" / Int16ub, # destroy_num is a short
+ ),
+ ),
Padding(3),
- "mission_data_list_length" / Rebuild(Int8ub, len_(this.mission_data_list)), # mission_data_list_length is a byte
- "mission_data_list" / Array(this.mission_data_list_length, Struct(
- "mission_id" / Int32ub, # enemy_kind_id is an int
- "clear_flag" / Int8ub, # boss_type is a byte
- "mission_difficulty_id" / Int16ub, # destroy_num is a short
- )),
+ "mission_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.mission_data_list)
+ ), # mission_data_list_length is a byte
+ "mission_data_list"
+ / Array(
+ this.mission_data_list_length,
+ Struct(
+ "mission_id" / Int32ub, # enemy_kind_id is an int
+ "clear_flag" / Int8ub, # boss_type is a byte
+ "mission_difficulty_id" / Int16ub, # destroy_num is a short
+ ),
+ ),
Padding(3),
- "score_data_length" / Rebuild(Int8ub, len_(this.score_data)), # score_data_length is a byte
- "score_data" / Array(this.score_data_length, Struct(
- "clear_time" / Int32ub, # clear_time is an int
- "combo_num" / Int32ub, # boss_type is a int
- "total_damage_size" / Rebuild(Int32ub, len_(this.total_damage) * 2), # calculates the length of the total_damage
- "total_damage" / PaddedString(this.total_damage_size, "utf_16_le"), # total_damage is a (zero) padded string
- "concurrent_destroying_num" / Int16ub, # concurrent_destroying_num is a short
- "reaching_skill_level" / Int16ub, # reaching_skill_level is a short
- "ko_chara_num" / Int8ub, # ko_chara_num is a byte
- "acceleration_invocation_num" / Int16ub, # acceleration_invocation_num is a short
- "boss_destroying_num" / Int16ub, # boss_destroying_num is a short
- "synchro_skill_used_flag" / Int8ub, # synchro_skill_used_flag is a byte
- "used_friend_skill_id" / Int32ub, # used_friend_skill_id is an int
- "friend_skill_used_flag" / Int8ub, # friend_skill_used_flag is a byte
- "continue_cnt" / Int16ub, # continue_cnt is a short
- "total_loss_num" / Int16ub, # total_loss_num is a short
- )),
-
+ "score_data_length"
+ / Rebuild(Int8ub, len_(this.score_data)), # score_data_length is a byte
+ "score_data"
+ / Array(
+ this.score_data_length,
+ Struct(
+ "clear_time" / Int32ub, # clear_time is an int
+ "combo_num" / Int32ub, # boss_type is a int
+ "total_damage_size"
+ / Rebuild(
+ Int32ub, len_(this.total_damage) * 2
+ ), # calculates the length of the total_damage
+ "total_damage"
+ / PaddedString(
+ this.total_damage_size, "utf_16_le"
+ ), # total_damage is a (zero) padded string
+ "concurrent_destroying_num"
+ / Int16ub, # concurrent_destroying_num is a short
+ "reaching_skill_level" / Int16ub, # reaching_skill_level is a short
+ "ko_chara_num" / Int8ub, # ko_chara_num is a byte
+ "acceleration_invocation_num"
+ / Int16ub, # acceleration_invocation_num is a short
+ "boss_destroying_num" / Int16ub, # boss_destroying_num is a short
+ "synchro_skill_used_flag"
+ / Int8ub, # synchro_skill_used_flag is a byte
+ "used_friend_skill_id" / Int32ub, # used_friend_skill_id is an int
+ "friend_skill_used_flag"
+ / Int8ub, # friend_skill_used_flag is a byte
+ "continue_cnt" / Int16ub, # continue_cnt is a short
+ "total_loss_num" / Int16ub, # total_loss_num is a short
+ ),
+ ),
)
req_data = req_struct.parse(req)
@@ -714,7 +1210,7 @@ class SaoBase:
profile = self.game_data.profile.get_profile(user_id)
vp = int(profile["own_vp"])
- exp = int(profile["rank_exp"]) + 100 #always 100 extra exp for some reason
+ exp = int(profile["rank_exp"]) + 100 # always 100 extra exp for some reason
col = int(profile["own_col"]) + int(req_data.base_get_data[0].get_col)
if quest_clear_flag is True:
@@ -734,69 +1230,80 @@ class SaoBase:
questId = self.game_data.static.get_quests_id(episode_id)
episode_id = questId[2]
- self.game_data.item.put_player_quest(user_id, episode_id, quest_clear_flag, clear_time, combo_num, total_damage, concurrent_destroying_num)
-
- vp = int(profile["own_vp"]) + 10 #always 10 VP per cleared stage
+ self.game_data.item.put_player_quest(
+ user_id,
+ episode_id,
+ quest_clear_flag,
+ clear_time,
+ combo_num,
+ total_damage,
+ concurrent_destroying_num,
+ )
+ vp = int(profile["own_vp"]) + 10 # always 10 VP per cleared stage
# Calculate level based off experience and the CSV list
- with open(r'titles/sao/data/PlayerRank.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/PlayerRank.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
- data.append(row)
-
- for i in range(0,len(data)):
- if exp>=int(data[i][1]) and exp= int(data[i][1]) and exp < int(data[i + 1][1]):
player_level = int(data[i][0])
break
# Update profile
updated_profile = self.game_data.profile.put_profile(
user_id,
- profile["user_type"],
- profile["nick_name"],
+ profile["user_type"],
+ profile["nick_name"],
player_level,
exp,
col,
- vp,
- profile["own_yui_medal"],
- profile["setting_title_id"]
- )
+ vp,
+ profile["own_yui_medal"],
+ profile["setting_title_id"],
+ )
# Update heroes from the used party
play_session = self.game_data.item.get_session(user_id)
- session_party = self.game_data.item.get_hero_party(user_id, play_session["user_party_team_id"])
+ session_party = self.game_data.item.get_hero_party(
+ user_id, play_session["user_party_team_id"]
+ )
hero_list = []
hero_list.append(session_party["user_hero_log_id_1"])
hero_list.append(session_party["user_hero_log_id_2"])
hero_list.append(session_party["user_hero_log_id_3"])
- for i in range(0,len(hero_list)):
+ for i in range(0, len(hero_list)):
hero_data = self.game_data.item.get_hero_log(user_id, hero_list[i])
- log_exp = int(hero_data["log_exp"]) + int(req_data.base_get_data[0].get_hero_log_exp)
+ log_exp = int(hero_data["log_exp"]) + int(
+ req_data.base_get_data[0].get_hero_log_exp
+ )
# Calculate hero level based off experience and the CSV list
- with open(r'titles/sao/data/HeroLogLevel.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/HeroLogLevel.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
data.append(row)
-
- for e in range(0,len(data)):
- if log_exp>=int(data[e][1]) and log_exp= int(data[e][1]) and log_exp < int(data[e + 1][1]):
hero_level = int(data[e][0])
break
@@ -811,80 +1318,155 @@ class SaoBase:
hero_data["skill_slot2_skill_id"],
hero_data["skill_slot3_skill_id"],
hero_data["skill_slot4_skill_id"],
- hero_data["skill_slot5_skill_id"]
+ hero_data["skill_slot5_skill_id"],
)
# Grab the rare loot from the table, match it with the right item and then push to the player profile
json_data = {"data": []}
- for r in range(0,req_data.get_rare_drop_data_list_length):
- rewardList = self.game_data.static.get_rare_drop_id(int(req_data.get_rare_drop_data_list[r].quest_rare_drop_id))
- commonRewardId = rewardList["commonRewardId"]
+ with self.game_data.item.conn.begin() or nullcontext():
+ for r in range(0, req_data.get_rare_drop_data_list_length):
+ rewardList = self.game_data.static.get_rare_drop_id(
+ int(req_data.get_rare_drop_data_list[r].quest_rare_drop_id)
+ )
+ commonRewardId = rewardList["commonRewardId"]
- heroList = self.game_data.static.get_hero_id(commonRewardId)
- equipmentList = self.game_data.static.get_equipment_id(commonRewardId)
- itemList = self.game_data.static.get_item_id(commonRewardId)
+ heroList = self.game_data.static.get_hero_id(commonRewardId)
+ equipmentList = self.game_data.static.get_equipment_id(commonRewardId)
+ itemList = self.game_data.static.get_item_id(commonRewardId)
- if heroList:
- self.game_data.item.put_hero_log(user_id, commonRewardId, 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0)
- if equipmentList:
- self.game_data.item.put_equipment_data(user_id, commonRewardId, 1, 200, 0, 0, 0)
- if itemList:
- self.game_data.item.put_item(user_id, commonRewardId)
-
- # Generate random hero(es) based off the response
- for a in range(0,req_data.get_unanalyzed_log_tmp_reward_data_list_length):
-
- with open('titles/sao/data/RewardTable.csv', 'r') as f:
- keys_unanalyzed = next(f).strip().split(',')
- data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed))
+ if heroList:
+ self.game_data.item.put_hero_log(
+ user_id, commonRewardId, 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0
+ )
+ if equipmentList:
+ self.game_data.item.put_equipment_data(
+ user_id, commonRewardId, 1, 200, 0, 0, 0
+ )
+ if itemList:
+ self.game_data.item.put_item(user_id, commonRewardId)
+
+ # Generate random hero(es) based off the response
+ for a in range(0, req_data.get_unanalyzed_log_tmp_reward_data_list_length):
+
+ with open("titles/sao/data/RewardTable.csv", "r") as f:
+ keys_unanalyzed = next(f).strip().split(",")
+ data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed))
- randomized_unanalyzed_id = choice(data_unanalyzed)
- while int(randomized_unanalyzed_id['UnanalyzedLogGradeId']) != req_data.get_unanalyzed_log_tmp_reward_data_list[a].unanalyzed_log_grade_id:
randomized_unanalyzed_id = choice(data_unanalyzed)
-
- heroList = self.game_data.static.get_hero_id(randomized_unanalyzed_id['CommonRewardId'])
- equipmentList = self.game_data.static.get_equipment_id(randomized_unanalyzed_id['CommonRewardId'])
- itemList = self.game_data.static.get_item_id(randomized_unanalyzed_id['CommonRewardId'])
- if heroList:
- self.game_data.item.put_hero_log(user_id, randomized_unanalyzed_id['CommonRewardId'], 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0)
- if equipmentList:
- self.game_data.item.put_equipment_data(user_id, randomized_unanalyzed_id['CommonRewardId'], 1, 200, 0, 0, 0)
- if itemList:
- self.game_data.item.put_item(user_id, randomized_unanalyzed_id['CommonRewardId'])
+ while (
+ int(randomized_unanalyzed_id["UnanalyzedLogGradeId"])
+ != req_data.get_unanalyzed_log_tmp_reward_data_list[
+ a
+ ].unanalyzed_log_grade_id
+ ):
+ randomized_unanalyzed_id = choice(data_unanalyzed)
+
+ heroList = self.game_data.static.get_hero_id(
+ randomized_unanalyzed_id["CommonRewardId"]
+ )
+ equipmentList = self.game_data.static.get_equipment_id(
+ randomized_unanalyzed_id["CommonRewardId"]
+ )
+ itemList = self.game_data.static.get_item_id(
+ randomized_unanalyzed_id["CommonRewardId"]
+ )
+ if heroList:
+ self.game_data.item.put_hero_log(
+ user_id,
+ randomized_unanalyzed_id["CommonRewardId"],
+ 1,
+ 0,
+ 101000016,
+ 0,
+ 30086,
+ 1001,
+ 1002,
+ 0,
+ 0,
+ )
+ if equipmentList:
+ self.game_data.item.put_equipment_data(
+ user_id, randomized_unanalyzed_id["CommonRewardId"], 1, 200, 0, 0, 0
+ )
+ if itemList:
+ self.game_data.item.put_item(
+ user_id, randomized_unanalyzed_id["CommonRewardId"]
+ )
+
+ json_data["data"].append(randomized_unanalyzed_id["CommonRewardId"])
- json_data["data"].append(randomized_unanalyzed_id['CommonRewardId'])
-
# Send response
+ self.game_data.item.create_end_session(
+ user_id, episode_id, quest_clear_flag, json_data["data"]
+ )
- self.game_data.item.create_end_session(user_id, episode_id, quest_clear_flag, json_data["data"])
-
- resp = SaoEpisodePlayEndResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ resp = SaoEpisodePlayEndResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c914(self, request: Any) -> bytes:
- #quest/trial_tower_play_start
+ # quest/trial_tower_play_start
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "ticket_id_size" / Rebuild(Int32ub, len_(this.ticket_id) * 2), # calculates the length of the ticket_id
- "ticket_id" / PaddedString(this.ticket_id_size, "utf_16_le"), # ticket_id is a (zero) padded string
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "ticket_id_size"
+ / Rebuild(
+ Int32ub, len_(this.ticket_id) * 2
+ ), # calculates the length of the ticket_id
+ "ticket_id"
+ / PaddedString(
+ this.ticket_id_size, "utf_16_le"
+ ), # ticket_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
"trial_tower_id" / Int32ub, # trial_tower_id is an int
"play_mode" / Int8ub, # play_mode is a byte
Padding(3),
- "play_start_request_data_length" / Rebuild(Int8ub, len_(this.play_start_request_data)), # play_start_request_data_length is a byte,
- "play_start_request_data" / Array(this.play_start_request_data_length, Struct(
- "user_party_id_size" / Rebuild(Int32ub, len_(this.user_party_id) * 2), # calculates the length of the user_party_id
- "user_party_id" / PaddedString(this.user_party_id_size, "utf_16_le"), # user_party_id is a (zero) padded string
- "appoint_leader_resource_card_code_size" / Rebuild(Int32ub, len_(this.appoint_leader_resource_card_code) * 2), # calculates the length of the total_damage
- "appoint_leader_resource_card_code" / PaddedString(this.appoint_leader_resource_card_code_size, "utf_16_le"), # total_damage is a (zero) padded string
- "use_profile_card_code_size" / Rebuild(Int32ub, len_(this.use_profile_card_code) * 2), # calculates the length of the total_damage
- "use_profile_card_code" / PaddedString(this.use_profile_card_code_size, "utf_16_le"), # use_profile_card_code is a (zero) padded string
- "quest_drop_boost_apply_flag" / Int8ub, # quest_drop_boost_apply_flag is a byte
- )),
+ "play_start_request_data_length"
+ / Rebuild(
+ Int8ub, len_(this.play_start_request_data)
+ ), # play_start_request_data_length is a byte,
+ "play_start_request_data"
+ / Array(
+ this.play_start_request_data_length,
+ Struct(
+ "user_party_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_party_id) * 2
+ ), # calculates the length of the user_party_id
+ "user_party_id"
+ / PaddedString(
+ this.user_party_id_size, "utf_16_le"
+ ), # user_party_id is a (zero) padded string
+ "appoint_leader_resource_card_code_size"
+ / Rebuild(
+ Int32ub, len_(this.appoint_leader_resource_card_code) * 2
+ ), # calculates the length of the total_damage
+ "appoint_leader_resource_card_code"
+ / PaddedString(
+ this.appoint_leader_resource_card_code_size, "utf_16_le"
+ ), # total_damage is a (zero) padded string
+ "use_profile_card_code_size"
+ / Rebuild(
+ Int32ub, len_(this.use_profile_card_code) * 2
+ ), # calculates the length of the total_damage
+ "use_profile_card_code"
+ / PaddedString(
+ this.use_profile_card_code_size, "utf_16_le"
+ ), # use_profile_card_code is a (zero) padded string
+ "quest_drop_boost_apply_flag"
+ / Int8ub, # quest_drop_boost_apply_flag is a byte
+ ),
+ ),
)
req_data = req_struct.parse(req)
@@ -894,26 +1476,34 @@ class SaoBase:
profile_data = self.game_data.profile.get_profile(user_id)
self.game_data.item.create_session(
- user_id,
- int(req_data.play_start_request_data[0].user_party_id),
- req_data.trial_tower_id,
- req_data.play_mode,
- req_data.play_start_request_data[0].quest_drop_boost_apply_flag
- )
+ user_id,
+ int(req_data.play_start_request_data[0].user_party_id),
+ req_data.trial_tower_id,
+ req_data.play_mode,
+ req_data.play_start_request_data[0].quest_drop_boost_apply_flag,
+ )
- resp = SaoEpisodePlayStartResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, profile_data)
+ resp = SaoEpisodePlayStartResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, profile_data
+ )
return resp.make()
def handle_c918(self, request: Any) -> bytes:
- #quest/trial_tower_play_end
+ # quest/trial_tower_play_end
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(20),
"ticket_id" / Bytes(1), # needs to be parsed as an int
Padding(1),
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
Padding(2),
"trial_tower_id" / Int16ub, # trial_tower_id is a short,
Padding(3),
@@ -921,76 +1511,156 @@ class SaoBase:
Padding(1),
"play_result_flag" / Int8ub, # play_result_flag is a byte
Padding(2),
- "base_get_data_length" / Rebuild(Int8ub, len_(this.base_get_data)), # base_get_data_length is a byte,
- "base_get_data" / Array(this.base_get_data_length, Struct(
- "get_hero_log_exp" / Int32ub, # get_hero_log_exp is an int
- "get_col" / Int32ub, # get_num is a short
- )),
+ "base_get_data_length"
+ / Rebuild(
+ Int8ub, len_(this.base_get_data)
+ ), # base_get_data_length is a byte,
+ "base_get_data"
+ / Array(
+ this.base_get_data_length,
+ Struct(
+ "get_hero_log_exp" / Int32ub, # get_hero_log_exp is an int
+ "get_col" / Int32ub, # get_num is a short
+ ),
+ ),
Padding(3),
- "get_player_trace_data_list_length" / Rebuild(Int8ub, len_(this.get_player_trace_data_list)), # get_player_trace_data_list_length is a byte
- "get_player_trace_data_list" / Array(this.get_player_trace_data_list_length, Struct(
- "user_quest_scene_player_trace_id" / Int32ub, # user_quest_scene_player_trace_id is an int
- )),
+ "get_player_trace_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_player_trace_data_list)
+ ), # get_player_trace_data_list_length is a byte
+ "get_player_trace_data_list"
+ / Array(
+ this.get_player_trace_data_list_length,
+ Struct(
+ "user_quest_scene_player_trace_id"
+ / Int32ub, # user_quest_scene_player_trace_id is an int
+ ),
+ ),
Padding(3),
- "get_rare_drop_data_list_length" / Rebuild(Int8ub, len_(this.get_rare_drop_data_list)), # get_rare_drop_data_list_length is a byte
- "get_rare_drop_data_list" / Array(this.get_rare_drop_data_list_length, Struct(
- "quest_rare_drop_id" / Int32ub, # quest_rare_drop_id is an int
- )),
+ "get_rare_drop_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_rare_drop_data_list)
+ ), # get_rare_drop_data_list_length is a byte
+ "get_rare_drop_data_list"
+ / Array(
+ this.get_rare_drop_data_list_length,
+ Struct(
+ "quest_rare_drop_id" / Int32ub, # quest_rare_drop_id is an int
+ ),
+ ),
Padding(3),
- "get_special_rare_drop_data_list_length" / Rebuild(Int8ub, len_(this.get_special_rare_drop_data_list)), # get_special_rare_drop_data_list_length is a byte
- "get_special_rare_drop_data_list" / Array(this.get_special_rare_drop_data_list_length, Struct(
- "quest_special_rare_drop_id" / Int32ub, # quest_special_rare_drop_id is an int
- )),
+ "get_special_rare_drop_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_special_rare_drop_data_list)
+ ), # get_special_rare_drop_data_list_length is a byte
+ "get_special_rare_drop_data_list"
+ / Array(
+ this.get_special_rare_drop_data_list_length,
+ Struct(
+ "quest_special_rare_drop_id"
+ / Int32ub, # quest_special_rare_drop_id is an int
+ ),
+ ),
Padding(3),
- "get_unanalyzed_log_tmp_reward_data_list_length" / Rebuild(Int8ub, len_(this.get_unanalyzed_log_tmp_reward_data_list)), # get_unanalyzed_log_tmp_reward_data_list_length is a byte
- "get_unanalyzed_log_tmp_reward_data_list" / Array(this.get_unanalyzed_log_tmp_reward_data_list_length, Struct(
- "unanalyzed_log_grade_id" / Int32ub, # unanalyzed_log_grade_id is an int,
- )),
+ "get_unanalyzed_log_tmp_reward_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_unanalyzed_log_tmp_reward_data_list)
+ ), # get_unanalyzed_log_tmp_reward_data_list_length is a byte
+ "get_unanalyzed_log_tmp_reward_data_list"
+ / Array(
+ this.get_unanalyzed_log_tmp_reward_data_list_length,
+ Struct(
+ "unanalyzed_log_grade_id"
+ / Int32ub, # unanalyzed_log_grade_id is an int,
+ ),
+ ),
Padding(3),
- "get_event_item_data_list_length" / Rebuild(Int8ub, len_(this.get_event_item_data_list)), # get_event_item_data_list_length is a byte,
- "get_event_item_data_list" / Array(this.get_event_item_data_list_length, Struct(
- "event_item_id" / Int32ub, # event_item_id is an int
- "get_num" / Int16ub, # get_num is a short
- )),
+ "get_event_item_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.get_event_item_data_list)
+ ), # get_event_item_data_list_length is a byte,
+ "get_event_item_data_list"
+ / Array(
+ this.get_event_item_data_list_length,
+ Struct(
+ "event_item_id" / Int32ub, # event_item_id is an int
+ "get_num" / Int16ub, # get_num is a short
+ ),
+ ),
Padding(3),
- "discovery_enemy_data_list_length" / Rebuild(Int8ub, len_(this.discovery_enemy_data_list)), # discovery_enemy_data_list_length is a byte
- "discovery_enemy_data_list" / Array(this.discovery_enemy_data_list_length, Struct(
- "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
- "destroy_num" / Int16ub, # destroy_num is a short
- )),
+ "discovery_enemy_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.discovery_enemy_data_list)
+ ), # discovery_enemy_data_list_length is a byte
+ "discovery_enemy_data_list"
+ / Array(
+ this.discovery_enemy_data_list_length,
+ Struct(
+ "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
+ "destroy_num" / Int16ub, # destroy_num is a short
+ ),
+ ),
Padding(3),
- "destroy_boss_data_list_length" / Rebuild(Int8ub, len_(this.destroy_boss_data_list)), # destroy_boss_data_list_length is a byte
- "destroy_boss_data_list" / Array(this.destroy_boss_data_list_length, Struct(
- "boss_type" / Int8ub, # boss_type is a byte
- "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
- "destroy_num" / Int16ub, # destroy_num is a short
- )),
+ "destroy_boss_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.destroy_boss_data_list)
+ ), # destroy_boss_data_list_length is a byte
+ "destroy_boss_data_list"
+ / Array(
+ this.destroy_boss_data_list_length,
+ Struct(
+ "boss_type" / Int8ub, # boss_type is a byte
+ "enemy_kind_id" / Int32ub, # enemy_kind_id is an int
+ "destroy_num" / Int16ub, # destroy_num is a short
+ ),
+ ),
Padding(3),
- "mission_data_list_length" / Rebuild(Int8ub, len_(this.mission_data_list)), # mission_data_list_length is a byte
- "mission_data_list" / Array(this.mission_data_list_length, Struct(
- "mission_id" / Int32ub, # enemy_kind_id is an int
- "clear_flag" / Int8ub, # boss_type is a byte
- "mission_difficulty_id" / Int16ub, # destroy_num is a short
- )),
+ "mission_data_list_length"
+ / Rebuild(
+ Int8ub, len_(this.mission_data_list)
+ ), # mission_data_list_length is a byte
+ "mission_data_list"
+ / Array(
+ this.mission_data_list_length,
+ Struct(
+ "mission_id" / Int32ub, # enemy_kind_id is an int
+ "clear_flag" / Int8ub, # boss_type is a byte
+ "mission_difficulty_id" / Int16ub, # destroy_num is a short
+ ),
+ ),
Padding(3),
- "score_data_length" / Rebuild(Int8ub, len_(this.score_data)), # score_data_length is a byte
- "score_data" / Array(this.score_data_length, Struct(
- "clear_time" / Int32ub, # clear_time is an int
- "combo_num" / Int32ub, # boss_type is a int
- "total_damage_size" / Rebuild(Int32ub, len_(this.total_damage) * 2), # calculates the length of the total_damage
- "total_damage" / PaddedString(this.total_damage_size, "utf_16_le"), # total_damage is a (zero) padded string
- "concurrent_destroying_num" / Int16ub, # concurrent_destroying_num is a short
- "reaching_skill_level" / Int16ub, # reaching_skill_level is a short
- "ko_chara_num" / Int8ub, # ko_chara_num is a byte
- "acceleration_invocation_num" / Int16ub, # acceleration_invocation_num is a short
- "boss_destroying_num" / Int16ub, # boss_destroying_num is a short
- "synchro_skill_used_flag" / Int8ub, # synchro_skill_used_flag is a byte
- "used_friend_skill_id" / Int32ub, # used_friend_skill_id is an int
- "friend_skill_used_flag" / Int8ub, # friend_skill_used_flag is a byte
- "continue_cnt" / Int16ub, # continue_cnt is a short
- "total_loss_num" / Int16ub, # total_loss_num is a short
- )),
-
+ "score_data_length"
+ / Rebuild(Int8ub, len_(this.score_data)), # score_data_length is a byte
+ "score_data"
+ / Array(
+ this.score_data_length,
+ Struct(
+ "clear_time" / Int32ub, # clear_time is an int
+ "combo_num" / Int32ub, # boss_type is a int
+ "total_damage_size"
+ / Rebuild(
+ Int32ub, len_(this.total_damage) * 2
+ ), # calculates the length of the total_damage
+ "total_damage"
+ / PaddedString(
+ this.total_damage_size, "utf_16_le"
+ ), # total_damage is a (zero) padded string
+ "concurrent_destroying_num"
+ / Int16ub, # concurrent_destroying_num is a short
+ "reaching_skill_level" / Int16ub, # reaching_skill_level is a short
+ "ko_chara_num" / Int8ub, # ko_chara_num is a byte
+ "acceleration_invocation_num"
+ / Int16ub, # acceleration_invocation_num is a short
+ "boss_destroying_num" / Int16ub, # boss_destroying_num is a short
+ "synchro_skill_used_flag"
+ / Int8ub, # synchro_skill_used_flag is a byte
+ "used_friend_skill_id" / Int32ub, # used_friend_skill_id is an int
+ "friend_skill_used_flag"
+ / Int8ub, # friend_skill_used_flag is a byte
+ "continue_cnt" / Int16ub, # continue_cnt is a short
+ "total_loss_num" / Int16ub, # total_loss_num is a short
+ ),
+ ),
)
req_data = req_struct.parse(req)
@@ -1007,27 +1677,27 @@ class SaoBase:
if quest_clear_flag is True:
# Save tower progression - to be revised to avoid saving worse score
- if trial_tower_id == 9:
+ if trial_tower_id == 9:
next_tower_id = 10001
- elif trial_tower_id == 10:
+ elif trial_tower_id == 10:
trial_tower_id = 10001
next_tower_id = 3011
- elif trial_tower_id == 19:
+ elif trial_tower_id == 19:
next_tower_id = 10002
elif trial_tower_id == 20:
trial_tower_id = 10002
next_tower_id = 3021
- elif trial_tower_id == 29:
+ elif trial_tower_id == 29:
next_tower_id = 10003
elif trial_tower_id == 30:
trial_tower_id = 10003
next_tower_id = 3031
- elif trial_tower_id == 39:
+ elif trial_tower_id == 39:
next_tower_id = 10004
elif trial_tower_id == 40:
trial_tower_id = 10004
next_tower_id = 3041
- elif trial_tower_id == 49:
+ elif trial_tower_id == 49:
next_tower_id = 10005
elif trial_tower_id == 50:
trial_tower_id = 10005
@@ -1036,153 +1706,217 @@ class SaoBase:
trial_tower_id = trial_tower_id + 3000
next_tower_id = trial_tower_id + 1
- self.game_data.item.put_player_quest(user_id, trial_tower_id, quest_clear_flag, clear_time, combo_num, total_damage, concurrent_destroying_num)
+ self.game_data.item.put_player_quest(
+ user_id,
+ trial_tower_id,
+ quest_clear_flag,
+ clear_time,
+ combo_num,
+ total_damage,
+ concurrent_destroying_num,
+ )
# Check if next stage is already done
checkQuest = self.game_data.item.get_quest_log(user_id, next_tower_id)
if not checkQuest:
if next_tower_id != 3101:
- self.game_data.item.put_player_quest(user_id, next_tower_id, 0, 0, 0, 0, 0)
+ self.game_data.item.put_player_quest(
+ user_id, next_tower_id, 0, 0, 0, 0, 0
+ )
- # Update the profile
+ # Update the profile
profile = self.game_data.profile.get_profile(user_id)
-
- exp = int(profile["rank_exp"]) + 100 #always 100 extra exp for some reason
+
+ exp = int(profile["rank_exp"]) + 100 # always 100 extra exp for some reason
col = int(profile["own_col"]) + int(req_data.base_get_data[0].get_col)
# Calculate level based off experience and the CSV list
- with open(r'titles/sao/data/PlayerRank.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/PlayerRank.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
- data.append(row)
-
- for i in range(0,len(data)):
- if exp>=int(data[i][1]) and exp= int(data[i][1]) and exp < int(data[i + 1][1]):
player_level = int(data[i][0])
break
updated_profile = self.game_data.profile.put_profile(
user_id,
- profile["user_type"],
- profile["nick_name"],
+ profile["user_type"],
+ profile["nick_name"],
player_level,
exp,
col,
- profile["own_vp"],
- profile["own_yui_medal"],
- profile["setting_title_id"]
- )
+ profile["own_vp"],
+ profile["own_yui_medal"],
+ profile["setting_title_id"],
+ )
# Update heroes from the used party
play_session = self.game_data.item.get_session(user_id)
- session_party = self.game_data.item.get_hero_party(user_id, play_session["user_party_team_id"])
+ session_party = self.game_data.item.get_hero_party(
+ user_id, play_session["user_party_team_id"]
+ )
hero_list = []
hero_list.append(session_party["user_hero_log_id_1"])
hero_list.append(session_party["user_hero_log_id_2"])
hero_list.append(session_party["user_hero_log_id_3"])
- for i in range(0,len(hero_list)):
- hero_data = self.game_data.item.get_hero_log(user_id, hero_list[i])
+ with self.game_data.item.conn.begin() or nullcontext():
+ for i in range(0, len(hero_list)):
+ hero_data = self.game_data.item.get_hero_log(user_id, hero_list[i])
- log_exp = int(hero_data["log_exp"]) + int(req_data.base_get_data[0].get_hero_log_exp)
+ log_exp = int(hero_data["log_exp"]) + int(
+ req_data.base_get_data[0].get_hero_log_exp
+ )
- # Calculate hero level based off experience and the CSV list
- with open(r'titles/sao/data/HeroLogLevel.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
- line_count = 0
- data = []
- rowf = False
- for row in csv_reader:
- if rowf==False:
- rowf=True
- else:
- data.append(row)
-
- for e in range(0,len(data)):
- if log_exp>=int(data[e][1]) and log_exp= int(data[e][1]) and log_exp < int(data[e + 1][1]):
+ hero_level = int(data[e][0])
+ break
- json_data = {"data": []}
-
- # Grab the rare loot from the table, match it with the right item and then push to the player profile
- for r in range(0,req_data.get_rare_drop_data_list_length):
- rewardList = self.game_data.static.get_rare_drop_id(int(req_data.get_rare_drop_data_list[r].quest_rare_drop_id))
- commonRewardId = rewardList["commonRewardId"]
+ self.game_data.item.put_hero_log(
+ user_id,
+ hero_data["user_hero_log_id"],
+ hero_level,
+ log_exp,
+ hero_data["main_weapon"],
+ hero_data["sub_equipment"],
+ hero_data["skill_slot1_skill_id"],
+ hero_data["skill_slot2_skill_id"],
+ hero_data["skill_slot3_skill_id"],
+ hero_data["skill_slot4_skill_id"],
+ hero_data["skill_slot5_skill_id"],
+ )
- heroList = self.game_data.static.get_hero_id(commonRewardId)
- equipmentList = self.game_data.static.get_equipment_id(commonRewardId)
- itemList = self.game_data.static.get_item_id(commonRewardId)
+ json_data = {"data": []}
- if heroList:
- self.game_data.item.put_hero_log(user_id, commonRewardId, 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0)
- if equipmentList:
- self.game_data.item.put_equipment_data(user_id, commonRewardId, 1, 200, 0, 0, 0)
- if itemList:
- self.game_data.item.put_item(user_id, commonRewardId)
+ # Grab the rare loot from the table, match it with the right item and then push to the player profile
+ for r in range(0, req_data.get_rare_drop_data_list_length):
+ rewardList = self.game_data.static.get_rare_drop_id(
+ int(req_data.get_rare_drop_data_list[r].quest_rare_drop_id)
+ )
+ commonRewardId = rewardList["commonRewardId"]
- # Generate random hero(es) based off the response
- for a in range(0,req_data.get_unanalyzed_log_tmp_reward_data_list_length):
-
- with open('titles/sao/data/RewardTable.csv', 'r') as f:
- keys_unanalyzed = next(f).strip().split(',')
- data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed))
+ heroList = self.game_data.static.get_hero_id(commonRewardId)
+ equipmentList = self.game_data.static.get_equipment_id(commonRewardId)
+ itemList = self.game_data.static.get_item_id(commonRewardId)
+
+ if heroList:
+ self.game_data.item.put_hero_log(
+ user_id, commonRewardId, 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0
+ )
+ if equipmentList:
+ self.game_data.item.put_equipment_data(
+ user_id, commonRewardId, 1, 200, 0, 0, 0
+ )
+ if itemList:
+ self.game_data.item.put_item(user_id, commonRewardId)
+
+ # Generate random hero(es) based off the response
+ for a in range(0, req_data.get_unanalyzed_log_tmp_reward_data_list_length):
+
+ with open("titles/sao/data/RewardTable.csv", "r") as f:
+ keys_unanalyzed = next(f).strip().split(",")
+ data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed))
- randomized_unanalyzed_id = choice(data_unanalyzed)
- while int(randomized_unanalyzed_id['UnanalyzedLogGradeId']) != req_data.get_unanalyzed_log_tmp_reward_data_list[a].unanalyzed_log_grade_id:
randomized_unanalyzed_id = choice(data_unanalyzed)
-
- heroList = self.game_data.static.get_hero_id(randomized_unanalyzed_id['CommonRewardId'])
- equipmentList = self.game_data.static.get_equipment_id(randomized_unanalyzed_id['CommonRewardId'])
- itemList = self.game_data.static.get_item_id(randomized_unanalyzed_id['CommonRewardId'])
- if heroList:
- self.game_data.item.put_hero_log(user_id, randomized_unanalyzed_id['CommonRewardId'], 1, 0, 101000016, 0, 30086, 1001, 1002, 0, 0)
- if equipmentList:
- self.game_data.item.put_equipment_data(user_id, randomized_unanalyzed_id['CommonRewardId'], 1, 200, 0, 0, 0)
- if itemList:
- self.game_data.item.put_item(user_id, randomized_unanalyzed_id['CommonRewardId'])
+ while (
+ int(randomized_unanalyzed_id["UnanalyzedLogGradeId"])
+ != req_data.get_unanalyzed_log_tmp_reward_data_list[
+ a
+ ].unanalyzed_log_grade_id
+ ):
+ randomized_unanalyzed_id = choice(data_unanalyzed)
+
+ heroList = self.game_data.static.get_hero_id(
+ randomized_unanalyzed_id["CommonRewardId"]
+ )
+ equipmentList = self.game_data.static.get_equipment_id(
+ randomized_unanalyzed_id["CommonRewardId"]
+ )
+ itemList = self.game_data.static.get_item_id(
+ randomized_unanalyzed_id["CommonRewardId"]
+ )
+ if heroList:
+ self.game_data.item.put_hero_log(
+ user_id,
+ randomized_unanalyzed_id["CommonRewardId"],
+ 1,
+ 0,
+ 101000016,
+ 0,
+ 30086,
+ 1001,
+ 1002,
+ 0,
+ 0,
+ )
+ if equipmentList:
+ self.game_data.item.put_equipment_data(
+ user_id, randomized_unanalyzed_id["CommonRewardId"], 1, 200, 0, 0, 0
+ )
+ if itemList:
+ self.game_data.item.put_item(
+ user_id, randomized_unanalyzed_id["CommonRewardId"]
+ )
+
+ json_data["data"].append(randomized_unanalyzed_id["CommonRewardId"])
- json_data["data"].append(randomized_unanalyzed_id['CommonRewardId'])
-
# Send response
- self.game_data.item.create_end_session(user_id, trial_tower_id, quest_clear_flag, json_data["data"])
+ self.game_data.item.create_end_session(
+ user_id, trial_tower_id, quest_clear_flag, json_data["data"]
+ )
- resp = SaoTrialTowerPlayEndResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ resp = SaoTrialTowerPlayEndResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c90a(self, request: Any) -> bytes:
- #quest/episode_play_end_unanalyzed_log_fixed
+ # quest/episode_play_end_unanalyzed_log_fixed
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "ticket_id_size" / Rebuild(Int32ub, len_(this.ticket_id) * 2), # calculates the length of the ticket_id
- "ticket_id" / PaddedString(this.ticket_id_size, "utf_16_le"), # ticket_id is a (zero) padded string
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "ticket_id_size"
+ / Rebuild(
+ Int32ub, len_(this.ticket_id) * 2
+ ), # calculates the length of the ticket_id
+ "ticket_id"
+ / PaddedString(
+ this.ticket_id_size, "utf_16_le"
+ ), # ticket_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
@@ -1190,19 +1924,33 @@ class SaoBase:
end_session_data = self.game_data.item.get_end_session(user_id)
- resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, end_session_data[4])
+ resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, end_session_data[4]
+ )
return resp.make()
- def handle_c91a(self, request: Any) -> bytes: # handler is identical to the episode
- #quest/trial_tower_play_end_unanalyzed_log_fixed
+ def handle_c91a(self, request: Any) -> bytes: # handler is identical to the episode
+ # quest/trial_tower_play_end_unanalyzed_log_fixed
req = bytes.fromhex(request)[24:]
req_struct = Struct(
Padding(16),
- "ticket_id_size" / Rebuild(Int32ub, len_(this.ticket_id) * 2), # calculates the length of the ticket_id
- "ticket_id" / PaddedString(this.ticket_id_size, "utf_16_le"), # ticket_id is a (zero) padded string
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "ticket_id_size"
+ / Rebuild(
+ Int32ub, len_(this.ticket_id) * 2
+ ), # calculates the length of the ticket_id
+ "ticket_id"
+ / PaddedString(
+ this.ticket_id_size, "utf_16_le"
+ ), # ticket_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
)
req_data = req_struct.parse(req)
@@ -1210,35 +1958,49 @@ class SaoBase:
end_session_data = self.game_data.item.get_end_session(user_id)
- resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1, end_session_data[4])
+ resp = SaoEpisodePlayEndUnanalyzedLogFixedResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1, end_session_data[4]
+ )
return resp.make()
def handle_cd00(self, request: Any) -> bytes:
- #defrag_match/get_defrag_match_basic_data
- resp = SaoGetDefragMatchBasicDataResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # defrag_match/get_defrag_match_basic_data
+ resp = SaoGetDefragMatchBasicDataResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_cd02(self, request: Any) -> bytes:
- #defrag_match/get_defrag_match_ranking_user_data
- resp = SaoGetDefragMatchRankingUserDataResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # defrag_match/get_defrag_match_ranking_user_data
+ resp = SaoGetDefragMatchRankingUserDataResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_cd04(self, request: Any) -> bytes:
- #defrag_match/get_defrag_match_league_point_ranking_list
- resp = SaoGetDefragMatchLeaguePointRankingListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # defrag_match/get_defrag_match_league_point_ranking_list
+ resp = SaoGetDefragMatchLeaguePointRankingListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_cd06(self, request: Any) -> bytes:
- #defrag_match/get_defrag_match_league_score_ranking_list
- resp = SaoGetDefragMatchLeagueScoreRankingListResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # defrag_match/get_defrag_match_league_score_ranking_list
+ resp = SaoGetDefragMatchLeagueScoreRankingListResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_d404(self, request: Any) -> bytes:
- #other/bnid_serial_code_check
- resp = SaoBnidSerialCodeCheckResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
+ # other/bnid_serial_code_check
+ resp = SaoBnidSerialCodeCheckResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
return resp.make()
def handle_c306(self, request: Any) -> bytes:
- #card/scan_qr_quest_profile_card
- resp = SaoScanQrQuestProfileCardResponse(int.from_bytes(bytes.fromhex(request[:4]), "big")+1)
- return resp.make()
\ No newline at end of file
+ # card/scan_qr_quest_profile_card
+ resp = SaoScanQrQuestProfileCardResponse(
+ int.from_bytes(bytes.fromhex(request[:4]), "big") + 1
+ )
+ return resp.make()
diff --git a/titles/sao/const.py b/titles/sao/const.py
index 8bdea0f..4561fc6 100644
--- a/titles/sao/const.py
+++ b/titles/sao/const.py
@@ -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):
diff --git a/titles/sao/database.py b/titles/sao/database.py
index b7026fb..91f810a 100644
--- a/titles/sao/database.py
+++ b/titles/sao/database.py
@@ -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)
\ No newline at end of file
+ self.static = SaoStaticData(cfg, self.session)
diff --git a/titles/sao/handlers/__init__.py b/titles/sao/handlers/__init__.py
index 90a6b4e..5f5d4f6 100644
--- a/titles/sao/handlers/__init__.py
+++ b/titles/sao/handlers/__init__.py
@@ -1 +1 @@
-from titles.sao.handlers.base import *
\ No newline at end of file
+from titles.sao.handlers.base import *
diff --git a/titles/sao/handlers/base.py b/titles/sao/handlers/base.py
index 93d0589..4cd9385 100644
--- a/titles/sao/handlers/base.py
+++ b/titles/sao/handlers/base.py
@@ -5,11 +5,13 @@ import sys
import csv
from csv import *
+
class SaoBaseRequest:
def __init__(self, data: bytes) -> None:
self.cmd = struct.unpack_from("!H", bytes)[0]
# TODO: The rest of the request header
+
class SaoBaseResponse:
def __init__(self, cmd_id: int) -> None:
self.cmd = cmd_id
@@ -19,24 +21,36 @@ class SaoBaseResponse:
self.game_id = 1
self.version_id = 1
self.length = 1
-
+
def make(self) -> bytes:
- return struct.pack("!HHIIIII", self.cmd, self.err_status, self.error_type, self.vendor_id, self.game_id, self.version_id, self.length)
+ return struct.pack(
+ "!HHIIIII",
+ self.cmd,
+ self.err_status,
+ self.error_type,
+ self.vendor_id,
+ self.game_id,
+ self.version_id,
+ self.length,
+ )
+
class SaoNoopResponse(SaoBaseResponse):
def __init__(self, cmd: int) -> None:
- super().__init__(cmd)
+ super().__init__(cmd)
self.result = 1
self.length = 5
def make(self) -> bytes:
return super().make() + struct.pack("!bI", self.result, 0)
-
+
+
class SaoGetMaintRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
# TODO: The rest of the mait info request
+
class SaoGetMaintResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
@@ -46,60 +60,73 @@ class SaoGetMaintResponse(SaoBaseResponse):
self.maint_end = datetime.fromtimestamp(0)
self.maint_end_int_ct = 6
self.dt_format = "%Y%m%d%H%M%S"
-
+
def make(self) -> bytes:
- maint_begin_list = [x for x in datetime.strftime(self.maint_begin, self.dt_format)]
+ maint_begin_list = [
+ x for x in datetime.strftime(self.maint_begin, self.dt_format)
+ ]
maint_end_list = [x for x in datetime.strftime(self.maint_end, self.dt_format)]
self.maint_begin_int_ct = len(maint_begin_list) * 2
self.maint_end_int_ct = len(maint_end_list) * 2
maint_begin_bytes = b""
maint_end_bytes = b""
-
+
for x in maint_begin_list:
maint_begin_bytes += struct.pack(" None:
super().__init__(data)
+
class SaoCommonAcCabinetBootNotificationResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoMasterDataVersionCheckRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoMasterDataVersionCheckResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
self.update_flag = 0
self.data_version = 100
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
@@ -108,83 +135,95 @@ class SaoMasterDataVersionCheckResponse(SaoBaseResponse):
"data_version" / Int32ub,
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- update_flag=self.update_flag,
- data_version=self.data_version,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ update_flag=self.update_flag,
+ data_version=self.data_version,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoCommonGetAppVersionsRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoCommonGetAppVersionsRequest(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.data_list_size = 1 # Number of arrays
+ self.data_list_size = 1 # Number of arrays
self.version_app_id = 1
self.applying_start_date = "20230520193000"
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"data_list_size" / Int32ub,
-
"version_app_id" / Int32ub,
- "applying_start_date_size" / Int32ub, # big endian
+ "applying_start_date_size" / Int32ub, # big endian
"applying_start_date" / Int16ul[len(self.applying_start_date)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- data_list_size=self.data_list_size,
-
- version_app_id=self.version_app_id,
- applying_start_date_size=len(self.applying_start_date) * 2,
- applying_start_date=[ord(x) for x in self.applying_start_date],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ data_list_size=self.data_list_size,
+ version_app_id=self.version_app_id,
+ applying_start_date_size=len(self.applying_start_date) * 2,
+ applying_start_date=[ord(x) for x in self.applying_start_date],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoCommonPayingPlayStartRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoCommonPayingPlayStartRequest(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
self.paying_session_id = "1"
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "paying_session_id_size" / Int32ub, # big endian
+ "paying_session_id_size" / Int32ub, # big endian
"paying_session_id" / Int16ul[len(self.paying_session_id)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- paying_session_id_size=len(self.paying_session_id) * 2,
- paying_session_id=[ord(x) for x in self.paying_session_id],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ paying_session_id_size=len(self.paying_session_id) * 2,
+ paying_session_id=[ord(x) for x in self.paying_session_id],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetAuthCardDataRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
-class SaoGetAuthCardDataResponse(SaoBaseResponse): #GssSite.dll / GssSiteSystem / GameConnectProt / public class get_auth_card_data_R : GameConnect.GssProtocolBase
+
+class SaoGetAuthCardDataResponse(
+ SaoBaseResponse
+): # GssSite.dll / GssSiteSystem / GameConnectProt / public class get_auth_card_data_R : GameConnect.GssProtocolBase
def __init__(self, cmd, profile_data) -> None:
super().__init__(cmd)
@@ -192,9 +231,9 @@ class SaoGetAuthCardDataResponse(SaoBaseResponse): #GssSite.dll / GssSiteSystem
self.unused_card_flag = ""
self.first_play_flag = 0
self.tutorial_complete_flag = 1
- self.nick_name = profile_data['nick_name'] # nick_name field #4
- self.personal_id = str(profile_data['user'])
-
+ self.nick_name = profile_data["nick_name"] # nick_name field #4
+ self.personal_id = str(profile_data["user"])
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
@@ -206,71 +245,77 @@ class SaoGetAuthCardDataResponse(SaoBaseResponse): #GssSite.dll / GssSiteSystem
"nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
"personal_id_size" / Int32ub, # big endian
- "personal_id" / Int16ul[len(self.personal_id)]
+ "personal_id" / Int16ul[len(self.personal_id)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- unused_card_flag_size=len(self.unused_card_flag) * 2,
- unused_card_flag=[ord(x) for x in self.unused_card_flag],
- first_play_flag=self.first_play_flag,
- tutorial_complete_flag=self.tutorial_complete_flag,
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- personal_id_size=len(self.personal_id) * 2,
- personal_id=[ord(x) for x in self.personal_id]
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ unused_card_flag_size=len(self.unused_card_flag) * 2,
+ unused_card_flag=[ord(x) for x in self.unused_card_flag],
+ first_play_flag=self.first_play_flag,
+ tutorial_complete_flag=self.tutorial_complete_flag,
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ personal_id_size=len(self.personal_id) * 2,
+ personal_id=[ord(x) for x in self.personal_id],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoHomeCheckAcLoginBonusRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoHomeCheckAcLoginBonusResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
self.reward_get_flag = 1
- self.get_ac_login_bonus_id_list_size = 2 # Array
+ self.get_ac_login_bonus_id_list_size = 2 # Array
+
+ self.get_ac_login_bonus_id_1 = 1 # "2020年7月9日~(アニメ&リコリス記念)"
+ self.get_ac_login_bonus_id_2 = 2 # "2020年10月6日~(秋のデビュー&カムバックCP)"
- self.get_ac_login_bonus_id_1 = 1 # "2020年7月9日~(アニメ&リコリス記念)"
- self.get_ac_login_bonus_id_2 = 2 # "2020年10月6日~(秋のデビュー&カムバックCP)"
-
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"reward_get_flag" / Int8ul, # result is either 0 or 1
"get_ac_login_bonus_id_list_size" / Int32ub,
-
"get_ac_login_bonus_id_1" / Int32ub,
"get_ac_login_bonus_id_2" / Int32ub,
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- reward_get_flag=self.reward_get_flag,
- get_ac_login_bonus_id_list_size=self.get_ac_login_bonus_id_list_size,
-
- get_ac_login_bonus_id_1=self.get_ac_login_bonus_id_1,
- get_ac_login_bonus_id_2=self.get_ac_login_bonus_id_2,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ reward_get_flag=self.reward_get_flag,
+ get_ac_login_bonus_id_list_size=self.get_ac_login_bonus_id_list_size,
+ get_ac_login_bonus_id_1=self.get_ac_login_bonus_id_1,
+ get_ac_login_bonus_id_2=self.get_ac_login_bonus_id_2,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetQuestSceneMultiPlayPhotonServerRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetQuestSceneMultiPlayPhotonServerResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
self.application_id = "7df3a2f6-d69d-4073-aafe-810ee61e1cea"
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
@@ -279,25 +324,29 @@ class SaoGetQuestSceneMultiPlayPhotonServerResponse(SaoBaseResponse):
"application_id" / Int16ul[len(self.application_id)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- application_id_size=len(self.application_id) * 2,
- application_id=[ord(x) for x in self.application_id],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ application_id_size=len(self.application_id) * 2,
+ application_id=[ord(x) for x in self.application_id],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoTicketRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoTicketResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = "1"
- self.ticket_id = "9" #up to 18
-
+ self.ticket_id = "9" # up to 18
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
@@ -307,40 +356,44 @@ class SaoTicketResponse(SaoBaseResponse):
"ticket_id" / Int16ul[len(self.result)],
)
- resp_data = resp_struct.build(dict(
- result_size=len(self.result) * 2,
- result=[ord(x) for x in self.result],
- ticket_id_size=len(self.ticket_id) * 2,
- ticket_id=[ord(x) for x in self.ticket_id],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result_size=len(self.result) * 2,
+ result=[ord(x) for x in self.result],
+ ticket_id_size=len(self.ticket_id) * 2,
+ ticket_id=[ord(x) for x in self.ticket_id],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoCommonLoginRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoCommonLoginResponse(SaoBaseResponse):
def __init__(self, cmd, profile_data) -> None:
super().__init__(cmd)
self.result = 1
- self.user_id = str(profile_data['user'])
+ self.user_id = str(profile_data["user"])
self.first_play_flag = 0
self.grantable_free_ticket_flag = 1
self.login_reward_vp = 99
self.today_paying_flag = 1
-
+
def make(self) -> bytes:
# create a resp struct
- '''
+ """
bool = Int8ul
short = Int16ub
int = Int32ub
- '''
+ """
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "user_id_size" / Int32ub, # big endian
+ "user_id_size" / Int32ub, # big endian
"user_id" / Int16ul[len(self.user_id)],
"first_play_flag" / Int8ul, # result is either 0 or 1
"grantable_free_ticket_flag" / Int8ul, # result is either 0 or 1
@@ -348,83 +401,94 @@ class SaoCommonLoginResponse(SaoBaseResponse):
"today_paying_flag" / Int8ul, # result is either 0 or 1
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- user_id_size=len(self.user_id) * 2,
- user_id=[ord(x) for x in self.user_id],
- first_play_flag=self.first_play_flag,
- grantable_free_ticket_flag=self.grantable_free_ticket_flag,
- login_reward_vp=self.login_reward_vp,
- today_paying_flag=self.today_paying_flag,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ user_id_size=len(self.user_id) * 2,
+ user_id=[ord(x) for x in self.user_id],
+ first_play_flag=self.first_play_flag,
+ grantable_free_ticket_flag=self.grantable_free_ticket_flag,
+ login_reward_vp=self.login_reward_vp,
+ today_paying_flag=self.today_paying_flag,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoCheckComebackEventRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoCheckComebackEventRequest(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
self.get_flag_ = 1
- self.get_comeback_event_id_list = "" # Array of events apparently
-
+ self.get_comeback_event_id_list = "" # Array of events apparently
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"get_flag_" / Int8ul, # result is either 0 or 1
- "get_comeback_event_id_list_size" / Int32ub, # big endian
- "get_comeback_event_id_list" / Int16ul[len(self.get_comeback_event_id_list)],
+ "get_comeback_event_id_list_size" / Int32ub, # big endian
+ "get_comeback_event_id_list"
+ / Int16ul[len(self.get_comeback_event_id_list)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- get_flag_=self.get_flag_,
- get_comeback_event_id_list_size=len(self.get_comeback_event_id_list) * 2,
- get_comeback_event_id_list=[ord(x) for x in self.get_comeback_event_id_list],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ get_flag_=self.get_flag_,
+ get_comeback_event_id_list_size=len(self.get_comeback_event_id_list)
+ * 2,
+ get_comeback_event_id_list=[
+ ord(x) for x in self.get_comeback_event_id_list
+ ],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetUserBasicDataRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetUserBasicDataResponse(SaoBaseResponse):
def __init__(self, cmd, profile_data) -> None:
super().__init__(cmd)
self.result = 1
- self.user_basic_data_size = 1 # Number of arrays
- self.user_type = profile_data['user_type']
- self.nick_name = profile_data['nick_name']
- self.rank_num = profile_data['rank_num']
- self.rank_exp = profile_data['rank_exp']
- self.own_col = profile_data['own_col']
- self.own_vp = profile_data['own_vp']
- self.own_yui_medal = profile_data['own_yui_medal']
- self.setting_title_id = profile_data['setting_title_id']
+ self.user_basic_data_size = 1 # Number of arrays
+ self.user_type = profile_data["user_type"]
+ self.nick_name = profile_data["nick_name"]
+ self.rank_num = profile_data["rank_num"]
+ self.rank_exp = profile_data["rank_exp"]
+ self.own_col = profile_data["own_col"]
+ self.own_vp = profile_data["own_vp"]
+ self.own_yui_medal = profile_data["own_yui_medal"]
+ self.setting_title_id = profile_data["setting_title_id"]
self.favorite_user_hero_log_id = ""
self.favorite_user_support_log_id = ""
self.my_store_id = "1"
self.my_store_name = "ARTEMiS"
self.user_reg_date = "20230101120000"
-
+
def make(self) -> bytes:
# create a resp struct
- '''
+ """
bool = Int8ul
short = Int16ub
int = Int32ub
- '''
+ """
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"user_basic_data_size" / Int32ub,
-
"user_type" / Int16ub,
"nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
@@ -435,55 +499,64 @@ class SaoGetUserBasicDataResponse(SaoBaseResponse):
"own_yui_medal" / Int32ub,
"setting_title_id" / Int32ub,
"favorite_user_hero_log_id_size" / Int32ub, # big endian
- "favorite_user_hero_log_id" / Int16ul[len(str(self.favorite_user_hero_log_id))],
+ "favorite_user_hero_log_id"
+ / Int16ul[len(str(self.favorite_user_hero_log_id))],
"favorite_user_support_log_id_size" / Int32ub, # big endian
- "favorite_user_support_log_id" / Int16ul[len(str(self.favorite_user_support_log_id))],
+ "favorite_user_support_log_id"
+ / Int16ul[len(str(self.favorite_user_support_log_id))],
"my_store_id_size" / Int32ub, # big endian
"my_store_id" / Int16ul[len(str(self.my_store_id))],
"my_store_name_size" / Int32ub, # big endian
- "my_store_name" / Int16ul[len(str(self.my_store_name))],
+ "my_store_name" / Int16ul[len(str(self.my_store_name))],
"user_reg_date_size" / Int32ub, # big endian
- "user_reg_date" / Int16ul[len(self.user_reg_date)]
-
+ "user_reg_date" / Int16ul[len(self.user_reg_date)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- user_basic_data_size=self.user_basic_data_size,
-
- user_type=self.user_type,
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- rank_num=self.rank_num,
- rank_exp=self.rank_exp,
- own_col=self.own_col,
- own_vp=self.own_vp,
- own_yui_medal=self.own_yui_medal,
- setting_title_id=self.setting_title_id,
- favorite_user_hero_log_id_size=len(self.favorite_user_hero_log_id) * 2,
- favorite_user_hero_log_id=[ord(x) for x in str(self.favorite_user_hero_log_id)],
- favorite_user_support_log_id_size=len(self.favorite_user_support_log_id) * 2,
- favorite_user_support_log_id=[ord(x) for x in str(self.favorite_user_support_log_id)],
- my_store_id_size=len(self.my_store_id) * 2,
- my_store_id=[ord(x) for x in str(self.my_store_id)],
- my_store_name_size=len(self.my_store_name) * 2,
- my_store_name=[ord(x) for x in str(self.my_store_name)],
- user_reg_date_size=len(self.user_reg_date) * 2,
- user_reg_date=[ord(x) for x in self.user_reg_date],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ user_basic_data_size=self.user_basic_data_size,
+ user_type=self.user_type,
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ rank_num=self.rank_num,
+ rank_exp=self.rank_exp,
+ own_col=self.own_col,
+ own_vp=self.own_vp,
+ own_yui_medal=self.own_yui_medal,
+ setting_title_id=self.setting_title_id,
+ favorite_user_hero_log_id_size=len(self.favorite_user_hero_log_id) * 2,
+ favorite_user_hero_log_id=[
+ ord(x) for x in str(self.favorite_user_hero_log_id)
+ ],
+ favorite_user_support_log_id_size=len(self.favorite_user_support_log_id)
+ * 2,
+ favorite_user_support_log_id=[
+ ord(x) for x in str(self.favorite_user_support_log_id)
+ ],
+ my_store_id_size=len(self.my_store_id) * 2,
+ my_store_id=[ord(x) for x in str(self.my_store_id)],
+ my_store_name_size=len(self.my_store_name) * 2,
+ my_store_name=[ord(x) for x in str(self.my_store_name)],
+ user_reg_date_size=len(self.user_reg_date) * 2,
+ user_reg_date=[ord(x) for x in self.user_reg_date],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetHeroLogUserDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetHeroLogUserDataListResponse(SaoBaseResponse):
def __init__(self, cmd, hero_data) -> None:
super().__init__(cmd)
self.result = 1
-
+
self.user_hero_log_id = []
self.log_level = []
self.max_log_level_extended_num = []
@@ -497,21 +570,21 @@ class SaoGetHeroLogUserDataListResponse(SaoBaseResponse):
for i in range(len(hero_data)):
# Calculate level based off experience and the CSV list
- with open(r'titles/sao/data/HeroLogLevel.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/HeroLogLevel.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
data.append(row)
exp = hero_data[i][4]
-
- for e in range(0,len(data)):
- if exp>=int(data[e][1]) and exp= int(data[e][1]) and exp < int(data[e + 1][1]):
hero_level = int(data[e][0])
break
@@ -525,60 +598,72 @@ class SaoGetHeroLogUserDataListResponse(SaoBaseResponse):
self.last_set_skill_slot4_skill_id.append(hero_data[i][10])
self.last_set_skill_slot5_skill_id.append(hero_data[i][11])
- #print(self.user_hero_log_id)
- #print(list(map(str,self.user_hero_log_id)))
+ # print(self.user_hero_log_id)
+ # print(list(map(str,self.user_hero_log_id)))
# hero_log_user_data_list
- self.user_hero_log_id = list(map(str,self.user_hero_log_id)) #str
- self.hero_log_id = list(map(int,self.user_hero_log_id)) #int
- self.log_level = list(map(int,self.log_level)) #short
- self.max_log_level_extended_num = list(map(int,self.max_log_level_extended_num)) #short
- self.log_exp = list(map(int,self.log_exp)) #int
- self.possible_awakening_flag = 0 #byte
- self.awakening_stage = 0 #short
- self.awakening_exp = 0 #int
- self.skill_slot_correction_value = 0 #byte
- self.last_set_skill_slot1_skill_id = list(map(int,self.last_set_skill_slot1_skill_id)) #short
- self.last_set_skill_slot2_skill_id = list(map(int,self.last_set_skill_slot2_skill_id)) #short
- self.last_set_skill_slot3_skill_id = list(map(int,self.last_set_skill_slot3_skill_id)) #short
- self.last_set_skill_slot4_skill_id = list(map(int,self.last_set_skill_slot4_skill_id)) #short
- self.last_set_skill_slot5_skill_id = list(map(int,self.last_set_skill_slot5_skill_id)) #short
- self.property1_property_id = 0 #int
- self.property1_value1 = 0 #int
- self.property1_value2 = 0 #int
- self.property2_property_id = 0 #int
- self.property2_value1 = 0 #int
- self.property2_value2 = 0 #int
- self.property3_property_id = 0 #int
- self.property3_value1 = 0 #int
- self.property3_value2 = 0 #int
- self.property4_property_id = 0 #int
- self.property4_value1 = 0 #int
- self.property4_value2 = 0 #int
- self.converted_card_num = 0 #short
- self.shop_purchase_flag = 1 #byte
- self.protect_flag = 0 #byte
- self.get_date = "20230101120000" #str
-
+ self.user_hero_log_id = list(map(str, self.user_hero_log_id)) # str
+ self.hero_log_id = list(map(int, self.user_hero_log_id)) # int
+ self.log_level = list(map(int, self.log_level)) # short
+ self.max_log_level_extended_num = list(
+ map(int, self.max_log_level_extended_num)
+ ) # short
+ self.log_exp = list(map(int, self.log_exp)) # int
+ self.possible_awakening_flag = 0 # byte
+ self.awakening_stage = 0 # short
+ self.awakening_exp = 0 # int
+ self.skill_slot_correction_value = 0 # byte
+ self.last_set_skill_slot1_skill_id = list(
+ map(int, self.last_set_skill_slot1_skill_id)
+ ) # short
+ self.last_set_skill_slot2_skill_id = list(
+ map(int, self.last_set_skill_slot2_skill_id)
+ ) # short
+ self.last_set_skill_slot3_skill_id = list(
+ map(int, self.last_set_skill_slot3_skill_id)
+ ) # short
+ self.last_set_skill_slot4_skill_id = list(
+ map(int, self.last_set_skill_slot4_skill_id)
+ ) # short
+ self.last_set_skill_slot5_skill_id = list(
+ map(int, self.last_set_skill_slot5_skill_id)
+ ) # short
+ self.property1_property_id = 0 # int
+ self.property1_value1 = 0 # int
+ self.property1_value2 = 0 # int
+ self.property2_property_id = 0 # int
+ self.property2_value1 = 0 # int
+ self.property2_value2 = 0 # int
+ self.property3_property_id = 0 # int
+ self.property3_value1 = 0 # int
+ self.property3_value2 = 0 # int
+ self.property4_property_id = 0 # int
+ self.property4_value1 = 0 # int
+ self.property4_value2 = 0 # int
+ self.converted_card_num = 0 # short
+ self.shop_purchase_flag = 1 # byte
+ self.protect_flag = 0 # byte
+ self.get_date = "20230101120000" # str
+
def make(self) -> bytes:
- #new stuff
+ # new stuff
hero_log_user_data_list_struct = Struct(
"user_hero_log_id_size" / Int32ub, # big endian
- "user_hero_log_id" / Int16ul[9], #string
- "hero_log_id" / Int32ub, #int
- "log_level" / Int16ub, #short
- "max_log_level_extended_num" / Int16ub, #short
- "log_exp" / Int32ub, #int
+ "user_hero_log_id" / Int16ul[9], # string
+ "hero_log_id" / Int32ub, # int
+ "log_level" / Int16ub, # short
+ "max_log_level_extended_num" / Int16ub, # short
+ "log_exp" / Int32ub, # int
"possible_awakening_flag" / Int8ul, # result is either 0 or 1
- "awakening_stage" / Int16ub, #short
- "awakening_exp" / Int32ub, #int
+ "awakening_stage" / Int16ub, # short
+ "awakening_exp" / Int32ub, # int
"skill_slot_correction_value" / Int8ul, # result is either 0 or 1
- "last_set_skill_slot1_skill_id" / Int16ub, #short
- "last_set_skill_slot2_skill_id" / Int16ub, #short
- "last_set_skill_slot3_skill_id" / Int16ub, #short
- "last_set_skill_slot4_skill_id" / Int16ub, #short
- "last_set_skill_slot5_skill_id" / Int16ub, #short
+ "last_set_skill_slot1_skill_id" / Int16ub, # short
+ "last_set_skill_slot2_skill_id" / Int16ub, # short
+ "last_set_skill_slot3_skill_id" / Int16ub, # short
+ "last_set_skill_slot4_skill_id" / Int16ub, # short
+ "last_set_skill_slot5_skill_id" / Int16ub, # short
"property1_property_id" / Int32ub,
"property1_value1" / Int32ub,
"property1_value2" / Int32ub,
@@ -601,15 +686,21 @@ class SaoGetHeroLogUserDataListResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "hero_log_user_data_list_size" / Rebuild(Int32ub, len_(this.hero_log_user_data_list)), # big endian
- "hero_log_user_data_list" / Array(this.hero_log_user_data_list_size, hero_log_user_data_list_struct),
+ "hero_log_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.hero_log_user_data_list)), # big endian
+ "hero_log_user_data_list"
+ / Array(this.hero_log_user_data_list_size, hero_log_user_data_list_struct),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- hero_log_user_data_list_size=0,
- hero_log_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ hero_log_user_data_list_size=0,
+ hero_log_user_data_list=[],
+ )
+ )
+ )
for i in range(len(self.hero_log_id)):
hero_data = dict(
@@ -645,9 +736,8 @@ class SaoGetHeroLogUserDataListResponse(SaoBaseResponse):
protect_flag=self.protect_flag,
get_date_size=len(self.get_date) * 2,
get_date=[ord(x) for x in self.get_date],
-
)
-
+
resp_data.hero_log_user_data_list.append(hero_data)
# finally, rebuild the resp_data
@@ -655,16 +745,18 @@ class SaoGetHeroLogUserDataListResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
-
+
+
class SaoGetEquipmentUserDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
def __init__(self, cmd, equipment_data) -> None:
super().__init__(cmd)
self.result = 1
-
+
self.user_equipment_id = []
self.enhancement_value = []
self.max_enhancement_value_extended_num = []
@@ -673,25 +765,25 @@ class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
self.awakening_exp = []
self.possible_awakening_flag = []
equipment_level = 0
-
+
for i in range(len(equipment_data)):
# Calculate level based off experience and the CSV list
- with open(r'titles/sao/data/EquipmentLevel.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/EquipmentLevel.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
data.append(row)
exp = equipment_data[i][4]
-
- for e in range(0,len(data)):
- if exp>=int(data[e][1]) and exp= int(data[e][1]) and exp < int(data[e + 1][1]):
equipment_level = int(data[e][0])
break
@@ -704,43 +796,47 @@ class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
self.possible_awakening_flag.append(equipment_data[i][7])
# equipment_user_data_list
- self.user_equipment_id = list(map(str,self.user_equipment_id)) #str
- self.equipment_id = list(map(int,self.user_equipment_id)) #int
- self.enhancement_value = list(map(int,self.enhancement_value)) #short
- self.max_enhancement_value_extended_num = list(map(int,self.max_enhancement_value_extended_num)) #short
- self.enhancement_exp = list(map(int,self.enhancement_exp)) #int
- self.possible_awakening_flag = list(map(int,self.possible_awakening_flag)) #byte
- self.awakening_stage = list(map(int,self.awakening_stage)) #short
- self.awakening_exp = list(map(int,self.awakening_exp)) #int
- self.property1_property_id = 0 #int
- self.property1_value1 = 0 #int
- self.property1_value2 = 0 #int
- self.property2_property_id = 0 #int
- self.property2_value1 = 0 #int
- self.property2_value2 = 0 #int
- self.property3_property_id = 0 #int
- self.property3_value1 = 0 #int
- self.property3_value2 = 0 #int
- self.property4_property_id = 0 #int
- self.property4_value1 = 0 #int
- self.property4_value2 = 0 #int
- self.converted_card_num = 1 #short
- self.shop_purchase_flag = 1 #byte
- self.protect_flag = 0 #byte
- self.get_date = "20230101120000" #str
-
+ self.user_equipment_id = list(map(str, self.user_equipment_id)) # str
+ self.equipment_id = list(map(int, self.user_equipment_id)) # int
+ self.enhancement_value = list(map(int, self.enhancement_value)) # short
+ self.max_enhancement_value_extended_num = list(
+ map(int, self.max_enhancement_value_extended_num)
+ ) # short
+ self.enhancement_exp = list(map(int, self.enhancement_exp)) # int
+ self.possible_awakening_flag = list(
+ map(int, self.possible_awakening_flag)
+ ) # byte
+ self.awakening_stage = list(map(int, self.awakening_stage)) # short
+ self.awakening_exp = list(map(int, self.awakening_exp)) # int
+ self.property1_property_id = 0 # int
+ self.property1_value1 = 0 # int
+ self.property1_value2 = 0 # int
+ self.property2_property_id = 0 # int
+ self.property2_value1 = 0 # int
+ self.property2_value2 = 0 # int
+ self.property3_property_id = 0 # int
+ self.property3_value1 = 0 # int
+ self.property3_value2 = 0 # int
+ self.property4_property_id = 0 # int
+ self.property4_value1 = 0 # int
+ self.property4_value2 = 0 # int
+ self.converted_card_num = 1 # short
+ self.shop_purchase_flag = 1 # byte
+ self.protect_flag = 0 # byte
+ self.get_date = "20230101120000" # str
+
def make(self) -> bytes:
equipment_user_data_list_struct = Struct(
"user_equipment_id_size" / Int32ub, # big endian
- "user_equipment_id" / Int16ul[9], #string
- "equipment_id" / Int32ub, #int
- "enhancement_value" / Int16ub, #short
- "max_enhancement_value_extended_num" / Int16ub, #short
- "enhancement_exp" / Int32ub, #int
+ "user_equipment_id" / Int16ul[9], # string
+ "equipment_id" / Int32ub, # int
+ "enhancement_value" / Int16ub, # short
+ "max_enhancement_value_extended_num" / Int16ub, # short
+ "enhancement_exp" / Int32ub, # int
"possible_awakening_flag" / Int8ul, # result is either 0 or 1
- "awakening_stage" / Int16ub, #short
- "awakening_exp" / Int32ub, #int
+ "awakening_stage" / Int16ub, # short
+ "awakening_exp" / Int32ub, # int
"property1_property_id" / Int32ub,
"property1_value1" / Int32ub,
"property1_value2" / Int32ub,
@@ -763,15 +859,23 @@ class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "equipment_user_data_list_size" / Rebuild(Int32ub, len_(this.equipment_user_data_list)), # big endian
- "equipment_user_data_list" / Array(this.equipment_user_data_list_size, equipment_user_data_list_struct),
+ "equipment_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.equipment_user_data_list)), # big endian
+ "equipment_user_data_list"
+ / Array(
+ this.equipment_user_data_list_size, equipment_user_data_list_struct
+ ),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- equipment_user_data_list_size=0,
- equipment_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ equipment_user_data_list_size=0,
+ equipment_user_data_list=[],
+ )
+ )
+ )
for i in range(len(self.equipment_id)):
equipment_data = dict(
@@ -779,7 +883,9 @@ class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
user_equipment_id=[ord(x) for x in self.user_equipment_id[i]],
equipment_id=self.equipment_id[i],
enhancement_value=self.enhancement_value[i],
- max_enhancement_value_extended_num=self.max_enhancement_value_extended_num[i],
+ max_enhancement_value_extended_num=self.max_enhancement_value_extended_num[
+ i
+ ],
enhancement_exp=self.enhancement_exp[i],
possible_awakening_flag=self.possible_awakening_flag[i],
awakening_stage=self.awakening_stage[i],
@@ -801,9 +907,8 @@ class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
protect_flag=self.protect_flag,
get_date_size=len(self.get_date) * 2,
get_date=[ord(x) for x in self.get_date],
-
)
-
+
resp_data.equipment_user_data_list.append(equipment_data)
# finally, rebuild the resp_data
@@ -811,11 +916,13 @@ class SaoGetEquipmentUserDataListResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
-
+
+
class SaoGetItemUserDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetItemUserDataListResponse(SaoBaseResponse):
def __init__(self, cmd, item_data) -> None:
super().__init__(cmd)
@@ -827,18 +934,21 @@ class SaoGetItemUserDataListResponse(SaoBaseResponse):
self.user_item_id.append(item_data[i][2])
# item_user_data_list
- self.user_item_id = list(map(str,self.user_item_id)) #str
- self.item_id = list(map(int,self.user_item_id)) #int
- self.protect_flag = 0 #byte
- self.get_date = "20230101120000" #str
-
+ self.user_item_id = list(map(str, self.user_item_id)) # str
+ self.item_id = list(map(int, self.user_item_id)) # int
+ self.protect_flag = 0 # byte
+ self.get_date = "20230101120000" # str
+
def make(self) -> bytes:
- #new stuff
+ # new stuff
item_user_data_list_struct = Struct(
"user_item_id_size" / Int32ub, # big endian
- "user_item_id" / Int16ul[6], #string but this will not work with 10000 IDs... only with 6 digits
- "item_id" / Int32ub, #int
+ "user_item_id"
+ / Int16ul[
+ 6
+ ], # string but this will not work with 10000 IDs... only with 6 digits
+ "item_id" / Int32ub, # int
"protect_flag" / Int8ul, # result is either 0 or 1
"get_date_size" / Int32ub, # big endian
"get_date" / Int16ul[len(self.get_date)],
@@ -847,15 +957,21 @@ class SaoGetItemUserDataListResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "item_user_data_list_size" / Rebuild(Int32ub, len_(this.item_user_data_list)), # big endian
- "item_user_data_list" / Array(this.item_user_data_list_size, item_user_data_list_struct),
+ "item_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.item_user_data_list)), # big endian
+ "item_user_data_list"
+ / Array(this.item_user_data_list_size, item_user_data_list_struct),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- item_user_data_list_size=0,
- item_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ item_user_data_list_size=0,
+ item_user_data_list=[],
+ )
+ )
+ )
for i in range(len(self.item_id)):
item_data = dict(
@@ -865,9 +981,8 @@ class SaoGetItemUserDataListResponse(SaoBaseResponse):
protect_flag=self.protect_flag,
get_date_size=len(self.get_date) * 2,
get_date=[ord(x) for x in self.get_date],
-
)
-
+
resp_data.item_user_data_list.append(item_data)
# finally, rebuild the resp_data
@@ -875,36 +990,38 @@ class SaoGetItemUserDataListResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
-
+
+
class SaoGetSupportLogUserDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetSupportLogUserDataListResponse(SaoBaseResponse):
def __init__(self, cmd, supportIdsData) -> None:
super().__init__(cmd)
self.result = 1
# support_log_user_data_list
- self.user_support_log_id = list(map(str,supportIdsData)) #str
- self.support_log_id = supportIdsData #int
+ self.user_support_log_id = list(map(str, supportIdsData)) # str
+ self.support_log_id = supportIdsData # int
self.possible_awakening_flag = 0
self.awakening_stage = 0
self.awakening_exp = 0
self.converted_card_num = 0
self.shop_purchase_flag = 0
- self.protect_flag = 0 #byte
- self.get_date = "20230101120000" #str
-
+ self.protect_flag = 0 # byte
+ self.get_date = "20230101120000" # str
+
def make(self) -> bytes:
support_log_user_data_list_struct = Struct(
"user_support_log_id_size" / Int32ub, # big endian
"user_support_log_id" / Int16ul[9],
- "support_log_id" / Int32ub, #int
+ "support_log_id" / Int32ub, # int
"possible_awakening_flag" / Int8ul, # result is either 0 or 1
- "awakening_stage" / Int16ub, #short
- "awakening_exp" / Int32ub, # int
- "converted_card_num" / Int16ub, #short
+ "awakening_stage" / Int16ub, # short
+ "awakening_exp" / Int32ub, # int
+ "converted_card_num" / Int16ub, # short
"shop_purchase_flag" / Int8ul, # result is either 0 or 1
"protect_flag" / Int8ul, # result is either 0 or 1
"get_date_size" / Int32ub, # big endian
@@ -914,15 +1031,23 @@ class SaoGetSupportLogUserDataListResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "support_log_user_data_list_size" / Rebuild(Int32ub, len_(this.support_log_user_data_list)), # big endian
- "support_log_user_data_list" / Array(this.support_log_user_data_list_size, support_log_user_data_list_struct),
+ "support_log_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.support_log_user_data_list)), # big endian
+ "support_log_user_data_list"
+ / Array(
+ this.support_log_user_data_list_size, support_log_user_data_list_struct
+ ),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- support_log_user_data_list_size=0,
- support_log_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ support_log_user_data_list_size=0,
+ support_log_user_data_list=[],
+ )
+ )
+ )
for i in range(len(self.support_log_id)):
support_data = dict(
@@ -937,48 +1062,55 @@ class SaoGetSupportLogUserDataListResponse(SaoBaseResponse):
protect_flag=self.protect_flag,
get_date_size=len(self.get_date) * 2,
get_date=[ord(x) for x in self.get_date],
-
)
-
+
resp_data.support_log_user_data_list.append(support_data)
resp_data = resp_struct.build(resp_data)
self.length = len(resp_data)
return super().make() + resp_data
-
+
+
class SaoGetTitleUserDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetTitleUserDataListResponse(SaoBaseResponse):
def __init__(self, cmd, titleIdsData) -> None:
super().__init__(cmd)
self.result = 1
# title_user_data_list
- self.user_title_id = list(map(str,titleIdsData)) #str
- self.title_id = titleIdsData #int
-
+ self.user_title_id = list(map(str, titleIdsData)) # str
+ self.title_id = titleIdsData # int
+
def make(self) -> bytes:
title_user_data_list_struct = Struct(
"user_title_id_size" / Int32ub, # big endian
- "user_title_id" / Int16ul[6], #string
- "title_id" / Int32ub, #int
+ "user_title_id" / Int16ul[6], # string
+ "title_id" / Int32ub, # int
)
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "title_user_data_list_size" / Rebuild(Int32ub, len_(this.title_user_data_list)), # big endian
- "title_user_data_list" / Array(this.title_user_data_list_size, title_user_data_list_struct),
+ "title_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.title_user_data_list)), # big endian
+ "title_user_data_list"
+ / Array(this.title_user_data_list_size, title_user_data_list_struct),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- title_user_data_list_size=0,
- title_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ title_user_data_list_size=0,
+ title_user_data_list=[],
+ )
+ )
+ )
for i in range(len(self.title_id)):
title_data = dict(
@@ -986,7 +1118,7 @@ class SaoGetTitleUserDataListResponse(SaoBaseResponse):
user_title_id=[ord(x) for x in self.user_title_id[i]],
title_id=self.title_id[i],
)
-
+
resp_data.title_user_data_list.append(title_data)
# finally, rebuild the resp_data
@@ -995,10 +1127,12 @@ class SaoGetTitleUserDataListResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetEpisodeAppendDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetEpisodeAppendDataListResponse(SaoBaseResponse):
def __init__(self, cmd, profile_data) -> None:
super().__init__(cmd)
@@ -1006,16 +1140,34 @@ class SaoGetEpisodeAppendDataListResponse(SaoBaseResponse):
self.result = 1
self.user_episode_append_id_list = ["10001", "10002", "10003", "10004", "10005"]
- self.user_id_list = [str(profile_data["user"]), str(profile_data["user"]), str(profile_data["user"]), str(profile_data["user"]), str(profile_data["user"])]
+ self.user_id_list = [
+ str(profile_data["user"]),
+ str(profile_data["user"]),
+ str(profile_data["user"]),
+ str(profile_data["user"]),
+ str(profile_data["user"]),
+ ]
self.episode_append_id_list = [10001, 10002, 10003, 10004, 10005]
- self.own_num_list = [3, 3, 3, 3 ,3]
-
+ self.own_num_list = [3, 3, 3, 3, 3]
+
def make(self) -> bytes:
episode_data_struct = Struct(
- "user_episode_append_id_size" / Rebuild(Int32ub, len_(this.user_episode_append_id) * 2), # calculates the length of the user_episode_append_id
- "user_episode_append_id" / PaddedString(this.user_episode_append_id_size, "utf_16_le"), # user_episode_append_id is a (zero) padded string
- "user_id_size" / Rebuild(Int32ub, len_(this.user_id) * 2), # calculates the length of the user_id
- "user_id" / PaddedString(this.user_id_size, "utf_16_le"), # user_id is a (zero) padded string
+ "user_episode_append_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_episode_append_id) * 2
+ ), # calculates the length of the user_episode_append_id
+ "user_episode_append_id"
+ / PaddedString(
+ this.user_episode_append_id_size, "utf_16_le"
+ ), # user_episode_append_id is a (zero) padded string
+ "user_id_size"
+ / Rebuild(
+ Int32ub, len_(this.user_id) * 2
+ ), # calculates the length of the user_id
+ "user_id"
+ / PaddedString(
+ this.user_id_size, "utf_16_le"
+ ), # user_id is a (zero) padded string
"episode_append_id" / Int32ub,
"own_num" / Int32ub,
)
@@ -1023,29 +1175,42 @@ class SaoGetEpisodeAppendDataListResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "episode_append_data_list_size" / Rebuild(Int32ub, len_(this.episode_append_data_list)), # big endian
- "episode_append_data_list" / Array(this.episode_append_data_list_size, episode_data_struct),
+ "episode_append_data_list_size"
+ / Rebuild(Int32ub, len_(this.episode_append_data_list)), # big endian
+ "episode_append_data_list"
+ / Array(this.episode_append_data_list_size, episode_data_struct),
)
# really dump to parse the build resp, but that creates a new object
# and is nicer to twork with
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- episode_append_data_list_size=0,
- episode_append_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ episode_append_data_list_size=0,
+ episode_append_data_list=[],
+ )
+ )
+ )
- if len(self.user_episode_append_id_list) != len(self.user_id_list) != len(self.episode_append_id_list) != len(self.own_num_list):
+ if (
+ len(self.user_episode_append_id_list)
+ != len(self.user_id_list)
+ != len(self.episode_append_id_list)
+ != len(self.own_num_list)
+ ):
raise ValueError("all lists must be of the same length")
for i in range(len(self.user_id_list)):
# add the episode_data_struct to the resp_struct.episode_append_data_list
- resp_data.episode_append_data_list.append(dict(
- user_episode_append_id=self.user_episode_append_id_list[i],
- user_id=self.user_id_list[i],
- episode_append_id=self.episode_append_id_list[i],
- own_num=self.own_num_list[i],
- ))
+ resp_data.episode_append_data_list.append(
+ dict(
+ user_episode_append_id=self.user_episode_append_id_list[i],
+ user_id=self.user_id_list[i],
+ episode_append_id=self.episode_append_id_list[i],
+ own_num=self.own_num_list[i],
+ )
+ )
# finally, rebuild the resp_data
resp_data = resp_struct.build(resp_data)
@@ -1053,20 +1218,22 @@ class SaoGetEpisodeAppendDataListResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetPartyDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
-class SaoGetPartyDataListResponse(SaoBaseResponse): # Default party
+
+class SaoGetPartyDataListResponse(SaoBaseResponse): # Default party
def __init__(self, cmd, hero1_data, hero2_data, hero3_data) -> None:
super().__init__(cmd)
-
+
self.result = 1
- self.party_data_list_size = 1 # Number of arrays
+ self.party_data_list_size = 1 # Number of arrays
self.user_party_id = "0"
self.team_no = 0
- self.party_team_data_list_size = 3 # Number of arrays
+ self.party_team_data_list_size = 3 # Number of arrays
self.user_party_team_id_1 = "0"
self.arrangement_num_1 = 0
@@ -1100,264 +1267,296 @@ class SaoGetPartyDataListResponse(SaoBaseResponse): # Default party
self.skill_slot3_skill_id_3 = hero3_data[9]
self.skill_slot4_skill_id_3 = hero3_data[10]
self.skill_slot5_skill_id_3 = hero3_data[11]
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "party_data_list_size" / Int32ub, # big endian
-
- "user_party_id_size" / Int32ub, # big endian
+ "result" / Int8ul, # result is either 0 or 1
+ "party_data_list_size" / Int32ub, # big endian
+ "user_party_id_size" / Int32ub, # big endian
"user_party_id" / Int16ul[len(self.user_party_id)],
- "team_no" / Int8ul, # result is either 0 or 1
- "party_team_data_list_size" / Int32ub, # big endian
-
- "user_party_team_id_1_size" / Int32ub, # big endian
+ "team_no" / Int8ul, # result is either 0 or 1
+ "party_team_data_list_size" / Int32ub, # big endian
+ "user_party_team_id_1_size" / Int32ub, # big endian
"user_party_team_id_1" / Int16ul[len(self.user_party_team_id_1)],
- "arrangement_num_1" / Int8ul, # big endian
- "user_hero_log_id_1_size" / Int32ub, # big endian
+ "arrangement_num_1" / Int8ul, # big endian
+ "user_hero_log_id_1_size" / Int32ub, # big endian
"user_hero_log_id_1" / Int16ul[len(self.user_hero_log_id_1)],
- "main_weapon_user_equipment_id_1_size" / Int32ub, # big endian
- "main_weapon_user_equipment_id_1" / Int16ul[len(self.main_weapon_user_equipment_id_1)],
- "sub_equipment_user_equipment_id_1_size" / Int32ub, # big endian
- "sub_equipment_user_equipment_id_1" / Int16ul[len(self.sub_equipment_user_equipment_id_1)],
+ "main_weapon_user_equipment_id_1_size" / Int32ub, # big endian
+ "main_weapon_user_equipment_id_1"
+ / Int16ul[len(self.main_weapon_user_equipment_id_1)],
+ "sub_equipment_user_equipment_id_1_size" / Int32ub, # big endian
+ "sub_equipment_user_equipment_id_1"
+ / Int16ul[len(self.sub_equipment_user_equipment_id_1)],
"skill_slot1_skill_id_1" / Int32ub,
"skill_slot2_skill_id_1" / Int32ub,
"skill_slot3_skill_id_1" / Int32ub,
"skill_slot4_skill_id_1" / Int32ub,
"skill_slot5_skill_id_1" / Int32ub,
-
- "user_party_team_id_2_size" / Int32ub, # big endian
+ "user_party_team_id_2_size" / Int32ub, # big endian
"user_party_team_id_2" / Int16ul[len(self.user_party_team_id_2)],
- "arrangement_num_2" / Int8ul, # result is either 0 or 1
- "user_hero_log_id_2_size" / Int32ub, # big endian
+ "arrangement_num_2" / Int8ul, # result is either 0 or 1
+ "user_hero_log_id_2_size" / Int32ub, # big endian
"user_hero_log_id_2" / Int16ul[len(self.user_hero_log_id_2)],
- "main_weapon_user_equipment_id_2_size" / Int32ub, # big endian
- "main_weapon_user_equipment_id_2" / Int16ul[len(self.main_weapon_user_equipment_id_2)],
- "sub_equipment_user_equipment_id_2_size" / Int32ub, # big endian
- "sub_equipment_user_equipment_id_2" / Int16ul[len(self.sub_equipment_user_equipment_id_2)],
+ "main_weapon_user_equipment_id_2_size" / Int32ub, # big endian
+ "main_weapon_user_equipment_id_2"
+ / Int16ul[len(self.main_weapon_user_equipment_id_2)],
+ "sub_equipment_user_equipment_id_2_size" / Int32ub, # big endian
+ "sub_equipment_user_equipment_id_2"
+ / Int16ul[len(self.sub_equipment_user_equipment_id_2)],
"skill_slot1_skill_id_2" / Int32ub,
"skill_slot2_skill_id_2" / Int32ub,
"skill_slot3_skill_id_2" / Int32ub,
"skill_slot4_skill_id_2" / Int32ub,
"skill_slot5_skill_id_2" / Int32ub,
-
- "user_party_team_id_3_size" / Int32ub, # big endian
+ "user_party_team_id_3_size" / Int32ub, # big endian
"user_party_team_id_3" / Int16ul[len(self.user_party_team_id_3)],
- "arrangement_num_3" / Int8ul, # result is either 0 or 1
- "user_hero_log_id_3_size" / Int32ub, # big endian
+ "arrangement_num_3" / Int8ul, # result is either 0 or 1
+ "user_hero_log_id_3_size" / Int32ub, # big endian
"user_hero_log_id_3" / Int16ul[len(self.user_hero_log_id_3)],
- "main_weapon_user_equipment_id_3_size" / Int32ub, # big endian
- "main_weapon_user_equipment_id_3" / Int16ul[len(self.main_weapon_user_equipment_id_3)],
- "sub_equipment_user_equipment_id_3_size" / Int32ub, # big endian
- "sub_equipment_user_equipment_id_3" / Int16ul[len(self.sub_equipment_user_equipment_id_3)],
+ "main_weapon_user_equipment_id_3_size" / Int32ub, # big endian
+ "main_weapon_user_equipment_id_3"
+ / Int16ul[len(self.main_weapon_user_equipment_id_3)],
+ "sub_equipment_user_equipment_id_3_size" / Int32ub, # big endian
+ "sub_equipment_user_equipment_id_3"
+ / Int16ul[len(self.sub_equipment_user_equipment_id_3)],
"skill_slot1_skill_id_3" / Int32ub,
"skill_slot2_skill_id_3" / Int32ub,
"skill_slot3_skill_id_3" / Int32ub,
"skill_slot4_skill_id_3" / Int32ub,
"skill_slot5_skill_id_3" / Int32ub,
-
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- party_data_list_size=self.party_data_list_size,
-
- user_party_id_size=len(self.user_party_id) * 2,
- user_party_id=[ord(x) for x in self.user_party_id],
- team_no=self.team_no,
- party_team_data_list_size=self.party_team_data_list_size,
-
- user_party_team_id_1_size=len(self.user_party_team_id_1) * 2,
- user_party_team_id_1=[ord(x) for x in self.user_party_team_id_1],
- arrangement_num_1=self.arrangement_num_1,
- user_hero_log_id_1_size=len(self.user_hero_log_id_1) * 2,
- user_hero_log_id_1=[ord(x) for x in self.user_hero_log_id_1],
- main_weapon_user_equipment_id_1_size=len(self.main_weapon_user_equipment_id_1) * 2,
- main_weapon_user_equipment_id_1=[ord(x) for x in self.main_weapon_user_equipment_id_1],
- sub_equipment_user_equipment_id_1_size=len(self.sub_equipment_user_equipment_id_1) * 2,
- sub_equipment_user_equipment_id_1=[ord(x) for x in self.sub_equipment_user_equipment_id_1],
- skill_slot1_skill_id_1=self.skill_slot1_skill_id_1,
- skill_slot2_skill_id_1=self.skill_slot2_skill_id_1,
- skill_slot3_skill_id_1=self.skill_slot3_skill_id_1,
- skill_slot4_skill_id_1=self.skill_slot4_skill_id_1,
- skill_slot5_skill_id_1=self.skill_slot5_skill_id_1,
-
- user_party_team_id_2_size=len(self.user_party_team_id_2) * 2,
- user_party_team_id_2=[ord(x) for x in self.user_party_team_id_2],
- arrangement_num_2=self.arrangement_num_2,
- user_hero_log_id_2_size=len(self.user_hero_log_id_2) * 2,
- user_hero_log_id_2=[ord(x) for x in self.user_hero_log_id_2],
- main_weapon_user_equipment_id_2_size=len(self.main_weapon_user_equipment_id_2) * 2,
- main_weapon_user_equipment_id_2=[ord(x) for x in self.main_weapon_user_equipment_id_2],
- sub_equipment_user_equipment_id_2_size=len(self.sub_equipment_user_equipment_id_2) * 2,
- sub_equipment_user_equipment_id_2=[ord(x) for x in self.sub_equipment_user_equipment_id_2],
- skill_slot1_skill_id_2=self.skill_slot1_skill_id_2,
- skill_slot2_skill_id_2=self.skill_slot2_skill_id_2,
- skill_slot3_skill_id_2=self.skill_slot3_skill_id_2,
- skill_slot4_skill_id_2=self.skill_slot4_skill_id_2,
- skill_slot5_skill_id_2=self.skill_slot5_skill_id_2,
-
- user_party_team_id_3_size=len(self.user_party_team_id_3) * 2,
- user_party_team_id_3=[ord(x) for x in self.user_party_team_id_3],
- arrangement_num_3=self.arrangement_num_3,
- user_hero_log_id_3_size=len(self.user_hero_log_id_3) * 2,
- user_hero_log_id_3=[ord(x) for x in self.user_hero_log_id_3],
- main_weapon_user_equipment_id_3_size=len(self.main_weapon_user_equipment_id_3) * 2,
- main_weapon_user_equipment_id_3=[ord(x) for x in self.main_weapon_user_equipment_id_3],
- sub_equipment_user_equipment_id_3_size=len(self.sub_equipment_user_equipment_id_3) * 2,
- sub_equipment_user_equipment_id_3=[ord(x) for x in self.sub_equipment_user_equipment_id_3],
- skill_slot1_skill_id_3=self.skill_slot1_skill_id_3,
- skill_slot2_skill_id_3=self.skill_slot2_skill_id_3,
- skill_slot3_skill_id_3=self.skill_slot3_skill_id_3,
- skill_slot4_skill_id_3=self.skill_slot4_skill_id_3,
- skill_slot5_skill_id_3=self.skill_slot5_skill_id_3,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ party_data_list_size=self.party_data_list_size,
+ user_party_id_size=len(self.user_party_id) * 2,
+ user_party_id=[ord(x) for x in self.user_party_id],
+ team_no=self.team_no,
+ party_team_data_list_size=self.party_team_data_list_size,
+ user_party_team_id_1_size=len(self.user_party_team_id_1) * 2,
+ user_party_team_id_1=[ord(x) for x in self.user_party_team_id_1],
+ arrangement_num_1=self.arrangement_num_1,
+ user_hero_log_id_1_size=len(self.user_hero_log_id_1) * 2,
+ user_hero_log_id_1=[ord(x) for x in self.user_hero_log_id_1],
+ main_weapon_user_equipment_id_1_size=len(
+ self.main_weapon_user_equipment_id_1
+ )
+ * 2,
+ main_weapon_user_equipment_id_1=[
+ ord(x) for x in self.main_weapon_user_equipment_id_1
+ ],
+ sub_equipment_user_equipment_id_1_size=len(
+ self.sub_equipment_user_equipment_id_1
+ )
+ * 2,
+ sub_equipment_user_equipment_id_1=[
+ ord(x) for x in self.sub_equipment_user_equipment_id_1
+ ],
+ skill_slot1_skill_id_1=self.skill_slot1_skill_id_1,
+ skill_slot2_skill_id_1=self.skill_slot2_skill_id_1,
+ skill_slot3_skill_id_1=self.skill_slot3_skill_id_1,
+ skill_slot4_skill_id_1=self.skill_slot4_skill_id_1,
+ skill_slot5_skill_id_1=self.skill_slot5_skill_id_1,
+ user_party_team_id_2_size=len(self.user_party_team_id_2) * 2,
+ user_party_team_id_2=[ord(x) for x in self.user_party_team_id_2],
+ arrangement_num_2=self.arrangement_num_2,
+ user_hero_log_id_2_size=len(self.user_hero_log_id_2) * 2,
+ user_hero_log_id_2=[ord(x) for x in self.user_hero_log_id_2],
+ main_weapon_user_equipment_id_2_size=len(
+ self.main_weapon_user_equipment_id_2
+ )
+ * 2,
+ main_weapon_user_equipment_id_2=[
+ ord(x) for x in self.main_weapon_user_equipment_id_2
+ ],
+ sub_equipment_user_equipment_id_2_size=len(
+ self.sub_equipment_user_equipment_id_2
+ )
+ * 2,
+ sub_equipment_user_equipment_id_2=[
+ ord(x) for x in self.sub_equipment_user_equipment_id_2
+ ],
+ skill_slot1_skill_id_2=self.skill_slot1_skill_id_2,
+ skill_slot2_skill_id_2=self.skill_slot2_skill_id_2,
+ skill_slot3_skill_id_2=self.skill_slot3_skill_id_2,
+ skill_slot4_skill_id_2=self.skill_slot4_skill_id_2,
+ skill_slot5_skill_id_2=self.skill_slot5_skill_id_2,
+ user_party_team_id_3_size=len(self.user_party_team_id_3) * 2,
+ user_party_team_id_3=[ord(x) for x in self.user_party_team_id_3],
+ arrangement_num_3=self.arrangement_num_3,
+ user_hero_log_id_3_size=len(self.user_hero_log_id_3) * 2,
+ user_hero_log_id_3=[ord(x) for x in self.user_hero_log_id_3],
+ main_weapon_user_equipment_id_3_size=len(
+ self.main_weapon_user_equipment_id_3
+ )
+ * 2,
+ main_weapon_user_equipment_id_3=[
+ ord(x) for x in self.main_weapon_user_equipment_id_3
+ ],
+ sub_equipment_user_equipment_id_3_size=len(
+ self.sub_equipment_user_equipment_id_3
+ )
+ * 2,
+ sub_equipment_user_equipment_id_3=[
+ ord(x) for x in self.sub_equipment_user_equipment_id_3
+ ],
+ skill_slot1_skill_id_3=self.skill_slot1_skill_id_3,
+ skill_slot2_skill_id_3=self.skill_slot2_skill_id_3,
+ skill_slot3_skill_id_3=self.skill_slot3_skill_id_3,
+ skill_slot4_skill_id_3=self.skill_slot4_skill_id_3,
+ skill_slot5_skill_id_3=self.skill_slot5_skill_id_3,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
-
+
+
class SaoGetQuestScenePrevScanProfileCardRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetQuestScenePrevScanProfileCardResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.profile_card_data = 1 # number of arrays
-
+ self.profile_card_data = 1 # number of arrays
+
self.profile_card_code = ""
self.nick_name = ""
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "profile_card_data" / Int32ub, # big endian
-
- "profile_card_code_size" / Int32ub, # big endian
+ "result" / Int8ul, # result is either 0 or 1
+ "profile_card_data" / Int32ub, # big endian
+ "profile_card_code_size" / Int32ub, # big endian
"profile_card_code" / Int16ul[len(self.profile_card_code)],
- "nick_name_size" / Int32ub, # big endian
+ "nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
- "rank_num" / Int16ub, #short
- "setting_title_id" / Int32ub, # int
- "skill_id" / Int16ub, # short
- "hero_log_hero_log_id" / Int32ub, # int
- "hero_log_log_level" / Int16ub, # short
- "hero_log_awakening_stage" / Int16ub, # short
- "hero_log_property1_property_id" / Int32ub, # int
- "hero_log_property1_value1" / Int32ub, # int
- "hero_log_property1_value2" / Int32ub, # int
- "hero_log_property2_property_id" / Int32ub, # int
- "hero_log_property2_value1" / Int32ub, # int
- "hero_log_property2_value2" / Int32ub, # int
- "hero_log_property3_property_id" / Int32ub, # int
- "hero_log_property3_value1" / Int32ub, # int
- "hero_log_property3_value2" / Int32ub, # int
- "hero_log_property4_property_id" / Int32ub, # int
- "hero_log_property4_value1" / Int32ub, # int
- "hero_log_property4_value2" / Int32ub, # int
- "main_weapon_equipment_id" / Int32ub, # int
- "main_weapon_enhancement_value" / Int16ub, # short
- "main_weapon_awakening_stage" / Int16ub, # short
- "main_weapon_property1_property_id" / Int32ub, # int
- "main_weapon_property1_value1" / Int32ub, # int
- "main_weapon_property1_value2" / Int32ub, # int
- "main_weapon_property2_property_id" / Int32ub, # int
- "main_weapon_property2_value1" / Int32ub, # int
- "main_weapon_property2_value2" / Int32ub, # int
- "main_weapon_property3_property_id" / Int32ub, # int
- "main_weapon_property3_value1" / Int32ub, # int
- "main_weapon_property3_value2" / Int32ub, # int
- "main_weapon_property4_property_id" / Int32ub, # int
- "main_weapon_property4_value1" / Int32ub, # int
- "main_weapon_property4_value2" / Int32ub, # int
- "sub_equipment_equipment_id" / Int32ub, # int
- "sub_equipment_enhancement_value" / Int16ub, # short
- "sub_equipment_awakening_stage" / Int16ub, # short
- "sub_equipment_property1_property_id" / Int32ub, # int
- "sub_equipment_property1_value1" / Int32ub, # int
- "sub_equipment_property1_value2" / Int32ub, # int
- "sub_equipment_property2_property_id" / Int32ub, # int
- "sub_equipment_property2_value1" / Int32ub, # int
- "sub_equipment_property2_value2" / Int32ub, # int
- "sub_equipment_property3_property_id" / Int32ub, # int
- "sub_equipment_property3_value1" / Int32ub, # int
- "sub_equipment_property3_value2" / Int32ub, # int
- "sub_equipment_property4_property_id" / Int32ub, # int
- "sub_equipment_property4_value1" / Int32ub, # int
- "sub_equipment_property4_value2" / Int32ub, # int
- "holographic_flag" / Int8ul, # result is either 0 or 1
+ "rank_num" / Int16ub, # short
+ "setting_title_id" / Int32ub, # int
+ "skill_id" / Int16ub, # short
+ "hero_log_hero_log_id" / Int32ub, # int
+ "hero_log_log_level" / Int16ub, # short
+ "hero_log_awakening_stage" / Int16ub, # short
+ "hero_log_property1_property_id" / Int32ub, # int
+ "hero_log_property1_value1" / Int32ub, # int
+ "hero_log_property1_value2" / Int32ub, # int
+ "hero_log_property2_property_id" / Int32ub, # int
+ "hero_log_property2_value1" / Int32ub, # int
+ "hero_log_property2_value2" / Int32ub, # int
+ "hero_log_property3_property_id" / Int32ub, # int
+ "hero_log_property3_value1" / Int32ub, # int
+ "hero_log_property3_value2" / Int32ub, # int
+ "hero_log_property4_property_id" / Int32ub, # int
+ "hero_log_property4_value1" / Int32ub, # int
+ "hero_log_property4_value2" / Int32ub, # int
+ "main_weapon_equipment_id" / Int32ub, # int
+ "main_weapon_enhancement_value" / Int16ub, # short
+ "main_weapon_awakening_stage" / Int16ub, # short
+ "main_weapon_property1_property_id" / Int32ub, # int
+ "main_weapon_property1_value1" / Int32ub, # int
+ "main_weapon_property1_value2" / Int32ub, # int
+ "main_weapon_property2_property_id" / Int32ub, # int
+ "main_weapon_property2_value1" / Int32ub, # int
+ "main_weapon_property2_value2" / Int32ub, # int
+ "main_weapon_property3_property_id" / Int32ub, # int
+ "main_weapon_property3_value1" / Int32ub, # int
+ "main_weapon_property3_value2" / Int32ub, # int
+ "main_weapon_property4_property_id" / Int32ub, # int
+ "main_weapon_property4_value1" / Int32ub, # int
+ "main_weapon_property4_value2" / Int32ub, # int
+ "sub_equipment_equipment_id" / Int32ub, # int
+ "sub_equipment_enhancement_value" / Int16ub, # short
+ "sub_equipment_awakening_stage" / Int16ub, # short
+ "sub_equipment_property1_property_id" / Int32ub, # int
+ "sub_equipment_property1_value1" / Int32ub, # int
+ "sub_equipment_property1_value2" / Int32ub, # int
+ "sub_equipment_property2_property_id" / Int32ub, # int
+ "sub_equipment_property2_value1" / Int32ub, # int
+ "sub_equipment_property2_value2" / Int32ub, # int
+ "sub_equipment_property3_property_id" / Int32ub, # int
+ "sub_equipment_property3_value1" / Int32ub, # int
+ "sub_equipment_property3_value2" / Int32ub, # int
+ "sub_equipment_property4_property_id" / Int32ub, # int
+ "sub_equipment_property4_value1" / Int32ub, # int
+ "sub_equipment_property4_value2" / Int32ub, # int
+ "holographic_flag" / Int8ul, # result is either 0 or 1
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- profile_card_data=self.profile_card_data,
-
- profile_card_code_size=len(self.profile_card_code) * 2,
- profile_card_code=[ord(x) for x in self.profile_card_code],
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- rank_num=0,
- setting_title_id=0,
- skill_id=0,
- hero_log_hero_log_id=0,
- hero_log_log_level=0,
- hero_log_awakening_stage=0,
- hero_log_property1_property_id=0,
- hero_log_property1_value1=0,
- hero_log_property1_value2=0,
- hero_log_property2_property_id=0,
- hero_log_property2_value1=0,
- hero_log_property2_value2=0,
- hero_log_property3_property_id=0,
- hero_log_property3_value1=0,
- hero_log_property3_value2=0,
- hero_log_property4_property_id=0,
- hero_log_property4_value1=0,
- hero_log_property4_value2=0,
- main_weapon_equipment_id=0,
- main_weapon_enhancement_value=0,
- main_weapon_awakening_stage=0,
- main_weapon_property1_property_id=0,
- main_weapon_property1_value1=0,
- main_weapon_property1_value2=0,
- main_weapon_property2_property_id=0,
- main_weapon_property2_value1=0,
- main_weapon_property2_value2=0,
- main_weapon_property3_property_id=0,
- main_weapon_property3_value1=0,
- main_weapon_property3_value2=0,
- main_weapon_property4_property_id=0,
- main_weapon_property4_value1=0,
- main_weapon_property4_value2=0,
- sub_equipment_equipment_id=0,
- sub_equipment_enhancement_value=0,
- sub_equipment_awakening_stage=0,
- sub_equipment_property1_property_id=0,
- sub_equipment_property1_value1=0,
- sub_equipment_property1_value2=0,
- sub_equipment_property2_property_id=0,
- sub_equipment_property2_value1=0,
- sub_equipment_property2_value2=0,
- sub_equipment_property3_property_id=0,
- sub_equipment_property3_value1=0,
- sub_equipment_property3_value2=0,
- sub_equipment_property4_property_id=0,
- sub_equipment_property4_value1=0,
- sub_equipment_property4_value2=0,
- holographic_flag=0,
-
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ profile_card_data=self.profile_card_data,
+ profile_card_code_size=len(self.profile_card_code) * 2,
+ profile_card_code=[ord(x) for x in self.profile_card_code],
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ rank_num=0,
+ setting_title_id=0,
+ skill_id=0,
+ hero_log_hero_log_id=0,
+ hero_log_log_level=0,
+ hero_log_awakening_stage=0,
+ hero_log_property1_property_id=0,
+ hero_log_property1_value1=0,
+ hero_log_property1_value2=0,
+ hero_log_property2_property_id=0,
+ hero_log_property2_value1=0,
+ hero_log_property2_value2=0,
+ hero_log_property3_property_id=0,
+ hero_log_property3_value1=0,
+ hero_log_property3_value2=0,
+ hero_log_property4_property_id=0,
+ hero_log_property4_value1=0,
+ hero_log_property4_value2=0,
+ main_weapon_equipment_id=0,
+ main_weapon_enhancement_value=0,
+ main_weapon_awakening_stage=0,
+ main_weapon_property1_property_id=0,
+ main_weapon_property1_value1=0,
+ main_weapon_property1_value2=0,
+ main_weapon_property2_property_id=0,
+ main_weapon_property2_value1=0,
+ main_weapon_property2_value2=0,
+ main_weapon_property3_property_id=0,
+ main_weapon_property3_value1=0,
+ main_weapon_property3_value2=0,
+ main_weapon_property4_property_id=0,
+ main_weapon_property4_value1=0,
+ main_weapon_property4_value2=0,
+ sub_equipment_equipment_id=0,
+ sub_equipment_enhancement_value=0,
+ sub_equipment_awakening_stage=0,
+ sub_equipment_property1_property_id=0,
+ sub_equipment_property1_value1=0,
+ sub_equipment_property1_value2=0,
+ sub_equipment_property2_property_id=0,
+ sub_equipment_property2_value1=0,
+ sub_equipment_property2_value2=0,
+ sub_equipment_property3_property_id=0,
+ sub_equipment_property3_value1=0,
+ sub_equipment_property3_value2=0,
+ sub_equipment_property4_property_id=0,
+ sub_equipment_property4_value1=0,
+ sub_equipment_property4_value2=0,
+ holographic_flag=0,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetResourcePathInfoRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetResourcePathInfoResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
@@ -1366,7 +1565,7 @@ class SaoGetResourcePathInfoResponse(SaoBaseResponse):
self.gasha_base_dir = "a"
self.ad_base_dir = "b"
self.event_base_dir = "c"
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
@@ -1381,72 +1580,84 @@ class SaoGetResourcePathInfoResponse(SaoBaseResponse):
"event_base_dir" / Int16ul[len(self.event_base_dir)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- resource_base_url_size=len(self.resource_base_url) * 2,
- resource_base_url=[ord(x) for x in self.resource_base_url],
- gasha_base_dir_size=len(self.gasha_base_dir) * 2,
- gasha_base_dir=[ord(x) for x in self.gasha_base_dir],
- ad_base_dir_size=len(self.ad_base_dir) * 2,
- ad_base_dir=[ord(x) for x in self.ad_base_dir],
- event_base_dir_size=len(self.event_base_dir) * 2,
- event_base_dir=[ord(x) for x in self.event_base_dir],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ resource_base_url_size=len(self.resource_base_url) * 2,
+ resource_base_url=[ord(x) for x in self.resource_base_url],
+ gasha_base_dir_size=len(self.gasha_base_dir) * 2,
+ gasha_base_dir=[ord(x) for x in self.gasha_base_dir],
+ ad_base_dir_size=len(self.ad_base_dir) * 2,
+ ad_base_dir=[ord(x) for x in self.ad_base_dir],
+ event_base_dir_size=len(self.event_base_dir) * 2,
+ event_base_dir=[ord(x) for x in self.event_base_dir],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoEpisodePlayStartRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoEpisodePlayStartResponse(SaoBaseResponse):
def __init__(self, cmd, profile_data) -> None:
super().__init__(cmd)
self.result = 1
- self.play_start_response_data_size = 1 # Number of arrays (minimum 1 mandatory)
- self.multi_play_start_response_data_size = 0 # Number of arrays (set 0 due to single play)
+ self.play_start_response_data_size = 1 # Number of arrays (minimum 1 mandatory)
+ self.multi_play_start_response_data_size = (
+ 0 # Number of arrays (set 0 due to single play)
+ )
self.appearance_player_trace_data_list_size = 1
self.user_quest_scene_player_trace_id = "1003"
self.nick_name = profile_data["nick_name"]
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"play_start_response_data_size" / Int32ub,
"multi_play_start_response_data_size" / Int32ub,
-
"appearance_player_trace_data_list_size" / Int32ub,
-
- "user_quest_scene_player_trace_id_size" / Int32ub, # big endian
- "user_quest_scene_player_trace_id" / Int16ul[len(self.user_quest_scene_player_trace_id)],
- "nick_name_size" / Int32ub, # big endian
+ "user_quest_scene_player_trace_id_size" / Int32ub, # big endian
+ "user_quest_scene_player_trace_id"
+ / Int16ul[len(self.user_quest_scene_player_trace_id)],
+ "nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- play_start_response_data_size=self.play_start_response_data_size,
- multi_play_start_response_data_size=self.multi_play_start_response_data_size,
-
- appearance_player_trace_data_list_size=self.appearance_player_trace_data_list_size,
-
- user_quest_scene_player_trace_id_size=len(self.user_quest_scene_player_trace_id) * 2,
- user_quest_scene_player_trace_id=[ord(x) for x in self.user_quest_scene_player_trace_id],
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ play_start_response_data_size=self.play_start_response_data_size,
+ multi_play_start_response_data_size=self.multi_play_start_response_data_size,
+ appearance_player_trace_data_list_size=self.appearance_player_trace_data_list_size,
+ user_quest_scene_player_trace_id_size=len(
+ self.user_quest_scene_player_trace_id
+ )
+ * 2,
+ user_quest_scene_player_trace_id=[
+ ord(x) for x in self.user_quest_scene_player_trace_id
+ ],
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoEpisodePlayEndRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoEpisodePlayEndResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
@@ -1468,81 +1679,75 @@ class SaoEpisodePlayEndResponse(SaoBaseResponse):
self.common_reward_data_size = 1 # Number of arrays
- self.common_reward_type = 0 # dummy values from 2,101000000,1 from RewardTable.csv
+ self.common_reward_type = (
+ 0 # dummy values from 2,101000000,1 from RewardTable.csv
+ )
self.common_reward_id = 0
self.common_reward_num = 0
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"play_end_response_data_size" / Int32ub, # big endian
-
"rarity_up_occurrence_flag" / Int8ul, # result is either 0 or 1
"adventure_ex_area_occurrences_flag" / Int8ul, # result is either 0 or 1
"ex_bonus_data_list_size" / Int32ub, # big endian
"play_end_player_trace_reward_data_list_size" / Int32ub, # big endian
-
# ex_bonus_data_list
"ex_bonus_table_id" / Int32ub,
"achievement_status" / Int8ul, # result is either 0 or 1
-
# play_end_player_trace_reward_data_list
"common_reward_data_size" / Int32ub,
-
# common_reward_data
"common_reward_type" / Int16ub, # short
"common_reward_id" / Int32ub,
"common_reward_num" / Int32ub,
-
"multi_play_end_response_data_size" / Int32ub, # big endian
-
# multi_play_end_response_data
"dummy_1" / Int8ul, # result is either 0 or 1
"dummy_2" / Int8ul, # result is either 0 or 1
"dummy_3" / Int8ul, # result is either 0 or 1
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- play_end_response_data_size=self.play_end_response_data_size,
-
- rarity_up_occurrence_flag=self.rarity_up_occurrence_flag,
- adventure_ex_area_occurrences_flag=self.adventure_ex_area_occurrences_flag,
- ex_bonus_data_list_size=self.ex_bonus_data_list_size,
- play_end_player_trace_reward_data_list_size=self.play_end_player_trace_reward_data_list_size,
-
- ex_bonus_table_id=self.ex_bonus_table_id,
- achievement_status=self.achievement_status,
-
- common_reward_data_size=self.common_reward_data_size,
-
- common_reward_type=self.common_reward_type,
- common_reward_id=self.common_reward_id,
- common_reward_num=self.common_reward_num,
-
- multi_play_end_response_data_size=self.multi_play_end_response_data_size,
-
- dummy_1=self.dummy_1,
- dummy_2=self.dummy_2,
- dummy_3=self.dummy_3,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ play_end_response_data_size=self.play_end_response_data_size,
+ rarity_up_occurrence_flag=self.rarity_up_occurrence_flag,
+ adventure_ex_area_occurrences_flag=self.adventure_ex_area_occurrences_flag,
+ ex_bonus_data_list_size=self.ex_bonus_data_list_size,
+ play_end_player_trace_reward_data_list_size=self.play_end_player_trace_reward_data_list_size,
+ ex_bonus_table_id=self.ex_bonus_table_id,
+ achievement_status=self.achievement_status,
+ common_reward_data_size=self.common_reward_data_size,
+ common_reward_type=self.common_reward_type,
+ common_reward_id=self.common_reward_id,
+ common_reward_num=self.common_reward_num,
+ multi_play_end_response_data_size=self.multi_play_end_response_data_size,
+ dummy_1=self.dummy_1,
+ dummy_2=self.dummy_2,
+ dummy_3=self.dummy_3,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoTrialTowerPlayEndRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoTrialTowerPlayEndResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
self.play_end_response_data_size = 1 # Number of arrays
self.multi_play_end_response_data_size = 1 # Unused on solo play
- self.trial_tower_play_end_updated_notification_data_size = 1 # Number of arrays
- self.treasure_hunt_play_end_response_data_size = 1 # Number of arrays
+ self.trial_tower_play_end_updated_notification_data_size = 1 # Number of arrays
+ self.treasure_hunt_play_end_response_data_size = 1 # Number of arrays
self.dummy_1 = 0
self.dummy_2 = 0
@@ -1558,7 +1763,9 @@ class SaoTrialTowerPlayEndResponse(SaoBaseResponse):
self.common_reward_data_size = 1 # Number of arrays
- self.common_reward_type = 0 # dummy values from 2,101000000,1 from RewardTable.csv
+ self.common_reward_type = (
+ 0 # dummy values from 2,101000000,1 from RewardTable.csv
+ )
self.common_reward_id = 0
self.common_reward_num = 0
@@ -1570,97 +1777,84 @@ class SaoTrialTowerPlayEndResponse(SaoBaseResponse):
self.get_event_point = 0
self.total_event_point = 0
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
"play_end_response_data_size" / Int32ub, # big endian
-
"rarity_up_occurrence_flag" / Int8ul, # result is either 0 or 1
"adventure_ex_area_occurrences_flag" / Int8ul, # result is either 0 or 1
"ex_bonus_data_list_size" / Int32ub, # big endian
"play_end_player_trace_reward_data_list_size" / Int32ub, # big endian
-
# ex_bonus_data_list
"ex_bonus_table_id" / Int32ub,
"achievement_status" / Int8ul, # result is either 0 or 1
-
# play_end_player_trace_reward_data_list
"common_reward_data_size" / Int32ub,
-
# common_reward_data
"common_reward_type" / Int16ub, # short
"common_reward_id" / Int32ub,
"common_reward_num" / Int32ub,
-
"multi_play_end_response_data_size" / Int32ub, # big endian
-
# multi_play_end_response_data
"dummy_1" / Int8ul, # result is either 0 or 1
"dummy_2" / Int8ul, # result is either 0 or 1
"dummy_3" / Int8ul, # result is either 0 or 1
-
- "trial_tower_play_end_updated_notification_data_size" / Int32ub, # big endian
-
- #trial_tower_play_end_updated_notification_data
+ "trial_tower_play_end_updated_notification_data_size"
+ / Int32ub, # big endian
+ # trial_tower_play_end_updated_notification_data
"store_best_score_clear_time_flag" / Int8ul, # result is either 0 or 1
"store_best_score_combo_num_flag" / Int8ul, # result is either 0 or 1
"store_best_score_total_damage_flag" / Int8ul, # result is either 0 or 1
- "store_best_score_concurrent_destroying_num_flag" / Int8ul, # result is either 0 or 1
+ "store_best_score_concurrent_destroying_num_flag"
+ / Int8ul, # result is either 0 or 1
"store_reaching_trial_tower_rank" / Int32ub,
-
"treasure_hunt_play_end_response_data_size" / Int32ub, # big endian
-
- #treasure_hunt_play_end_response_data
+ # treasure_hunt_play_end_response_data
"get_event_point" / Int32ub,
"total_event_point" / Int32ub,
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- play_end_response_data_size=self.play_end_response_data_size,
-
- rarity_up_occurrence_flag=self.rarity_up_occurrence_flag,
- adventure_ex_area_occurrences_flag=self.adventure_ex_area_occurrences_flag,
- ex_bonus_data_list_size=self.ex_bonus_data_list_size,
- play_end_player_trace_reward_data_list_size=self.play_end_player_trace_reward_data_list_size,
-
- ex_bonus_table_id=self.ex_bonus_table_id,
- achievement_status=self.achievement_status,
-
- common_reward_data_size=self.common_reward_data_size,
-
- common_reward_type=self.common_reward_type,
- common_reward_id=self.common_reward_id,
- common_reward_num=self.common_reward_num,
-
- multi_play_end_response_data_size=self.multi_play_end_response_data_size,
-
- dummy_1=self.dummy_1,
- dummy_2=self.dummy_2,
- dummy_3=self.dummy_3,
-
- trial_tower_play_end_updated_notification_data_size=self.trial_tower_play_end_updated_notification_data_size,
- store_best_score_clear_time_flag=self.store_best_score_clear_time_flag,
- store_best_score_combo_num_flag=self.store_best_score_combo_num_flag,
- store_best_score_total_damage_flag=self.store_best_score_total_damage_flag,
- store_best_score_concurrent_destroying_num_flag=self.store_best_score_concurrent_destroying_num_flag,
- store_reaching_trial_tower_rank=self.store_reaching_trial_tower_rank,
-
- treasure_hunt_play_end_response_data_size=self.treasure_hunt_play_end_response_data_size,
-
- get_event_point=self.get_event_point,
- total_event_point=self.total_event_point,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ play_end_response_data_size=self.play_end_response_data_size,
+ rarity_up_occurrence_flag=self.rarity_up_occurrence_flag,
+ adventure_ex_area_occurrences_flag=self.adventure_ex_area_occurrences_flag,
+ ex_bonus_data_list_size=self.ex_bonus_data_list_size,
+ play_end_player_trace_reward_data_list_size=self.play_end_player_trace_reward_data_list_size,
+ ex_bonus_table_id=self.ex_bonus_table_id,
+ achievement_status=self.achievement_status,
+ common_reward_data_size=self.common_reward_data_size,
+ common_reward_type=self.common_reward_type,
+ common_reward_id=self.common_reward_id,
+ common_reward_num=self.common_reward_num,
+ multi_play_end_response_data_size=self.multi_play_end_response_data_size,
+ dummy_1=self.dummy_1,
+ dummy_2=self.dummy_2,
+ dummy_3=self.dummy_3,
+ trial_tower_play_end_updated_notification_data_size=self.trial_tower_play_end_updated_notification_data_size,
+ store_best_score_clear_time_flag=self.store_best_score_clear_time_flag,
+ store_best_score_combo_num_flag=self.store_best_score_combo_num_flag,
+ store_best_score_total_damage_flag=self.store_best_score_total_damage_flag,
+ store_best_score_concurrent_destroying_num_flag=self.store_best_score_concurrent_destroying_num_flag,
+ store_reaching_trial_tower_rank=self.store_reaching_trial_tower_rank,
+ treasure_hunt_play_end_response_data_size=self.treasure_hunt_play_end_response_data_size,
+ get_event_point=self.get_event_point,
+ total_event_point=self.total_event_point,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoEpisodePlayEndUnanalyzedLogFixedRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse):
def __init__(self, cmd, end_session_data) -> None:
super().__init__(cmd)
@@ -1675,22 +1869,30 @@ class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse):
for x in range(len(end_session_data)):
self.common_reward_id.append(end_session_data[x])
- with open('titles/sao/data/RewardTable.csv', 'r') as f:
- keys_unanalyzed = next(f).strip().split(',')
+ with open("titles/sao/data/RewardTable.csv", "r") as f:
+ keys_unanalyzed = next(f).strip().split(",")
data_unanalyzed = list(DictReader(f, fieldnames=keys_unanalyzed))
for i in range(len(data_unanalyzed)):
- if int(data_unanalyzed[i]["CommonRewardId"]) == int(end_session_data[x]):
- self.unanalyzed_log_grade_id.append(int(data_unanalyzed[i]["UnanalyzedLogGradeId"]))
- self.common_reward_type.append(int(data_unanalyzed[i]["CommonRewardType"]))
+ if int(data_unanalyzed[i]["CommonRewardId"]) == int(
+ end_session_data[x]
+ ):
+ self.unanalyzed_log_grade_id.append(
+ int(data_unanalyzed[i]["UnanalyzedLogGradeId"])
+ )
+ self.common_reward_type.append(
+ int(data_unanalyzed[i]["CommonRewardType"])
+ )
break
- self.unanalyzed_log_grade_id = list(map(int,self.unanalyzed_log_grade_id)) #int
- self.common_reward_type = list(map(int,self.common_reward_type)) #int
- self.common_reward_id = list(map(int,self.common_reward_id)) #int
-
+ self.unanalyzed_log_grade_id = list(
+ map(int, self.unanalyzed_log_grade_id)
+ ) # int
+ self.common_reward_type = list(map(int, self.common_reward_type)) # int
+ self.common_reward_id = list(map(int, self.common_reward_id)) # int
+
def make(self) -> bytes:
- #new stuff
+ # new stuff
common_reward_data_struct = Struct(
"common_reward_type" / Int16ub,
"common_reward_id" / Int32ub,
@@ -1699,22 +1901,35 @@ class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse):
play_end_unanalyzed_log_reward_data_list_struct = Struct(
"unanalyzed_log_grade_id" / Int32ub,
- "common_reward_data_size" / Rebuild(Int32ub, len_(this.common_reward_data)), # big endian
- "common_reward_data" / Array(this.common_reward_data_size, common_reward_data_struct),
+ "common_reward_data_size"
+ / Rebuild(Int32ub, len_(this.common_reward_data)), # big endian
+ "common_reward_data"
+ / Array(this.common_reward_data_size, common_reward_data_struct),
)
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "play_end_unanalyzed_log_reward_data_list_size" / Rebuild(Int32ub, len_(this.play_end_unanalyzed_log_reward_data_list)), # big endian
- "play_end_unanalyzed_log_reward_data_list" / Array(this.play_end_unanalyzed_log_reward_data_list_size, play_end_unanalyzed_log_reward_data_list_struct),
+ "play_end_unanalyzed_log_reward_data_list_size"
+ / Rebuild(
+ Int32ub, len_(this.play_end_unanalyzed_log_reward_data_list)
+ ), # big endian
+ "play_end_unanalyzed_log_reward_data_list"
+ / Array(
+ this.play_end_unanalyzed_log_reward_data_list_size,
+ play_end_unanalyzed_log_reward_data_list_struct,
+ ),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- play_end_unanalyzed_log_reward_data_list_size=0,
- play_end_unanalyzed_log_reward_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ play_end_unanalyzed_log_reward_data_list_size=0,
+ play_end_unanalyzed_log_reward_data_list=[],
+ )
+ )
+ )
for i in range(len(self.common_reward_id)):
reward_resp_data = dict(
@@ -1723,12 +1938,14 @@ class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse):
common_reward_data=[],
)
- reward_resp_data["common_reward_data"].append(dict(
- common_reward_type=self.common_reward_type[i],
- common_reward_id=self.common_reward_id[i],
- common_reward_num=self.common_reward_num,
- ))
-
+ reward_resp_data["common_reward_data"].append(
+ dict(
+ common_reward_type=self.common_reward_type[i],
+ common_reward_id=self.common_reward_id[i],
+ common_reward_num=self.common_reward_num,
+ )
+ )
+
resp_data.play_end_unanalyzed_log_reward_data_list.append(reward_resp_data)
# finally, rebuild the resp_data
@@ -1737,10 +1954,12 @@ class SaoEpisodePlayEndUnanalyzedLogFixedResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetQuestSceneUserDataListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetQuestSceneUserDataListResponse(SaoBaseResponse):
def __init__(self, cmd, quest_data) -> None:
super().__init__(cmd)
@@ -1754,7 +1973,7 @@ class SaoGetQuestSceneUserDataListResponse(SaoBaseResponse):
# quest_scene_best_score_user_data
self.clear_time = []
self.combo_num = []
- self.total_damage = [] #string
+ self.total_damage = [] # string
self.concurrent_destroying_num = []
for i in range(len(quest_data)):
@@ -1764,80 +1983,104 @@ class SaoGetQuestSceneUserDataListResponse(SaoBaseResponse):
self.clear_time.append(quest_data[i][4])
self.combo_num.append(quest_data[i][5])
- self.total_damage.append(0) #totally absurd but Int16ul[1] is a big problem due to different lenghts...
+ self.total_damage.append(
+ 0
+ ) # totally absurd but Int16ul[1] is a big problem due to different lenghts...
self.concurrent_destroying_num.append(quest_data[i][7])
# quest_scene_ex_bonus_user_data_list
- self.achievement_flag = [1,1,1]
- self.ex_bonus_table_id = [1,2,3]
+ self.achievement_flag = [1, 1, 1]
+ self.ex_bonus_table_id = [1, 2, 3]
+ self.quest_type = list(map(int, self.quest_type)) # int
+ self.quest_scene_id = list(map(int, self.quest_scene_id)) # int
+ self.clear_flag = list(map(int, self.clear_flag)) # int
+ self.clear_time = list(map(int, self.clear_time)) # int
+ self.combo_num = list(map(int, self.combo_num)) # int
+ self.total_damage = list(map(str, self.total_damage)) # string
+ self.concurrent_destroying_num = list(map(int, self.combo_num)) # int
- self.quest_type = list(map(int,self.quest_type)) #int
- self.quest_scene_id = list(map(int,self.quest_scene_id)) #int
- self.clear_flag = list(map(int,self.clear_flag)) #int
- self.clear_time = list(map(int,self.clear_time)) #int
- self.combo_num = list(map(int,self.combo_num)) #int
- self.total_damage = list(map(str,self.total_damage)) #string
- self.concurrent_destroying_num = list(map(int,self.combo_num)) #int
-
def make(self) -> bytes:
- #new stuff
+ # new stuff
quest_scene_ex_bonus_user_data_list_struct = Struct(
"ex_bonus_table_id" / Int32ub, # big endian
- "achievement_flag" / Int8ul, # result is either 0 or 1
+ "achievement_flag" / Int8ul, # result is either 0 or 1
)
quest_scene_best_score_user_data_struct = Struct(
"clear_time" / Int32ub, # big endian
"combo_num" / Int32ub, # big endian
- "total_damage_size" / Int32ub, # big endian
+ "total_damage_size" / Int32ub, # big endian
"total_damage" / Int16ul[1],
"concurrent_destroying_num" / Int16ub,
)
quest_scene_user_data_list_struct = Struct(
- "quest_type" / Int8ul, # result is either 0 or 1
- "quest_scene_id" / Int16ub, #short
- "clear_flag" / Int8ul, # result is either 0 or 1
- "quest_scene_best_score_user_data_size" / Rebuild(Int32ub, len_(this.quest_scene_best_score_user_data)), # big endian
- "quest_scene_best_score_user_data" / Array(this.quest_scene_best_score_user_data_size, quest_scene_best_score_user_data_struct),
- "quest_scene_ex_bonus_user_data_list_size" / Rebuild(Int32ub, len_(this.quest_scene_ex_bonus_user_data_list)), # big endian
- "quest_scene_ex_bonus_user_data_list" / Array(this.quest_scene_ex_bonus_user_data_list_size, quest_scene_ex_bonus_user_data_list_struct),
+ "quest_type" / Int8ul, # result is either 0 or 1
+ "quest_scene_id" / Int16ub, # short
+ "clear_flag" / Int8ul, # result is either 0 or 1
+ "quest_scene_best_score_user_data_size"
+ / Rebuild(
+ Int32ub, len_(this.quest_scene_best_score_user_data)
+ ), # big endian
+ "quest_scene_best_score_user_data"
+ / Array(
+ this.quest_scene_best_score_user_data_size,
+ quest_scene_best_score_user_data_struct,
+ ),
+ "quest_scene_ex_bonus_user_data_list_size"
+ / Rebuild(
+ Int32ub, len_(this.quest_scene_ex_bonus_user_data_list)
+ ), # big endian
+ "quest_scene_ex_bonus_user_data_list"
+ / Array(
+ this.quest_scene_ex_bonus_user_data_list_size,
+ quest_scene_ex_bonus_user_data_list_struct,
+ ),
)
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "quest_scene_user_data_list_size" / Rebuild(Int32ub, len_(this.quest_scene_user_data_list)), # big endian
- "quest_scene_user_data_list" / Array(this.quest_scene_user_data_list_size, quest_scene_user_data_list_struct),
+ "quest_scene_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.quest_scene_user_data_list)), # big endian
+ "quest_scene_user_data_list"
+ / Array(
+ this.quest_scene_user_data_list_size, quest_scene_user_data_list_struct
+ ),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- quest_scene_user_data_list_size=0,
- quest_scene_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ quest_scene_user_data_list_size=0,
+ quest_scene_user_data_list=[],
+ )
+ )
+ )
for i in range(len(self.quest_scene_id)):
quest_resp_data = dict(
quest_type=self.quest_type[i],
quest_scene_id=self.quest_scene_id[i],
clear_flag=self.clear_flag[i],
-
quest_scene_best_score_user_data_size=0,
quest_scene_best_score_user_data=[],
quest_scene_ex_bonus_user_data_list_size=0,
quest_scene_ex_bonus_user_data_list=[],
)
- quest_resp_data["quest_scene_best_score_user_data"].append(dict(
- clear_time=self.clear_time[i],
- combo_num=self.combo_num[i],
- total_damage_size=len(self.total_damage[i]) * 2,
- total_damage=[ord(x) for x in self.total_damage[i]],
- concurrent_destroying_num=self.concurrent_destroying_num[i],
- ))
-
+ quest_resp_data["quest_scene_best_score_user_data"].append(
+ dict(
+ clear_time=self.clear_time[i],
+ combo_num=self.combo_num[i],
+ total_damage_size=len(self.total_damage[i]) * 2,
+ total_damage=[ord(x) for x in self.total_damage[i]],
+ concurrent_destroying_num=self.concurrent_destroying_num[i],
+ )
+ )
+
resp_data.quest_scene_user_data_list.append(quest_resp_data)
# finally, rebuild the resp_data
@@ -1846,10 +2089,12 @@ class SaoGetQuestSceneUserDataListResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoCheckYuiMedalGetConditionRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoCheckYuiMedalGetConditionResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
@@ -1857,75 +2102,80 @@ class SaoCheckYuiMedalGetConditionResponse(SaoBaseResponse):
self.get_flag = 1
self.elapsed_days = 0
self.get_yui_medal_num = 0
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "get_flag" / Int8ul, # result is either 0 or 1
- "elapsed_days" / Int16ub, #short
- "get_yui_medal_num" / Int16ub, #short
+ "result" / Int8ul, # result is either 0 or 1
+ "get_flag" / Int8ul, # result is either 0 or 1
+ "elapsed_days" / Int16ub, # short
+ "get_yui_medal_num" / Int16ub, # short
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- get_flag=self.get_flag,
- elapsed_days=self.elapsed_days,
- get_yui_medal_num=self.get_yui_medal_num,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ get_flag=self.get_flag,
+ elapsed_days=self.elapsed_days,
+ get_yui_medal_num=self.get_yui_medal_num,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetYuiMedalBonusUserDataRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetYuiMedalBonusUserDataResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.data_size = 1 # number of arrays
+ self.data_size = 1 # number of arrays
self.elapsed_days = 1
self.loop_num = 1
self.last_check_date = "20230520193000"
self.last_get_date = "20230520193000"
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "data_size" / Int32ub, # big endian
-
- "elapsed_days" / Int32ub, # big endian
- "loop_num" / Int32ub, # big endian
- "last_check_date_size" / Int32ub, # big endian
+ "result" / Int8ul, # result is either 0 or 1
+ "data_size" / Int32ub, # big endian
+ "elapsed_days" / Int32ub, # big endian
+ "loop_num" / Int32ub, # big endian
+ "last_check_date_size" / Int32ub, # big endian
"last_check_date" / Int16ul[len(self.last_check_date)],
- "last_get_date_size" / Int32ub, # big endian
+ "last_get_date_size" / Int32ub, # big endian
"last_get_date" / Int16ul[len(self.last_get_date)],
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- data_size=self.data_size,
-
- elapsed_days=self.elapsed_days,
- loop_num=self.loop_num,
- last_check_date_size=len(self.last_check_date) * 2,
- last_check_date=[ord(x) for x in self.last_check_date],
- last_get_date_size=len(self.last_get_date) * 2,
- last_get_date=[ord(x) for x in self.last_get_date],
-
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ data_size=self.data_size,
+ elapsed_days=self.elapsed_days,
+ loop_num=self.loop_num,
+ last_check_date_size=len(self.last_check_date) * 2,
+ last_check_date=[ord(x) for x in self.last_check_date],
+ last_get_date_size=len(self.last_get_date) * 2,
+ last_get_date=[ord(x) for x in self.last_get_date],
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoCheckProfileCardUsedRewardRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoCheckProfileCardUsedRewardResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
@@ -1933,106 +2183,109 @@ class SaoCheckProfileCardUsedRewardResponse(SaoBaseResponse):
self.get_flag = 1
self.used_num = 0
self.get_vp = 1
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "get_flag" / Int8ul, # result is either 0 or 1
- "used_num" / Int32ub, # big endian
- "get_vp" / Int32ub, # big endian
+ "result" / Int8ul, # result is either 0 or 1
+ "get_flag" / Int8ul, # result is either 0 or 1
+ "used_num" / Int32ub, # big endian
+ "get_vp" / Int32ub, # big endian
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- get_flag=self.get_flag,
- used_num=self.used_num,
- get_vp=self.get_vp,
-
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ get_flag=self.get_flag,
+ used_num=self.used_num,
+ get_vp=self.get_vp,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoSynthesizeEnhancementHeroLogRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoSynthesizeEnhancementHeroLogResponse(SaoBaseResponse):
def __init__(self, cmd, hero_data) -> None:
super().__init__(cmd)
self.result = 1
# Calculate level based off experience and the CSV list
- with open(r'titles/sao/data/HeroLogLevel.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/HeroLogLevel.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
data.append(row)
exp = hero_data[4]
-
- for e in range(0,len(data)):
- if exp>=int(data[e][1]) and exp= int(data[e][1]) and exp < int(data[e + 1][1]):
hero_level = int(data[e][0])
break
# hero_log_user_data_list
self.user_hero_log_id = str(hero_data[2])
- self.hero_log_id = int(self.user_hero_log_id) #int
+ self.hero_log_id = int(self.user_hero_log_id) # int
self.log_level = hero_level
- self.max_log_level_extended_num = hero_level #short
+ self.max_log_level_extended_num = hero_level # short
self.log_exp = hero_data[4]
- self.possible_awakening_flag = 0 #byte
- self.awakening_stage = 0 #short
- self.awakening_exp = 0 #int
- self.skill_slot_correction_value = 0 #byte
- self.last_set_skill_slot1_skill_id = hero_data[7] #short
- self.last_set_skill_slot2_skill_id = hero_data[8] #short
- self.last_set_skill_slot3_skill_id = hero_data[9] #short
- self.last_set_skill_slot4_skill_id = hero_data[10] #short
- self.last_set_skill_slot5_skill_id = hero_data[11] #short
- self.property1_property_id = 0 #int
- self.property1_value1 = 0 #int
- self.property1_value2 = 0 #int
- self.property2_property_id = 0 #int
- self.property2_value1 = 0 #int
- self.property2_value2 = 0 #int
- self.property3_property_id = 0 #int
- self.property3_value1 = 0 #int
- self.property3_value2 = 0 #int
- self.property4_property_id = 0 #int
- self.property4_value1 = 0 #int
- self.property4_value2 = 0 #int
- self.converted_card_num = 0 #short
- self.shop_purchase_flag = 1 #byte
- self.protect_flag = 0 #byte
- self.get_date = "20230101120000" #str
-
+ self.possible_awakening_flag = 0 # byte
+ self.awakening_stage = 0 # short
+ self.awakening_exp = 0 # int
+ self.skill_slot_correction_value = 0 # byte
+ self.last_set_skill_slot1_skill_id = hero_data[7] # short
+ self.last_set_skill_slot2_skill_id = hero_data[8] # short
+ self.last_set_skill_slot3_skill_id = hero_data[9] # short
+ self.last_set_skill_slot4_skill_id = hero_data[10] # short
+ self.last_set_skill_slot5_skill_id = hero_data[11] # short
+ self.property1_property_id = 0 # int
+ self.property1_value1 = 0 # int
+ self.property1_value2 = 0 # int
+ self.property2_property_id = 0 # int
+ self.property2_value1 = 0 # int
+ self.property2_value2 = 0 # int
+ self.property3_property_id = 0 # int
+ self.property3_value1 = 0 # int
+ self.property3_value2 = 0 # int
+ self.property4_property_id = 0 # int
+ self.property4_value1 = 0 # int
+ self.property4_value2 = 0 # int
+ self.converted_card_num = 0 # short
+ self.shop_purchase_flag = 1 # byte
+ self.protect_flag = 0 # byte
+ self.get_date = "20230101120000" # str
+
def make(self) -> bytes:
- #new stuff
+ # new stuff
hero_log_user_data_list_struct = Struct(
"user_hero_log_id_size" / Int32ub, # big endian
- "user_hero_log_id" / Int16ul[9], #string
- "hero_log_id" / Int32ub, #int
- "log_level" / Int16ub, #short
- "max_log_level_extended_num" / Int16ub, #short
- "log_exp" / Int32ub, #int
+ "user_hero_log_id" / Int16ul[9], # string
+ "hero_log_id" / Int32ub, # int
+ "log_level" / Int16ub, # short
+ "max_log_level_extended_num" / Int16ub, # short
+ "log_exp" / Int32ub, # int
"possible_awakening_flag" / Int8ul, # result is either 0 or 1
- "awakening_stage" / Int16ub, #short
- "awakening_exp" / Int32ub, #int
+ "awakening_stage" / Int16ub, # short
+ "awakening_exp" / Int32ub, # int
"skill_slot_correction_value" / Int8ul, # result is either 0 or 1
- "last_set_skill_slot1_skill_id" / Int16ub, #short
- "last_set_skill_slot2_skill_id" / Int16ub, #short
- "last_set_skill_slot3_skill_id" / Int16ub, #short
- "last_set_skill_slot4_skill_id" / Int16ub, #short
- "last_set_skill_slot5_skill_id" / Int16ub, #short
+ "last_set_skill_slot1_skill_id" / Int16ub, # short
+ "last_set_skill_slot2_skill_id" / Int16ub, # short
+ "last_set_skill_slot3_skill_id" / Int16ub, # short
+ "last_set_skill_slot4_skill_id" / Int16ub, # short
+ "last_set_skill_slot5_skill_id" / Int16ub, # short
"property1_property_id" / Int32ub,
"property1_value1" / Int32ub,
"property1_value2" / Int32ub,
@@ -2055,15 +2308,21 @@ class SaoSynthesizeEnhancementHeroLogResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "hero_log_user_data_list_size" / Rebuild(Int32ub, len_(this.hero_log_user_data_list)), # big endian
- "hero_log_user_data_list" / Array(this.hero_log_user_data_list_size, hero_log_user_data_list_struct),
+ "hero_log_user_data_list_size"
+ / Rebuild(Int32ub, len_(this.hero_log_user_data_list)), # big endian
+ "hero_log_user_data_list"
+ / Array(this.hero_log_user_data_list_size, hero_log_user_data_list_struct),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- hero_log_user_data_list_size=0,
- hero_log_user_data_list=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ hero_log_user_data_list_size=0,
+ hero_log_user_data_list=[],
+ )
+ )
+ )
hero_data = dict(
user_hero_log_id_size=len(self.user_hero_log_id) * 2,
@@ -2098,9 +2357,8 @@ class SaoSynthesizeEnhancementHeroLogResponse(SaoBaseResponse):
protect_flag=self.protect_flag,
get_date_size=len(self.get_date) * 2,
get_date=[ord(x) for x in self.get_date],
-
)
-
+
resp_data.hero_log_user_data_list.append(hero_data)
# finally, rebuild the resp_data
@@ -2109,10 +2367,12 @@ class SaoSynthesizeEnhancementHeroLogResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoSynthesizeEnhancementEquipment(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse):
def __init__(self, cmd, synthesize_equipment_data) -> None:
super().__init__(cmd)
@@ -2120,62 +2380,62 @@ class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse):
equipment_level = 0
# Calculate level based off experience and the CSV list
- with open(r'titles/sao/data/EquipmentLevel.csv') as csv_file:
- csv_reader = csv.reader(csv_file, delimiter=',')
+ with open(r"titles/sao/data/EquipmentLevel.csv") as csv_file:
+ csv_reader = csv.reader(csv_file, delimiter=",")
line_count = 0
data = []
rowf = False
for row in csv_reader:
- if rowf==False:
- rowf=True
+ if rowf == False:
+ rowf = True
else:
data.append(row)
exp = synthesize_equipment_data[4]
-
- for e in range(0,len(data)):
- if exp>=int(data[e][1]) and exp= int(data[e][1]) and exp < int(data[e + 1][1]):
equipment_level = int(data[e][0])
break
# equipment_user_data_list
- self.user_equipment_id = str(synthesize_equipment_data[2]) #str
- self.equipment_id = synthesize_equipment_data[2] #int
- self.enhancement_value = equipment_level #short
- self.max_enhancement_value_extended_num = equipment_level #short
- self.enhancement_exp = synthesize_equipment_data[4] #int
- self.possible_awakening_flag = synthesize_equipment_data[7] #byte
- self.awakening_stage = synthesize_equipment_data[5] #short
- self.awakening_exp = synthesize_equipment_data[6] #int
- self.property1_property_id = 0 #int
- self.property1_value1 = 0 #int
- self.property1_value2 = 0 #int
- self.property2_property_id = 0 #int
- self.property2_value1 = 0 #int
- self.property2_value2 = 0 #int
- self.property3_property_id = 0 #int
- self.property3_value1 = 0 #int
- self.property3_value2 = 0 #int
- self.property4_property_id = 0 #int
- self.property4_value1 = 0 #int
- self.property4_value2 = 0 #int
- self.converted_card_num = 1 #short
- self.shop_purchase_flag = 1 #byte
- self.protect_flag = 0 #byte
- self.get_date = "20230101120000" #str
-
+ self.user_equipment_id = str(synthesize_equipment_data[2]) # str
+ self.equipment_id = synthesize_equipment_data[2] # int
+ self.enhancement_value = equipment_level # short
+ self.max_enhancement_value_extended_num = equipment_level # short
+ self.enhancement_exp = synthesize_equipment_data[4] # int
+ self.possible_awakening_flag = synthesize_equipment_data[7] # byte
+ self.awakening_stage = synthesize_equipment_data[5] # short
+ self.awakening_exp = synthesize_equipment_data[6] # int
+ self.property1_property_id = 0 # int
+ self.property1_value1 = 0 # int
+ self.property1_value2 = 0 # int
+ self.property2_property_id = 0 # int
+ self.property2_value1 = 0 # int
+ self.property2_value2 = 0 # int
+ self.property3_property_id = 0 # int
+ self.property3_value1 = 0 # int
+ self.property3_value2 = 0 # int
+ self.property4_property_id = 0 # int
+ self.property4_value1 = 0 # int
+ self.property4_value2 = 0 # int
+ self.converted_card_num = 1 # short
+ self.shop_purchase_flag = 1 # byte
+ self.protect_flag = 0 # byte
+ self.get_date = "20230101120000" # str
+
def make(self) -> bytes:
after_equipment_user_data_struct = Struct(
"user_equipment_id_size" / Int32ub, # big endian
- "user_equipment_id" / Int16ul[9], #string
- "equipment_id" / Int32ub, #int
- "enhancement_value" / Int16ub, #short
- "max_enhancement_value_extended_num" / Int16ub, #short
- "enhancement_exp" / Int32ub, #int
+ "user_equipment_id" / Int16ul[9], # string
+ "equipment_id" / Int32ub, # int
+ "enhancement_value" / Int16ub, # short
+ "max_enhancement_value_extended_num" / Int16ub, # short
+ "enhancement_exp" / Int32ub, # int
"possible_awakening_flag" / Int8ul, # result is either 0 or 1
- "awakening_stage" / Int16ub, #short
- "awakening_exp" / Int32ub, #int
+ "awakening_stage" / Int16ub, # short
+ "awakening_exp" / Int32ub, # int
"property1_property_id" / Int32ub,
"property1_value1" / Int32ub,
"property1_value2" / Int32ub,
@@ -2198,15 +2458,23 @@ class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse):
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "after_equipment_user_data_size" / Rebuild(Int32ub, len_(this.after_equipment_user_data)), # big endian
- "after_equipment_user_data" / Array(this.after_equipment_user_data_size, after_equipment_user_data_struct),
+ "after_equipment_user_data_size"
+ / Rebuild(Int32ub, len_(this.after_equipment_user_data)), # big endian
+ "after_equipment_user_data"
+ / Array(
+ this.after_equipment_user_data_size, after_equipment_user_data_struct
+ ),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- after_equipment_user_data_size=0,
- after_equipment_user_data=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ after_equipment_user_data_size=0,
+ after_equipment_user_data=[],
+ )
+ )
+ )
synthesize_equipment_data = dict(
user_equipment_id_size=len(self.user_equipment_id) * 2,
@@ -2235,9 +2503,8 @@ class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse):
protect_flag=self.protect_flag,
get_date_size=len(self.get_date) * 2,
get_date=[ord(x) for x in self.get_date],
-
)
-
+
resp_data.after_equipment_user_data.append(synthesize_equipment_data)
# finally, rebuild the resp_data
@@ -2246,122 +2513,129 @@ class SaoSynthesizeEnhancementEquipmentResponse(SaoBaseResponse):
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetDefragMatchBasicDataRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetDefragMatchBasicDataResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.defrag_match_basic_user_data_size = 1 # number of arrays
+ self.defrag_match_basic_user_data_size = 1 # number of arrays
self.seed_flag = 1
self.ad_confirm_flag = 1
self.total_league_point = 0
self.have_league_score = 0
- self.class_num = 1 # 1 to 6
+ self.class_num = 1 # 1 to 6
self.hall_of_fame_confirm_flag = 0
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "defrag_match_basic_user_data_size" / Int32ub, # big endian
-
- "seed_flag" / Int16ub, #short
- "ad_confirm_flag" / Int8ul, # result is either 0 or 1
- "total_league_point" / Int32ub, #int
- "have_league_score" / Int16ub, #short
- "class_num" / Int16ub, #short
- "hall_of_fame_confirm_flag" / Int8ul, # result is either 0 or 1
-
+ "result" / Int8ul, # result is either 0 or 1
+ "defrag_match_basic_user_data_size" / Int32ub, # big endian
+ "seed_flag" / Int16ub, # short
+ "ad_confirm_flag" / Int8ul, # result is either 0 or 1
+ "total_league_point" / Int32ub, # int
+ "have_league_score" / Int16ub, # short
+ "class_num" / Int16ub, # short
+ "hall_of_fame_confirm_flag" / Int8ul, # result is either 0 or 1
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- defrag_match_basic_user_data_size=self.defrag_match_basic_user_data_size,
-
- seed_flag=self.seed_flag,
- ad_confirm_flag=self.ad_confirm_flag,
- total_league_point=self.total_league_point,
- have_league_score=self.have_league_score,
- class_num=self.class_num,
- hall_of_fame_confirm_flag=self.hall_of_fame_confirm_flag,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ defrag_match_basic_user_data_size=self.defrag_match_basic_user_data_size,
+ seed_flag=self.seed_flag,
+ ad_confirm_flag=self.ad_confirm_flag,
+ total_league_point=self.total_league_point,
+ have_league_score=self.have_league_score,
+ class_num=self.class_num,
+ hall_of_fame_confirm_flag=self.hall_of_fame_confirm_flag,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetDefragMatchRankingUserDataRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetDefragMatchRankingUserDataResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.ranking_user_data_size = 1 # number of arrays
+ self.ranking_user_data_size = 1 # number of arrays
self.league_point_rank = 1
self.league_score_rank = 1
self.nick_name = "PLAYER"
- self.setting_title_id = 20005 # Default saved during profile creation, no changing for those atm
- self.favorite_hero_log_id = 101000010 # Default saved during profile creation
+ self.setting_title_id = (
+ 20005 # Default saved during profile creation, no changing for those atm
+ )
+ self.favorite_hero_log_id = 101000010 # Default saved during profile creation
self.favorite_hero_log_awakening_stage = 0
self.favorite_support_log_id = 0
self.favorite_support_log_awakening_stage = 0
self.total_league_point = 1
self.have_league_score = 1
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "ranking_user_data_size" / Int32ub, # big endian
-
- "league_point_rank" / Int32ub, #int
- "league_score_rank" / Int32ub, #int
+ "result" / Int8ul, # result is either 0 or 1
+ "ranking_user_data_size" / Int32ub, # big endian
+ "league_point_rank" / Int32ub, # int
+ "league_score_rank" / Int32ub, # int
"nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
- "setting_title_id" / Int32ub, #int
- "favorite_hero_log_id" / Int32ub, #int
- "favorite_hero_log_awakening_stage" / Int16ub, #short
- "favorite_support_log_id" / Int32ub, #int
- "favorite_support_log_awakening_stage" / Int16ub, #short
- "total_league_point" / Int32ub, #int
- "have_league_score" / Int16ub, #short
+ "setting_title_id" / Int32ub, # int
+ "favorite_hero_log_id" / Int32ub, # int
+ "favorite_hero_log_awakening_stage" / Int16ub, # short
+ "favorite_support_log_id" / Int32ub, # int
+ "favorite_support_log_awakening_stage" / Int16ub, # short
+ "total_league_point" / Int32ub, # int
+ "have_league_score" / Int16ub, # short
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- ranking_user_data_size=self.ranking_user_data_size,
-
- league_point_rank=self.league_point_rank,
- league_score_rank=self.league_score_rank,
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- setting_title_id=self.setting_title_id,
- favorite_hero_log_id=self.favorite_hero_log_id,
- favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage,
- favorite_support_log_id=self.favorite_support_log_id,
- favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage,
- total_league_point=self.total_league_point,
- have_league_score=self.have_league_score,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ ranking_user_data_size=self.ranking_user_data_size,
+ league_point_rank=self.league_point_rank,
+ league_score_rank=self.league_score_rank,
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ setting_title_id=self.setting_title_id,
+ favorite_hero_log_id=self.favorite_hero_log_id,
+ favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage,
+ favorite_support_log_id=self.favorite_support_log_id,
+ favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage,
+ total_league_point=self.total_league_point,
+ have_league_score=self.have_league_score,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetDefragMatchLeaguePointRankingListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetDefragMatchLeaguePointRankingListResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.ranking_user_data_size = 1 # number of arrays
+ self.ranking_user_data_size = 1 # number of arrays
self.rank = 1
self.user_id = "1"
@@ -2375,14 +2649,13 @@ class SaoGetDefragMatchLeaguePointRankingListResponse(SaoBaseResponse):
self.favorite_support_log_awakening_stage = 0
self.class_num = 1
self.total_league_point = 1
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "ranking_user_data_size" / Int32ub, # big endian
-
- "rank" / Int32ub, #int
+ "result" / Int8ul, # result is either 0 or 1
+ "ranking_user_data_size" / Int32ub, # big endian
+ "rank" / Int32ub, # int
"user_id_size" / Int32ub, # big endian
"user_id" / Int16ul[len(self.user_id)],
"store_id_size" / Int32ub, # big endian
@@ -2391,49 +2664,52 @@ class SaoGetDefragMatchLeaguePointRankingListResponse(SaoBaseResponse):
"store_name" / Int16ul[len(self.store_name)],
"nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
- "setting_title_id" / Int32ub, #int
- "favorite_hero_log_id" / Int32ub, #int
- "favorite_hero_log_awakening_stage" / Int16ub, #short
- "favorite_support_log_id" / Int32ub, #int
- "favorite_support_log_awakening_stage" / Int16ub, #short
- "class_num" / Int16ub, #short
- "total_league_point" / Int32ub, #int
+ "setting_title_id" / Int32ub, # int
+ "favorite_hero_log_id" / Int32ub, # int
+ "favorite_hero_log_awakening_stage" / Int16ub, # short
+ "favorite_support_log_id" / Int32ub, # int
+ "favorite_support_log_awakening_stage" / Int16ub, # short
+ "class_num" / Int16ub, # short
+ "total_league_point" / Int32ub, # int
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- ranking_user_data_size=self.ranking_user_data_size,
-
- rank=self.rank,
- user_id_size=len(self.user_id) * 2,
- user_id=[ord(x) for x in self.user_id],
- store_id_size=len(self.store_id) * 2,
- store_id=[ord(x) for x in self.store_id],
- store_name_size=len(self.store_name) * 2,
- store_name=[ord(x) for x in self.store_name],
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- setting_title_id=self.setting_title_id,
- favorite_hero_log_id=self.favorite_hero_log_id,
- favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage,
- favorite_support_log_id=self.favorite_support_log_id,
- favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage,
- class_num=self.class_num,
- total_league_point=self.total_league_point,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ ranking_user_data_size=self.ranking_user_data_size,
+ rank=self.rank,
+ user_id_size=len(self.user_id) * 2,
+ user_id=[ord(x) for x in self.user_id],
+ store_id_size=len(self.store_id) * 2,
+ store_id=[ord(x) for x in self.store_id],
+ store_name_size=len(self.store_name) * 2,
+ store_name=[ord(x) for x in self.store_name],
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ setting_title_id=self.setting_title_id,
+ favorite_hero_log_id=self.favorite_hero_log_id,
+ favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage,
+ favorite_support_log_id=self.favorite_support_log_id,
+ favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage,
+ class_num=self.class_num,
+ total_league_point=self.total_league_point,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoGetDefragMatchLeagueScoreRankingListRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoGetDefragMatchLeagueScoreRankingListResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
- self.ranking_user_data_size = 1 # number of arrays
+ self.ranking_user_data_size = 1 # number of arrays
self.rank = 1
self.user_id = "1"
@@ -2447,14 +2723,13 @@ class SaoGetDefragMatchLeagueScoreRankingListResponse(SaoBaseResponse):
self.favorite_support_log_awakening_stage = 0
self.class_num = 1
self.have_league_score = 1
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
- "result" / Int8ul, # result is either 0 or 1
- "ranking_user_data_size" / Int32ub, # big endian
-
- "rank" / Int32ub, #int
+ "result" / Int8ul, # result is either 0 or 1
+ "ranking_user_data_size" / Int32ub, # big endian
+ "rank" / Int32ub, # int
"user_id_size" / Int32ub, # big endian
"user_id" / Int16ul[len(self.user_id)],
"store_id_size" / Int32ub, # big endian
@@ -2463,44 +2738,47 @@ class SaoGetDefragMatchLeagueScoreRankingListResponse(SaoBaseResponse):
"store_name" / Int16ul[len(self.store_name)],
"nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
- "setting_title_id" / Int32ub, #int
- "favorite_hero_log_id" / Int32ub, #int
- "favorite_hero_log_awakening_stage" / Int16ub, #short
- "favorite_support_log_id" / Int32ub, #int
- "favorite_support_log_awakening_stage" / Int16ub, #short
- "class_num" / Int16ub, #short
- "have_league_score" / Int16ub, #short
+ "setting_title_id" / Int32ub, # int
+ "favorite_hero_log_id" / Int32ub, # int
+ "favorite_hero_log_awakening_stage" / Int16ub, # short
+ "favorite_support_log_id" / Int32ub, # int
+ "favorite_support_log_awakening_stage" / Int16ub, # short
+ "class_num" / Int16ub, # short
+ "have_league_score" / Int16ub, # short
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- ranking_user_data_size=self.ranking_user_data_size,
-
- rank=self.rank,
- user_id_size=len(self.user_id) * 2,
- user_id=[ord(x) for x in self.user_id],
- store_id_size=len(self.store_id) * 2,
- store_id=[ord(x) for x in self.store_id],
- store_name_size=len(self.store_name) * 2,
- store_name=[ord(x) for x in self.store_name],
- nick_name_size=len(self.nick_name) * 2,
- nick_name=[ord(x) for x in self.nick_name],
- setting_title_id=self.setting_title_id,
- favorite_hero_log_id=self.favorite_hero_log_id,
- favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage,
- favorite_support_log_id=self.favorite_support_log_id,
- favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage,
- class_num=self.class_num,
- have_league_score=self.have_league_score,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ ranking_user_data_size=self.ranking_user_data_size,
+ rank=self.rank,
+ user_id_size=len(self.user_id) * 2,
+ user_id=[ord(x) for x in self.user_id],
+ store_id_size=len(self.store_id) * 2,
+ store_id=[ord(x) for x in self.store_id],
+ store_name_size=len(self.store_name) * 2,
+ store_name=[ord(x) for x in self.store_name],
+ nick_name_size=len(self.nick_name) * 2,
+ nick_name=[ord(x) for x in self.nick_name],
+ setting_title_id=self.setting_title_id,
+ favorite_hero_log_id=self.favorite_hero_log_id,
+ favorite_hero_log_awakening_stage=self.favorite_hero_log_awakening_stage,
+ favorite_support_log_id=self.favorite_support_log_id,
+ favorite_support_log_awakening_stage=self.favorite_support_log_awakening_stage,
+ class_num=self.class_num,
+ have_league_score=self.have_league_score,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoBnidSerialCodeCheckRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoBnidSerialCodeCheckResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
@@ -2508,7 +2786,7 @@ class SaoBnidSerialCodeCheckResponse(SaoBaseResponse):
self.result = 1
self.bnid_item_id = "130050"
self.use_status = 0
-
+
def make(self) -> bytes:
# create a resp struct
resp_struct = Struct(
@@ -2518,176 +2796,177 @@ class SaoBnidSerialCodeCheckResponse(SaoBaseResponse):
"use_status" / Int8ul, # result is either 0 or 1
)
- resp_data = resp_struct.build(dict(
- result=self.result,
- bnid_item_id_size=len(self.bnid_item_id) * 2,
- bnid_item_id=[ord(x) for x in self.bnid_item_id],
- use_status=self.use_status,
- ))
+ resp_data = resp_struct.build(
+ dict(
+ result=self.result,
+ bnid_item_id_size=len(self.bnid_item_id) * 2,
+ bnid_item_id=[ord(x) for x in self.bnid_item_id],
+ use_status=self.use_status,
+ )
+ )
self.length = len(resp_data)
return super().make() + resp_data
+
class SaoScanQrQuestProfileCardRequest(SaoBaseRequest):
def __init__(self, data: bytes) -> None:
super().__init__(data)
+
class SaoScanQrQuestProfileCardResponse(SaoBaseResponse):
def __init__(self, cmd) -> None:
super().__init__(cmd)
self.result = 1
# read_profile_card_data
- self.profile_card_code = "1234123412341234123" # ID of the QR code
+ self.profile_card_code = "1234123412341234123" # ID of the QR code
self.nick_name = "PLAYER"
- self.rank_num = 1 #short
- self.setting_title_id = 20005 #int
- self.skill_id = 0 #short
- self.hero_log_hero_log_id = 118000230 #int
- self.hero_log_log_level = 1 #short
- self.hero_log_awakening_stage = 1 #short
+ self.rank_num = 1 # short
+ self.setting_title_id = 20005 # int
+ self.skill_id = 0 # short
+ self.hero_log_hero_log_id = 118000230 # int
+ self.hero_log_log_level = 1 # short
+ self.hero_log_awakening_stage = 1 # short
- self.hero_log_property1_property_id = 0 #int
- self.hero_log_property1_value1 = 0 #int
- self.hero_log_property1_value2 = 0 #int
- self.hero_log_property2_property_id = 0 #int
- self.hero_log_property2_value1 = 0 #int
- self.hero_log_property2_value2 = 0 #int
- self.hero_log_property3_property_id = 0 #int
- self.hero_log_property3_value1 = 0 #int
- self.hero_log_property3_value2 = 0 #int
- self.hero_log_property4_property_id = 0 #int
- self.hero_log_property4_value1 = 0 #int
- self.hero_log_property4_value2 = 0 #int
+ self.hero_log_property1_property_id = 0 # int
+ self.hero_log_property1_value1 = 0 # int
+ self.hero_log_property1_value2 = 0 # int
+ self.hero_log_property2_property_id = 0 # int
+ self.hero_log_property2_value1 = 0 # int
+ self.hero_log_property2_value2 = 0 # int
+ self.hero_log_property3_property_id = 0 # int
+ self.hero_log_property3_value1 = 0 # int
+ self.hero_log_property3_value2 = 0 # int
+ self.hero_log_property4_property_id = 0 # int
+ self.hero_log_property4_value1 = 0 # int
+ self.hero_log_property4_value2 = 0 # int
- self.main_weapon_equipment_id = 0 #int
- self.main_weapon_enhancement_value = 0 #short
- self.main_weapon_awakening_stage = 0 #short
+ self.main_weapon_equipment_id = 0 # int
+ self.main_weapon_enhancement_value = 0 # short
+ self.main_weapon_awakening_stage = 0 # short
- self.main_weapon_property1_property_id = 0 #int
- self.main_weapon_property1_value1 = 0 #int
- self.main_weapon_property1_value2 = 0 #int
- self.main_weapon_property2_property_id = 0 #int
- self.main_weapon_property2_value1 = 0 #int
- self.main_weapon_property2_value2 = 0 #int
- self.main_weapon_property3_property_id = 0 #int
- self.main_weapon_property3_value1 = 0 #int
- self.main_weapon_property3_value2 = 0 #int
- self.main_weapon_property4_property_id = 0 #int
- self.main_weapon_property4_value1 = 0 #int
- self.main_weapon_property4_value2 = 0 #int
+ self.main_weapon_property1_property_id = 0 # int
+ self.main_weapon_property1_value1 = 0 # int
+ self.main_weapon_property1_value2 = 0 # int
+ self.main_weapon_property2_property_id = 0 # int
+ self.main_weapon_property2_value1 = 0 # int
+ self.main_weapon_property2_value2 = 0 # int
+ self.main_weapon_property3_property_id = 0 # int
+ self.main_weapon_property3_value1 = 0 # int
+ self.main_weapon_property3_value2 = 0 # int
+ self.main_weapon_property4_property_id = 0 # int
+ self.main_weapon_property4_value1 = 0 # int
+ self.main_weapon_property4_value2 = 0 # int
- self.sub_equipment_equipment_id = 0 #int
- self.sub_equipment_enhancement_value = 0 #short
- self.sub_equipment_awakening_stage = 0 #short
+ self.sub_equipment_equipment_id = 0 # int
+ self.sub_equipment_enhancement_value = 0 # short
+ self.sub_equipment_awakening_stage = 0 # short
- self.sub_equipment_property1_property_id = 0 #int
- self.sub_equipment_property1_value1 = 0 #int
- self.sub_equipment_property1_value2 = 0 #int
- self.sub_equipment_property2_property_id = 0 #int
- self.sub_equipment_property2_value1 = 0 #int
- self.sub_equipment_property2_value2 = 0 #int
- self.sub_equipment_property3_property_id = 0 #int
- self.sub_equipment_property3_value1 = 0 #int
- self.sub_equipment_property3_value2 = 0 #int
- self.sub_equipment_property4_property_id = 0 #int
- self.sub_equipment_property4_value1 = 0 #int
- self.sub_equipment_property4_value2 = 0 #int
+ self.sub_equipment_property1_property_id = 0 # int
+ self.sub_equipment_property1_value1 = 0 # int
+ self.sub_equipment_property1_value2 = 0 # int
+ self.sub_equipment_property2_property_id = 0 # int
+ self.sub_equipment_property2_value1 = 0 # int
+ self.sub_equipment_property2_value2 = 0 # int
+ self.sub_equipment_property3_property_id = 0 # int
+ self.sub_equipment_property3_value1 = 0 # int
+ self.sub_equipment_property3_value2 = 0 # int
+ self.sub_equipment_property4_property_id = 0 # int
+ self.sub_equipment_property4_value1 = 0 # int
+ self.sub_equipment_property4_value2 = 0 # int
+
+ self.holographic_flag = 1 # byte
- self.holographic_flag = 1 #byte
-
def make(self) -> bytes:
- #new stuff
+ # new stuff
read_profile_card_data_struct = Struct(
"profile_card_code_size" / Int32ub, # big endian
"profile_card_code" / Int16ul[len(self.profile_card_code)],
"nick_name_size" / Int32ub, # big endian
"nick_name" / Int16ul[len(self.nick_name)],
- "rank_num" / Int16ub, #short
- "setting_title_id" / Int32ub, #int
- "skill_id" / Int16ub, #short
- "hero_log_hero_log_id" / Int32ub, #int
- "hero_log_log_level" / Int16ub, #short
- "hero_log_awakening_stage" / Int16ub, #short
-
- "hero_log_property1_property_id" / Int32ub, #int
- "hero_log_property1_value1" / Int32ub, #int
- "hero_log_property1_value2" / Int32ub, #int
- "hero_log_property2_property_id" / Int32ub, #int
- "hero_log_property2_value1" / Int32ub, #int
- "hero_log_property2_value2" / Int32ub, #int
- "hero_log_property3_property_id" / Int32ub, #int
- "hero_log_property3_value1" / Int32ub, #int
- "hero_log_property3_value2" / Int32ub, #int
- "hero_log_property4_property_id" / Int32ub, #int
- "hero_log_property4_value1" / Int32ub, #int
- "hero_log_property4_value2" / Int32ub, #int
-
- "main_weapon_equipment_id" / Int32ub, #int
- "main_weapon_enhancement_value" / Int16ub, #short
- "main_weapon_awakening_stage" / Int16ub, #short
-
- "main_weapon_property1_property_id" / Int32ub, #int
- "main_weapon_property1_value1" / Int32ub, #int
- "main_weapon_property1_value2" / Int32ub, #int
- "main_weapon_property2_property_id" / Int32ub, #int
- "main_weapon_property2_value1" / Int32ub, #int
- "main_weapon_property2_value2" / Int32ub, #int
- "main_weapon_property3_property_id" / Int32ub, #int
- "main_weapon_property3_value1" / Int32ub, #int
- "main_weapon_property3_value2" / Int32ub, #int
- "main_weapon_property4_property_id" / Int32ub, #int
- "main_weapon_property4_value1" / Int32ub, #int
- "main_weapon_property4_value2" / Int32ub, #int
-
- "sub_equipment_equipment_id" / Int32ub, #int
- "sub_equipment_enhancement_value" / Int16ub, #short
- "sub_equipment_awakening_stage" / Int16ub, #short
-
- "sub_equipment_property1_property_id" / Int32ub, #int
- "sub_equipment_property1_value1" / Int32ub, #int
- "sub_equipment_property1_value2" / Int32ub, #int
- "sub_equipment_property2_property_id" / Int32ub, #int
- "sub_equipment_property2_value1" / Int32ub, #int
- "sub_equipment_property2_value2" / Int32ub, #int
- "sub_equipment_property3_property_id" / Int32ub, #int
- "sub_equipment_property3_value1" / Int32ub, #int
- "sub_equipment_property3_value2" / Int32ub, #int
- "sub_equipment_property4_property_id" / Int32ub, #int
- "sub_equipment_property4_value1" / Int32ub, #int
- "sub_equipment_property4_value2" / Int32ub, #int
-
+ "rank_num" / Int16ub, # short
+ "setting_title_id" / Int32ub, # int
+ "skill_id" / Int16ub, # short
+ "hero_log_hero_log_id" / Int32ub, # int
+ "hero_log_log_level" / Int16ub, # short
+ "hero_log_awakening_stage" / Int16ub, # short
+ "hero_log_property1_property_id" / Int32ub, # int
+ "hero_log_property1_value1" / Int32ub, # int
+ "hero_log_property1_value2" / Int32ub, # int
+ "hero_log_property2_property_id" / Int32ub, # int
+ "hero_log_property2_value1" / Int32ub, # int
+ "hero_log_property2_value2" / Int32ub, # int
+ "hero_log_property3_property_id" / Int32ub, # int
+ "hero_log_property3_value1" / Int32ub, # int
+ "hero_log_property3_value2" / Int32ub, # int
+ "hero_log_property4_property_id" / Int32ub, # int
+ "hero_log_property4_value1" / Int32ub, # int
+ "hero_log_property4_value2" / Int32ub, # int
+ "main_weapon_equipment_id" / Int32ub, # int
+ "main_weapon_enhancement_value" / Int16ub, # short
+ "main_weapon_awakening_stage" / Int16ub, # short
+ "main_weapon_property1_property_id" / Int32ub, # int
+ "main_weapon_property1_value1" / Int32ub, # int
+ "main_weapon_property1_value2" / Int32ub, # int
+ "main_weapon_property2_property_id" / Int32ub, # int
+ "main_weapon_property2_value1" / Int32ub, # int
+ "main_weapon_property2_value2" / Int32ub, # int
+ "main_weapon_property3_property_id" / Int32ub, # int
+ "main_weapon_property3_value1" / Int32ub, # int
+ "main_weapon_property3_value2" / Int32ub, # int
+ "main_weapon_property4_property_id" / Int32ub, # int
+ "main_weapon_property4_value1" / Int32ub, # int
+ "main_weapon_property4_value2" / Int32ub, # int
+ "sub_equipment_equipment_id" / Int32ub, # int
+ "sub_equipment_enhancement_value" / Int16ub, # short
+ "sub_equipment_awakening_stage" / Int16ub, # short
+ "sub_equipment_property1_property_id" / Int32ub, # int
+ "sub_equipment_property1_value1" / Int32ub, # int
+ "sub_equipment_property1_value2" / Int32ub, # int
+ "sub_equipment_property2_property_id" / Int32ub, # int
+ "sub_equipment_property2_value1" / Int32ub, # int
+ "sub_equipment_property2_value2" / Int32ub, # int
+ "sub_equipment_property3_property_id" / Int32ub, # int
+ "sub_equipment_property3_value1" / Int32ub, # int
+ "sub_equipment_property3_value2" / Int32ub, # int
+ "sub_equipment_property4_property_id" / Int32ub, # int
+ "sub_equipment_property4_value1" / Int32ub, # int
+ "sub_equipment_property4_value2" / Int32ub, # int
"holographic_flag" / Int8ul, # result is either 0 or 1
-
)
# create a resp struct
resp_struct = Struct(
"result" / Int8ul, # result is either 0 or 1
- "read_profile_card_data_size" / Rebuild(Int32ub, len_(this.read_profile_card_data)), # big endian
- "read_profile_card_data" / Array(this.read_profile_card_data_size, read_profile_card_data_struct),
+ "read_profile_card_data_size"
+ / Rebuild(Int32ub, len_(this.read_profile_card_data)), # big endian
+ "read_profile_card_data"
+ / Array(this.read_profile_card_data_size, read_profile_card_data_struct),
)
- resp_data = resp_struct.parse(resp_struct.build(dict(
- result=self.result,
- read_profile_card_data_size=0,
- read_profile_card_data=[],
- )))
+ resp_data = resp_struct.parse(
+ resp_struct.build(
+ dict(
+ result=self.result,
+ read_profile_card_data_size=0,
+ read_profile_card_data=[],
+ )
+ )
+ )
hero_data = dict(
profile_card_code_size=len(self.profile_card_code) * 2,
profile_card_code=[ord(x) for x in self.profile_card_code],
nick_name_size=len(self.nick_name) * 2,
nick_name=[ord(x) for x in self.nick_name],
-
rank_num=self.rank_num,
setting_title_id=self.setting_title_id,
skill_id=self.skill_id,
hero_log_hero_log_id=self.hero_log_hero_log_id,
hero_log_log_level=self.hero_log_log_level,
hero_log_awakening_stage=self.hero_log_awakening_stage,
-
hero_log_property1_property_id=self.hero_log_property1_property_id,
hero_log_property1_value1=self.hero_log_property1_value1,
hero_log_property1_value2=self.hero_log_property1_value2,
@@ -2700,11 +2979,9 @@ class SaoScanQrQuestProfileCardResponse(SaoBaseResponse):
hero_log_property4_property_id=self.hero_log_property4_property_id,
hero_log_property4_value1=self.hero_log_property4_value1,
hero_log_property4_value2=self.hero_log_property4_value2,
-
main_weapon_equipment_id=self.main_weapon_equipment_id,
main_weapon_enhancement_value=self.main_weapon_enhancement_value,
main_weapon_awakening_stage=self.main_weapon_awakening_stage,
-
main_weapon_property1_property_id=self.main_weapon_property1_property_id,
main_weapon_property1_value1=self.main_weapon_property1_value1,
main_weapon_property1_value2=self.main_weapon_property1_value2,
@@ -2717,11 +2994,9 @@ class SaoScanQrQuestProfileCardResponse(SaoBaseResponse):
main_weapon_property4_property_id=self.main_weapon_property4_property_id,
main_weapon_property4_value1=self.main_weapon_property4_value1,
main_weapon_property4_value2=self.main_weapon_property4_value2,
-
sub_equipment_equipment_id=self.sub_equipment_equipment_id,
sub_equipment_enhancement_value=self.sub_equipment_enhancement_value,
sub_equipment_awakening_stage=self.sub_equipment_awakening_stage,
-
sub_equipment_property1_property_id=self.sub_equipment_property1_property_id,
sub_equipment_property1_value1=self.sub_equipment_property1_value1,
sub_equipment_property1_value2=self.sub_equipment_property1_value2,
@@ -2734,14 +3009,13 @@ class SaoScanQrQuestProfileCardResponse(SaoBaseResponse):
sub_equipment_property4_property_id=self.sub_equipment_property4_property_id,
sub_equipment_property4_value1=self.sub_equipment_property4_value1,
sub_equipment_property4_value2=self.sub_equipment_property4_value2,
-
holographic_flag=self.holographic_flag,
)
-
+
resp_data.read_profile_card_data.append(hero_data)
# finally, rebuild the resp_data
resp_data = resp_struct.build(resp_data)
self.length = len(resp_data)
- return super().make() + resp_data
\ No newline at end of file
+ return super().make() + resp_data
diff --git a/titles/sao/index.py b/titles/sao/index.py
index ef0bf89..645dc92 100644
--- a/titles/sao/index.py
+++ b/titles/sao/index.py
@@ -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
\ No newline at end of file
+ return resp
diff --git a/titles/sao/read.py b/titles/sao/read.py
index 649fa02..5e88891 100644
--- a/titles/sao/read.py
+++ b/titles/sao/read.py
@@ -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)
diff --git a/titles/sao/schema/__init__.py b/titles/sao/schema/__init__.py
index 3e75fc0..53cd146 100644
--- a/titles/sao/schema/__init__.py
+++ b/titles/sao/schema/__init__.py
@@ -1,3 +1,3 @@
-from .profile import SaoProfileData
-from .static import SaoStaticData
-from .item import SaoItemData
\ No newline at end of file
+from .profile import SaoProfileData
+from .static import SaoStaticData
+from .item import SaoItemData
diff --git a/titles/sao/schema/item.py b/titles/sao/schema/item.py
index 11adf27..6324c67 100644
--- a/titles/sao/schema/item.py
+++ b/titles/sao/schema/item.py
@@ -1,506 +1,560 @@
-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
-
-equipment_data = Table(
- "sao_equipment_data",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("equipment_id", Integer, nullable=False),
- Column("enhancement_value", Integer, nullable=False),
- Column("enhancement_exp", Integer, nullable=False),
- Column("awakening_exp", Integer, nullable=False),
- Column("awakening_stage", Integer, nullable=False),
- Column("possible_awakening_flag", Integer, nullable=False),
- Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "equipment_id", name="sao_equipment_data_uk"),
- mysql_charset="utf8mb4",
-)
-
-item_data = Table(
- "sao_item_data",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("item_id", Integer, nullable=False),
- Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "item_id", name="sao_item_data_uk"),
- mysql_charset="utf8mb4",
-)
-
-hero_log_data = Table(
- "sao_hero_log_data",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("user_hero_log_id", Integer, nullable=False),
- Column("log_level", Integer, nullable=False),
- Column("log_exp", Integer, nullable=False),
- Column("main_weapon", Integer, nullable=False),
- Column("sub_equipment", Integer, nullable=False),
- Column("skill_slot1_skill_id", Integer, nullable=False),
- Column("skill_slot2_skill_id", Integer, nullable=False),
- Column("skill_slot3_skill_id", Integer, nullable=False),
- Column("skill_slot4_skill_id", Integer, nullable=False),
- Column("skill_slot5_skill_id", Integer, nullable=False),
- Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "user_hero_log_id", name="sao_hero_log_data_uk"),
- mysql_charset="utf8mb4",
-)
-
-hero_party = Table(
- "sao_hero_party",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("user_party_team_id", Integer, nullable=False),
- Column("user_hero_log_id_1", Integer, nullable=False),
- Column("user_hero_log_id_2", Integer, nullable=False),
- Column("user_hero_log_id_3", Integer, nullable=False),
- UniqueConstraint("user", "user_party_team_id", name="sao_hero_party_uk"),
- mysql_charset="utf8mb4",
-)
-
-quest = Table(
- "sao_player_quest",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("episode_id", Integer, nullable=False),
- Column("quest_clear_flag", Boolean, nullable=False),
- Column("clear_time", Integer, nullable=False),
- Column("combo_num", Integer, nullable=False),
- Column("total_damage", Integer, nullable=False),
- Column("concurrent_destroying_num", Integer, nullable=False),
- Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "episode_id", name="sao_player_quest_uk"),
- mysql_charset="utf8mb4",
-)
-
-sessions = Table(
- "sao_play_sessions",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("user_party_team_id", Integer, nullable=False),
- Column("episode_id", Integer, nullable=False),
- Column("play_mode", Integer, nullable=False),
- Column("quest_drop_boost_apply_flag", Integer, nullable=False),
- Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "user_party_team_id", "play_date", name="sao_play_sessions_uk"),
- mysql_charset="utf8mb4",
-)
-
-end_sessions = Table(
- "sao_end_sessions",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("quest_id", Integer, nullable=False),
- Column("play_result_flag", Boolean, nullable=False),
- Column("reward_data", JSON, nullable=True),
- Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()),
- mysql_charset="utf8mb4",
-)
-
-class SaoItemData(BaseData):
- def create_session(self, user_id: int, user_party_team_id: int, episode_id: int, play_mode: int, quest_drop_boost_apply_flag: int) -> Optional[int]:
- sql = insert(sessions).values(
- user=user_id,
- user_party_team_id=user_party_team_id,
- episode_id=episode_id,
- play_mode=play_mode,
- quest_drop_boost_apply_flag=quest_drop_boost_apply_flag
- )
-
- 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 session for user {user_id}!")
- return None
- return result.lastrowid
-
- def create_end_session(self, user_id: int, quest_id: int, play_result_flag: bool, reward_data: JSON) -> Optional[int]:
- sql = insert(end_sessions).values(
- user=user_id,
- quest_id=quest_id,
- play_result_flag=play_result_flag,
- reward_data=reward_data,
- )
-
- 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 end session for user {user_id}!")
- return None
- return result.lastrowid
-
- def put_item(self, user_id: int, item_id: int) -> Optional[int]:
- sql = insert(item_data).values(
- user=user_id,
- item_id=item_id,
- )
-
- conflict = sql.on_duplicate_key_update(
- item_id=item_id,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}"
- )
- return None
-
- return result.lastrowid
-
- def put_equipment_data(self, user_id: int, equipment_id: int, enhancement_value: int, enhancement_exp: int, awakening_exp: int, awakening_stage: int, possible_awakening_flag: int) -> Optional[int]:
- sql = insert(equipment_data).values(
- user=user_id,
- equipment_id=equipment_id,
- enhancement_value=enhancement_value,
- enhancement_exp=enhancement_exp,
- awakening_exp=awakening_exp,
- awakening_stage=awakening_stage,
- possible_awakening_flag=possible_awakening_flag,
- )
-
- conflict = sql.on_duplicate_key_update(
- enhancement_value=enhancement_value,
- enhancement_exp=enhancement_exp,
- awakening_exp=awakening_exp,
- awakening_stage=awakening_stage,
- possible_awakening_flag=possible_awakening_flag,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert equipment! user: {user_id}, equipment_id: {equipment_id}"
- )
- return None
-
- return result.lastrowid
-
- def put_hero_log(self, user_id: int, user_hero_log_id: int, log_level: int, log_exp: int, main_weapon: int, sub_equipment: int, skill_slot1_skill_id: int, skill_slot2_skill_id: int, skill_slot3_skill_id: int, skill_slot4_skill_id: int, skill_slot5_skill_id: int) -> Optional[int]:
- sql = insert(hero_log_data).values(
- user=user_id,
- user_hero_log_id=user_hero_log_id,
- log_level=log_level,
- log_exp=log_exp,
- main_weapon=main_weapon,
- sub_equipment=sub_equipment,
- skill_slot1_skill_id=skill_slot1_skill_id,
- skill_slot2_skill_id=skill_slot2_skill_id,
- skill_slot3_skill_id=skill_slot3_skill_id,
- skill_slot4_skill_id=skill_slot4_skill_id,
- skill_slot5_skill_id=skill_slot5_skill_id,
- )
-
- conflict = sql.on_duplicate_key_update(
- log_level=log_level,
- log_exp=log_exp,
- main_weapon=main_weapon,
- sub_equipment=sub_equipment,
- skill_slot1_skill_id=skill_slot1_skill_id,
- skill_slot2_skill_id=skill_slot2_skill_id,
- skill_slot3_skill_id=skill_slot3_skill_id,
- skill_slot4_skill_id=skill_slot4_skill_id,
- skill_slot5_skill_id=skill_slot5_skill_id,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert hero! user: {user_id}, user_hero_log_id: {user_hero_log_id}"
- )
- return None
-
- return result.lastrowid
-
- def put_hero_party(self, user_id: int, user_party_team_id: int, user_hero_log_id_1: int, user_hero_log_id_2: int, user_hero_log_id_3: int) -> Optional[int]:
- sql = insert(hero_party).values(
- user=user_id,
- user_party_team_id=user_party_team_id,
- user_hero_log_id_1=user_hero_log_id_1,
- user_hero_log_id_2=user_hero_log_id_2,
- user_hero_log_id_3=user_hero_log_id_3,
- )
-
- conflict = sql.on_duplicate_key_update(
- user_hero_log_id_1=user_hero_log_id_1,
- user_hero_log_id_2=user_hero_log_id_2,
- user_hero_log_id_3=user_hero_log_id_3,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert hero party! user: {user_id}, user_party_team_id: {user_party_team_id}"
- )
- return None
-
- return result.lastrowid
-
- def put_player_quest(self, user_id: int, episode_id: int, quest_clear_flag: bool, clear_time: int, combo_num: int, total_damage: int, concurrent_destroying_num: int) -> Optional[int]:
- sql = insert(quest).values(
- user=user_id,
- episode_id=episode_id,
- quest_clear_flag=quest_clear_flag,
- clear_time=clear_time,
- combo_num=combo_num,
- total_damage=total_damage,
- concurrent_destroying_num=concurrent_destroying_num
- )
-
- conflict = sql.on_duplicate_key_update(
- quest_clear_flag=quest_clear_flag,
- clear_time=clear_time,
- combo_num=combo_num,
- total_damage=total_damage,
- concurrent_destroying_num=concurrent_destroying_num
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert quest! user: {user_id}, episode_id: {episode_id}"
- )
- return None
-
- return result.lastrowid
-
- def get_user_equipment(self, user_id: int, equipment_id: int) -> Optional[Dict]:
- sql = equipment_data.select(equipment_data.c.user == user_id and equipment_data.c.equipment_id == equipment_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_user_equipments(
- self, user_id: int
- ) -> Optional[List[Row]]:
- """
- A catch-all equipments lookup given a profile
- """
- sql = equipment_data.select(
- and_(
- equipment_data.c.user == user_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_user_items(
- self, user_id: int
- ) -> Optional[List[Row]]:
- """
- A catch-all items lookup given a profile
- """
- sql = item_data.select(
- and_(
- item_data.c.user == user_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_hero_log(
- self, user_id: int, user_hero_log_id: int = None
- ) -> Optional[List[Row]]:
- """
- A catch-all hero lookup given a profile and user_party_team_id and ID specifiers
- """
- sql = hero_log_data.select(
- and_(
- hero_log_data.c.user == user_id,
- hero_log_data.c.user_hero_log_id == user_hero_log_id if user_hero_log_id is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_hero_logs(
- self, user_id: int
- ) -> Optional[List[Row]]:
- """
- A catch-all hero lookup given a profile
- """
- sql = hero_log_data.select(
- and_(
- hero_log_data.c.user == user_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_hero_party(
- self, user_id: int, user_party_team_id: int = None
- ) -> Optional[List[Row]]:
- sql = hero_party.select(
- and_(
- hero_party.c.user == user_id,
- hero_party.c.user_party_team_id == user_party_team_id if user_party_team_id is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_quest_log(
- self, user_id: int, episode_id: int = None
- ) -> Optional[List[Row]]:
- """
- A catch-all quest lookup given a profile and episode_id
- """
- sql = quest.select(
- and_(
- quest.c.user == user_id,
- quest.c.episode_id == episode_id if episode_id is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_quest_logs(
- self, user_id: int
- ) -> Optional[List[Row]]:
- """
- A catch-all quest lookup given a profile
- """
- sql = quest.select(
- and_(
- quest.c.user == user_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_session(
- self, user_id: int = None
- ) -> Optional[List[Row]]:
- sql = sessions.select(
- and_(
- sessions.c.user == user_id,
- )
- ).order_by(
- sessions.c.play_date.asc()
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_end_session(
- self, user_id: int = None
- ) -> Optional[List[Row]]:
- sql = end_sessions.select(
- and_(
- end_sessions.c.user == user_id,
- )
- ).order_by(
- end_sessions.c.play_date.asc()
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def remove_hero_log(self, user_id: int, user_hero_log_id: int) -> None:
- sql = hero_log_data.delete(
- and_(
- hero_log_data.c.user == user_id,
- hero_log_data.c.user_hero_log_id == user_hero_log_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to remove hero log! profile: {user_id}, user_hero_log_id: {user_hero_log_id}"
- )
- return None
-
- def remove_equipment(self, user_id: int, equipment_id: int) -> None:
- sql = equipment_data.delete(
- and_(equipment_data.c.user == user_id, equipment_data.c.equipment_id == equipment_id)
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to remove equipment! profile: {user_id}, equipment_id: {equipment_id}"
- )
- return None
-
- def remove_item(self, user_id: int, item_id: int) -> None:
- sql = item_data.delete(
- and_(item_data.c.user == user_id, item_data.c.item_id == item_id)
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to remove item! profile: {user_id}, item_id: {item_id}"
- )
- return None
\ No newline at end of file
+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
+
+equipment_data = Table(
+ "sao_equipment_data",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("equipment_id", Integer, nullable=False),
+ Column("enhancement_value", Integer, nullable=False),
+ Column("enhancement_exp", Integer, nullable=False),
+ Column("awakening_exp", Integer, nullable=False),
+ Column("awakening_stage", Integer, nullable=False),
+ Column("possible_awakening_flag", Integer, nullable=False),
+ Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "equipment_id", name="sao_equipment_data_uk"),
+ mysql_charset="utf8mb4",
+)
+
+item_data = Table(
+ "sao_item_data",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("item_id", Integer, nullable=False),
+ Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "item_id", name="sao_item_data_uk"),
+ mysql_charset="utf8mb4",
+)
+
+hero_log_data = Table(
+ "sao_hero_log_data",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("user_hero_log_id", Integer, nullable=False),
+ Column("log_level", Integer, nullable=False),
+ Column("log_exp", Integer, nullable=False),
+ Column("main_weapon", Integer, nullable=False),
+ Column("sub_equipment", Integer, nullable=False),
+ Column("skill_slot1_skill_id", Integer, nullable=False),
+ Column("skill_slot2_skill_id", Integer, nullable=False),
+ Column("skill_slot3_skill_id", Integer, nullable=False),
+ Column("skill_slot4_skill_id", Integer, nullable=False),
+ Column("skill_slot5_skill_id", Integer, nullable=False),
+ Column("get_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "user_hero_log_id", name="sao_hero_log_data_uk"),
+ mysql_charset="utf8mb4",
+)
+
+hero_party = Table(
+ "sao_hero_party",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("user_party_team_id", Integer, nullable=False),
+ Column("user_hero_log_id_1", Integer, nullable=False),
+ Column("user_hero_log_id_2", Integer, nullable=False),
+ Column("user_hero_log_id_3", Integer, nullable=False),
+ UniqueConstraint("user", "user_party_team_id", name="sao_hero_party_uk"),
+ mysql_charset="utf8mb4",
+)
+
+quest = Table(
+ "sao_player_quest",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("episode_id", Integer, nullable=False),
+ Column("quest_clear_flag", Boolean, nullable=False),
+ Column("clear_time", Integer, nullable=False),
+ Column("combo_num", Integer, nullable=False),
+ Column("total_damage", Integer, nullable=False),
+ Column("concurrent_destroying_num", Integer, nullable=False),
+ Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "episode_id", name="sao_player_quest_uk"),
+ mysql_charset="utf8mb4",
+)
+
+sessions = Table(
+ "sao_play_sessions",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("user_party_team_id", Integer, nullable=False),
+ Column("episode_id", Integer, nullable=False),
+ Column("play_mode", Integer, nullable=False),
+ Column("quest_drop_boost_apply_flag", Integer, nullable=False),
+ Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint(
+ "user", "user_party_team_id", "play_date", name="sao_play_sessions_uk"
+ ),
+ mysql_charset="utf8mb4",
+)
+
+end_sessions = Table(
+ "sao_end_sessions",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("quest_id", Integer, nullable=False),
+ Column("play_result_flag", Boolean, nullable=False),
+ Column("reward_data", JSON, nullable=True),
+ Column("play_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ mysql_charset="utf8mb4",
+)
+
+
+class SaoItemData(BaseData):
+ def create_session(
+ self,
+ user_id: int,
+ user_party_team_id: int,
+ episode_id: int,
+ play_mode: int,
+ quest_drop_boost_apply_flag: int,
+ ) -> Optional[int]:
+ sql = insert(sessions).values(
+ user=user_id,
+ user_party_team_id=user_party_team_id,
+ episode_id=episode_id,
+ play_mode=play_mode,
+ quest_drop_boost_apply_flag=quest_drop_boost_apply_flag,
+ )
+
+ 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 session for user {user_id}!")
+ return None
+ return result.lastrowid
+
+ def create_end_session(
+ self, user_id: int, quest_id: int, play_result_flag: bool, reward_data: JSON
+ ) -> Optional[int]:
+ sql = insert(end_sessions).values(
+ user=user_id,
+ quest_id=quest_id,
+ play_result_flag=play_result_flag,
+ reward_data=reward_data,
+ )
+
+ 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 end session for user {user_id}!")
+ return None
+ return result.lastrowid
+
+ def put_item(self, user_id: int, item_id: int) -> Optional[int]:
+ sql = insert(item_data).values(
+ user=user_id,
+ item_id=item_id,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ item_id=item_id,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def put_equipment_data(
+ self,
+ user_id: int,
+ equipment_id: int,
+ enhancement_value: int,
+ enhancement_exp: int,
+ awakening_exp: int,
+ awakening_stage: int,
+ possible_awakening_flag: int,
+ ) -> Optional[int]:
+ sql = insert(equipment_data).values(
+ user=user_id,
+ equipment_id=equipment_id,
+ enhancement_value=enhancement_value,
+ enhancement_exp=enhancement_exp,
+ awakening_exp=awakening_exp,
+ awakening_stage=awakening_stage,
+ possible_awakening_flag=possible_awakening_flag,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ enhancement_value=enhancement_value,
+ enhancement_exp=enhancement_exp,
+ awakening_exp=awakening_exp,
+ awakening_stage=awakening_stage,
+ possible_awakening_flag=possible_awakening_flag,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert equipment! user: {user_id}, equipment_id: {equipment_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def put_hero_log(
+ self,
+ user_id: int,
+ user_hero_log_id: int,
+ log_level: int,
+ log_exp: int,
+ main_weapon: int,
+ sub_equipment: int,
+ skill_slot1_skill_id: int,
+ skill_slot2_skill_id: int,
+ skill_slot3_skill_id: int,
+ skill_slot4_skill_id: int,
+ skill_slot5_skill_id: int,
+ ) -> Optional[int]:
+ sql = insert(hero_log_data).values(
+ user=user_id,
+ user_hero_log_id=user_hero_log_id,
+ log_level=log_level,
+ log_exp=log_exp,
+ main_weapon=main_weapon,
+ sub_equipment=sub_equipment,
+ skill_slot1_skill_id=skill_slot1_skill_id,
+ skill_slot2_skill_id=skill_slot2_skill_id,
+ skill_slot3_skill_id=skill_slot3_skill_id,
+ skill_slot4_skill_id=skill_slot4_skill_id,
+ skill_slot5_skill_id=skill_slot5_skill_id,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ log_level=log_level,
+ log_exp=log_exp,
+ main_weapon=main_weapon,
+ sub_equipment=sub_equipment,
+ skill_slot1_skill_id=skill_slot1_skill_id,
+ skill_slot2_skill_id=skill_slot2_skill_id,
+ skill_slot3_skill_id=skill_slot3_skill_id,
+ skill_slot4_skill_id=skill_slot4_skill_id,
+ skill_slot5_skill_id=skill_slot5_skill_id,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert hero! user: {user_id}, user_hero_log_id: {user_hero_log_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def put_hero_party(
+ self,
+ user_id: int,
+ user_party_team_id: int,
+ user_hero_log_id_1: int,
+ user_hero_log_id_2: int,
+ user_hero_log_id_3: int,
+ ) -> Optional[int]:
+ sql = insert(hero_party).values(
+ user=user_id,
+ user_party_team_id=user_party_team_id,
+ user_hero_log_id_1=user_hero_log_id_1,
+ user_hero_log_id_2=user_hero_log_id_2,
+ user_hero_log_id_3=user_hero_log_id_3,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ user_hero_log_id_1=user_hero_log_id_1,
+ user_hero_log_id_2=user_hero_log_id_2,
+ user_hero_log_id_3=user_hero_log_id_3,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert hero party! user: {user_id}, user_party_team_id: {user_party_team_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def put_player_quest(
+ self,
+ user_id: int,
+ episode_id: int,
+ quest_clear_flag: bool,
+ clear_time: int,
+ combo_num: int,
+ total_damage: int,
+ concurrent_destroying_num: int,
+ ) -> Optional[int]:
+ sql = insert(quest).values(
+ user=user_id,
+ episode_id=episode_id,
+ quest_clear_flag=quest_clear_flag,
+ clear_time=clear_time,
+ combo_num=combo_num,
+ total_damage=total_damage,
+ concurrent_destroying_num=concurrent_destroying_num,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ quest_clear_flag=quest_clear_flag,
+ clear_time=clear_time,
+ combo_num=combo_num,
+ total_damage=total_damage,
+ concurrent_destroying_num=concurrent_destroying_num,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert quest! user: {user_id}, episode_id: {episode_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def get_user_equipment(self, user_id: int, equipment_id: int) -> Optional[Dict]:
+ sql = equipment_data.select(
+ equipment_data.c.user == user_id
+ and equipment_data.c.equipment_id == equipment_id
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_user_equipments(self, user_id: int) -> Optional[List[Row]]:
+ """
+ A catch-all equipments lookup given a profile
+ """
+ sql = equipment_data.select(
+ and_(
+ equipment_data.c.user == user_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_user_items(self, user_id: int) -> Optional[List[Row]]:
+ """
+ A catch-all items lookup given a profile
+ """
+ sql = item_data.select(
+ and_(
+ item_data.c.user == user_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_hero_log(
+ self, user_id: int, user_hero_log_id: int = None
+ ) -> Optional[List[Row]]:
+ """
+ A catch-all hero lookup given a profile and user_party_team_id and ID specifiers
+ """
+ sql = hero_log_data.select(
+ and_(
+ hero_log_data.c.user == user_id,
+ hero_log_data.c.user_hero_log_id == user_hero_log_id
+ if user_hero_log_id is not None
+ else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_hero_logs(self, user_id: int) -> Optional[List[Row]]:
+ """
+ A catch-all hero lookup given a profile
+ """
+ sql = hero_log_data.select(
+ and_(
+ hero_log_data.c.user == user_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_hero_party(
+ self, user_id: int, user_party_team_id: int = None
+ ) -> Optional[List[Row]]:
+ sql = hero_party.select(
+ and_(
+ hero_party.c.user == user_id,
+ hero_party.c.user_party_team_id == user_party_team_id
+ if user_party_team_id is not None
+ else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_quest_log(
+ self, user_id: int, episode_id: int = None
+ ) -> Optional[List[Row]]:
+ """
+ A catch-all quest lookup given a profile and episode_id
+ """
+ sql = quest.select(
+ and_(
+ quest.c.user == user_id,
+ quest.c.episode_id == episode_id if episode_id is not None else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_quest_logs(self, user_id: int) -> Optional[List[Row]]:
+ """
+ A catch-all quest lookup given a profile
+ """
+ sql = quest.select(
+ and_(
+ quest.c.user == user_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_session(self, user_id: int = None) -> Optional[List[Row]]:
+ sql = sessions.select(
+ and_(
+ sessions.c.user == user_id,
+ )
+ ).order_by(sessions.c.play_date.asc())
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_end_session(self, user_id: int = None) -> Optional[List[Row]]:
+ sql = end_sessions.select(
+ and_(
+ end_sessions.c.user == user_id,
+ )
+ ).order_by(end_sessions.c.play_date.asc())
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def remove_hero_log(self, user_id: int, user_hero_log_id: int) -> None:
+ sql = hero_log_data.delete(
+ and_(
+ hero_log_data.c.user == user_id,
+ hero_log_data.c.user_hero_log_id == user_hero_log_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to remove hero log! profile: {user_id}, user_hero_log_id: {user_hero_log_id}"
+ )
+ return None
+
+ def remove_equipment(self, user_id: int, equipment_id: int) -> None:
+ sql = equipment_data.delete(
+ and_(
+ equipment_data.c.user == user_id,
+ equipment_data.c.equipment_id == equipment_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to remove equipment! profile: {user_id}, equipment_id: {equipment_id}"
+ )
+ return None
+
+ def remove_item(self, user_id: int, item_id: int) -> None:
+ sql = item_data.delete(
+ and_(item_data.c.user == user_id, item_data.c.item_id == item_id)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to remove item! profile: {user_id}, item_id: {item_id}"
+ )
+ return None
diff --git a/titles/sao/schema/profile.py b/titles/sao/schema/profile.py
index b125717..58ac301 100644
--- a/titles/sao/schema/profile.py
+++ b/titles/sao/schema/profile.py
@@ -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()
\ No newline at end of file
+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()
diff --git a/titles/sao/schema/static.py b/titles/sao/schema/static.py
index ce9a6a9..5bd3e32 100644
--- a/titles/sao/schema/static.py
+++ b/titles/sao/schema/static.py
@@ -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()]
\ No newline at end of file
+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()]
diff --git a/titles/wacca/base.py b/titles/wacca/base.py
index 2351001..953f460 100644
--- a/titles/wacca/base.py
+++ b/titles/wacca/base.py
@@ -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:
diff --git a/titles/wacca/frontend.py b/titles/wacca/frontend.py
index cc40644..99c8bc4 100644
--- a/titles/wacca/frontend.py
+++ b/titles/wacca/frontend.py
@@ -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")
diff --git a/titles/wacca/handlers/user_music.py b/titles/wacca/handlers/user_music.py
index 26c2167..79c33b3 100644
--- a/titles/wacca/handlers/user_music.py
+++ b/titles/wacca/handlers/user_music.py
@@ -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
diff --git a/titles/wacca/lilyr.py b/titles/wacca/lilyr.py
index 0bb12ed..fc28354 100644
--- a/titles/wacca/lilyr.py
+++ b/titles/wacca/lilyr.py
@@ -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
diff --git a/titles/wacca/read.py b/titles/wacca/read.py
index 109d7ff..baf0eab 100644
--- a/titles/wacca/read.py
+++ b/titles/wacca/read.py
@@ -2,6 +2,7 @@ from typing import Optional
import wacky
import json
from os import walk, path
+from contextlib import nullcontext
from read import BaseReader
from core.config import CoreConfig
@@ -37,7 +38,8 @@ class WaccaReader(BaseReader):
self.logger.error("Could not find Table or Message folder, nothing to read")
return
- self.read_music(f"{self.bin_dir}/Table", "MusicParameterTable")
+ with self.data.static.conn.begin() or nullcontext():
+ self.read_music(f"{self.bin_dir}/Table", "MusicParameterTable")
def read_music(self, base_dir: str, table: str) -> None:
if not self.check_valid_pair(base_dir, table):
diff --git a/titles/wacca/reverse.py b/titles/wacca/reverse.py
index 728ef0a..a2aa5b3 100644
--- a/titles/wacca/reverse.py
+++ b/titles/wacca/reverse.py
@@ -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.lilyr import WaccaLilyR
@@ -309,11 +310,12 @@ class WaccaReverse(WaccaLilyR):
req = UserStatusCreateRequest(data)
resp = super().handle_user_status_create_request(data)
- self.data.item.put_item(
- req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310001
- ) # Added reverse
- self.data.item.put_item(
- req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310002
- ) # Added reverse
+ with self.data.item.conn.begin() or nullcontext():
+ self.data.item.put_item(
+ req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310001
+ ) # Added reverse
+ self.data.item.put_item(
+ req.aimeId, WaccaConstants.ITEM_TYPES["navigator"], 310002
+ ) # Added reverse
return resp
diff --git a/titles/wacca/schema/__init__.py b/titles/wacca/schema/__init__.py
index 2ccb661..2a897ae 100644
--- a/titles/wacca/schema/__init__.py
+++ b/titles/wacca/schema/__init__.py
@@ -1,6 +1,6 @@
-from titles.wacca.schema.profile import WaccaProfileData
-from titles.wacca.schema.score import WaccaScoreData
-from titles.wacca.schema.item import WaccaItemData
-from titles.wacca.schema.static import WaccaStaticData
-
-__all__ = ["WaccaProfileData", "WaccaScoreData", "WaccaItemData", "WaccaStaticData"]
+from titles.wacca.schema.profile import WaccaProfileData
+from titles.wacca.schema.score import WaccaScoreData
+from titles.wacca.schema.item import WaccaItemData
+from titles.wacca.schema.static import WaccaStaticData
+
+__all__ = ["WaccaProfileData", "WaccaScoreData", "WaccaItemData", "WaccaStaticData"]
diff --git a/titles/wacca/schema/item.py b/titles/wacca/schema/item.py
index 2341afa..2111256 100644
--- a/titles/wacca/schema/item.py
+++ b/titles/wacca/schema/item.py
@@ -1,207 +1,209 @@
-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, select, update, delete
-from sqlalchemy.engine import Row
-from sqlalchemy.dialects.mysql import insert
-
-from core.data.schema import BaseData, metadata
-
-item = Table(
- "wacca_item",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("item_id", Integer, nullable=False),
- Column("type", Integer, nullable=False),
- Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
- Column("use_count", Integer, server_default="0"),
- UniqueConstraint("user", "item_id", "type", name="wacca_item_uk"),
- mysql_charset="utf8mb4",
-)
-
-ticket = Table(
- "wacca_ticket",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("ticket_id", Integer, nullable=False),
- Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
- Column("expire_date", TIMESTAMP),
- mysql_charset="utf8mb4",
-)
-
-song_unlock = Table(
- "wacca_song_unlock",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("song_id", Integer, nullable=False),
- Column("highest_difficulty", Integer, nullable=False),
- Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
- UniqueConstraint("user", "song_id", name="wacca_song_unlock_uk"),
- mysql_charset="utf8mb4",
-)
-
-trophy = Table(
- "wacca_trophy",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("trophy_id", Integer, nullable=False),
- Column("season", Integer, nullable=False),
- Column("progress", Integer, nullable=False, server_default="0"),
- Column("badge_type", Integer, nullable=False, server_default="0"),
- UniqueConstraint("user", "trophy_id", "season", name="wacca_trophy_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-class WaccaItemData(BaseData):
- def get_song_unlocks(self, user_id: int) -> Optional[List[Row]]:
- sql = song_unlock.select(song_unlock.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
-
- return result.fetchall()
-
- def unlock_song(self, user_id: int, song_id: int, difficulty: int) -> Optional[int]:
- sql = insert(song_unlock).values(
- user=user_id, song_id=song_id, highest_difficulty=difficulty
- )
-
- conflict = sql.on_duplicate_key_update(
- highest_difficulty=case(
- (
- song_unlock.c.highest_difficulty >= difficulty,
- song_unlock.c.highest_difficulty,
- ),
- (song_unlock.c.highest_difficulty < difficulty, difficulty),
- )
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to unlock song! user: {user_id}, song_id: {song_id}, difficulty: {difficulty}"
- )
- return None
-
- return result.lastrowid
-
- def put_item(self, user_id: int, item_type: int, item_id: int) -> Optional[int]:
- sql = insert(item).values(
- user=user_id,
- item_id=item_id,
- type=item_type,
- )
-
- conflict = sql.on_duplicate_key_update(use_count=item.c.use_count + 1)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}, item_type: {item_type}"
- )
- return None
-
- return result.lastrowid
-
- def get_items(
- self, user_id: int, item_type: int = None, item_id: int = None
- ) -> Optional[List[Row]]:
- """
- A catch-all item lookup given a profile and option item type and ID specifiers
- """
- sql = item.select(
- and_(
- item.c.user == user_id,
- item.c.type == item_type if item_type is not None else True,
- item.c.item_id == item_id if item_id is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_tickets(self, user_id: int) -> Optional[List[Row]]:
- sql = select(ticket).where(ticket.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def add_ticket(self, user_id: int, ticket_id: int) -> None:
- sql = insert(ticket).values(user=user_id, ticket_id=ticket_id)
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"add_ticket: Failed to insert wacca ticket! user_id: {user_id} ticket_id {ticket_id}"
- )
- return None
- return result.lastrowid
-
- def spend_ticket(self, id: int) -> None:
- sql = delete(ticket).where(ticket.c.id == id)
-
- result = self.execute(sql)
- if result is None:
- self.logger.warn(f"Failed to delete ticket id {id}")
- return None
-
- def get_trophies(self, user_id: int, season: int = None) -> Optional[List[Row]]:
- if season is None:
- sql = select(trophy).where(trophy.c.user == user_id)
- else:
- sql = select(trophy).where(
- and_(trophy.c.user == user_id, trophy.c.season == season)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def update_trophy(
- self, user_id: int, trophy_id: int, season: int, progress: int, badge_type: int
- ) -> Optional[int]:
- sql = insert(trophy).values(
- user=user_id,
- trophy_id=trophy_id,
- season=season,
- progress=progress,
- badge_type=badge_type,
- )
-
- conflict = sql.on_duplicate_key_update(progress=progress)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"update_trophy: Failed to insert wacca trophy! user_id: {user_id} trophy_id: {trophy_id} progress {progress}"
- )
- 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
+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(
+ "wacca_item",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("item_id", Integer, nullable=False),
+ Column("type", Integer, nullable=False),
+ Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ Column("use_count", Integer, server_default="0"),
+ UniqueConstraint("user", "item_id", "type", name="wacca_item_uk"),
+ mysql_charset="utf8mb4",
+)
+
+ticket = Table(
+ "wacca_ticket",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("ticket_id", Integer, nullable=False),
+ Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ Column("expire_date", TIMESTAMP),
+ mysql_charset="utf8mb4",
+)
+
+song_unlock = Table(
+ "wacca_song_unlock",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("song_id", Integer, nullable=False),
+ Column("highest_difficulty", Integer, nullable=False),
+ Column("acquire_date", TIMESTAMP, nullable=False, server_default=func.now()),
+ UniqueConstraint("user", "song_id", name="wacca_song_unlock_uk"),
+ mysql_charset="utf8mb4",
+)
+
+trophy = Table(
+ "wacca_trophy",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("trophy_id", Integer, nullable=False),
+ Column("season", Integer, nullable=False),
+ Column("progress", Integer, nullable=False, server_default="0"),
+ Column("badge_type", Integer, nullable=False, server_default="0"),
+ UniqueConstraint("user", "trophy_id", "season", name="wacca_trophy_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class WaccaItemData(BaseData):
+ def get_song_unlocks(self, user_id: int) -> Optional[List[Row]]:
+ sql = song_unlock.select(song_unlock.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+
+ return result.fetchall()
+
+ def unlock_song(self, user_id: int, song_id: int, difficulty: int) -> Optional[int]:
+ sql = insert(song_unlock).values(
+ user=user_id, song_id=song_id, highest_difficulty=difficulty
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ highest_difficulty=case(
+ (
+ song_unlock.c.highest_difficulty >= difficulty,
+ song_unlock.c.highest_difficulty,
+ ),
+ (song_unlock.c.highest_difficulty < difficulty, difficulty),
+ )
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to unlock song! user: {user_id}, song_id: {song_id}, difficulty: {difficulty}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def put_item(self, user_id: int, item_type: int, item_id: int) -> Optional[int]:
+ sql = insert(item).values(
+ user=user_id,
+ item_id=item_id,
+ type=item_type,
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(use_count=item.c.use_count + 1))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert item! user: {user_id}, item_id: {item_id}, item_type: {item_type}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def get_items(
+ self, user_id: int, item_type: int = None, item_id: int = None
+ ) -> Optional[List[Row]]:
+ """
+ A catch-all item lookup given a profile and option item type and ID specifiers
+ """
+ sql = item.select(
+ and_(
+ item.c.user == user_id,
+ item.c.type == item_type if item_type is not None else True,
+ item.c.item_id == item_id if item_id is not None else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_tickets(self, user_id: int) -> Optional[List[Row]]:
+ sql = select(ticket).where(ticket.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def add_ticket(self, user_id: int, ticket_id: int) -> None:
+ sql = insert(ticket).values(user=user_id, ticket_id=ticket_id)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"add_ticket: Failed to insert wacca ticket! user_id: {user_id} ticket_id {ticket_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def spend_ticket(self, id: int) -> None:
+ sql = delete(ticket).where(ticket.c.id == id)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.warn(f"Failed to delete ticket id {id}")
+ return None
+
+ def get_trophies(self, user_id: int, season: int = None) -> Optional[List[Row]]:
+ if season is None:
+ sql = select(trophy).where(trophy.c.user == user_id)
+ else:
+ sql = select(trophy).where(
+ and_(trophy.c.user == user_id, trophy.c.season == season)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def update_trophy(
+ self, user_id: int, trophy_id: int, season: int, progress: int, badge_type: int
+ ) -> Optional[int]:
+ sql = insert(trophy).values(
+ user=user_id,
+ trophy_id=trophy_id,
+ season=season,
+ progress=progress,
+ badge_type=badge_type,
+ )
+
+ conflict = sql.on_conflict_do_update(set_=dict(progress=progress))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"update_trophy: Failed to insert wacca trophy! user_id: {user_id} trophy_id: {trophy_id} progress {progress}"
+ )
+ return None
+ return result.lastrowid
diff --git a/titles/wacca/schema/profile.py b/titles/wacca/schema/profile.py
index 48eb800..7ccc68b 100644
--- a/titles/wacca/schema/profile.py
+++ b/titles/wacca/schema/profile.py
@@ -1,522 +1,526 @@
-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.engine import Row
-from sqlalchemy.dialects.mysql import insert
-
-from core.data.schema import BaseData, metadata
-from ..handlers.helpers import PlayType
-
-profile = Table(
- "wacca_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),
- Column("username", String(8), nullable=False),
- Column("xp", Integer, server_default="0"),
- Column("wp", Integer, server_default="0"),
- Column("wp_total", Integer, server_default="0"),
- Column("wp_spent", Integer, server_default="0"),
- Column("dan_type", Integer, server_default="0"),
- Column("dan_level", Integer, server_default="0"),
- Column("title_0", Integer, server_default="0"),
- Column("title_1", Integer, server_default="0"),
- Column("title_2", Integer, server_default="0"),
- Column("rating", Integer, server_default="0"),
- Column("vip_expire_time", TIMESTAMP),
- Column("always_vip", Boolean, server_default="0"),
- Column("login_count", Integer, server_default="0"),
- Column("login_count_consec", Integer, server_default="0"),
- Column("login_count_days", Integer, server_default="0"),
- Column("login_count_days_consec", Integer, server_default="0"),
- Column("login_count_today", Integer, server_default="0"),
- Column("playcount_single", Integer, server_default="0"),
- Column("playcount_multi_vs", Integer, server_default="0"),
- Column("playcount_multi_coop", Integer, server_default="0"),
- Column("playcount_stageup", Integer, server_default="0"),
- Column("playcount_time_free", Integer, server_default="0"),
- Column("friend_view_1", Integer),
- Column("friend_view_2", Integer),
- Column("friend_view_3", Integer),
- Column("last_game_ver", String(50)),
- Column("last_song_id", Integer, server_default="0"),
- Column("last_song_difficulty", Integer, server_default="0"),
- Column("last_folder_order", Integer, server_default="0"),
- Column("last_folder_id", Integer, server_default="0"),
- Column("last_song_order", Integer, server_default="0"),
- Column("last_login_date", TIMESTAMP, server_default=func.now()),
- Column("gate_tutorial_flags", JSON),
- UniqueConstraint("user", "version", name="wacca_profile_uk"),
- mysql_charset="utf8mb4",
-)
-
-option = Table(
- "wacca_option",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("opt_id", Integer, nullable=False),
- Column("value", Integer, nullable=False),
- UniqueConstraint("user", "opt_id", name="wacca_option_uk"),
-)
-
-bingo = Table(
- "wacca_bingo",
- metadata,
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- primary_key=True,
- nullable=False,
- ),
- Column("page_number", Integer, nullable=False),
- Column("page_progress", JSON, nullable=False),
- UniqueConstraint("user", "page_number", name="wacca_bingo_uk"),
- mysql_charset="utf8mb4",
-)
-
-friend = Table(
- "wacca_friend",
- metadata,
- Column(
- "profile_sender",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column(
- "profile_reciever",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("is_accepted", Boolean, server_default="0"),
- PrimaryKeyConstraint("profile_sender", "profile_reciever", name="arcade_owner_pk"),
- mysql_charset="utf8mb4",
-)
-
-favorite = Table(
- "wacca_favorite_song",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("song_id", Integer, nullable=False),
- UniqueConstraint("user", "song_id", name="wacca_favorite_song_uk"),
- mysql_charset="utf8mb4",
-)
-
-gate = Table(
- "wacca_gate",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("gate_id", Integer, nullable=False),
- Column("page", Integer, nullable=False, server_default="0"),
- Column("progress", Integer, nullable=False, server_default="0"),
- Column("loops", Integer, nullable=False, server_default="0"),
- Column("last_used", TIMESTAMP, nullable=False, server_default=func.now()),
- Column("mission_flag", Integer, nullable=False, server_default="0"),
- Column("total_points", Integer, nullable=False, server_default="0"),
- UniqueConstraint("user", "gate_id", name="wacca_gate_uk"),
-)
-
-
-class WaccaProfileData(BaseData):
- def create_profile(
- self, aime_id: int, username: str, version: int
- ) -> Optional[int]:
- """
- Given a game version, aime id, and username, create a profile and return it's ID
- """
- sql = insert(profile).values(user=aime_id, username=username, version=version)
-
- conflict = sql.on_duplicate_key_update(username=sql.inserted.username)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} Failed to insert wacca profile! aime id: {aime_id} username: {username}"
- )
- return None
- return result.lastrowid
-
- def update_profile_playtype(
- self, profile_id: int, play_type: int, game_version: str
- ) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- playcount_single=profile.c.playcount_single + 1
- if play_type == PlayType.PlayTypeSingle.value
- else profile.c.playcount_single,
- playcount_multi_vs=profile.c.playcount_multi_vs + 1
- if play_type == PlayType.PlayTypeVs.value
- else profile.c.playcount_multi_vs,
- playcount_multi_coop=profile.c.playcount_multi_coop + 1
- if play_type == PlayType.PlayTypeCoop.value
- else profile.c.playcount_multi_coop,
- playcount_stageup=profile.c.playcount_stageup + 1
- if play_type == PlayType.PlayTypeStageup.value
- else profile.c.playcount_stageup,
- playcount_time_free=profile.c.playcount_time_free + 1
- if play_type == PlayType.PlayTypeTimeFree.value
- else profile.c.playcount_time_free,
- last_game_ver=game_version,
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"update_profile: failed to update profile! profile: {profile_id}"
- )
- return None
-
- def update_profile_lastplayed(
- self,
- profile_id: int,
- last_song_id: int,
- last_song_difficulty: int,
- last_folder_order: int,
- last_folder_id: int,
- last_song_order: int,
- ) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- last_song_id=last_song_id,
- last_song_difficulty=last_song_difficulty,
- last_folder_order=last_folder_order,
- last_folder_id=last_folder_id,
- last_song_order=last_song_order,
- )
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"update_profile_lastplayed: failed to update profile! profile: {profile_id}"
- )
- return None
-
- def update_profile_dan(
- self, profile_id: int, dan_level: int, dan_type: int
- ) -> Optional[int]:
- sql = profile.update(profile.c.id == profile_id).values(
- dan_level=dan_level, dan_type=dan_type
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.warn(
- f"update_profile_dan: Failed to update! profile {profile_id}"
- )
- return None
- return result.lastrowid
-
- def get_profile(self, profile_id: int = 0, aime_id: int = None) -> Optional[Row]:
- """
- Given a game version and either a profile or aime id, return the profile
- """
- if aime_id is not None:
- sql = profile.select(profile.c.user == aime_id)
- elif profile_id > 0:
- sql = profile.select(profile.c.id == profile_id)
- else:
- self.logger.error(
- f"get_profile: Bad arguments!! profile_id {profile_id} aime_id {aime_id}"
- )
- return None
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_options(self, user_id: int, option_id: int = None) -> Optional[List[Row]]:
- """
- Get a specific user option for a profile, or all of them if none specified
- """
- sql = option.select(
- and_(
- option.c.user == user_id,
- option.c.opt_id == option_id if option_id is not None else True,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- if option_id is not None:
- return result.fetchone()
- else:
- return result.fetchall()
-
- def update_option(self, user_id: int, option_id: int, value: int) -> Optional[int]:
- sql = insert(option).values(user=user_id, opt_id=option_id, value=value)
-
- conflict = sql.on_duplicate_key_update(value=value)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert option! profile: {user_id}, option: {option_id}, value: {value}"
- )
- return None
-
- return result.lastrowid
-
- def add_favorite_song(self, user_id: int, song_id: int) -> Optional[int]:
- sql = favorite.insert().values(user=user_id, song_id=song_id)
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert favorite! profile: {user_id}, song_id: {song_id}"
- )
- return None
- return result.lastrowid
-
- def remove_favorite_song(self, user_id: int, song_id: int) -> None:
- sql = favorite.delete(
- and_(favorite.c.user == user_id, favorite.c.song_id == song_id)
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to remove favorite! profile: {user_id}, song_id: {song_id}"
- )
- return None
-
- def get_favorite_songs(self, user_id: int) -> Optional[List[Row]]:
- sql = favorite.select(favorite.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_gates(self, user_id: int) -> Optional[List[Row]]:
- sql = select(gate).where(gate.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def update_gate(
- self,
- user_id: int,
- gate_id: int,
- page: int,
- progress: int,
- loop: int,
- mission_flag: int,
- total_points: int,
- ) -> Optional[int]:
- sql = insert(gate).values(
- user=user_id,
- gate_id=gate_id,
- page=page,
- progress=progress,
- loops=loop,
- mission_flag=mission_flag,
- total_points=total_points,
- )
-
- conflict = sql.on_duplicate_key_update(
- page=sql.inserted.page,
- progress=sql.inserted.progress,
- loops=sql.inserted.loops,
- mission_flag=sql.inserted.mission_flag,
- total_points=sql.inserted.total_points,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__} failed to update gate! user: {user_id}, gate_id: {gate_id}"
- )
- return None
- return result.lastrowid
-
- def get_friends(self, user_id: int) -> Optional[List[Row]]:
- sql = friend.select(friend.c.profile_sender == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def profile_to_aime_user(self, profile_id: int) -> Optional[int]:
- sql = select(profile.c.user).where(profile.c.id == profile_id)
-
- result = self.execute(sql)
- if result is None:
- self.logger.info(
- f"profile_to_aime_user: No user found for profile {profile_id}"
- )
- return None
-
- this_profile = result.fetchone()
- if this_profile is None:
- self.logger.info(
- f"profile_to_aime_user: No user found for profile {profile_id}"
- )
- return None
-
- return this_profile["user"]
-
- def session_login(
- self, profile_id: int, is_new_day: bool, is_consec_day: bool
- ) -> None:
- # TODO: Reset consec days counter
- sql = profile.update(profile.c.id == profile_id).values(
- login_count=profile.c.login_count + 1,
- login_count_consec=profile.c.login_count_consec + 1,
- login_count_days=profile.c.login_count_days + 1
- if is_new_day
- else profile.c.login_count_days,
- login_count_days_consec=profile.c.login_count_days_consec + 1
- if is_new_day and is_consec_day
- else profile.c.login_count_days_consec,
- login_count_today=1 if is_new_day else profile.c.login_count_today + 1,
- last_login_date=func.now(),
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"session_login: failed to update profile! profile: {profile_id}"
- )
- return None
-
- def session_logout(self, profile_id: int) -> None:
- sql = profile.update(profile.c.id == id).values(login_count_consec=0)
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to update profile! profile: {profile_id}"
- )
- return None
-
- def add_xp(self, profile_id: int, xp: int) -> None:
- sql = profile.update(profile.c.id == profile_id).values(xp=profile.c.xp + xp)
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"add_xp: Failed to update profile! profile_id {profile_id} xp {xp}"
- )
- return None
-
- def add_wp(self, profile_id: int, wp: int) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- wp=profile.c.wp + wp,
- wp_total=profile.c.wp_total + wp,
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"add_wp: Failed to update profile! profile_id {profile_id} wp {wp}"
- )
- return None
-
- def spend_wp(self, profile_id: int, wp: int) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- wp=profile.c.wp - wp,
- wp_spent=profile.c.wp_spent + wp,
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"spend_wp: Failed to update profile! profile_id {profile_id} wp {wp}"
- )
- return None
-
- def activate_vip(self, profile_id: int, expire_time) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- vip_expire_time=expire_time
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"activate_vip: Failed to update profile! profile_id {profile_id} expire_time {expire_time}"
- )
- return None
-
- def update_user_rating(self, profile_id: int, new_rating: int) -> None:
- sql = profile.update(profile.c.id == profile_id).values(rating=new_rating)
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"update_user_rating: Failed to update profile! profile_id {profile_id} new_rating {new_rating}"
- )
- return None
-
- def update_bingo(self, aime_id: int, page: int, progress: int) -> Optional[int]:
- sql = insert(bingo).values(
- user=aime_id, page_number=page, page_progress=progress
- )
-
- conflict = sql.on_duplicate_key_update(page_number=page, page_progress=progress)
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(f"put_bingo: failed to update! aime_id: {aime_id}")
- return None
- return result.lastrowid
-
- def get_bingo(self, aime_id: int) -> Optional[List[Row]]:
- sql = select(bingo).where(bingo.c.user == aime_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def get_bingo_page(self, aime_id: int, page: Dict) -> Optional[List[Row]]:
- sql = select(bingo).where(
- and_(bingo.c.user == aime_id, bingo.c.page_number == page)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
-
- def update_vip_time(self, profile_id: int, time_left) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- vip_expire_time=time_left
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(f"Failed to update VIP time for profile {profile_id}")
-
- def update_tutorial_flags(self, profile_id: int, flags: Dict) -> None:
- sql = profile.update(profile.c.id == profile_id).values(
- gate_tutorial_flags=flags
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"Failed to update tutorial flags for profile {profile_id}"
- )
+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.engine import Row
+from sqlalchemy.dialects.sqlite import insert
+
+from core.data.schema import BaseData, metadata
+from ..handlers.helpers import PlayType
+
+profile = Table(
+ "wacca_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),
+ Column("username", String(8), nullable=False),
+ Column("xp", Integer, server_default="0"),
+ Column("wp", Integer, server_default="0"),
+ Column("wp_total", Integer, server_default="0"),
+ Column("wp_spent", Integer, server_default="0"),
+ Column("dan_type", Integer, server_default="0"),
+ Column("dan_level", Integer, server_default="0"),
+ Column("title_0", Integer, server_default="0"),
+ Column("title_1", Integer, server_default="0"),
+ Column("title_2", Integer, server_default="0"),
+ Column("rating", Integer, server_default="0"),
+ Column("vip_expire_time", TIMESTAMP),
+ Column("always_vip", Boolean, server_default="0"),
+ Column("login_count", Integer, server_default="0"),
+ Column("login_count_consec", Integer, server_default="0"),
+ Column("login_count_days", Integer, server_default="0"),
+ Column("login_count_days_consec", Integer, server_default="0"),
+ Column("login_count_today", Integer, server_default="0"),
+ Column("playcount_single", Integer, server_default="0"),
+ Column("playcount_multi_vs", Integer, server_default="0"),
+ Column("playcount_multi_coop", Integer, server_default="0"),
+ Column("playcount_stageup", Integer, server_default="0"),
+ Column("playcount_time_free", Integer, server_default="0"),
+ Column("friend_view_1", Integer),
+ Column("friend_view_2", Integer),
+ Column("friend_view_3", Integer),
+ Column("last_game_ver", String(50)),
+ Column("last_song_id", Integer, server_default="0"),
+ Column("last_song_difficulty", Integer, server_default="0"),
+ Column("last_folder_order", Integer, server_default="0"),
+ Column("last_folder_id", Integer, server_default="0"),
+ Column("last_song_order", Integer, server_default="0"),
+ Column("last_login_date", TIMESTAMP, server_default=func.now()),
+ Column("gate_tutorial_flags", JSON),
+ UniqueConstraint("user", "version", name="wacca_profile_uk"),
+ mysql_charset="utf8mb4",
+)
+
+option = Table(
+ "wacca_option",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("opt_id", Integer, nullable=False),
+ Column("value", Integer, nullable=False),
+ UniqueConstraint("user", "opt_id", name="wacca_option_uk"),
+)
+
+bingo = Table(
+ "wacca_bingo",
+ metadata,
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ primary_key=True,
+ nullable=False,
+ ),
+ Column("page_number", Integer, nullable=False),
+ Column("page_progress", JSON, nullable=False),
+ UniqueConstraint("user", "page_number", name="wacca_bingo_uk"),
+ mysql_charset="utf8mb4",
+)
+
+friend = Table(
+ "wacca_friend",
+ metadata,
+ Column(
+ "profile_sender",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column(
+ "profile_reciever",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("is_accepted", Boolean, server_default="0"),
+ PrimaryKeyConstraint("profile_sender", "profile_reciever", name="arcade_owner_pk"),
+ mysql_charset="utf8mb4",
+)
+
+favorite = Table(
+ "wacca_favorite_song",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("song_id", Integer, nullable=False),
+ UniqueConstraint("user", "song_id", name="wacca_favorite_song_uk"),
+ mysql_charset="utf8mb4",
+)
+
+gate = Table(
+ "wacca_gate",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("gate_id", Integer, nullable=False),
+ Column("page", Integer, nullable=False, server_default="0"),
+ Column("progress", Integer, nullable=False, server_default="0"),
+ Column("loops", Integer, nullable=False, server_default="0"),
+ Column("last_used", TIMESTAMP, nullable=False, server_default=func.now()),
+ Column("mission_flag", Integer, nullable=False, server_default="0"),
+ Column("total_points", Integer, nullable=False, server_default="0"),
+ UniqueConstraint("user", "gate_id", name="wacca_gate_uk"),
+)
+
+
+class WaccaProfileData(BaseData):
+ def create_profile(
+ self, aime_id: int, username: str, version: int
+ ) -> Optional[int]:
+ """
+ Given a game version, aime id, and username, create a profile and return it's ID
+ """
+ sql = insert(profile).values(user=aime_id, username=username, version=version)
+
+ conflict = sql.on_conflict_do_update(set_=dict(username=sql.inserted.username))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} Failed to insert wacca profile! aime id: {aime_id} username: {username}"
+ )
+ return None
+ return result.lastrowid
+
+ def update_profile_playtype(
+ self, profile_id: int, play_type: int, game_version: str
+ ) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ playcount_single=profile.c.playcount_single + 1
+ if play_type == PlayType.PlayTypeSingle.value
+ else profile.c.playcount_single,
+ playcount_multi_vs=profile.c.playcount_multi_vs + 1
+ if play_type == PlayType.PlayTypeVs.value
+ else profile.c.playcount_multi_vs,
+ playcount_multi_coop=profile.c.playcount_multi_coop + 1
+ if play_type == PlayType.PlayTypeCoop.value
+ else profile.c.playcount_multi_coop,
+ playcount_stageup=profile.c.playcount_stageup + 1
+ if play_type == PlayType.PlayTypeStageup.value
+ else profile.c.playcount_stageup,
+ playcount_time_free=profile.c.playcount_time_free + 1
+ if play_type == PlayType.PlayTypeTimeFree.value
+ else profile.c.playcount_time_free,
+ last_game_ver=game_version,
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"update_profile: failed to update profile! profile: {profile_id}"
+ )
+ return None
+
+ def update_profile_lastplayed(
+ self,
+ profile_id: int,
+ last_song_id: int,
+ last_song_difficulty: int,
+ last_folder_order: int,
+ last_folder_id: int,
+ last_song_order: int,
+ ) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ last_song_id=last_song_id,
+ last_song_difficulty=last_song_difficulty,
+ last_folder_order=last_folder_order,
+ last_folder_id=last_folder_id,
+ last_song_order=last_song_order,
+ )
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"update_profile_lastplayed: failed to update profile! profile: {profile_id}"
+ )
+ return None
+
+ def update_profile_dan(
+ self, profile_id: int, dan_level: int, dan_type: int
+ ) -> Optional[int]:
+ sql = profile.update(profile.c.id == profile_id).values(
+ dan_level=dan_level, dan_type=dan_type
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.warn(
+ f"update_profile_dan: Failed to update! profile {profile_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_profile(self, profile_id: int = 0, aime_id: int = None) -> Optional[Row]:
+ """
+ Given a game version and either a profile or aime id, return the profile
+ """
+ if aime_id is not None:
+ sql = profile.select(profile.c.user == aime_id)
+ elif profile_id > 0:
+ sql = profile.select(profile.c.id == profile_id)
+ else:
+ self.logger.error(
+ f"get_profile: Bad arguments!! profile_id {profile_id} aime_id {aime_id}"
+ )
+ return None
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_options(self, user_id: int, option_id: int = None) -> Optional[List[Row]]:
+ """
+ Get a specific user option for a profile, or all of them if none specified
+ """
+ sql = option.select(
+ and_(
+ option.c.user == user_id,
+ option.c.opt_id == option_id if option_id is not None else True,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ if option_id is not None:
+ return result.fetchone()
+ else:
+ return result.fetchall()
+
+ def update_option(self, user_id: int, option_id: int, value: int) -> Optional[int]:
+ sql = insert(option).values(user=user_id, opt_id=option_id, value=value)
+
+ conflict = sql.on_conflict_do_update(set_=dict(value=value))
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert option! profile: {user_id}, option: {option_id}, value: {value}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def add_favorite_song(self, user_id: int, song_id: int) -> Optional[int]:
+ sql = favorite.insert().values(user=user_id, song_id=song_id)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert favorite! profile: {user_id}, song_id: {song_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def remove_favorite_song(self, user_id: int, song_id: int) -> None:
+ sql = favorite.delete(
+ and_(favorite.c.user == user_id, favorite.c.song_id == song_id)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to remove favorite! profile: {user_id}, song_id: {song_id}"
+ )
+ return None
+
+ def get_favorite_songs(self, user_id: int) -> Optional[List[Row]]:
+ sql = favorite.select(favorite.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_gates(self, user_id: int) -> Optional[List[Row]]:
+ sql = select(gate).where(gate.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def update_gate(
+ self,
+ user_id: int,
+ gate_id: int,
+ page: int,
+ progress: int,
+ loop: int,
+ mission_flag: int,
+ total_points: int,
+ ) -> Optional[int]:
+ sql = insert(gate).values(
+ user=user_id,
+ gate_id=gate_id,
+ page=page,
+ progress=progress,
+ loops=loop,
+ mission_flag=mission_flag,
+ total_points=total_points,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ page=sql.inserted.page,
+ progress=sql.inserted.progress,
+ loops=sql.inserted.loops,
+ mission_flag=sql.inserted.mission_flag,
+ total_points=sql.inserted.total_points,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to update gate! user: {user_id}, gate_id: {gate_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_friends(self, user_id: int) -> Optional[List[Row]]:
+ sql = friend.select(friend.c.profile_sender == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def profile_to_aime_user(self, profile_id: int) -> Optional[int]:
+ sql = select(profile.c.user).where(profile.c.id == profile_id)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.info(
+ f"profile_to_aime_user: No user found for profile {profile_id}"
+ )
+ return None
+
+ this_profile = result.fetchone()
+ if this_profile is None:
+ self.logger.info(
+ f"profile_to_aime_user: No user found for profile {profile_id}"
+ )
+ return None
+
+ return this_profile["user"]
+
+ def session_login(
+ self, profile_id: int, is_new_day: bool, is_consec_day: bool
+ ) -> None:
+ # TODO: Reset consec days counter
+ sql = profile.update(profile.c.id == profile_id).values(
+ login_count=profile.c.login_count + 1,
+ login_count_consec=profile.c.login_count_consec + 1,
+ login_count_days=profile.c.login_count_days + 1
+ if is_new_day
+ else profile.c.login_count_days,
+ login_count_days_consec=profile.c.login_count_days_consec + 1
+ if is_new_day and is_consec_day
+ else profile.c.login_count_days_consec,
+ login_count_today=1 if is_new_day else profile.c.login_count_today + 1,
+ last_login_date=func.now(),
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"session_login: failed to update profile! profile: {profile_id}"
+ )
+ return None
+
+ def session_logout(self, profile_id: int) -> None:
+ sql = profile.update(profile.c.id == id).values(login_count_consec=0)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to update profile! profile: {profile_id}"
+ )
+ return None
+
+ def add_xp(self, profile_id: int, xp: int) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(xp=profile.c.xp + xp)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"add_xp: Failed to update profile! profile_id {profile_id} xp {xp}"
+ )
+ return None
+
+ def add_wp(self, profile_id: int, wp: int) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ wp=profile.c.wp + wp,
+ wp_total=profile.c.wp_total + wp,
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"add_wp: Failed to update profile! profile_id {profile_id} wp {wp}"
+ )
+ return None
+
+ def spend_wp(self, profile_id: int, wp: int) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ wp=profile.c.wp - wp,
+ wp_spent=profile.c.wp_spent + wp,
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"spend_wp: Failed to update profile! profile_id {profile_id} wp {wp}"
+ )
+ return None
+
+ def activate_vip(self, profile_id: int, expire_time) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ vip_expire_time=expire_time
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"activate_vip: Failed to update profile! profile_id {profile_id} expire_time {expire_time}"
+ )
+ return None
+
+ def update_user_rating(self, profile_id: int, new_rating: int) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(rating=new_rating)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"update_user_rating: Failed to update profile! profile_id {profile_id} new_rating {new_rating}"
+ )
+ return None
+
+ def update_bingo(self, aime_id: int, page: int, progress: int) -> Optional[int]:
+ sql = insert(bingo).values(
+ user=aime_id, page_number=page, page_progress=progress
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(page_number=page, page_progress=progress)
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(f"put_bingo: failed to update! aime_id: {aime_id}")
+ return None
+ return result.lastrowid
+
+ def get_bingo(self, aime_id: int) -> Optional[List[Row]]:
+ sql = select(bingo).where(bingo.c.user == aime_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def get_bingo_page(self, aime_id: int, page: Dict) -> Optional[List[Row]]:
+ sql = select(bingo).where(
+ and_(bingo.c.user == aime_id, bingo.c.page_number == page)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
+
+ def update_vip_time(self, profile_id: int, time_left) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ vip_expire_time=time_left
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(f"Failed to update VIP time for profile {profile_id}")
+
+ def update_tutorial_flags(self, profile_id: int, flags: Dict) -> None:
+ sql = profile.update(profile.c.id == profile_id).values(
+ gate_tutorial_flags=flags
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"Failed to update tutorial flags for profile {profile_id}"
+ )
diff --git a/titles/wacca/schema/score.py b/titles/wacca/schema/score.py
index 250740f..de35efd 100644
--- a/titles/wacca/schema/score.py
+++ b/titles/wacca/schema/score.py
@@ -1,327 +1,331 @@
-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.engine import Row
-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
-
-best_score = Table(
- "wacca_score_best",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("song_id", Integer),
- Column("chart_id", Integer),
- Column("score", Integer),
- Column("play_ct", Integer),
- Column("clear_ct", Integer),
- Column("missless_ct", Integer),
- Column("fullcombo_ct", Integer),
- Column("allmarv_ct", Integer),
- Column("grade_d_ct", Integer),
- Column("grade_c_ct", Integer),
- Column("grade_b_ct", Integer),
- Column("grade_a_ct", Integer),
- Column("grade_aa_ct", Integer),
- Column("grade_aaa_ct", Integer),
- Column("grade_s_ct", Integer),
- Column("grade_ss_ct", Integer),
- Column("grade_sss_ct", Integer),
- Column("grade_master_ct", Integer),
- Column("grade_sp_ct", Integer),
- Column("grade_ssp_ct", Integer),
- Column("grade_sssp_ct", Integer),
- Column("best_combo", Integer),
- Column("lowest_miss_ct", Integer),
- Column("rating", Integer),
- UniqueConstraint("user", "song_id", "chart_id", name="wacca_score_uk"),
- mysql_charset="utf8mb4",
-)
-
-playlog = Table(
- "wacca_score_playlog",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("song_id", Integer),
- Column("chart_id", Integer),
- Column("score", Integer),
- Column("clear", Integer),
- Column("grade", Integer),
- Column("max_combo", Integer),
- Column("marv_ct", Integer),
- Column("great_ct", Integer),
- Column("good_ct", Integer),
- Column("miss_ct", Integer),
- Column("fast_ct", Integer),
- Column("late_ct", Integer),
- Column("season", Integer),
- Column("date_scored", TIMESTAMP, server_default=func.now()),
- mysql_charset="utf8mb4",
-)
-
-stageup = Table(
- "wacca_score_stageup",
- metadata,
- Column("id", Integer, primary_key=True, nullable=False),
- Column(
- "user",
- ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
- nullable=False,
- ),
- Column("version", Integer),
- Column("stage_id", Integer),
- Column("clear_status", Integer),
- Column("clear_song_ct", Integer),
- Column("song1_score", Integer),
- Column("song2_score", Integer),
- Column("song3_score", Integer),
- Column("play_ct", Integer, server_default="1"),
- UniqueConstraint("user", "stage_id", name="wacca_score_stageup_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-class WaccaScoreData(BaseData):
- def put_best_score(
- self,
- user_id: int,
- song_id: int,
- chart_id: int,
- score: int,
- clear: List[int],
- grade: List[int],
- best_combo: int,
- lowest_miss_ct: int,
- ) -> Optional[int]:
- """
- Update the user's best score for a chart
- """
- while len(grade) < 13:
- grade.append(0)
-
- sql = insert(best_score).values(
- user=user_id,
- song_id=song_id,
- chart_id=chart_id,
- score=score,
- play_ct=clear[0],
- clear_ct=clear[1],
- missless_ct=clear[2],
- fullcombo_ct=clear[3],
- allmarv_ct=clear[4],
- grade_d_ct=grade[0],
- grade_c_ct=grade[1],
- grade_b_ct=grade[2],
- grade_a_ct=grade[3],
- grade_aa_ct=grade[4],
- grade_aaa_ct=grade[5],
- grade_s_ct=grade[6],
- grade_ss_ct=grade[7],
- grade_sss_ct=grade[8],
- grade_master_ct=grade[9],
- grade_sp_ct=grade[10],
- grade_ssp_ct=grade[11],
- grade_sssp_ct=grade[12],
- best_combo=best_combo,
- lowest_miss_ct=lowest_miss_ct,
- rating=0,
- )
-
- conflict = sql.on_duplicate_key_update(
- score=score,
- play_ct=clear[0],
- clear_ct=clear[1],
- missless_ct=clear[2],
- fullcombo_ct=clear[3],
- allmarv_ct=clear[4],
- grade_d_ct=grade[0],
- grade_c_ct=grade[1],
- grade_b_ct=grade[2],
- grade_a_ct=grade[3],
- grade_aa_ct=grade[4],
- grade_aaa_ct=grade[5],
- grade_s_ct=grade[6],
- grade_ss_ct=grade[7],
- grade_sss_ct=grade[8],
- grade_master_ct=grade[9],
- grade_sp_ct=grade[10],
- grade_ssp_ct=grade[11],
- grade_sssp_ct=grade[12],
- best_combo=best_combo,
- lowest_miss_ct=lowest_miss_ct,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.error(
- f"{__name__}: failed to insert best score! profile: {user_id}, song: {song_id}, chart: {chart_id}"
- )
- return None
-
- return result.lastrowid
-
- def put_playlog(
- self,
- user_id: int,
- song_id: int,
- chart_id: int,
- this_score: int,
- clear: int,
- grade: int,
- max_combo: int,
- marv_ct: int,
- great_ct: int,
- good_ct: int,
- miss_ct: int,
- fast_ct: int,
- late_ct: int,
- season: int,
- ) -> Optional[int]:
- """
- Add an entry to the user's play log
- """
- sql = playlog.insert().values(
- user=user_id,
- song_id=song_id,
- chart_id=chart_id,
- score=this_score,
- clear=clear,
- grade=grade,
- max_combo=max_combo,
- marv_ct=marv_ct,
- great_ct=great_ct,
- good_ct=good_ct,
- miss_ct=miss_ct,
- fast_ct=fast_ct,
- late_ct=late_ct,
- season=season,
- )
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {chart_id}"
- )
- return None
-
- return result.lastrowid
-
- 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 get_best_scores(self, user_id: int) -> Optional[List[Row]]:
- sql = best_score.select(best_score.c.user == user_id)
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def update_song_rating(
- self, user_id: int, song_id: int, chart_id: int, new_rating: int
- ) -> None:
- sql = best_score.update(
- and_(
- best_score.c.user == user_id,
- best_score.c.song_id == song_id,
- best_score.c.chart_id == chart_id,
- )
- ).values(rating=new_rating)
-
- result = self.execute(sql)
- if result is None:
- self.logger.error(
- f"update_song_rating: failed to update rating! user_id: {user_id} song_id: {song_id} chart_id {chart_id} new_rating {new_rating}"
- )
- return None
-
- def put_stageup(
- self,
- user_id: int,
- version: int,
- stage_id: int,
- clear_status: int,
- clear_song_ct: int,
- score1: int,
- score2: int,
- score3: int,
- ) -> Optional[int]:
- sql = insert(stageup).values(
- user=user_id,
- version=version,
- stage_id=stage_id,
- clear_status=clear_status,
- clear_song_ct=clear_song_ct,
- song1_score=score1,
- song2_score=score2,
- song3_score=score3,
- )
-
- conflict = sql.on_duplicate_key_update(
- clear_status=clear_status,
- clear_song_ct=clear_song_ct,
- song1_score=score1,
- song2_score=score2,
- song3_score=score3,
- play_ct=stageup.c.play_ct + 1,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(
- f"put_stageup: failed to update! user_id: {user_id} version: {version} stage_id: {stage_id}"
- )
- return None
- return result.lastrowid
-
- def get_stageup(self, user_id: int, version: int) -> Optional[List[Row]]:
- sql = select(stageup).where(
- and_(stageup.c.user == user_id, stageup.c.version == version)
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchall()
-
- def get_stageup_stage(
- self, user_id: int, version: int, stage_id: int
- ) -> Optional[Row]:
- sql = select(stageup).where(
- and_(
- stageup.c.user == user_id,
- stageup.c.version == version,
- stageup.c.stage_id == stage_id,
- )
- )
-
- result = self.execute(sql)
- if result is None:
- return None
- return result.fetchone()
+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.engine import Row
+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
+
+best_score = Table(
+ "wacca_score_best",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("song_id", Integer),
+ Column("chart_id", Integer),
+ Column("score", Integer),
+ Column("play_ct", Integer),
+ Column("clear_ct", Integer),
+ Column("missless_ct", Integer),
+ Column("fullcombo_ct", Integer),
+ Column("allmarv_ct", Integer),
+ Column("grade_d_ct", Integer),
+ Column("grade_c_ct", Integer),
+ Column("grade_b_ct", Integer),
+ Column("grade_a_ct", Integer),
+ Column("grade_aa_ct", Integer),
+ Column("grade_aaa_ct", Integer),
+ Column("grade_s_ct", Integer),
+ Column("grade_ss_ct", Integer),
+ Column("grade_sss_ct", Integer),
+ Column("grade_master_ct", Integer),
+ Column("grade_sp_ct", Integer),
+ Column("grade_ssp_ct", Integer),
+ Column("grade_sssp_ct", Integer),
+ Column("best_combo", Integer),
+ Column("lowest_miss_ct", Integer),
+ Column("rating", Integer),
+ UniqueConstraint("user", "song_id", "chart_id", name="wacca_score_uk"),
+ mysql_charset="utf8mb4",
+)
+
+playlog = Table(
+ "wacca_score_playlog",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("song_id", Integer),
+ Column("chart_id", Integer),
+ Column("score", Integer),
+ Column("clear", Integer),
+ Column("grade", Integer),
+ Column("max_combo", Integer),
+ Column("marv_ct", Integer),
+ Column("great_ct", Integer),
+ Column("good_ct", Integer),
+ Column("miss_ct", Integer),
+ Column("fast_ct", Integer),
+ Column("late_ct", Integer),
+ Column("season", Integer),
+ Column("date_scored", TIMESTAMP, server_default=func.now()),
+ mysql_charset="utf8mb4",
+)
+
+stageup = Table(
+ "wacca_score_stageup",
+ metadata,
+ Column("id", Integer, primary_key=True, nullable=False),
+ Column(
+ "user",
+ ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade"),
+ nullable=False,
+ ),
+ Column("version", Integer),
+ Column("stage_id", Integer),
+ Column("clear_status", Integer),
+ Column("clear_song_ct", Integer),
+ Column("song1_score", Integer),
+ Column("song2_score", Integer),
+ Column("song3_score", Integer),
+ Column("play_ct", Integer, server_default="1"),
+ UniqueConstraint("user", "stage_id", name="wacca_score_stageup_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class WaccaScoreData(BaseData):
+ def put_best_score(
+ self,
+ user_id: int,
+ song_id: int,
+ chart_id: int,
+ score: int,
+ clear: List[int],
+ grade: List[int],
+ best_combo: int,
+ lowest_miss_ct: int,
+ ) -> Optional[int]:
+ """
+ Update the user's best score for a chart
+ """
+ while len(grade) < 13:
+ grade.append(0)
+
+ sql = insert(best_score).values(
+ user=user_id,
+ song_id=song_id,
+ chart_id=chart_id,
+ score=score,
+ play_ct=clear[0],
+ clear_ct=clear[1],
+ missless_ct=clear[2],
+ fullcombo_ct=clear[3],
+ allmarv_ct=clear[4],
+ grade_d_ct=grade[0],
+ grade_c_ct=grade[1],
+ grade_b_ct=grade[2],
+ grade_a_ct=grade[3],
+ grade_aa_ct=grade[4],
+ grade_aaa_ct=grade[5],
+ grade_s_ct=grade[6],
+ grade_ss_ct=grade[7],
+ grade_sss_ct=grade[8],
+ grade_master_ct=grade[9],
+ grade_sp_ct=grade[10],
+ grade_ssp_ct=grade[11],
+ grade_sssp_ct=grade[12],
+ best_combo=best_combo,
+ lowest_miss_ct=lowest_miss_ct,
+ rating=0,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ score=score,
+ play_ct=clear[0],
+ clear_ct=clear[1],
+ missless_ct=clear[2],
+ fullcombo_ct=clear[3],
+ allmarv_ct=clear[4],
+ grade_d_ct=grade[0],
+ grade_c_ct=grade[1],
+ grade_b_ct=grade[2],
+ grade_a_ct=grade[3],
+ grade_aa_ct=grade[4],
+ grade_aaa_ct=grade[5],
+ grade_s_ct=grade[6],
+ grade_ss_ct=grade[7],
+ grade_sss_ct=grade[8],
+ grade_master_ct=grade[9],
+ grade_sp_ct=grade[10],
+ grade_ssp_ct=grade[11],
+ grade_sssp_ct=grade[12],
+ best_combo=best_combo,
+ lowest_miss_ct=lowest_miss_ct,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.error(
+ f"{__name__}: failed to insert best score! profile: {user_id}, song: {song_id}, chart: {chart_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ def put_playlog(
+ self,
+ user_id: int,
+ song_id: int,
+ chart_id: int,
+ this_score: int,
+ clear: int,
+ grade: int,
+ max_combo: int,
+ marv_ct: int,
+ great_ct: int,
+ good_ct: int,
+ miss_ct: int,
+ fast_ct: int,
+ late_ct: int,
+ season: int,
+ ) -> Optional[int]:
+ """
+ Add an entry to the user's play log
+ """
+ sql = playlog.insert().values(
+ user=user_id,
+ song_id=song_id,
+ chart_id=chart_id,
+ score=this_score,
+ clear=clear,
+ grade=grade,
+ max_combo=max_combo,
+ marv_ct=marv_ct,
+ great_ct=great_ct,
+ good_ct=good_ct,
+ miss_ct=miss_ct,
+ fast_ct=fast_ct,
+ late_ct=late_ct,
+ season=season,
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"{__name__} failed to insert playlog! profile: {user_id}, song: {song_id}, chart: {chart_id}"
+ )
+ return None
+
+ return result.lastrowid
+
+ 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 get_best_scores(self, user_id: int) -> Optional[List[Row]]:
+ sql = best_score.select(best_score.c.user == user_id)
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def update_song_rating(
+ self, user_id: int, song_id: int, chart_id: int, new_rating: int
+ ) -> None:
+ sql = best_score.update(
+ and_(
+ best_score.c.user == user_id,
+ best_score.c.song_id == song_id,
+ best_score.c.chart_id == chart_id,
+ )
+ ).values(rating=new_rating)
+
+ result = self.execute(sql)
+ if result is None:
+ self.logger.error(
+ f"update_song_rating: failed to update rating! user_id: {user_id} song_id: {song_id} chart_id {chart_id} new_rating {new_rating}"
+ )
+ return None
+
+ def put_stageup(
+ self,
+ user_id: int,
+ version: int,
+ stage_id: int,
+ clear_status: int,
+ clear_song_ct: int,
+ score1: int,
+ score2: int,
+ score3: int,
+ ) -> Optional[int]:
+ sql = insert(stageup).values(
+ user=user_id,
+ version=version,
+ stage_id=stage_id,
+ clear_status=clear_status,
+ clear_song_ct=clear_song_ct,
+ song1_score=score1,
+ song2_score=score2,
+ song3_score=score3,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ clear_status=clear_status,
+ clear_song_ct=clear_song_ct,
+ song1_score=score1,
+ song2_score=score2,
+ song3_score=score3,
+ play_ct=stageup.c.play_ct + 1,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(
+ f"put_stageup: failed to update! user_id: {user_id} version: {version} stage_id: {stage_id}"
+ )
+ return None
+ return result.lastrowid
+
+ def get_stageup(self, user_id: int, version: int) -> Optional[List[Row]]:
+ sql = select(stageup).where(
+ and_(stageup.c.user == user_id, stageup.c.version == version)
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchall()
+
+ def get_stageup_stage(
+ self, user_id: int, version: int, stage_id: int
+ ) -> Optional[Row]:
+ sql = select(stageup).where(
+ and_(
+ stageup.c.user == user_id,
+ stageup.c.version == version,
+ stageup.c.stage_id == stage_id,
+ )
+ )
+
+ result = self.execute(sql)
+ if result is None:
+ return None
+ return result.fetchone()
diff --git a/titles/wacca/schema/static.py b/titles/wacca/schema/static.py
index d09a440..69b80d2 100644
--- a/titles/wacca/schema/static.py
+++ b/titles/wacca/schema/static.py
@@ -1,84 +1,86 @@
-from sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
-from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean, Float
-from sqlalchemy.schema import ForeignKey
-from sqlalchemy.sql import func, select
-from sqlalchemy.engine import Row
-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
-
-music = Table(
- "wacca_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("bpm", String(255)),
- Column("difficulty", Float),
- Column("chartDesigner", String(255)),
- Column("jacketFile", String(255)),
- UniqueConstraint("version", "songId", "chartId", name="wacca_static_music_uk"),
- mysql_charset="utf8mb4",
-)
-
-
-class WaccaStaticData(BaseData):
- def put_music(
- self,
- version: int,
- song_id: int,
- chart_id: int,
- title: str,
- artist: str,
- bpm: str,
- difficulty: float,
- chart_designer: str,
- jacket: str,
- ) -> Optional[int]:
- sql = insert(music).values(
- version=version,
- songId=song_id,
- chartId=chart_id,
- title=title,
- artist=artist,
- bpm=bpm,
- difficulty=difficulty,
- chartDesigner=chart_designer,
- jacketFile=jacket,
- )
-
- conflict = sql.on_duplicate_key_update(
- title=title,
- artist=artist,
- bpm=bpm,
- difficulty=difficulty,
- chartDesigner=chart_designer,
- jacketFile=jacket,
- )
-
- result = self.execute(conflict)
- if result is None:
- self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}")
- return None
- return result.lastrowid
-
- 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 sqlalchemy import Table, Column, UniqueConstraint, PrimaryKeyConstraint, and_
+from sqlalchemy.types import Integer, String, TIMESTAMP, JSON, Boolean, Float
+from sqlalchemy.schema import ForeignKey
+from sqlalchemy.sql import func, select
+from sqlalchemy.engine import Row
+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
+
+music = Table(
+ "wacca_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("bpm", String(255)),
+ Column("difficulty", Float),
+ Column("chartDesigner", String(255)),
+ Column("jacketFile", String(255)),
+ UniqueConstraint("version", "songId", "chartId", name="wacca_static_music_uk"),
+ mysql_charset="utf8mb4",
+)
+
+
+class WaccaStaticData(BaseData):
+ def put_music(
+ self,
+ version: int,
+ song_id: int,
+ chart_id: int,
+ title: str,
+ artist: str,
+ bpm: str,
+ difficulty: float,
+ chart_designer: str,
+ jacket: str,
+ ) -> Optional[int]:
+ sql = insert(music).values(
+ version=version,
+ songId=song_id,
+ chartId=chart_id,
+ title=title,
+ artist=artist,
+ bpm=bpm,
+ difficulty=difficulty,
+ chartDesigner=chart_designer,
+ jacketFile=jacket,
+ )
+
+ conflict = sql.on_conflict_do_update(
+ set_=dict(
+ title=title,
+ artist=artist,
+ bpm=bpm,
+ difficulty=difficulty,
+ chartDesigner=chart_designer,
+ jacketFile=jacket,
+ )
+ )
+
+ result = self.execute(conflict)
+ if result is None:
+ self.logger.warn(f"Failed to insert music {song_id} chart {chart_id}")
+ return None
+ return result.lastrowid
+
+ 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()