From c99bfda01545d9dabc5425e67a7abf5ca5877459 Mon Sep 17 00:00:00 2001 From: Dniel97 Date: Sat, 18 Feb 2023 21:01:31 +0100 Subject: [PATCH] diva: added all previous commits, added username and password change - Changed `update_profile()` function to allow a single Dict instead of multiple values - Added `passwd*` Columns to profile table and added corresponding update/rollback sql scripts - Added `handle_card_procedure_request()`, `handle_change_name_request ()` and `handle_change_passwd_request()` functions to DivaBase --- core/data/schema/versions/SBZV_1_rollback.sql | 9 + core/data/schema/versions/SBZV_2_rollback.sql | 17 + core/data/schema/versions/SBZV_2_upgrade.sql | 9 + core/data/schema/versions/SBZV_3_rollback.sql | 2 + core/data/schema/versions/SBZV_3_upgrade.sql | 33 ++ core/data/schema/versions/SBZV_4_upgrade.sql | 2 + example_config/diva.yaml | 3 + titles/diva/base.py | 395 ++++++++++++++---- titles/diva/config.py | 22 +- titles/diva/database.py | 6 +- titles/diva/schema/__init__.py | 7 +- titles/diva/schema/customize.py | 63 +++ titles/diva/schema/item.py | 24 +- titles/diva/schema/module.py | 63 +++ titles/diva/schema/profile.py | 102 +++-- titles/diva/schema/pv_customize.py | 69 +++ titles/diva/schema/score.py | 146 +++++-- titles/diva/schema/static.py | 42 +- 18 files changed, 824 insertions(+), 190 deletions(-) create mode 100644 core/data/schema/versions/SBZV_1_rollback.sql create mode 100644 core/data/schema/versions/SBZV_2_rollback.sql create mode 100644 core/data/schema/versions/SBZV_2_upgrade.sql create mode 100644 core/data/schema/versions/SBZV_3_rollback.sql create mode 100644 core/data/schema/versions/SBZV_3_upgrade.sql create mode 100644 core/data/schema/versions/SBZV_4_upgrade.sql create mode 100644 titles/diva/schema/customize.py create mode 100644 titles/diva/schema/module.py create mode 100644 titles/diva/schema/pv_customize.py diff --git a/core/data/schema/versions/SBZV_1_rollback.sql b/core/data/schema/versions/SBZV_1_rollback.sql new file mode 100644 index 0000000..a7bccce --- /dev/null +++ b/core/data/schema/versions/SBZV_1_rollback.sql @@ -0,0 +1,9 @@ +SET FOREIGN_KEY_CHECKS=0; +ALTER TABLE diva_score DROP COLUMN edition; +ALTER TABLE diva_playlog DROP COLUMN edition; + +ALTER TABLE diva_score DROP FOREIGN KEY diva_score_ibfk_1; +ALTER TABLE diva_score DROP CONSTRAINT diva_score_uk; +ALTER TABLE diva_score ADD CONSTRAINT diva_score_uk UNIQUE (user, pv_id, difficulty); +ALTER TABLE diva_score ADD CONSTRAINT diva_score_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE; +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file diff --git a/core/data/schema/versions/SBZV_2_rollback.sql b/core/data/schema/versions/SBZV_2_rollback.sql new file mode 100644 index 0000000..5bd46c0 --- /dev/null +++ b/core/data/schema/versions/SBZV_2_rollback.sql @@ -0,0 +1,17 @@ +ALTER TABLE diva_profile_shop DROP COLUMN c_itm_eqp_ary; +ALTER TABLE diva_profile_shop DROP COLUMN ms_itm_flg_ary; + +ALTER TABLE diva_profile DROP COLUMN use_pv_mdl_eqp; +ALTER TABLE diva_profile DROP COLUMN use_mdl_pri; +ALTER TABLE diva_profile DROP COLUMN use_pv_skn_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_btn_se_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_sld_se_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_chn_sld_se_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_sldr_tch_se_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_mdl_eqp VARCHAR(8) NOT NULL DEFAULT "true" AFTER sort_kind; +ALTER TABLE diva_profile ADD COLUMN use_pv_btn_se_eqp VARCHAR(8) NOT NULL DEFAULT "true" AFTER use_pv_mdl_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_sld_se_eqp VARCHAR(8) NOT NULL DEFAULT "false" AFTER use_pv_btn_se_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_chn_sld_se_eqp VARCHAR(8) NOT NULL DEFAULT "false" AFTER use_pv_sld_se_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_sldr_tch_se_eqp VARCHAR(8) NOT NULL DEFAULT "false" AFTER use_pv_chn_sld_se_eqp; + +DROP TABLE IF EXISTS `diva_profile_pv_customize`; \ No newline at end of file diff --git a/core/data/schema/versions/SBZV_2_upgrade.sql b/core/data/schema/versions/SBZV_2_upgrade.sql new file mode 100644 index 0000000..26cb2ed --- /dev/null +++ b/core/data/schema/versions/SBZV_2_upgrade.sql @@ -0,0 +1,9 @@ +SET FOREIGN_KEY_CHECKS=0; +ALTER TABLE diva_score ADD COLUMN edition int(11) DEFAULT 0 AFTER difficulty; +ALTER TABLE diva_playlog ADD COLUMN edition int(11) DEFAULT 0 AFTER difficulty; + +ALTER TABLE diva_score DROP FOREIGN KEY diva_score_ibfk_1; +ALTER TABLE diva_score DROP CONSTRAINT diva_score_uk; +ALTER TABLE diva_score ADD CONSTRAINT diva_score_uk UNIQUE (user, pv_id, difficulty, edition); +ALTER TABLE diva_score ADD CONSTRAINT diva_score_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user(id) ON DELETE CASCADE; +SET FOREIGN_KEY_CHECKS=1; \ No newline at end of file diff --git a/core/data/schema/versions/SBZV_3_rollback.sql b/core/data/schema/versions/SBZV_3_rollback.sql new file mode 100644 index 0000000..00d95e1 --- /dev/null +++ b/core/data/schema/versions/SBZV_3_rollback.sql @@ -0,0 +1,2 @@ +ALTER TABLE diva_profile DROP COLUMN passwd_stat; +ALTER TABLE diva_profile DROP COLUMN passwd; diff --git a/core/data/schema/versions/SBZV_3_upgrade.sql b/core/data/schema/versions/SBZV_3_upgrade.sql new file mode 100644 index 0000000..a06d7f4 --- /dev/null +++ b/core/data/schema/versions/SBZV_3_upgrade.sql @@ -0,0 +1,33 @@ +ALTER TABLE diva_profile_shop ADD COLUMN c_itm_eqp_ary varchar(59) DEFAULT "-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999"; +ALTER TABLE diva_profile_shop ADD COLUMN ms_itm_flg_ary varchar(59) DEFAULT "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1"; + +ALTER TABLE diva_profile DROP COLUMN use_pv_mdl_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_btn_se_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_sld_se_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_chn_sld_se_eqp; +ALTER TABLE diva_profile DROP COLUMN use_pv_sldr_tch_se_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_mdl_eqp BOOLEAN NOT NULL DEFAULT true AFTER sort_kind; +ALTER TABLE diva_profile ADD COLUMN use_mdl_pri BOOLEAN NOT NULL DEFAULT false AFTER use_pv_mdl_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_skn_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_mdl_pri; +ALTER TABLE diva_profile ADD COLUMN use_pv_btn_se_eqp BOOLEAN NOT NULL DEFAULT true AFTER use_pv_skn_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_sld_se_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_pv_btn_se_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_chn_sld_se_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_pv_sld_se_eqp; +ALTER TABLE diva_profile ADD COLUMN use_pv_sldr_tch_se_eqp BOOLEAN NOT NULL DEFAULT false AFTER use_pv_chn_sld_se_eqp; + + +CREATE TABLE diva_profile_pv_customize ( + id INT PRIMARY KEY NOT NULL AUTO_INCREMENT, + user INT NOT NULL, + version INT NOT NULL, + pv_id INT NOT NULL, + mdl_eqp_ary VARCHAR(14) DEFAULT '-999,-999,-999', + c_itm_eqp_ary VARCHAR(59) DEFAULT '-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999', + ms_itm_flg_ary VARCHAR(59) DEFAULT '-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1', + skin INT DEFAULT '-1', + btn_se INT DEFAULT '-1', + sld_se INT DEFAULT '-1', + chsld_se INT DEFAULT '-1', + sldtch_se INT DEFAULT '-1', + UNIQUE KEY diva_profile_pv_customize_uk (user, version, pv_id), + CONSTRAINT diva_profile_pv_customize_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE +); diff --git a/core/data/schema/versions/SBZV_4_upgrade.sql b/core/data/schema/versions/SBZV_4_upgrade.sql new file mode 100644 index 0000000..d3aacb6 --- /dev/null +++ b/core/data/schema/versions/SBZV_4_upgrade.sql @@ -0,0 +1,2 @@ +ALTER TABLE diva_profile ADD COLUMN passwd_stat INTEGER NOT NULL DEFAULT 0; +ALTER TABLE diva_profile ADD COLUMN passwd VARCHAR(12) NOT NULL DEFAULT "**********"; diff --git a/example_config/diva.yaml b/example_config/diva.yaml index 1354910..ad1842a 100644 --- a/example_config/diva.yaml +++ b/example_config/diva.yaml @@ -2,3 +2,6 @@ server: enable: True loglevel: "info" +mods: + unlock_all_modules: True + unlock_all_items: True diff --git a/titles/diva/base.py b/titles/diva/base.py index aef5841..7ad5793 100644 --- a/titles/diva/base.py +++ b/titles/diva/base.py @@ -127,32 +127,54 @@ class DivaBase(): def handle_shop_catalog_request(self, data: Dict) -> Dict: catalog = "" - shopList = self.data.static.get_enabled_shop(self.version) + shopList = self.data.static.get_enabled_shops(self.version) if not shopList: with open(r"titles/diva/data/ShopCatalog.dat", encoding="utf-8") as shop: lines = shop.readlines() for line in lines: line = urllib.parse.quote(line) + "," catalog += f"{urllib.parse.quote(line)}" - catalog = catalog.replace("+", "%20") - response = "" - response += f"&shp_ctlg_lut={self.time_lut}" - response += f"&shp_ctlg={catalog[:-3]}" else: for shop in shopList: line = str(shop["shopId"]) + "," + str(shop['unknown_0']) + "," + shop['name'] + "," + str(shop['points']) + "," + shop['start_date'] + "," + shop['end_date'] + "," + str(shop["type"]) line = urllib.parse.quote(line) + "," catalog += f"{urllib.parse.quote(line)}" - catalog = catalog.replace("+", "%20") - - response = "" - response += f"&shp_ctlg_lut={self.time_lut}" - response += f"&shp_ctlg={catalog[:-3]}" + catalog = catalog.replace("+", "%20") + + response = f"&shp_ctlg_lut={self.time_lut}" + response += f"&shp_ctlg={catalog[:-3]}" return ( response ) + def handle_buy_module_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + module = self.data.static.get_enabled_shop(self.version, int(data["mdl_id"])) + + # make sure module is available to purchase + if not module: + return f"&shp_rslt=0&vcld_pts={profile['vcld_pts']}" + + # make sure player has enough vocaloid points to buy module + if profile["vcld_pts"] < int(data["mdl_price"]): + return f"&shp_rslt=0&vcld_pts={profile['vcld_pts']}" + + new_vcld_pts = profile["vcld_pts"] - int(data["mdl_price"]) + + self.data.profile.update_profile(data["pd_id"], profile["lv_num"], profile["lv_pnt"], new_vcld_pts, profile["hp_vol"], profile["btn_se_vol"], profile["btn_se_vol2"], profile["sldr_se_vol2"], profile["sort_kind"], profile["use_pv_mdl_eqp"], profile["use_mdl_pri"], profile["use_pv_skn_eqp"], profile["use_pv_btn_se_eqp"], profile["use_pv_sld_se_eqp"], profile["use_pv_chn_sld_se_eqp"], profile["use_pv_sldr_tch_se_eqp"], profile["nxt_pv_id"], profile["nxt_dffclty"], profile["nxt_edtn"], profile["dsp_clr_brdr"], profile["dsp_intrm_rnk"], profile["dsp_clr_sts"], profile["rgo_sts"], profile["lv_efct_id"], profile["lv_plt_id"], profile["my_qst_id"], profile["my_qst_sts"]) + self.data.module.put_module(data["pd_id"], self.version, data["mdl_id"]) + + # generate the mdl_have string + mdl_have = self.data.module.get_modules_have_string(data["pd_id"], self.version) + + response = "&shp_rslt=1" + response += f"&mdl_id={data['mdl_id']}" + response += f"&mdl_have={mdl_have}" + response += f"&vcld_pts={new_vcld_pts}" + + return response + def handle_cstmz_itm_ctlg_request(self, data: Dict) -> Dict: catalog = "" @@ -163,25 +185,51 @@ class DivaBase(): for line in lines: line = urllib.parse.quote(line) + "," catalog += f"{urllib.parse.quote(line)}" - catalog = catalog.replace("+", "%20") - response = "" - response += f"&cstmz_itm_ctlg_lut={self.time_lut}" - response += f"&cstmz_itm_ctlg={catalog[:-3]}" else: for item in itemList: line = str(item["itemId"]) + "," + str(item['unknown_0']) + "," + item['name'] + "," + str(item['points']) + "," + item['start_date'] + "," + item['end_date'] + "," + str(item["type"]) line = urllib.parse.quote(line) + "," catalog += f"{urllib.parse.quote(line)}" - catalog = catalog.replace("+", "%20") + catalog = catalog.replace("+", "%20") - response = "" - response += f"&cstmz_itm_ctlg_lut={self.time_lut}" - response += f"&cstmz_itm_ctlg={catalog[:-3]}" + response = f"&cstmz_itm_ctlg_lut={self.time_lut}" + response += f"&cstmz_itm_ctlg={catalog[:-3]}" return ( response ) + def handle_buy_cstmz_itm_request(self, data: Dict) -> Dict: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + item = self.data.static.get_enabled_item(self.version, int(data["cstmz_itm_id"])) + + # make sure module is available to purchase + if not item: + return f"&shp_rslt=0&vcld_pts={profile['vcld_pts']}" + + # make sure player has enough vocaloid points to buy the customize item + if profile["vcld_pts"] < int(data["cstmz_itm_price"]): + return f"&shp_rslt=0&vcld_pts={profile['vcld_pts']}" + + new_vcld_pts = profile["vcld_pts"] - int(data["cstmz_itm_price"]) + + # save new Vocaloid Points balance + profile = dict(profile) + profile["vcld_pts"] = new_vcld_pts + self.data.profile.update_profile(profile) + + self.data.customize.put_customize_item(data["pd_id"], self.version, data["cstmz_itm_id"]) + + # generate the cstmz_itm_have string + cstmz_itm_have = self.data.customize.get_customize_items_have_string(data["pd_id"], self.version) + + response = "&shp_rslt=1" + response += f"&cstmz_itm_id={data['cstmz_itm_id']}" + response += f"&cstmz_itm_have={cstmz_itm_have}" + response += f"&vcld_pts={new_vcld_pts}" + + return response + def handle_festa_info_request(self, data: Dict) -> Dict: encoded = "&" params = { @@ -191,7 +239,7 @@ class DivaBase(): 'fi_difficulty': '-1,-1', 'fi_pv_id_lst': 'ALL,ALL', 'fi_attr': '7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF', - 'fi_add_vp': '10,0', + 'fi_add_vp': '20,0', 'fi_mul_vp': '1,1', 'fi_st': '2022-06-17 17:00:00.0,2014-07-08 18:10:11.0', 'fi_et': '2029-01-01 10:00:00.0,2014-07-08 18:10:11.0', @@ -278,48 +326,68 @@ class DivaBase(): def handle_pstd_item_ng_lst_request(self, data: Dict) -> Dict: return ( f'' ) - def handle_pre_start_request(self, data: Dict) -> Dict: + def handle_pre_start_request(self, data: Dict) -> str: profile = self.data.profile.get_profile(data["aime_id"], self.version) profile_shop = self.data.item.get_shop(data["aime_id"], self.version) + if profile is None: - return ( f"&ps_result=-3") + return f"&ps_result=-3" else: - response = "" - response += "&ps_result=1" - response += f"&pd_id={data['aime_id']}" + response = "&ps_result=1" + response += "&accept_idx=100" response += "&nblss_ltt_stts=-1" response += "&nblss_ltt_tckt=-1" response += "&nblss_ltt_is_opn=-1" - response += f"&vcld_pts={profile['vcld_pts']}" + response += f"&pd_id={data['aime_id']}" response += f"&player_name={profile['player_name']}" + response += f"&sort_kind={profile['player_name']}" response += f"&lv_efct_id={profile['lv_efct_id']}" response += f"&lv_plt_id={profile['lv_plt_id']}" response += f"&lv_str={profile['lv_str']}" response += f"&lv_num={profile['lv_num']}" response += f"&lv_pnt={profile['lv_pnt']}" + response += f"&vcld_pts={profile['vcld_pts']}" + response += f"&skn_eqp={profile['use_pv_skn_eqp']}" + response += f"&btn_se_eqp={profile['use_pv_btn_se_eqp']}" + response += f"&sld_se_eqp={profile['use_pv_sld_se_eqp']}" + response += f"&chn_sld_se_eqp={profile['use_pv_chn_sld_se_eqp']}" + response += f"&sldr_tch_se_eqp={profile['use_pv_sldr_tch_se_eqp']}" + response += f"&passwd_stat={profile['passwd_stat']}" - #Store stuff to add to rework + # Store stuff to add to rework response += f"&mdl_eqp_tm={self.time_lut}" + mdl_eqp_ary = "-999,-999,-999" + + # get the common_modules from the profile shop if profile_shop: - response += f"&mdl_eqp_ary={profile_shop['mdl_eqp_ary']}" + mdl_eqp_ary = profile_shop["mdl_eqp_ary"] - response += f"&c_itm_eqp_ary=-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999" - response += f"&ms_itm_flg_ary=1,1,1,1,1,1,1,1,1,1,1,1" + response += f"&mdl_eqp_ary={mdl_eqp_ary}" - return ( response ) - - def handle_registration_request(self, data: Dict) -> Dict: #DONE + return response + + def handle_registration_request(self, data: Dict) -> Dict: self.data.profile.create_profile(self.version, data["aime_id"], data["player_name"]) - return ( f"&cd_adm_result=1&pd_id={data['aime_id']}") - + return (f"&cd_adm_result=1&pd_id={data['aime_id']}") + def handle_start_request(self, data: Dict) -> Dict: profile = self.data.profile.get_profile(data["pd_id"], self.version) profile_shop = self.data.item.get_shop(data["pd_id"], self.version) - if profile is None: return - - response = "" - response += f"&pd_id={data['pd_id']}" + if profile is None: + return + + mdl_have = "F" * 250 + # generate the mdl_have string if "unlock_all_modules" is disabled + if not self.game_config.mods.unlock_all_modules: + mdl_have = self.data.module.get_modules_have_string(data["pd_id"], self.version) + + cstmz_itm_have = "F" * 250 + # generate the cstmz_itm_have string if "unlock_all_items" is disabled + if not self.game_config.mods.unlock_all_items: + cstmz_itm_have = self.data.customize.get_customize_items_have_string(data["pd_id"], self.version) + + response = f"&pd_id={data['pd_id']}" response += "&start_result=1" response += "&accept_idx=100" @@ -333,13 +401,15 @@ class DivaBase(): response += f"&lv_pnt={profile['lv_pnt']}" response += f"&lv_efct_id={profile['lv_efct_id']}" response += f"&lv_plt_id={profile['lv_plt_id']}" - response += "&mdl_have=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - response += "&cstmz_itm_have=FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" - response += f"&use_pv_mdl_eqp={profile['use_pv_mdl_eqp']}" - response += f"&use_pv_btn_se_eqp={profile['use_pv_btn_se_eqp']}" - response += f"&use_pv_sld_se_eqp={profile['use_pv_sld_se_eqp']}" - response += f"&use_pv_chn_sld_se_eqp={profile['use_pv_chn_sld_se_eqp']}" - response += f"&use_pv_sldr_tch_se_eqp={profile['use_pv_sldr_tch_se_eqp']}" + response += f"&mdl_have={mdl_have}" + response += f"&cstmz_itm_have={cstmz_itm_have}" + response += f"&use_pv_mdl_eqp={int(profile['use_pv_mdl_eqp'])}" + response += f"&use_mdl_pri={int(profile['use_mdl_pri'])}" + response += f"&use_pv_skn_eqp={int(profile['use_pv_skn_eqp'])}" + response += f"&use_pv_btn_se_eqp={int(profile['use_pv_btn_se_eqp'])}" + response += f"&use_pv_sld_se_eqp={int(profile['use_pv_sld_se_eqp'])}" + response += f"&use_pv_chn_sld_se_eqp={int(profile['use_pv_chn_sld_se_eqp'])}" + response += f"&use_pv_sldr_tch_se_eqp={int(profile['use_pv_sldr_tch_se_eqp'])}" response += f"&vcld_pts={profile['lv_efct_id']}" response += f"&nxt_pv_id={profile['nxt_pv_id']}" response += f"&nxt_dffclty={profile['nxt_dffclty']}" @@ -349,7 +419,7 @@ class DivaBase(): response += f"&dsp_clr_sts={profile['dsp_clr_sts']}" response += f"&rgo_sts={profile['rgo_sts']}" - #To be fully fixed + # To be fully fixed if "my_qst_id" not in profile: response += f"&my_qst_id=-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" response += f"&my_qst_sts=0,0,0,0,0,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" @@ -361,14 +431,23 @@ class DivaBase(): response += f"&my_qst_et=2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2022-06-19%2010%3A28%3A52.0,2100-01-01%2008%3A59%3A59.0,2100-01-01%2008%3A59%3A59.0,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx,xxx" response += f"&clr_sts=0,0,0,0,0,0,0,0,56,52,35,6,6,3,1,0,0,0,0,0" - #Store stuff to add to rework + # Store stuff to add to rework response += f"&mdl_eqp_tm={self.time_lut}" - if profile_shop: - response += f"&mdl_eqp_ary={profile_shop['mdl_eqp_ary']}" + mdl_eqp_ary = "-999,-999,-999" + c_itm_eqp_ary = "-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999" + ms_itm_flg_ary = "1,1,1,1,1,1,1,1,1,1,1,1" - response += f"&c_itm_eqp_ary=-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999" - response += f"&ms_itm_flg_ary=1,1,1,1,1,1,1,1,1,1,1,1" + # get the common_modules, customize_items and customize_item_flags + # from the profile shop + if profile_shop: + mdl_eqp_ary = profile_shop["mdl_eqp_ary"] + c_itm_eqp_ary = profile_shop["c_itm_eqp_ary"] + ms_itm_flg_ary = profile_shop["ms_itm_flg_ary"] + + response += f"&mdl_eqp_ary={mdl_eqp_ary}" + response += f"&c_itm_eqp_ary={c_itm_eqp_ary}" + response += f"&ms_itm_flg_ary={ms_itm_flg_ary}" return ( response ) @@ -390,22 +469,89 @@ class DivaBase(): return ( response ) + def _get_pv_pd_result(self, song: int, pd_db_song: Dict, pd_db_ranking: Dict, + pd_db_customize: Dict, edition: int) -> str: + """ + Helper function to generate the pv_result string for every song, ranking and edition + """ + global_ranking = -1 + if pd_db_ranking: + # make sure there are enough max scores to calculate a ranking + if pd_db_ranking["ranking"] != 0: + global_ranking = pd_db_ranking["ranking"] + + # pv_no + pv_result = f"{song}," + # edition + pv_result += f"{edition}," + # rslt + pv_result += f"{pd_db_song['clr_kind']}," if pd_db_song else "-1," + # max_score + pv_result += f"{pd_db_song['score']}," if pd_db_song else "-1," + # max_atn_pnt + pv_result += f"{pd_db_song['atn_pnt']}," if pd_db_song else "-1," + # challenge_kind + pv_result += f"{pd_db_song['sort_kind']}," if pd_db_song else "0," + + module_eqp = "-999,-999,-999" + customize_eqp = "-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999" + customize_flag = "-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1" + # skin, btn_se, sld_se, chsld_se, sldtch_se + pv_settings = "-1,-1,-1,-1,-1" + if pd_db_customize: + module_eqp = pd_db_customize["mdl_eqp_ary"] + customize_eqp = pd_db_customize["c_itm_eqp_ary"] + customize_flag = pd_db_customize["ms_itm_flg_ary"] + pv_settings = ( + f"{pd_db_customize['skin']}," + f"{pd_db_customize['btn_se']}," + f"{pd_db_customize['sld_se']}," + f"{pd_db_customize['chsld_se']}," + f"{pd_db_customize['sldtch_se']}" + ) + + pv_result += f"{module_eqp}," + pv_result += f"{customize_eqp}," + pv_result += f"{customize_flag}," + pv_result += f"{pv_settings}," + # rvl_pd_id, rvl_score, rvl_attn_pnt, -1, -1 + pv_result += "-1,-1,-1,-1,-1," + # countrywide_ranking + pv_result += f"{global_ranking}," + # rgo_purchased + pv_result += "1,1,1," + # rgo_played + pv_result += "0,0,0" + + return pv_result + def handle_get_pv_pd_request(self, data: Dict) -> Dict: song_id = data["pd_pv_id_lst"].split(",") pv = "" for song in song_id: if int(song) > 0: - pd_db_song = self.data.score.get_best_score(data["pd_id"], int(song), data["difficulty"]) - if pd_db_song is not None: - - pv += urllib.parse.quote(f"{song},0,{pd_db_song['clr_kind']},{pd_db_song['score']},{pd_db_song['atn_pnt']},{pd_db_song['sort_kind']},-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,1,1,1,1,1,1,1,1,1,1,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,1337,1,1,1,0,0,0") - - else: - #self.logger.debug(f"No score saved for ID: {song}!") - pv += urllib.parse.quote(f"{song},0,-1,-1,-1,0,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,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,1,0,0,0") + # the request do not send a edition so just perform a query best score and ranking for each edition. + # 0=ORIGINAL, 1=EXTRA + pd_db_song_0 = self.data.score.get_best_user_score(data["pd_id"], int(song), data["difficulty"], edition=0) + pd_db_song_1 = self.data.score.get_best_user_score(data["pd_id"], int(song), data["difficulty"], edition=1) - #pv_no, edition, rslt, max_score, max_atn_pnt, challenge_kind, module_eqp[-999,-999,-999], customize_eqp[-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999,-999], customize_flag[1,1,1,1,1,1,1,1,1,1,1,1], skin, btn_se, sld_se, chsld_se, sldtch_se, rvl_pd_id, rvl_score, rvl_attn_pnt, countrywide_ranking, rgo_hispeed, rgo_hidden, rgo_sudden, rgo_hispeed_cleared, rgo_hidden_cleared, rgo_sudden_cleared, chain_challenge_num, chain_challenge_max, chain_challenge_open, version + pd_db_ranking_0, pd_db_ranking_1 = None, None + if pd_db_song_0: + pd_db_ranking_0 = self.data.score.get_global_ranking(data["pd_id"], int(song), data["difficulty"], edition=0) + + if pd_db_song_1: + pd_db_ranking_1 = self.data.score.get_global_ranking(data["pd_id"], int(song), data["difficulty"], edition=1) + + pd_db_customize = self.data.pv_customize.get_pv_customize(data["pd_id"], int(song)) + + # generate the pv_result string with the ORIGINAL edition and the EXTRA edition appended + pv_result = self._get_pv_pd_result(int(song), pd_db_song_0, pd_db_ranking_0, pd_db_customize, edition=0) + pv_result += "," + self._get_pv_pd_result(int(song), pd_db_song_1, pd_db_ranking_1, pd_db_customize, edition=1) + + self.logger.debug(f"pv_result = {pv_result}") + + pv += urllib.parse.quote(pv_result) else: pv += urllib.parse.quote(f"{song}***") pv += "," @@ -426,6 +572,7 @@ class DivaBase(): pd_song_list = data["stg_ply_pv_id"].split(",") pd_song_difficulty = data["stg_difficulty"].split(",") + pd_song_edition = data["stg_edtn"].split(",") pd_song_max_score = data["stg_score"].split(",") pd_song_max_atn_pnt = data["stg_atn_pnt"].split(",") pd_song_ranking = data["stg_clr_kind"].split(",") @@ -439,31 +586,52 @@ class DivaBase(): 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_score(data["pd_id"], pd_song_list[index], pd_song_difficulty[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(data["pd_id"], self.version, pd_song_list[index], pd_song_difficulty[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_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_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_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_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_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_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]) # Profile saving based on registration list - old_level = int(profile['lv_num']) - new_level = (int(data["ttl_vp_add"]) + int(profile["lv_pnt"])) / 12 + # Calculate new level + best_scores = self.data.score.get_best_scores(data["pd_id"]) - self.data.profile.update_profile(data["pd_id"], int(new_level), int(profile["lv_pnt"]) + int(data["ttl_vp_add"]), int(data["vcld_pts"]), int(data["hp_vol"]), int(data["btn_se_vol"]), int(data["btn_se_vol2"]), int(data["sldr_se_vol2"]), int(data["sort_kind"]), int(data["use_pv_mdl_eqp"]), profile["use_pv_btn_se_eqp"], profile["use_pv_sld_se_eqp"], profile["use_pv_chn_sld_se_eqp"], profile["use_pv_sldr_tch_se_eqp"], int(data["ply_pv_id"]), int(data["nxt_dffclty"]), int(data["nxt_edtn"]), profile["dsp_clr_brdr"], profile["dsp_intrm_rnk"], profile["dsp_clr_sts"], profile["rgo_sts"], profile["lv_efct_id"], profile["lv_plt_id"], data["my_qst_id"], data["my_qst_sts"]) + total_atn_pnt = 0 + for best_score in best_scores: + total_atn_pnt += best_score["atn_pnt"] + + new_level = (total_atn_pnt // 13979) + 1 + new_level_pnt = round((total_atn_pnt % 13979) / 13979 * 100) - response = "" - - response += "&chllng_kind=-1" - response += f"&lv_num_old={int(old_level)}" + response = "&chllng_kind=-1" + response += f"&lv_num_old={int(profile['lv_num'])}" response += f"&lv_pnt_old={int(profile['lv_pnt'])}" - response += f"&lv_num={int(profile['lv_num'])}" + + # update the profile and commit changes to the db + profile = dict(profile) + profile["lv_num"] = new_level + profile["lv_pnt"] = new_level_pnt + profile["vcld_pts"] = int(data["vcld_pts"]) + profile["hp_vol"] = int(data["hp_vol"]) + profile["btn_se_vol"] = int(data["btn_se_vol"]) + profile["sldr_se_vol2"] = int(data["sldr_se_vol2"]) + profile["sort_kind"] = int(data["sort_kind"]) + profile["nxt_pv_id"] = int(data["ply_pv_id"]) + profile["nxt_dffclty"] = int(data["nxt_dffclty"]) + profile["nxt_edtn"] = int(data["nxt_edtn"]) + profile["my_qst_id"] = data["my_qst_id"] + profile["my_qst_sts"] = data["my_qst_sts"] + + self.data.profile.update_profile(profile) + + response += f"&lv_num={new_level}" response += f"&lv_str={profile['lv_str']}" - response += f"&lv_pnt={int(profile['lv_pnt']) + int(data['ttl_vp_add'])}" + response += f"&lv_pnt={new_level_pnt}" response += f"&lv_efct_id={int(profile['lv_efct_id'])}" response += f"&lv_plt_id={int(profile['lv_plt_id'])}" response += f"&vcld_pts={int(data['vcld_pts'])}" @@ -495,12 +663,79 @@ class DivaBase(): def handle_end_request(self, data: Dict) -> Dict: profile = self.data.profile.get_profile(data["pd_id"], self.version) - self.data.profile.update_profile(data["pd_id"], profile["lv_num"], profile["lv_pnt"], profile["vcld_pts"], profile["hp_vol"], profile["btn_se_vol"], profile["btn_se_vol2"], profile["sldr_se_vol2"], profile["sort_kind"], profile["use_pv_mdl_eqp"], profile["use_pv_btn_se_eqp"], profile["use_pv_sld_se_eqp"], profile["use_pv_chn_sld_se_eqp"], profile["use_pv_sldr_tch_se_eqp"], profile["nxt_pv_id"], profile["nxt_dffclty"], profile["nxt_edtn"], profile["dsp_clr_brdr"], profile["dsp_intrm_rnk"], profile["dsp_clr_sts"], profile["rgo_sts"], profile["lv_efct_id"], profile["lv_plt_id"], data["my_qst_id"], data["my_qst_sts"]) - return ( f'' ) + + profile = dict(profile) + profile["my_qst_id"] = data["my_qst_id"] + profile["my_qst_sts"] = data["my_qst_sts"] + + self.data.profile.update_profile(profile) + return (f'') def handle_shop_exit_request(self, data: Dict) -> Dict: - self.data.item.put_shop(data["pd_id"], self.version, data["mdl_eqp_cmn_ary"]) + self.data.item.put_shop(data["pd_id"], self.version, data["mdl_eqp_cmn_ary"], data["c_itm_eqp_cmn_ary"], data["ms_itm_flg_cmn_ary"]) + if int(data["use_pv_mdl_eqp"]) == 1: + self.data.pv_customize.put_pv_customize(data["pd_id"], self.version, data["ply_pv_id"], + data["mdl_eqp_pv_ary"], data["c_itm_eqp_pv_ary"], data["ms_itm_flg_pv_ary"]) + else: + self.data.pv_customize.put_pv_customize(data["pd_id"], self.version, data["ply_pv_id"], + "-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,1,1") - response = "" - response += "&shp_rslt=1" + response = "&shp_rslt=1" return ( response ) + + def handle_card_procedure_request(self, data: Dict) -> str: + profile = self.data.profile.get_profile(data["aime_id"], self.version) + if profile is None: + return "&cd_adm_result=0" + + response = "&cd_adm_result=1" + response += "&chg_name_price=100" + response += "&accept_idx=100" + response += f"&pd_id={profile['user']}" + response += f"&player_name={profile['player_name']}" + response += f"&lv_num={profile['lv_num']}" + response += f"&lv_pnt={profile['lv_pnt']}" + response += f"&lv_str={profile['lv_str']}" + response += f"&lv_efct_id={profile['lv_efct_id']}" + response += f"&lv_plt_id={profile['lv_plt_id']}" + response += f"&vcld_pts={profile['vcld_pts']}" + response += f"&passwd_stat={profile['passwd_stat']}" + + return response + + def handle_change_name_request(self, data: Dict) -> str: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + + # make sure user has enough Vocaloid Points + if profile["vcld_pts"] < int(data["chg_name_price"]): + return "&cd_adm_result=0" + + # update the vocaloid points and player name + profile = dict(profile) + profile["vcld_pts"] -= int(data["chg_name_price"]) + profile["player_name"] = data["player_name"] + + self.data.profile.update_profile(profile) + + response = "&cd_adm_result=1" + response += "&accept_idx=100" + response += f"&pd_id={profile['user']}" + response += f"&player_name={profile['player_name']}" + + return response + + def handle_change_passwd_request(self, data: Dict) -> str: + profile = self.data.profile.get_profile(data["pd_id"], self.version) + + # set password to true and update the saved password + profile = dict(profile) + profile["passwd_stat"] = 1 + profile["passwd"] = data["new_passwd"] + + self.data.profile.update_profile(profile) + + response = "&cd_adm_result=1" + response += "&accept_idx=100" + response += f"&pd_id={profile['user']}" + + return response diff --git a/titles/diva/config.py b/titles/diva/config.py index aba99f2..af4d626 100644 --- a/titles/diva/config.py +++ b/titles/diva/config.py @@ -1,17 +1,33 @@ from core.config import CoreConfig + class DivaServerConfig(): def __init__(self, parent_config: "DivaConfig") -> None: self.__config = parent_config - + @property def enable(self) -> bool: return CoreConfig.get_config_field(self.__config, 'diva', 'server', 'enable', default=True) - + @property def loglevel(self) -> int: return CoreConfig.str_to_loglevel(CoreConfig.get_config_field(self.__config, 'diva', 'server', 'loglevel', default="info")) + +class DivaModsConfig(): + def __init__(self, parent_config: "DivaConfig") -> None: + self.__config = parent_config + + @property + def unlock_all_modules(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'diva', 'mods', 'unlock_all_modules', default=True) + + @property + def unlock_all_items(self) -> bool: + return CoreConfig.get_config_field(self.__config, 'diva', 'mods', 'unlock_all_items', default=True) + + class DivaConfig(dict): def __init__(self) -> None: - self.server = DivaServerConfig(self) \ No newline at end of file + self.server = DivaServerConfig(self) + self.mods = DivaModsConfig(self) diff --git a/titles/diva/database.py b/titles/diva/database.py index f43c42e..a7e4193 100644 --- a/titles/diva/database.py +++ b/titles/diva/database.py @@ -1,6 +1,7 @@ from core.data import Data from core.config import CoreConfig -from titles.diva.schema import DivaProfileData, DivaScoreData, DivaItemData, DivaStaticData +from titles.diva.schema import DivaProfileData, DivaScoreData, DivaModuleData, DivaCustomizeItemData, DivaPvCustomizeData, DivaItemData, DivaStaticData + class DivaData(Data): def __init__(self, cfg: CoreConfig) -> None: @@ -8,5 +9,8 @@ class DivaData(Data): self.profile = DivaProfileData(self.config, self.session) self.score = DivaScoreData(self.config, self.session) + self.module = DivaModuleData(self.config, self.session) + self.customize = DivaCustomizeItemData(self.config, self.session) + self.pv_customize = DivaPvCustomizeData(self.config, self.session) self.item = DivaItemData(self.config, self.session) self.static = DivaStaticData(self.config, self.session) diff --git a/titles/diva/schema/__init__.py b/titles/diva/schema/__init__.py index 57431ef..72cd97f 100644 --- a/titles/diva/schema/__init__.py +++ b/titles/diva/schema/__init__.py @@ -1,6 +1,11 @@ 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, DivaItemData, DivaStaticData] +__all__ = [DivaProfileData, DivaScoreData, DivaModuleData, + DivaCustomizeItemData, DivaPvCustomizeData, DivaItemData, + DivaStaticData] diff --git a/titles/diva/schema/customize.py b/titles/diva/schema/customize.py new file mode 100644 index 0000000..f372349 --- /dev/null +++ b/titles/diva/schema/customize.py @@ -0,0 +1,63 @@ +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:]) diff --git a/titles/diva/schema/item.py b/titles/diva/schema/item.py index 96d8ca5..ce7d910 100644 --- a/titles/diva/schema/item.py +++ b/titles/diva/schema/item.py @@ -14,20 +14,29 @@ shop = Table( 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) -> None: + +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 + 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 = sql.inserted.mdl_eqp_ary + 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) @@ -44,7 +53,8 @@ class DivaItemData(BaseData): shop.c.version == version, shop.c.user == aime_id )) - + result = self.execute(sql) - if result is None: return None - return result.fetchone() \ No newline at end of file + if result is None: + return None + return result.fetchone() diff --git a/titles/diva/schema/module.py b/titles/diva/schema/module.py new file mode 100644 index 0000000..f9c930c --- /dev/null +++ b/titles/diva/schema/module.py @@ -0,0 +1,63 @@ +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:]) diff --git a/titles/diva/schema/profile.py b/titles/diva/schema/profile.py index 72d68ed..76e8241 100644 --- a/titles/diva/schema/profile.py +++ b/titles/diva/schema/profile.py @@ -11,7 +11,8 @@ 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("user", ForeignKey("aime_user.id", ondelete="cascade", + onupdate="cascade"), nullable=False), Column("version", Integer, nullable=False), Column("player_name", String(8), nullable=False), Column("lv_str", String(24), nullable=False, server_default="Dab on 'em"), @@ -23,11 +24,15 @@ profile = Table( 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", String(8), nullable=False, server_default="true"), - Column("use_pv_btn_se_eqp", String(8), nullable=False, server_default="true"), - Column("use_pv_sld_se_eqp", String(8), nullable=False, server_default="false"), - Column("use_pv_chn_sld_se_eqp", String(8), nullable=False, server_default="false"), - Column("use_pv_sldr_tch_se_eqp", String(8), nullable=False, server_default="false"), + 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("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"), @@ -37,14 +42,20 @@ profile = Table( 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("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"), + 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]: + 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 """ @@ -55,48 +66,46 @@ class DivaProfileData(BaseData): ) conflict = sql.on_duplicate_key_update( - player_name = sql.inserted.player_name + 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}") + 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, profile_id: int, lv_num: int, lv_pnt: int, vcld_pts: int, hp_vol: int, btn_se_vol: int, btn_se_vol2: int, sldr_se_vol2: int, sort_kind: int, use_pv_mdl_eqp: str, use_pv_btn_se_eqp: str, use_pv_sld_se_eqp: str, use_pv_chn_sld_se_eqp: str, use_pv_sldr_tch_se_eqp: str, nxt_pv_id: int, nxt_dffclty: int, nxt_edtn: int, dsp_clr_brdr: int, dsp_intrm_rnk: int, dsp_clr_sts: int, rgo_sts: int, lv_efct_id: int, lv_plt_id: int, my_qst_id: str, my_qst_sts: str) -> None: - sql = profile.update(profile.c.user == profile_id).values( - - lv_num = lv_num, - lv_pnt = lv_pnt, - vcld_pts = vcld_pts, - hp_vol = hp_vol, - btn_se_vol = btn_se_vol, - btn_se_vol2 = btn_se_vol2, - sldr_se_vol2 = sldr_se_vol2, - sort_kind = sort_kind, - use_pv_mdl_eqp = use_pv_mdl_eqp, - use_pv_btn_se_eqp = use_pv_btn_se_eqp, - use_pv_sld_se_eqp = use_pv_sld_se_eqp, - use_pv_chn_sld_se_eqp = use_pv_chn_sld_se_eqp, - use_pv_sldr_tch_se_eqp = use_pv_sldr_tch_se_eqp, - nxt_pv_id = nxt_pv_id, - nxt_dffclty = nxt_dffclty, - nxt_edtn = nxt_edtn, - dsp_clr_brdr = dsp_clr_brdr, - dsp_intrm_rnk = dsp_intrm_rnk, - dsp_clr_sts = dsp_clr_sts, - rgo_sts = rgo_sts, - lv_efct_id = lv_efct_id, - lv_plt_id = lv_plt_id, - my_qst_id = my_qst_id, - my_qst_sts = my_qst_sts - + def update_profile(self, profile_updated: Dict) -> None: + sql = profile.update(profile.c.user == profile_updated["user"]).values( + player_name=profile_updated["player_name"], + lv_num=profile_updated["lv_num"], + lv_pnt=profile_updated["lv_pnt"], + vcld_pts=profile_updated["vcld_pts"], + hp_vol=profile_updated["hp_vol"], + btn_se_vol=profile_updated["btn_se_vol"], + btn_se_vol2=profile_updated["btn_se_vol2"], + sldr_se_vol2=profile_updated["sldr_se_vol2"], + sort_kind=profile_updated["sort_kind"], + nxt_pv_id=profile_updated["nxt_pv_id"], + nxt_dffclty=profile_updated["nxt_dffclty"], + nxt_edtn=profile_updated["nxt_edtn"], + dsp_clr_brdr=profile_updated["dsp_clr_brdr"], + dsp_intrm_rnk=profile_updated["dsp_intrm_rnk"], + dsp_clr_sts=profile_updated["dsp_clr_sts"], + rgo_sts=profile_updated["rgo_sts"], + lv_efct_id=profile_updated["lv_efct_id"], + lv_plt_id=profile_updated["lv_plt_id"], + my_qst_id=profile_updated["my_qst_id"], + my_qst_sts=profile_updated["my_qst_sts"], + passwd_stat=profile_updated["passwd_stat"], + passwd=profile_updated["passwd"] ) result = self.execute(sql) - if result is None: - self.logger.error(f"update_profile: failed to update profile! profile: {profile_id}") + if result is None: + self.logger.error( + f"update_profile: failed to update profile! profile: {profile_id}") return None def get_profile(self, aime_id: int, version: int) -> Optional[List[Dict]]: @@ -104,10 +113,11 @@ class DivaProfileData(BaseData): 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 - )) - + profile.c.version == version, + profile.c.user == aime_id + )) + result = self.execute(sql) - if result is None: return None + 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 new file mode 100644 index 0000000..e456e06 --- /dev/null +++ b/titles/diva/schema/pv_customize.py @@ -0,0 +1,69 @@ +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() diff --git a/titles/diva/schema/score.py b/titles/diva/schema/score.py index 6cd0346..638c25a 100644 --- a/titles/diva/schema/score.py +++ b/titles/diva/schema/score.py @@ -1,7 +1,7 @@ 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.sql import func, select from sqlalchemy.dialects.mysql import insert from typing import Optional, List, Dict, Any @@ -16,6 +16,7 @@ score = Table( Column("version", Integer), Column("pv_id", Integer), Column("difficulty", Integer), + Column("edition", Integer), Column("score", Integer), Column("atn_pnt", Integer), Column("clr_kind", Integer), @@ -26,7 +27,7 @@ score = Table( Column("sad", Integer), Column("worst", Integer), Column("max_combo", Integer), - UniqueConstraint("user", "pv_id", "difficulty", name="diva_score_uk"), + UniqueConstraint("user", "pv_id", "difficulty", "edition", name="diva_score_uk"), mysql_charset='utf8mb4' ) @@ -38,6 +39,7 @@ playlog = Table( Column("version", Integer), Column("pv_id", Integer), Column("difficulty", Integer), + Column("edition", Integer), Column("score", Integer), Column("atn_pnt", Integer), Column("clr_kind", Integer), @@ -52,90 +54,144 @@ playlog = Table( mysql_charset='utf8mb4' ) + class DivaScoreData(BaseData): - def put_best_score(self, user_id: int, game_version: int, song_id: int, difficulty: 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]: + 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, + 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, + 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, + 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}") + 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, 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]: + + 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, + 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 + 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}") + 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_score(self, user_id: int, pv_id: int, chart_id: int) -> Optional[Dict]: + def get_best_user_score(self, user_id: int, pv_id: int, difficulty: int, + edition: int) -> Optional[Dict]: sql = score.select( - and_(score.c.user == user_id, score.c.pv_id == pv_id, score.c.difficulty == chart_id) + 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 + if result is None: + return None return result.fetchone() - def get_best_scores(self, user_id: int) -> Optional[Dict]: + def get_top3_scores(self, pv_id: int, difficulty: int, + edition: int) -> Optional[List[Dict]]: + 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]: + # 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]: sql = score.select(score.c.user == user_id) result = self.execute(sql) - if result is None: return None + if result is None: + return None return result.fetchall() diff --git a/titles/diva/schema/static.py b/titles/diva/schema/static.py index 6544420..c8d83bd 100644 --- a/titles/diva/schema/static.py +++ b/titles/diva/schema/static.py @@ -132,11 +132,25 @@ class DivaStaticData(BaseData): if result is None: return None return result.lastrowid - def get_enabled_shop(self, version: int) -> Optional[List[Row]]: - sql = select(shop).where(and_(shop.c.version == version, shop.c.enabled == True)) + 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 + 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]: @@ -158,12 +172,26 @@ class DivaStaticData(BaseData): result = self.execute(conflict) if result is None: return None return result.lastrowid - - def get_enabled_items(self, version: int) -> Optional[List[Row]]: - sql = select(items).where(and_(items.c.version == version, items.c.enabled == True)) + + 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 + 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,