diff --git a/README.md b/README.md index de42081..be9ac44 100644 --- a/README.md +++ b/README.md @@ -73,18 +73,12 @@ It is just so interesting. What it can do is under exploration. > > Tips: When updating, please keep the original database in case of data loss. -### Version 2.11.0 - -- 适用于Arcaea 4.3.0版本 For Arcaea 4.3.0 -- 新搭档 **霞玛(大~宇~宙)**、**米露可(大~宇~宙)**、**紫黑**、**百合咲美香** 已解锁 Unlock the character **Shama(UNiVERSE)**, **Milk(UNiVERSE)**, **Shikoku**, **Mika Yurisaki**. -- 搭档 **依莉丝** 已觉醒 Uncap the character **Ilith**. -- 为觉醒 **依莉丝** 以及 **百合咲美香** 的技能提供支持 Add support for the skills of uncapped **Ilith** and **Mika Yurisaki**. -- 为 Beyond 图倍增提供支持 Add support for beyond gauge boost. -- 为 Beyond 连锁图提供支持 Add support for beyond chain maps. -- 修复联机时无人房间仍可进入的问题 Fix a logic bug that the room without anyone can be entered in multiplayer. -- 对一些数值的算法进行了更改 Some changes in some values' algorithms. -- 小重构 Link Play 子程序 Refactor simply for Link Play subprogram. -- 新增增删改兑换码、购买项目、登陆奖励、物品的API接口 Add some API endpoints, including creating, changing, deleting about redeem, purchase, login present and item. +### Version 2.11.1 + +- 适用于Arcaea 4.4.0版本 For Arcaea 4.4.0 +- 新搭档 **密特拉·泰尔塞拉**、**不来方斗亚** 已解锁 Unlock the character **Mithra Tercera** and **Toa Kozukata**. +- 为 **密特拉·泰尔塞拉** 的技能提供支持 Add support for the skill of **Mithra Tercera**. +- 新增修改搭档的API接口 Add some API endpoints about characters. ## 运行环境与依赖 Running environment and requirements diff --git a/latest version/api/__init__.py b/latest version/api/__init__.py index 0eab643..7be2d23 100644 --- a/latest version/api/__init__.py +++ b/latest version/api/__init__.py @@ -1,7 +1,7 @@ from flask import Blueprint from . import (users, songs, token, system, items, - purchases, presents, redeems) + purchases, presents, redeems, characters) bp = Blueprint('api', __name__, url_prefix='/api/v1') bp.register_blueprint(users.bp) @@ -12,3 +12,4 @@ bp.register_blueprint(purchases.bp) bp.register_blueprint(presents.bp) bp.register_blueprint(redeems.bp) +bp.register_blueprint(characters.bp) diff --git a/latest version/api/api_auth.py b/latest version/api/api_auth.py index 8f8cfdd..4322cc5 100644 --- a/latest version/api/api_auth.py +++ b/latest version/api/api_auth.py @@ -52,11 +52,12 @@ def wrapped_view(*args, **kwargs): def request_json_handle(request, required_keys: list = [], optional_keys: list = [], must_change: bool = False, is_batch: bool = False): ''' - 提取post参数,返回dict,写成了修饰器\ - parameters: \ - `request`: `Request` - 当前请求\ - `required_keys`: `list` - 必须的参数\ - `optional_keys`: `list` - 可选的参数\ + 提取post参数,返回dict,写成了修饰器 + + parameters: + `request`: `Request` - 当前请求 + `required_keys`: `list` - 必须的参数 + `optional_keys`: `list` - 可选的参数 `must_change`: `bool` - 当全都是可选参数时,是否必须有至少一项修改 ''' @@ -113,8 +114,7 @@ def wrapped_view(*args, **kwargs): data = view(*args, **kwargs) if data is None: return error_return() - else: - return data + return data except ArcError as e: if Config.ALLOW_WARNING_LOG: current_app.logger.warning(format_exc()) diff --git a/latest version/api/api_code.py b/latest version/api/api_code.py index d290431..bf7a807 100644 --- a/latest version/api/api_code.py +++ b/latest version/api/api_code.py @@ -23,6 +23,8 @@ -122: 'Item already exists', -123: 'The collection already has this item', -124: 'The collection does not have this item', + -130: 'No such character', + -131: 'Invalid skill ID', -200: 'No permission', # 2xx用户相关错误 -201: 'Wrong username or password', -202: 'User is banned', diff --git a/latest version/api/characters.py b/latest version/api/characters.py new file mode 100644 index 0000000..db44830 --- /dev/null +++ b/latest version/api/characters.py @@ -0,0 +1,134 @@ +from flask import Blueprint, request + +from core.error import InputError, NoData +from core.item import ItemFactory +from core.character import Character +from core.sql import Connect, Query, Sql + +from .api_auth import api_try, request_json_handle, role_required +from .api_code import success_return +from .constant import Constant + + +bp = Blueprint('characters', __name__, url_prefix='/characters') + + +@bp.route('', methods=['GET']) +@role_required(request, ['select']) +@request_json_handle(request, optional_keys=Constant.QUERY_KEYS) +@api_try +def characters_get(data, user): + '''查询全角色信息''' + A = ['character_id', 'name', 'skill_id', + 'skill_id_uncap', 'char_type', 'is_uncapped'] + B = ['name', 'skill_id', 'skill_id_uncap'] + C = ['name', 'frag1', 'prog1', 'overdrive1', 'frag20', + 'prog20', 'overdrive20', 'frag30', 'prog30', 'overdrive30'] + with Connect() as c: + query = Query(A, B, C).from_dict(data) + x = Sql(c).select('character', query=query) + r = [Character().from_list(i) for i in x] + + if not r: + raise NoData(api_error_code=-2) + + return success_return([x.to_dict() for x in r]) + + +@bp.route('/', methods=['GET']) +@role_required(request, ['select']) +@api_try +def characters_character_get(user, character_id: int): + # 包含core + with Connect() as c: + c = Character(c).select(character_id) + c.select_character_core() + return success_return(c.to_dict(has_cores=True)) + + +@bp.route('/', methods=['PUT']) +@role_required(request, ['change']) +@request_json_handle(request, optional_keys=['max_level', 'skill_id', 'skill_id_uncap', 'skill_unlock_level', 'skill_requires_uncap', 'char_type', 'is_uncapped', 'frag1', 'prog1', 'overdrive1', 'frag20', 'prog20', 'overdrive20', 'frag30', 'prog30', 'overdrive30'], must_change=True) +@api_try +def characters_character_put(data, user, character_id: int): + '''修改角色信息''' + if ('skill_id' in data and data['skill_id'] != '' and data['skill_id'] not in Constant.SKILL_IDS) or ('skill_id_uncap' in data and data['skill_id_uncap'] != '' and data['skill_id_uncap'] not in Constant.SKILL_IDS): + raise InputError('Invalid skill_id', api_error_code=-131) + with Connect() as c: + c = Character(c).select(character_id) + try: + if 'max_level' in data: + c.max_level = int(data['max_level']) + if 'skill_id' in data: + c.skill_id = data['skill_id'] + if 'skill_id_uncap' in data: + c.skill_id_uncap = data['skill_id_uncap'] + if 'skill_unlock_level' in data: + c.skill_unlock_level = int(data['skill_unlock_level']) + if 'skill_requires_uncap' in data: + c.skill_requires_uncap = data['skill_requires_uncap'] == 1 + if 'char_type' in data: + c.char_type = int(data['char_type']) + if 'is_uncapped' in data: + c.is_uncapped = data['is_uncapped'] == 1 + t = ['frag1', 'prog1', 'overdrive1', 'frag20', 'prog20', + 'overdrive20', 'frag30', 'prog30', 'overdrive30'] + for i in t: + if i not in data: + continue + if i.endswith('1'): + x = getattr(c, i[:-1]) + x.start = float(data[i]) + elif i.endswith('20'): + x = getattr(c, i[:-2]) + x.mid = float(data[i]) + else: + x = getattr(c, i[:-2]) + x.end = float(data[i]) + except ValueError as e: + raise InputError('Invalid input', api_error_code=-101) from e + c.update() + return success_return(c.to_dict()) + + +@bp.route('//cores', methods=['GET']) +@role_required(request, ['select']) +@api_try +def characters_character_cores_get(user, character_id: int): + with Connect() as c: + c = Character(c) + c.character_id = character_id + c.select_character_core() + return success_return(c.uncap_cores_to_dict()) + + +@bp.route('//cores', methods=['PATCH']) +@role_required(request, ['change']) +@request_json_handle(request, is_batch=True) +@api_try +def characters_character_cores_patch(data, user, character_id: int): + '''修改角色觉醒cores''' + def force_type_core(x: dict) -> dict: + x['item_type'] = 'core' + x['type'] = 'core' + return x + + with Connect() as c: + ch = Character(c) + ch.character_id = character_id + ch.select_character_core() + ch.remove_items([ItemFactory.from_dict(x, c=c) + for x in map(force_type_core, data.get('remove', []))]) + ch.add_items([ItemFactory.from_dict(x, c=c) + for x in map(force_type_core, data.get('create', []))]) + updates = list(map(force_type_core, data.get('update', []))) + for x in updates: + if 'amount' not in x: + raise InputError('`amount` is required in `update`') + if not isinstance(x['amount'], int) or x['amount'] <= 0: + raise InputError( + '`amount` must be a positive integer', api_error_code=-101) + + ch.update_items( + [ItemFactory.from_dict(x, c=c) for x in updates]) + return success_return(ch.uncap_cores_to_dict()) diff --git a/latest version/api/constant.py b/latest version/api/constant.py index 1b4cf66..c17e9cb 100644 --- a/latest version/api/constant.py +++ b/latest version/api/constant.py @@ -2,3 +2,6 @@ class Constant: QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort'] PATCH_KEYS = ['create', 'update', 'remove'] + + SKILL_IDS = ['gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere', 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', + 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa'] diff --git a/latest version/api/token.py b/latest version/api/token.py index d096779..a5e3609 100644 --- a/latest version/api/token.py +++ b/latest version/api/token.py @@ -17,21 +17,22 @@ @api_try def token_post(data): ''' - 登录,获取token\ + 登录,获取token + {'auth': base64(':')} ''' try: auth_decode = bytes.decode(b64decode(data['auth'])) - except: - raise PostError(api_error_code=-100) - if not ':' in auth_decode: + except Exception as e: + raise PostError(api_error_code=-100) from e + if ':' not in auth_decode: raise PostError(api_error_code=-100) name, password = auth_decode.split(':', 1) with Connect() as c: user = APIUser(c) user.login(name, password, request.remote_addr) - return success_return({'token': user.token, 'user_id': user.user_id}) + return success_return({'token': user.api_token, 'user_id': user.user_id}) @bp.route('', methods=['GET']) diff --git a/latest version/api/users.py b/latest version/api/users.py index c23139a..b63376e 100644 --- a/latest version/api/users.py +++ b/latest version/api/users.py @@ -128,7 +128,7 @@ def users_user_b30_get(user, user_id): f'No best30 data of user `{user_id}`', api_error_code=-3) x.select_song_name() r = x.to_dict_list() - rating_sum = sum([i.rating for i in x.scores]) + rating_sum = sum(i.rating for i in x.scores) return success_return({'user_id': user_id, 'b30_ptt': rating_sum / 30, 'data': r}) diff --git a/latest version/core/api_user.py b/latest version/core/api_user.py index ec0ba03..314ae9c 100644 --- a/latest version/core/api_user.py +++ b/latest version/core/api_user.py @@ -55,8 +55,8 @@ def select_from_id(self, role_id: int = None) -> 'Role': {'a': self.role_id}) x = self.c.fetchone() if x is None: - raise NoData('The role `%s` does not exist.' % - self.role_id, api_error_code=-200) + raise NoData( + f'The role `{self.role_id}` does not exist.', api_error_code=-200) self.caption = x[0] return self @@ -133,19 +133,19 @@ def login(self, name: str = None, password: str = None, ip: str = None) -> None: 'a': self.name}) x = self.c.fetchone() if x is None: - raise NoData('The user `%s` does not exist.' % - self.name, api_error_code=-201, status=401) + raise NoData( + f'The user `{self.name}` does not exist.', api_error_code=-201, status=401) if x[1] == '': - raise UserBan('The user `%s` is banned.' % self.name) + raise UserBan(f'The user `{self.name}` is banned.') if self.hash_pwd != x[1]: raise NoAccess('The password is incorrect.', api_error_code=-201, status=401) self.user_id = x[0] now = int(time() * 1000) - self.token = sha256( + self.api_token = sha256( (str(self.user_id) + str(now)).encode("utf8") + urandom(8)).hexdigest() self.logout() self.c.execute('''insert into api_login values(?,?,?,?)''', - (self.user_id, self.token, now, self.ip)) + (self.user_id, self.api_token, now, self.ip)) diff --git a/latest version/core/character.py b/latest version/core/character.py index 41db641..2a910ec 100644 --- a/latest version/core/character.py +++ b/latest version/core/character.py @@ -1,7 +1,7 @@ from .config_manager import Config from .constant import Constant from .error import ArcError, InputError, ItemNotEnough, NoData -from .item import Item, ItemCore +from .item import CollectionItemMixin, ItemCore class Level: @@ -9,9 +9,9 @@ class Level: min_level = 1 def __init__(self) -> None: - self.max_level = None - self.level = None - self.exp = None + self.max_level: int = None + self.level: int = None + self.exp: float = None @property def level_exp(self): @@ -29,9 +29,9 @@ def add_exp(self, exp_addition: float): a = [] b = [] - for i in Constant.LEVEL_STEPS: - a.append(i) - b.append(Constant.LEVEL_STEPS[i]) + for k, v in Constant.LEVEL_STEPS.items(): + a.append(k) + b.append(v) if exp < b[0]: # 向下溢出,是异常状态,不该被try捕获,不然数据库无法回滚 raise ValueError('EXP value error.') @@ -46,23 +46,10 @@ def add_exp(self, exp_addition: float): class Skill: def __init__(self) -> None: - self.skill_id = None - self.skill_id_uncap = None - self.skill_unlock_level = None - self.skill_requires_uncap = None - - -class Core(Item): - item_type = 'core' - - def __init__(self, core_type: str = '', amount: int = 0) -> None: - super().__init__() - self.item_id = core_type - self.amount = amount - self.is_available = True - - def to_dict(self): - return {'core_type': self.item_id, 'amount': self.amount} + self.skill_id: str = None + self.skill_id_uncap: str = None + self.skill_unlock_level: int = None + self.skill_requires_uncap: bool = None class CharacterValue: @@ -75,8 +62,7 @@ def _calc_char_value_20_math(level: int, value_1: float, value_20: float) -> flo # 4/6859 = 0.00058317539 if level <= 10: return 0.00058317539 * (level - 1) ** 3 * (value_20 - value_1) + value_1 - else: - return - 0.00058317539 * (20 - level) ** 3 * (value_20 - value_1) + value_20 + return - 0.00058317539 * (20 - level) ** 3 * (value_20 - value_1) + value_20 # @staticmethod # def _calc_char_value_20(level: int, stata, statb, lva=1, lvb=20) -> float: @@ -95,44 +81,51 @@ def _calc_char_value_30(level, stata, statb, lva=20, lvb=30): return (level - lva) * (statb - stata) / (lvb - lva) + stata def set_parameter(self, start: float = 0, mid: float = 0, end: float = 0): - self.start = start - self.mid = mid - self.end = end + self.start: float = start + self.mid: float = mid + self.end: float = end def get_value(self, level: Level): if level.min_level <= level.level <= level.mid_level: return self._calc_char_value_20_math(level.level, self.start, self.mid) - elif level.mid_level < level.level <= level.max_level: + if level.mid_level < level.level <= level.max_level: return self._calc_char_value_30(level.level, self.mid, self.end) - else: - return 0 + return 0 -class Character: +class Character(CollectionItemMixin): database_table_name = None + collection_item_const = { + 'name': 'character', + 'table_name': 'char_item', + 'table_primary_key': 'character_id', + 'id_name': 'character_id', + 'items_name': 'uncap_cores' + } + def __init__(self, c=None) -> None: self.c = c - self.character_id = None - self.name = None - self.char_type = None - self.is_uncapped = None - self.is_uncapped_override = None + self.character_id: int = None + self.name: str = None + self.char_type: int = None + self.is_uncapped: bool = None + self.is_uncapped_override: bool = None self.skill = Skill() self.level = Level() self.frag = CharacterValue() self.prog = CharacterValue() self.overdrive = CharacterValue() - self.uncap_cores = [] - self.voice = None + self.uncap_cores: list = [] + self.voice: list = None @property def skill_id_displayed(self) -> str: return None def uncap_cores_to_dict(self): - return [x.to_dict() for x in self.uncap_cores] + return [x.to_dict(character_format=True) for x in self.uncap_cores] @property def is_uncapped_displayed(self) -> bool: @@ -144,11 +137,71 @@ def is_base_character(self) -> bool: # 应该是只有对立这样 return self.character_id == 1 - def to_dict(self) -> dict: - pass + def to_dict(self, has_cores=False) -> dict: + # 用于API里,游戏内的数据不太一样,故不能super + r = { + 'character_id': self.character_id, + 'name': self.name, + 'char_type': self.char_type, + 'is_uncapped': self.is_uncapped, + 'max_level': self.level.max_level, + 'skill_id': self.skill.skill_id, + 'skill_unlock_level': self.skill.skill_unlock_level, + 'skill_requires_uncap': self.skill.skill_requires_uncap, + 'skill_id_uncap': self.skill.skill_id_uncap, + 'frag1': self.frag.start, + 'frag20': self.frag.mid, + 'frag30': self.frag.end, + 'prog1': self.prog.start, + 'prog20': self.prog.mid, + 'prog30': self.prog.end, + 'overdrive1': self.overdrive.start, + 'overdrive20': self.overdrive.mid, + 'overdrive30': self.overdrive.end, + } + if has_cores: + r['uncap_cores'] = self.uncap_cores_to_dict() + return r - def from_list(self, l: list) -> 'Character': - pass + def from_list(self, l: tuple) -> 'Character': + self.character_id = l[0] + self.name = l[1] + self.level.max_level = l[2] + self.frag.set_parameter(l[3], l[6], l[9]) + self.prog.set_parameter(l[4], l[7], l[10]) + self.overdrive.set_parameter(l[5], l[8], l[11]) + self.skill.skill_id = l[12] + self.skill.skill_unlock_level = l[13] + self.skill.skill_requires_uncap = l[14] == 1 + self.skill.skill_id_uncap = l[15] + self.char_type = l[16] + self.is_uncapped = l[17] == 1 + return self + + def select(self, character_id: int = None) -> 'Character': + if character_id is not None: + self.character_id = character_id + self.c.execute( + 'select * from character where character_id = ?', (self.character_id,)) + x = self.c.fetchone() + if not x: + raise NoData( + f'No such character: {self.character_id}', api_error_code=-130) + return self.from_list(x) + + def update(self) -> None: + self.c.execute('''update character set name = ?, max_level = ?, frag1 = ?, frag20 = ?, frag30 = ?, prog1 = ?, prog20 = ?, prog30 = ?, overdrive1 = ?, overdrive20 = ?, overdrive30 = ?, skill_id = ?, skill_unlock_level = ?, skill_requires_uncap = ?, skill_id_uncap = ?, char_type = ?, is_uncapped = ? where character_id = ?''', ( + self.name, self.level.max_level, self.frag.start, self.frag.mid, self.frag.end, self.prog.start, self.prog.mid, self.prog.end, self.overdrive.start, self.overdrive.mid, self.overdrive.end, self.skill.skill_id, self.skill.skill_unlock_level, self.skill.skill_requires_uncap, self.skill.skill_id_uncap, self.char_type, self.is_uncapped, self.character_id)) + + def select_character_core(self): + # 获取此角色所需核心 + self.c.execute( + '''select item_id, amount from char_item where character_id = ? and type="core"''', (self.character_id,)) + x = self.c.fetchall() + if x: + self.uncap_cores = [] + for i in x: + self.uncap_cores.append(ItemCore(self.c, i[0], i[1])) class UserCharacter(Character): @@ -169,27 +222,16 @@ def skill_id_displayed(self) -> str: '''对外显示的技能id''' if self.is_uncapped_displayed and self.skill.skill_id_uncap: return self.skill.skill_id_uncap - elif self.skill.skill_id and self.level.level >= self.skill.skill_unlock_level: + if self.skill.skill_id and self.level.level >= self.skill.skill_unlock_level: return self.skill.skill_id - else: - return None - - def select_character_core(self): - # 获取此角色所需核心 - self.c.execute( - '''select item_id, amount from char_item where character_id = ? and type="core"''', (self.character_id,)) - x = self.c.fetchall() - if x: - self.uncap_cores = [] - for i in x: - self.uncap_cores.append(Core(i[0], i[1])) + return None def select_character_uncap_condition(self, user=None): # parameter: user - User类或子类的实例 # 获取此角色的觉醒信息 if user: self.user = user - self.c.execute('''select is_uncapped, is_uncapped_override from %s where user_id = :a and character_id = :b''' % self.database_table_name, + self.c.execute(f'''select is_uncapped, is_uncapped_override from {self.database_table_name} where user_id = :a and character_id = :b''', {'a': self.user.user_id, 'b': self.character_id}) x = self.c.fetchone() @@ -206,7 +248,7 @@ def select_character_info(self, user=None): # 获取所给用户此角色信息 if user: self.user = user - self.c.execute('''select * from %s a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''' % self.database_table_name, + self.c.execute(f'''select * from {self.database_table_name} a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''', (self.user.user_id, self.character_id)) y = self.c.fetchone() @@ -228,7 +270,7 @@ def select_character_info(self, user=None): self.skill.skill_unlock_level = y[19] self.skill.skill_requires_uncap = y[20] == 1 - if self.character_id == 21 or self.character_id == 46: + if self.character_id in (21, 46): self.voice = [0, 1, 2, 3, 100, 1000, 1001] self.select_character_core() @@ -267,7 +309,7 @@ def change_uncap_override(self, user=None): # 切换觉醒状态 if user: self.user = user - self.c.execute('''select is_uncapped, is_uncapped_override from %s where user_id = :a and character_id = :b''' % self.database_table_name, + self.c.execute(f'''select is_uncapped, is_uncapped_override from {self.database_table_name} where user_id = :a and character_id = :b''', {'a': self.user.user_id, 'b': self.character_id}) x = self.c.fetchone() @@ -277,7 +319,7 @@ def change_uncap_override(self, user=None): self.c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', { 'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id}) - self.c.execute('''update %s set is_uncapped_override = :a where user_id = :b and character_id = :c''' % self.database_table_name, { + self.c.execute(f'''update {self.database_table_name} set is_uncapped_override = :a where user_id = :b and character_id = :c''', { 'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id, 'c': self.character_id}) self.is_uncapped_override = x[1] == 0 @@ -311,7 +353,7 @@ def character_uncap(self, user=None): raise ItemNotEnough('The cores are not enough.') for i in self.uncap_cores: - ItemCore(self.c, i, reverse=True).user_claim_item(self.user) + i.user_claim_item(self.user, reverse=True) self.c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''', (self.user.user_id, self.character_id)) @@ -382,7 +424,7 @@ def __init__(self, c=None, user=None): def select_user_characters(self): self.c.execute( - '''select character_id from %s where user_id=?''' % self.database_table_name, (self.user.user_id,)) + f'''select character_id from {self.database_table_name} where user_id=?''', (self.user.user_id,)) x = self.c.fetchall() self.characters: list = [] if x: diff --git a/latest version/core/config_manager.py b/latest version/core/config_manager.py index 59359fb..4588b41 100644 --- a/latest version/core/config_manager.py +++ b/latest version/core/config_manager.py @@ -12,7 +12,7 @@ class Config: SONG_FILE_HASH_PRE_CALCULATE = True - GAME_API_PREFIX = '/join/21' + GAME_API_PREFIX = '/pollen/22' ALLOW_APPVERSION = [] # list[str] diff --git a/latest version/core/constant.py b/latest version/core/constant.py index edfc1ec..d534c2e 100644 --- a/latest version/core/constant.py +++ b/latest version/core/constant.py @@ -1,6 +1,6 @@ from .config_manager import Config -ARCAEA_SERVER_VERSION = 'v2.11.0' +ARCAEA_SERVER_VERSION = 'v2.11.1' class Constant: diff --git a/latest version/core/course.py b/latest version/core/course.py index ddda103..cf6c30c 100644 --- a/latest version/core/course.py +++ b/latest version/core/course.py @@ -96,7 +96,7 @@ def select_course(self, course_id: str = None) -> 'Course': '''select * from course where course_id = ?''', (self.course_id,)) x = self.c.fetchone() if x is None: - raise NoData('The course `%s` is not found.' % self.course_id) + raise NoData(f'The course `{self.course_id}` is not found.') return self.from_list(x) def select_course_chart(self) -> None: @@ -151,7 +151,8 @@ def insert_all(self) -> None: class UserCourse(Course): ''' - 用户课题类\ + 用户课题类 + parameter: `user` - `User`类或子类的实例 ''' @@ -200,7 +201,8 @@ def update_user_course(self) -> None: class UserCourseList: ''' - 用户课题列表类\ + 用户课题列表类 + parameter: `user` - `User`类或子类的实例 ''' @@ -237,8 +239,9 @@ def select_all(self) -> None: class CoursePlay(UserCourse): ''' - 课题模式打歌类,联动UserPlay\ - parameter: `user` - `UserOnline`类或子类的实例\ + 课题模式打歌类,联动UserPlay + + parameter: `user` - `UserOnline`类或子类的实例 'user_play` - `UserPlay`类的实例 ''' diff --git a/latest version/core/download.py b/latest version/core/download.py index 14f1edf..2718fc9 100644 --- a/latest version/core/download.py +++ b/latest version/core/download.py @@ -88,7 +88,8 @@ def parse(self) -> None: class UserDownload: ''' - 用户下载类\ + 用户下载类 + properties: `user` - `User`类或子类的实例 ''' @@ -158,8 +159,7 @@ def url(self) -> str: if prefix[-1] != '/': prefix += '/' return f'{prefix}{self.song_id}/{self.file_name}?t={self.token}' - else: - return url_for('download', file_path=f'{self.song_id}/{self.file_name}', t=self.token, _external=True) + return url_for('download', file_path=f'{self.song_id}/{self.file_name}', t=self.token, _external=True) @property def hash(self) -> str: @@ -243,7 +243,7 @@ def add_one_song(self, song_id: str) -> None: re['audio']['3'] = {"checksum": x.hash, "url": x.url} else: re['audio']['3'] = {"checksum": x.hash} - elif i == 'video.mp4' or i == 'video_audio.ogg': + elif i in ('video.mp4', 'video_audio.ogg'): if 'additional_files' not in re: re['additional_files'] = [] diff --git a/latest version/core/init.py b/latest version/core/init.py index 46ecd27..fa8d172 100644 --- a/latest version/core/init.py +++ b/latest version/core/init.py @@ -41,16 +41,17 @@ def single_path(self) -> str: def table_init(self) -> None: '''初始化数据库结构''' - with open(self.sql_path, 'r') as f: + with open(self.sql_path, 'r', encoding='utf-8') as f: self.c.executescript(f.read()) self.c.execute('''insert into config values("version", :a);''', { 'a': ARCAEA_SERVER_VERSION}) def character_init(self) -> None: '''初始化搭档信息''' + uncapped_characters = self.init_data.char_core.keys() for i in range(0, len(self.init_data.char)): skill_requires_uncap = 1 if i == 2 else 0 - if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21, 42, 43, 11, 12, 19, 5, 10]: + if i in uncapped_characters: max_level = 30 uncapable = 1 else: @@ -129,7 +130,7 @@ def admin_init(self) -> None: x._insert_user_char() - self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt, + self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt, character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email) values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email) ''', {'user_code': x.user_code, 'user_id': x.user_id, 'join_date': now, 'name': x.name, 'password': '41e5653fc7aeb894026d6bb7b2db7f65902b454945fa8fd65a6327047b5277fb', 'memories': 114514, 'email': x.email}) diff --git a/latest version/core/item.py b/latest version/core/item.py index 25a9c67..e6d2c60 100644 --- a/latest version/core/item.py +++ b/latest version/core/item.py @@ -1,5 +1,5 @@ from .config_manager import Config -from .error import InputError, ItemNotEnough, ItemUnavailable, NoData +from .error import DataExist, InputError, ItemNotEnough, ItemUnavailable, NoData class Item: @@ -73,7 +73,8 @@ def __init__(self, c=None) -> None: def select_user_item(self, user=None): ''' - 查询用户item\ + 查询用户item + parameter: `user` - `User`类或子类的实例 ''' if user is not None: @@ -102,8 +103,7 @@ def user_claim_item(self, user): if x[0] == 0: self.is_available = False raise ItemUnavailable('The item is unavailable.') - else: - self.is_available = True + self.is_available = True else: raise NoData('No item data.') @@ -142,16 +142,29 @@ def user_claim_item(self, user): class ItemCore(PositiveItem): item_type = 'core' - def __init__(self, c, core=None, reverse=False) -> None: + def __init__(self, c, core_type: str = '', amount: int = 0) -> None: super().__init__(c) self.is_available = True - if core: - self.item_id = core.item_id - self.amount = - core.amount if reverse else core.amount + self.item_id = core_type + self.amount = amount def __str__(self) -> str: return self.item_id + '_' + str(self.amount) + def to_dict(self, has_is_available: bool = False, has_amount: bool = True, character_format: bool = False) -> dict: + if character_format: + # 搭档的core是特殊格式的 + return {'core_type': self.item_id, 'amount': self.amount} + return super().to_dict(has_is_available=has_is_available, has_amount=has_amount) + + def user_claim_item(self, user, reverse: bool = False) -> None: + # 骚操作,将amount变为负数后使用再变回来 + if reverse: + self.amount = -self.amount + super().user_claim_item(user) + if reverse: + self.amount = -self.amount + class ItemCharacter(UserItem): item_type = 'character' @@ -257,16 +270,10 @@ def __str__(self) -> str: class Single(NormalItem): item_type = 'single' - def __init__(self, c) -> None: - super().__init__(c) - class Pack(NormalItem): item_type = 'pack' - def __init__(self, c) -> None: - super().__init__(c) - class ProgBoost(UserItem): item_type = 'prog_boost_300' @@ -276,7 +283,8 @@ def __init__(self, c) -> None: def user_claim_item(self, user): ''' - 世界模式prog_boost\ + 世界模式prog_boost + parameters: `user` - `UserOnline`类或子类的实例 ''' user.update_user_one_column('prog_boost', 300) @@ -290,7 +298,7 @@ def __init__(self, c) -> None: def user_claim_item(self, user): ''' - 世界模式记忆源点或残片买体力+6\ + 世界模式记忆源点或残片买体力+6 顺手清一下世界模式过载状态 ''' user.select_user_about_stamina() @@ -386,8 +394,8 @@ def from_str(cls, s: str, c=None): class UserItemList: ''' - 用户的item列表\ - 注意只能查在user_item里面的,character不行\ + 用户的item列表 + 注意只能查在user_item里面的,character不行 properties: `user` - `User`类或子类的实例 ''' @@ -420,3 +428,63 @@ def select_from_type(self, item_type: str) -> 'UserItemList': self.items.append(ItemFactory.from_dict( {'item_id': i[0], 'amount': amount, 'item_type': item_type}, self.c)) return self + + +class CollectionItemMixin: + ''' + 批量修改一些集合中的items + ''' + collection_item_const = { + 'name': 'collection', + 'table_name': 'collection_item', + 'table_primary_key': 'collection_id', + 'id_name': 'collection_id', + 'items_name': 'items' + } + + def add_items(self, items: 'list[Item]') -> None: + collection_id: 'str' = getattr( + self, self.collection_item_const['id_name']) + collection_items: 'list[Item]' = getattr( + self, self.collection_item_const['items_name']) + + for i in items: + if not i.select_exists(): + raise NoData( + f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121) + if i in collection_items: + raise DataExist( + f'Item `{i.item_type}`: `{i.item_id}` already exists in {self.collection_item_const["name"]} `{collection_id}`', api_error_code=-123) + self.c.executemany(f'''insert into {self.collection_item_const["table_name"]} values (?, ?, ?, ?)''', [ + (collection_id, i.item_id, i.item_type, i.amount) for i in items]) + collection_items.extend(items) + + def remove_items(self, items: 'list[Item]') -> None: + collection_id: 'str' = getattr( + self, self.collection_item_const['id_name']) + collection_items: 'list[Item]' = getattr( + self, self.collection_item_const['items_name']) + + for i in items: + if i not in collection_items: + raise NoData( + f'No such item `{i.item_type}`: `{i.item_id}` in {self.collection_item_const["name"]} `{collection_id}`', api_error_code=-124) + self.c.executemany(f'''delete from {self.collection_item_const["table_name"]} where {self.collection_item_const["table_primary_key"]}=? and item_id=? and type=?''', [ + (collection_id, i.item_id, i.item_type) for i in items]) + for i in items: + collection_items.remove(i) + + def update_items(self, items: 'list[Item]') -> None: + collection_id: 'str' = getattr( + self, self.collection_item_const['id_name']) + collection_items: 'list[Item]' = getattr( + self, self.collection_item_const['items_name']) + + for i in items: + if i not in collection_items: + raise NoData( + f'No such item `{i.item_type}`: `{i.item_id}` in {self.collection_item_const["name"]} `{collection_id}`', api_error_code=-124) + self.c.executemany(f'''update {self.collection_item_const["table_name"]} set amount=? where {self.collection_item_const["table_primary_key"]}=? and item_id=? and type=?''', [ + (i.amount, collection_id, i.item_id, i.item_type) for i in items]) + for i in items: + collection_items[collection_items.index(i)].amount = i.amount diff --git a/latest version/core/linkplay.py b/latest version/core/linkplay.py index 0888915..a0a549f 100644 --- a/latest version/core/linkplay.py +++ b/latest version/core/linkplay.py @@ -103,9 +103,9 @@ def tcp(data: str) -> str: sock.sendall(bytes(data + "\n", "utf-8")) try: received = str(sock.recv(1024), "utf-8").strip() - except socket.timeout: + except socket.timeout as e: raise Timeout( - 'Timeout when waiting for data from link play server.', status=400) + 'Timeout when waiting for data from link play server.', status=400) from e # print(received) return received diff --git a/latest version/core/operation.py b/latest version/core/operation.py index c3813a4..830a4ef 100644 --- a/latest version/core/operation.py +++ b/latest version/core/operation.py @@ -53,8 +53,7 @@ def run(self): where_values = [] for k in y: ptt = Score.calculate_rating(defnum, k[1]) - if ptt < 0: - ptt = 0 + ptt = max(ptt, 0) values.append((ptt,)) where_values.append((k[0], i[0], j)) if values: @@ -76,7 +75,7 @@ def run(self): class SaveUpdateScore(BaseOperation): ''' - 云存档更新成绩,是覆盖式更新\ + 云存档更新成绩,是覆盖式更新 提供user参数时,只更新该用户的成绩,否则更新所有用户的成绩 ''' _name = 'save_update_score' @@ -125,8 +124,7 @@ def _one_user_update(self): if i['song_id'] in song_chart_const: rating = Score.calculate_rating( song_chart_const[i['song_id']][i['difficulty']] / 10, i['score']) - if rating < 0: - rating = 0 + rating = max(rating, 0) y = f'{i["song_id"]}{i["difficulty"]}' if y in clear_state: @@ -143,7 +141,7 @@ def _one_user_update(self): def _all_update(self): with Connect() as c: c.execute( - f'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''') + '''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''') song_chart_const = {i[0]: [i[1], i[2], i[3], i[4]] for i in c.fetchall()} # chart const * 10 c.execute('''select user_id from user_save''') @@ -162,8 +160,7 @@ def _all_update(self): if i['song_id'] in song_chart_const: rating = Score.calculate_rating( song_chart_const[i['song_id']][i['difficulty']] / 10, i['score']) - if rating < 0: - rating = 0 + rating = max(rating, 0) y = f'{i["song_id"]}{i["difficulty"]}' if y in clear_state: @@ -180,7 +177,7 @@ def _all_update(self): class UnlockUserItem(BaseOperation): ''' - 全解锁/锁定用户物品\ + 全解锁/锁定用户物品 提供user参数时,只更新该用户的,否则更新所有用户的 ''' _name = 'unlock_user_item' @@ -198,7 +195,7 @@ def set_params(self, user_id: int = None, method: str = 'unlock', item_types: li self.user.user_id = int(user_id) if method in ['unlock', 'lock']: self.method = method - if isinstance(item_types, list) and all([i in self.ALLOW_TYPES for i in item_types]): + if isinstance(item_types, list) and all(i in self.ALLOW_TYPES for i in item_types): self.item_types = item_types def run(self): diff --git a/latest version/core/present.py b/latest version/core/present.py index 8ca67b2..5a680a7 100644 --- a/latest version/core/present.py +++ b/latest version/core/present.py @@ -1,10 +1,18 @@ from time import time -from .error import ArcError, DataExist, NoData -from .item import ItemFactory +from .error import ArcError, NoData +from .item import CollectionItemMixin, ItemFactory -class Present: +class Present(CollectionItemMixin): + collection_item_const = { + 'name': 'present', + 'table_name': 'present_item', + 'table_primary_key': 'present_id', + 'id_name': 'present_id', + 'items_name': 'items' + } + def __init__(self, c=None) -> None: self.c = c self.present_id: str = None @@ -109,47 +117,11 @@ def update(self) -> None: self.c.execute('''update present set expire_ts=?, description=? where present_id=?''', (self.expire_ts, self.description, self.present_id)) - def remove_items(self, items: list) -> None: - '''删除present_item表中的物品''' - for i in items: - if i not in self.items: - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}` in present `{self.present_id}`', api_error_code=-124) - self.c.executemany('''delete from present_item where present_id=? and item_id=? and type=?''', [ - (self.present_id, i.item_id, i.item_type) for i in items]) - for i in items: - self.items.remove(i) - - def add_items(self, items: list) -> None: - '''添加物品到present_item表''' - for i in items: - if not i.select_exists(): - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121) - if i in self.items: - raise DataExist( - f'Item `{i.item_type}`: `{i.item_id}` already exists in present `{self.present_id}`', api_error_code=-123) - self.c.executemany('''insert into present_item values(?,?,?,?)''', [ - (self.present_id, i.item_id, i.item_type, i.amount) for i in items]) - self.items.extend(items) - - def update_items(self, items: list) -> None: - '''更新present_item表中的物品''' - for i in items: - if i not in self.items: - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}` in present `{self.present_id}`', api_error_code=-124) - self.c.executemany('''update present_item set amount=? where present_id=? and item_id=? and type=?''', [ - (i.amount, self.present_id, i.item_id, i.item_type) for i in items]) - - for i in items: - self.items[self.items.index(i)].amount = i.amount - class UserPresent(Present): ''' - 用户登录奖励类\ - 忽视了description的多语言\ + 用户登录奖励类 + 忽视了description的多语言 properties: `user` - `User`类或子类的实例 ''' diff --git a/latest version/core/purchase.py b/latest version/core/purchase.py index 546ce77..a3e5075 100644 --- a/latest version/core/purchase.py +++ b/latest version/core/purchase.py @@ -1,14 +1,21 @@ from time import time -from .error import DataExist, InputError, NoData, TicketNotEnough -from .item import ItemFactory +from .error import InputError, NoData, TicketNotEnough +from .item import CollectionItemMixin, ItemFactory -class Purchase: +class Purchase(CollectionItemMixin): ''' - 购买类\ + 购买类 properties: `user` - `User`类或子类的实例 ''' + collection_item_const = { + 'name': 'purchase', + 'table_name': 'purchase_item', + 'table_primary_key': 'purchase_name', + 'id_name': 'purchase_name', + 'items_name': 'items' + } def __init__(self, c=None, user=None): self.c = c @@ -212,45 +219,11 @@ def update(self) -> None: self.c.execute('''update purchase set price=:a, orig_price=:b, discount_from=:c, discount_to=:d, discount_reason=:e where purchase_name=:f''', { 'a': self.price, 'b': self.orig_price, 'c': self.discount_from, 'd': self.discount_to, 'e': self.discount_reason, 'f': self.purchase_name}) - def add_items(self, items: list) -> None: - '''添加purchase_item表''' - for i in items: - if not i.select_exists(): - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121) - if i in self.items: - raise DataExist( - f'Item `{i.item_type}`: `{i.item_id}` already exists in purchase `{self.purchase_name}`', api_error_code=-123) - self.c.executemany('''insert into purchase_item values (?, ?, ?, ?)''', [ - (self.purchase_name, i.item_id, i.item_type, i.amount) for i in items]) - self.items.extend(items) - - def remove_items(self, items: list) -> None: - '''删除purchase_item表''' - for i in items: - if i not in self.items: - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}` in purchase `{self.purchase_name}`', api_error_code=-124) - self.c.executemany('''delete from purchase_item where purchase_name=? and item_id=? and type=?''', [ - (self.purchase_name, i.item_id, i.item_type) for i in items]) - for i in items: - self.items.remove(i) - - def update_items(self, items: list) -> None: - '''更新purchase_item表,只能更新amount''' - for i in items: - if i not in self.items: - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}` in purchase `{self.purchase_name}`', api_error_code=-124) - self.c.executemany('''update purchase_item set amount=? where purchase_name=? and item_id=? and type=?''', [ - (i.amount, self.purchase_name, i.item_id, i.item_type) for i in items]) - for i in items: - self.items[self.items.index(i)].amount = i.amount - class PurchaseList: ''' - 购买列表类\ + 购买列表类 + property: `user` - `User`类或子类的实例 ''' diff --git a/latest version/core/rank.py b/latest version/core/rank.py index ced73a1..6dd030c 100644 --- a/latest version/core/rank.py +++ b/latest version/core/rank.py @@ -7,8 +7,9 @@ class RankList: ''' - 排行榜类\ - 默认limit=20,limit<0认为是all\ + 排行榜类 + 默认limit=20,limit<0认为是all + property: `user` - `User`类或者子类的实例 ''' @@ -85,7 +86,8 @@ def select_friend(self, user=None, limit=Constant.MAX_FRIEND_COUNT) -> None: @staticmethod def get_my_rank_parameter(my_rank: int, amount: int, all_limit: int = 20, max_local_position: int = Constant.MY_RANK_MAX_LOCAL_POSITION, max_global_position: int = Constant.MY_RANK_MAX_GLOBAL_POSITION): ''' - 计算我的排名中的查询参数\ + 计算我的排名中的查询参数 + returns: `sql_limit`: int - 查询limit参数 `sql_offset`: int - 查询offset参数 @@ -103,7 +105,7 @@ def get_my_rank_parameter(my_rank: int, amount: int, all_limit: int = 20, max_lo need_myself = True elif amount - my_rank < all_limit - max_local_position: # 后方人数不足,显示排名 sql_offset = amount - all_limit - elif my_rank >= max_local_position and my_rank <= max_global_position - all_limit + max_local_position - 1: # 前方人数足够,显示排名 + elif max_local_position <= my_rank <= max_global_position - all_limit + max_local_position - 1: # 前方人数足够,显示排名 sql_offset = my_rank - max_local_position else: # 我已经忘了这是什么了 sql_offset = max_global_position - all_limit diff --git a/latest version/core/redeem.py b/latest version/core/redeem.py index c958df4..4feef7c 100644 --- a/latest version/core/redeem.py +++ b/latest version/core/redeem.py @@ -1,8 +1,16 @@ -from .error import DataExist, NoData, RedeemUnavailable -from .item import ItemFactory +from .error import NoData, RedeemUnavailable +from .item import CollectionItemMixin, ItemFactory -class Redeem: +class Redeem(CollectionItemMixin): + collection_item_const = { + 'name': 'redeem', + 'table_name': 'redeem_item', + 'table_primary_key': 'code', + 'id_name': 'code', + 'items_name': 'items' + } + def __init__(self, c=None) -> None: self.c = c self.code: str = None @@ -87,46 +95,11 @@ def update(self) -> None: self.c.execute('''update redeem set type=? where code=?''', (self.redeem_type, self.code)) - def remove_items(self, items: list) -> None: - '''删除redeem_item表中的物品''' - for i in items: - if i not in self.items: - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}` in redeem `{self.code}`', api_error_code=-124) - self.c.executemany('''delete from redeem_item where code=? and item_id=? and type=?''', [ - (self.code, i.item_id, i.item_type) for i in items]) - for i in items: - self.items.remove(i) - - def add_items(self, items: list) -> None: - '''添加物品到redeem_item表''' - for i in items: - if not i.select_exists(): - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121) - if i in self.items: - raise DataExist( - f'Item `{i.item_type}`: `{i.item_id}` already exists in redeem `{self.code}`', api_error_code=-123) - self.c.executemany('''insert into redeem_item values(?,?,?,?)''', [ - (self.code, i.item_id, i.item_type, i.amount) for i in items]) - self.items.extend(items) - - def update_items(self, items: list) -> None: - '''更新redeem_item表中的物品''' - for i in items: - if i not in self.items: - raise NoData( - f'No such item `{i.item_type}`: `{i.item_id}` in redeem `{self.code}`', api_error_code=-124) - self.c.executemany('''update redeem_item set amount=? where code=? and item_id=? and type=?''', [ - (i.amount, self.code, i.item_id, i.item_type) for i in items]) - - for i in items: - self.items[self.items.index(i)].amount = i.amount - class UserRedeem(Redeem): ''' - 用户兑换码类\ + 用户兑换码类 + properties: `user` - `User`类或子类的实例 ''' @@ -165,7 +138,7 @@ def claim_user_redeem(self, code: str = None) -> None: if self.redeem_type == 0: raise RedeemUnavailable( 'The redeem `%s` is unavailable.' % self.code) - elif self.redeem_type == 1: + if self.redeem_type == 1: raise RedeemUnavailable( 'The redeem `%s` is unavailable.' % self.code, 506) diff --git a/latest version/core/save.py b/latest version/core/save.py index bd6cccf..16cd7d4 100644 --- a/latest version/core/save.py +++ b/latest version/core/save.py @@ -11,6 +11,8 @@ class SaveData: def __init__(self, c=None) -> None: self.c = c + self.user = None + self.scores_data = [] self.clearlamps_data = [] self.clearedsongs_data = [] @@ -127,7 +129,7 @@ def set_value(self, key: str, value: str, checksum: str) -> None: 'Property `%s` is not found in the instance of `SaveData` class.' % key) if md5(value) == checksum: - if key == 'installid_data' or key == 'devicemodelname_data' or key == 'finalestate_data': + if key in ('installid_data', 'devicemodelname_data', 'finalestate_data'): self.__dict__[key] = json.loads(value)['val'] else: self.__dict__[key] = json.loads(value)[''] diff --git a/latest version/core/score.py b/latest version/core/score.py index 8d4cbfd..0c68f47 100644 --- a/latest version/core/score.py +++ b/latest version/core/score.py @@ -14,6 +14,8 @@ class Score: def __init__(self) -> None: + self.c = None + self.song: 'Chart' = Chart() self.score: int = None self.shiny_perfect_count: int = None @@ -45,18 +47,17 @@ def get_song_grade(score: int) -> int: '''分数转换为评级''' if score >= 9900000: # EX+ return 6 - elif 9800000 <= score < 9900000: # EX + if score >= 9800000: # EX return 5 - elif 9500000 <= score < 9800000: # AA + if score >= 9500000: # AA return 4 - elif 9200000 <= score < 9500000: # A + if score >= 9200000: # A return 3 - elif 8900000 <= score < 9200000: # B + if score >= 8900000: # B return 2 - elif 8600000 <= score < 8900000: # C + if score >= 8600000: # C return 1 - else: - return 0 + return 0 @property def song_grade(self) -> int: @@ -67,21 +68,24 @@ def get_song_state(clear_type: int) -> int: '''clear_type转换为成绩状态,用数字大小标识便于比较''' if clear_type == 3: # PM return 5 - elif clear_type == 2: # FC + if clear_type == 2: # FC return 4 - elif clear_type == 5: # Hard Clear + if clear_type == 5: # Hard Clear return 3 - elif clear_type == 1: # Clear + if clear_type == 1: # Clear return 2 - elif clear_type == 4: # Easy Clear + if clear_type == 4: # Easy Clear return 1 - else: # Track Lost - return 0 + return 0 # Track Lost @property def song_state(self) -> int: return self.get_song_state(self.clear_type) + @property + def all_note_count(self) -> int: + return self.perfect_count + self.near_count + self.miss_count + @property def is_valid(self) -> bool: '''分数有效性检查''' @@ -90,7 +94,7 @@ def is_valid(self) -> bool: if self.song.difficulty not in (0, 1, 2, 3): return False - all_note = self.perfect_count + self.near_count + self.miss_count + all_note = self.all_note_count if all_note == 0: return False @@ -112,8 +116,7 @@ def calculate_rating(defnum: float, score: int) -> float: ptt = defnum + 2 elif score < 9800000: ptt = defnum + (score-9500000) / 300000 - if ptt < 0: - ptt = 0 + ptt = max(ptt, 0) else: ptt = defnum + 1 + (score-9800000) / 200000 @@ -217,7 +220,10 @@ def __init__(self, c=None, user=None) -> None: self.course_play_state: int = None self.course_play: 'CoursePlay' = None + self.combo_interval_bonus: int = None # 不能给 None 以外的默认值 + def to_dict(self) -> dict: + # 不能super if self.is_world_mode is None or self.course_play_state is None: return {} if self.course_play_state == 4: @@ -249,10 +255,15 @@ def is_valid(self) -> bool: if songfile_hash and songfile_hash != self.song_hash: return False - x = self.song_token + self.song_hash + self.song.song_id + str(self.song.difficulty) + str(self.score) + str(self.shiny_perfect_count) + str( - self.perfect_count) + str(self.near_count) + str(self.miss_count) + str(self.health) + str(self.modifier) + str(self.clear_type) - y = str(self.user.user_id) + self.song_hash + x = f'''{self.song_token}{self.song_hash}{self.song.song_id}{self.song.difficulty}{self.score}{self.shiny_perfect_count}{self.perfect_count}{self.near_count}{self.miss_count}{self.health}{self.modifier}{self.clear_type}''' + if self.combo_interval_bonus is not None: + if self.combo_interval_bonus < 0 or self.combo_interval_bonus > self.all_note_count / 150: + return False + x = x + str(self.combo_interval_bonus) + + y = f'{self.user.user_id}{self.song_hash}' checksum = md5(x+md5(y)) + if checksum != self.submission_hash: return False @@ -302,7 +313,7 @@ def set_play_state_for_world(self, stamina_multiply: int = 1, fragment_multiply: x = self.c.fetchone() if x: self.prog_boost_multiply = 300 if x[0] == 300 else 0 - if x[1] < self.beyond_boost_gauge_usage or (self.beyond_boost_gauge_usage != 100 and self.beyond_boost_gauge_usage != 200): + if x[1] < self.beyond_boost_gauge_usage or self.beyond_boost_gauge_usage not in (100, 200): # 注意:偷懒了,没判断是否是beyond图 self.beyond_boost_gauge_usage = 0 @@ -374,8 +385,8 @@ def update_recent30(self) -> None: '''更新此分数对应用户的recent30''' old_recent_10 = self.ptt.recent_10 if self.is_protected: - old_r30 = [x for x in self.ptt.r30] - old_s30 = [x for x in self.ptt.s30] + old_r30 = self.ptt.r30.copy() + old_s30 = self.ptt.s30.copy() # 寻找pop位置 songs = list(set(self.ptt.s30)) @@ -479,7 +490,8 @@ def upload_score(self) -> None: class Potential: ''' - 用户潜力值计算处理类\ + 用户潜力值计算处理类 + property: `user` - `User`类或子类的实例 ''' @@ -487,8 +499,8 @@ def __init__(self, c=None, user=None): self.c = c self.user = user - self.r30: list = None - self.s30: list = None + self.r30: 'list[float]' = None + self.s30: 'list[str]' = None self.songs_selected: list = None self.b30: list = None @@ -503,7 +515,7 @@ def best_30(self) -> float: '''获取用户best30的总潜力值''' self.c.execute('''select rating from best_score where user_id = :a order by rating DESC limit 30''', { 'a': self.user.user_id}) - return sum([x[0] for x in self.c.fetchall()]) + return sum(x[0] for x in self.c.fetchall()) def select_recent_30(self) -> None: '''获取用户recent30数据''' @@ -578,7 +590,8 @@ def insert_recent_30(self) -> None: class UserScoreList: ''' - 用户分数查询类\ + 用户分数查询类 + properties: `user` - `User`类或子类的实例 ''' diff --git a/latest version/core/song.py b/latest version/core/song.py index bea9d9d..2b391ea 100644 --- a/latest version/core/song.py +++ b/latest version/core/song.py @@ -7,6 +7,8 @@ class Chart: def __init__(self, c=None, song_id: str = None, difficulty: int = None) -> None: self.c = c + self.song_id: str = None + self.difficulty: int = None self.set_chart(song_id, difficulty) self.defnum: int = None self.song_name: str = None diff --git a/latest version/core/sql.py b/latest version/core/sql.py index 693cc6c..66f31b0 100644 --- a/latest version/core/sql.py +++ b/latest version/core/sql.py @@ -12,8 +12,8 @@ class Connect: def __init__(self, file_path: str = Constant.SQLITE_DATABASE_PATH, in_memory: bool = False, logger=None) -> None: """ - 数据库连接,默认连接arcaea_database.db\ - 接受:文件路径\ + 数据库连接,默认连接arcaea_database.db + 接受:文件路径 返回:sqlite3连接操作对象 """ self.file_path = file_path @@ -21,6 +21,9 @@ def __init__(self, file_path: str = Constant.SQLITE_DATABASE_PATH, in_memory: bo if logger is not None: self.logger = logger + self.conn: sqlite3.Connection = None + self.c: sqlite3.Cursor = None + def __enter__(self) -> sqlite3.Cursor: if self.in_memory: self.conn = sqlite3.connect( @@ -144,19 +147,19 @@ def sort(self, sort: list) -> None: raise InputError(api_error_code=-104) self.__sort = sort - def set_value(self, limit=-1, offset=0, query={}, fuzzy_query={}, sort=[]) -> None: + def set_value(self, limit=-1, offset=0, query=None, fuzzy_query=None, sort=None) -> None: self.limit = limit self.offset = offset - self.query = query - self.fuzzy_query = fuzzy_query - self.sort = sort + self.query = query if query is not None else {} + self.fuzzy_query = fuzzy_query if fuzzy_query is not None else {} + self.sort = sort if sort is not None else [] def from_dict(self, d: dict) -> 'Query': self.set_value(d.get('limit', -1), d.get('offset', 0), d.get('query', {}), d.get('fuzzy_query', {}), d.get('sort', [])) return self - def from_args(self, query: dict, limit: int = -1, offset: int = 0, sort: list = [], fuzzy_query: dict = {}) -> 'Query': + def from_args(self, query: dict, limit: int = -1, offset: int = 0, sort: list = None, fuzzy_query: dict = None) -> 'Query': self.set_value(limit, offset, query, fuzzy_query, sort) return self @@ -170,7 +173,7 @@ def __init__(self, c=None) -> None: self.c = c @staticmethod - def get_select_sql(table_name: str, target_column: list = [], query: 'Query' = None): + def get_select_sql(table_name: str, target_column: list = None, query: 'Query' = None): '''拼接单表内行查询单句sql语句,返回语句和参数列表''' sql_list = [] if not target_column: @@ -210,8 +213,10 @@ def get_select_sql(table_name: str, target_column: list = [], query: 'Query' = N return sql, sql_list @staticmethod - def get_insert_sql(table_name: str, key: list = [], value_len: int = None, insert_type: str = None) -> str: + def get_insert_sql(table_name: str, key: list = None, value_len: int = None, insert_type: str = None) -> str: '''拼接insert语句,请注意只返回sql语句,insert_type为replace或ignore''' + if key is None: + key = [] insert_type = 'replace' if insert_type in [ 'replace', 'R', 'r', 'REPLACE'] else 'ignore' return ('insert into ' if insert_type is None else 'insert or ' + insert_type + ' into ') + table_name + ('(' + ','.join(key) + ')' if key else '') + ' values(' + ','.join(['?'] * (len(key) if value_len is None else value_len)) + ')' @@ -281,13 +286,13 @@ def get_delete_sql(table_name: str, query: 'Query' = None): return sql, sql_list - def select(self, table_name: str, target_column: list = [], query: 'Query' = None) -> list: + def select(self, table_name: str, target_column: list = None, query: 'Query' = None) -> list: '''单表内行select单句sql语句,返回fetchall数据''' sql, sql_list = self.get_select_sql(table_name, target_column, query) self.c.execute(sql, sql_list) return self.c.fetchall() - def select_exists(self, table_name: str, target_column: list = [], query: 'Query' = None) -> bool: + def select_exists(self, table_name: str, target_column: list = None, query: 'Query' = None) -> bool: '''单表内行select exists单句sql语句,返回bool值''' sql, sql_list = self.get_select_sql(table_name, target_column, query) self.c.execute('select exists(' + sql + ')', sql_list) @@ -329,7 +334,7 @@ def get_table_info(self, table_name: str): pk = [] name = [] - self.c.execute('''pragma table_info ("%s")''' % table_name) # 这里无法参数化 + self.c.execute(f'''pragma table_info ("{table_name}")''') # 这里无法参数化 x = self.c.fetchall() if x: for i in x: @@ -390,8 +395,8 @@ def update_database(self) -> None: ''' with Connect(self.c2_path) as c2: with Connect(self.c1_path) as c1: - [self.update_one_table(c1, c2, i) - for i in Constant.DATABASE_MIGRATE_TABLES] + for i in Constant.DATABASE_MIGRATE_TABLES: + self.update_one_table(c1, c2, i) if not Constant.UPDATE_WITH_NEW_CHARACTER_DATA: self.update_one_table(c1, c2, 'character') @@ -406,7 +411,7 @@ def __init__(self): self.c = self.conn.cursor() self.c.execute('''PRAGMA journal_mode = OFF''') self.c.execute('''PRAGMA synchronous = 0''') - self.c.execute('''create table if not exists download_token(user_id int, + self.c.execute('''create table if not exists download_token(user_id int, song_id text,file_name text,token text,time int,primary key(user_id, song_id, file_name));''') self.c.execute( '''create index if not exists download_token_1 on download_token (song_id, file_name);''') diff --git a/latest version/core/system.py b/latest version/core/system.py index dd20c33..f7d68c3 100644 --- a/latest version/core/system.py +++ b/latest version/core/system.py @@ -13,7 +13,7 @@ def to_dict(self) -> dict: "stamina_recover_tick": Constant.STAMINA_RECOVER_TICK, "core_exp": Constant.CORE_EXP, "curr_ts": int(time()*1000), - "level_steps": [{'level': i, 'level_exp': Constant.LEVEL_STEPS[i]} for i in Constant.LEVEL_STEPS], + "level_steps": [{'level': k, 'level_exp': v} for k, v in Constant.LEVEL_STEPS.items()], "world_ranking_enabled": True, "is_byd_chapter_unlocked": True } diff --git a/latest version/core/user.py b/latest version/core/user.py index b25bf12..2718369 100644 --- a/latest version/core/user.py +++ b/latest version/core/user.py @@ -2,6 +2,7 @@ import hashlib import time from os import urandom +from random import randint from .character import UserCharacter, UserCharacterList from .config_manager import Config @@ -100,7 +101,6 @@ def set_user_code(self, user_code: str) -> None: def _build_user_code(self): # 生成9位的user_code,用的自然是随机 - from random import randint random_times = 0 while random_times <= 1000: @@ -148,7 +148,7 @@ def register(self): self._build_user_id() self._insert_user_char() - self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt, + self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt, character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email) values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email) ''', {'user_code': self.user_code, 'user_id': self.user_id, 'join_date': now, 'name': self.name, 'password': self.hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': self.email}) @@ -309,6 +309,8 @@ def __init__(self, c, user_id=None) -> None: self.beyond_boost_gauge: float = 0 self.next_fragstam_ts: int = None self.world_mode_locked_end_ts: int = None + self.current_map: 'Map' = None + self.stamina: 'UserStamina' = None self.__cores: list = None self.__packs: list = None @@ -406,7 +408,7 @@ def friends(self) -> list: self.c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''', {'x': i[0], 'y': self.user_id}) - is_mutual = True if self.c.fetchone() == (1,) else False + is_mutual = self.c.fetchone() == (1,) you = UserOnline(self.c, i[0]) you.select_user() @@ -626,7 +628,7 @@ def select_user_about_world_play(self) -> None: self.character.is_uncapped_override = x[5] == 1 self.current_map = UserMap(self.c, x[6], self) self.world_mode_locked_end_ts = x[7] if x[7] else -1 - self.beyond_boost_gauge = x[8] if x[8] else 0 + self.beyond_boost_gauge = x[8] if x[8] else 0 @property def global_rank(self) -> int: @@ -660,16 +662,16 @@ def update_global_rank(self) -> None: score_sum = 0 if len(song_list_ftr) >= 2: - self.c.execute('''select sum(score) from best_score where user_id=? and difficulty=2 and song_id in ({0})'''.format( - ','.join(['?']*(len(song_list_ftr)-1))), tuple(song_list_ftr)) + self.c.execute( + f'''select sum(score) from best_score where user_id=? and difficulty=2 and song_id in ({','.join(['?']*(len(song_list_ftr)-1))})''', tuple(song_list_ftr)) x = self.c.fetchone() if x[0] is not None: score_sum += x[0] if len(song_list_byn) >= 2: - self.c.execute('''select sum(score) from best_score where user_id=? and difficulty=3 and song_id in ({0})'''.format( - ','.join(['?']*(len(song_list_byn)-1))), tuple(song_list_byn)) + self.c.execute( + f'''select sum(score) from best_score where user_id=? and difficulty=3 and song_id in ({','.join(['?']*(len(song_list_byn)-1))})''', tuple(song_list_byn)) x = self.c.fetchone() if x[0] is not None: @@ -682,13 +684,13 @@ def update_global_rank(self) -> None: def select_user_one_column(self, column_name: str, default_value=None) -> None: ''' - 查询user表的某个属性\ + 查询user表的某个属性 请注意必须是一个普通属性,不能是一个类的实例 ''' if column_name not in self.__dict__: raise InputError('No such column.') - self.c.execute('''select %s from user where user_id = :a''' % - column_name, {'a': self.user_id}) + self.c.execute(f'''select {column_name} from user where user_id = :a''', { + 'a': self.user_id}) x = self.c.fetchone() if not x: raise NoData('No user.', 108, -3) @@ -697,15 +699,15 @@ def select_user_one_column(self, column_name: str, default_value=None) -> None: def update_user_one_column(self, column_name: str, value=None) -> None: ''' - 更新user表的某个属性\ + 更新user表的某个属性 请注意必须是一个普通属性,不能是一个类的实例 ''' if column_name not in self.__dict__: raise InputError('No such column.') if value is not None: self.__dict__[column_name] = value - self.c.execute('''update user set %s = :a where user_id = :b''' % - column_name, {'a': self.__dict__[column_name], 'b': self.user_id}) + self.c.execute(f'''update user set {column_name} = :a where user_id = :b''', { + 'a': self.__dict__[column_name], 'b': self.user_id}) class UserOnline(UserInfo): diff --git a/latest version/core/world.py b/latest version/core/world.py index 7f37c29..6f19eac 100644 --- a/latest version/core/world.py +++ b/latest version/core/world.py @@ -1,6 +1,6 @@ -import json import os from functools import lru_cache +from json import load from random import random from time import time @@ -25,15 +25,15 @@ def get_world_name(file_dir: str = Constant.WORLD_MAP_FOLDER_PATH) -> list: def get_world_info(map_id: str) -> dict: '''读取json文件内容,返回字典''' world_info = {} - with open(os.path.join(Constant.WORLD_MAP_FOLDER_PATH, map_id+'.json'), 'r') as f: - world_info = json.load(f) + with open(os.path.join(Constant.WORLD_MAP_FOLDER_PATH, f'{map_id}.json'), 'rb') as f: + world_info = load(f) return world_info def get_world_all(c, user) -> list: ''' - 读取所有地图信息,返回列表\ + 读取所有地图信息,返回列表 parameter: `user` - `User`类或子类的实例 ''' worlds = get_world_name() @@ -44,7 +44,7 @@ class Step: '''台阶类''' def __init__(self) -> None: - self.postion: int = None + self.position: int = None self.capture: int = None self.items: list = [] self.restrict_id: str = None @@ -198,7 +198,7 @@ def select_map_info(self): class UserMap(Map): ''' - 用户地图类\ + 用户地图类 parameters: `user` - `User`类或者子类的实例 ''' @@ -413,7 +413,8 @@ def stamina(self, value: int) -> None: class UserStamina(Stamina): ''' - 用户体力类\ + 用户体力类 + parameter: `user` - `User`类或子类的实例 ''' @@ -439,8 +440,9 @@ def update(self): class WorldPlay: ''' - 世界模式打歌类,处理特殊角色技能,联动UserMap和UserPlay\ - parameter: `user` - `UserOnline`类或子类的实例\ + 世界模式打歌类,处理特殊角色技能,联动UserMap和UserPlay + + parameter: `user` - `UserOnline`类或子类的实例 'user_play` - `UserPlay`类的实例 ''' @@ -598,8 +600,8 @@ def update(self) -> None: if self.user_play.beyond_gauge == 0: # 更新byd大招蓄力条 self.user.beyond_boost_gauge += self.beyond_boost_gauge_addition - if self.user.beyond_boost_gauge > 200: - self.user.beyond_boost_gauge = 200 + self.user.beyond_boost_gauge = min( + self.user.beyond_boost_gauge, 200) self.user.update_user_one_column( 'beyond_boost_gauge', self.user.beyond_boost_gauge) elif self.user_play.beyond_boost_gauge_usage != 0 and self.user_play.beyond_boost_gauge_usage <= self.user.beyond_boost_gauge: @@ -656,6 +658,8 @@ def before_calculate(self) -> None: self._special_tempest() elif self.character_used.skill_id_displayed == 'ilith_awakened_skill': self._ilith_awakened_skill() + elif self.character_used.skill_id_displayed == 'skill_mithra': + self._skill_mithra() else: if self.character_used.skill_id_displayed == 'skill_vita': self._skill_vita() @@ -684,7 +688,7 @@ def _special_tempest(self) -> None: def _skill_vita(self) -> None: ''' - vita技能,overdrive随回忆率提升,提升量最多为10\ + vita技能,overdrive随回忆率提升,提升量最多为10 此处采用线性函数 ''' self.over_skill_increase = 0 @@ -745,7 +749,7 @@ def _skill_amane(self) -> None: ''' x: 'Step' = self.user.current_map.steps_for_climbing[0] if ('randomsong' in x.step_type or 'speedlimit' in x.step_type) and self.user_play.song_grade < 5: - self.character_bonus_progress = -self.step_value / 2 / self.step_times + self.character_bonus_progress = -1 * self.step_value / 2 / self.step_times self.step_value = self.step_value / 2 self.user.current_map.reclimb(self.step_value) @@ -765,3 +769,10 @@ def _skill_mika(self) -> None: self.character_used.level) self.prog_skill_increase = self.character_used.prog.get_value( self.character_used.level) + + def _skill_mithra(self) -> None: + ''' + mithra 技能,每 150 combo 增加世界模式进度+1,直接理解成 prog 值增加 + ''' + if self.user_play.combo_interval_bonus: + self.prog_skill_increase = self.user_play.combo_interval_bonus diff --git a/latest version/database/init/arc_data.py b/latest version/database/init/arc_data.py index 03a2578..c0a0d31 100644 --- a/latest version/database/init/arc_data.py +++ b/latest version/database/init/arc_data.py @@ -1,45 +1,45 @@ class InitData: char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', 'hikari&tairitsu(reunion)', 'Tairitsu(Axium)', 'Tairitsu(Grievous Lady)', 'stella', 'Hikari & Fisica', 'ilith', 'eto', 'luna', 'shirabe', 'Hikari(Zero)', 'Hikari(Fracture)', 'Hikari(Summer)', 'Tairitsu(Summer)', 'Tairitsu & Trin', - 'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)', 'shama(UNiVERSE)', 'milk(UNiVERSE)', 'shikoku', 'mika yurisaki'] + 'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)', 'shama(UNiVERSE)', 'milk(UNiVERSE)', 'shikoku', 'mika yurisaki', 'Mithra Tercera', 'Toa Kozukata'] skill_id = ['gauge_easy', '', '', '', 'note_mirror', 'skill_reunion', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere', - 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika'] + 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'skill_mithra', 'skill_toa'] skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', 'ilith_awakened_skill', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee', - '', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] + '', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, - 0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0] + 0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] frag1 = [55, 55, 60, 50, 47, 79, 47, 57, 41, 22, 50, 54, 60, 56, 78, 42, 41, 61, 52, 50, 52, 32, - 42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42, 38, 25, 58, 50, 61, 45, 45, 38, 34, 27, 18, 56, 47, 30, 45, 57, 55.5, 47, 33, 26] + 42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42, 38, 25, 58, 50, 61, 45, 45, 38, 34, 27, 18, 56, 47, 30, 45, 57, 56, 47, 33, 26, 29, 66] prog1 = [35, 55, 47, 50, 60, 70, 60, 70, 58, 45, 70, 45, 42, 46, 61, 67, 49, 44, 28, 45, 24, 46, 52, - 59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42, 50, 50, 45, 41, 61, 45, 45, 58, 50, 130, 18, 57, 55, 50, 45, 70, 37.5, 29, 44, 26] + 59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42, 50, 50, 45, 41, 61, 45, 45, 58, 50, 130, 18, 57, 55, 50, 45, 70, 37.5, 29, 44, 26, 26, 35] overdrive1 = [35, 55, 25, 50, 47, 70, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18, - 48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 38, 30, 49, 15, 34, 45, 45, 38, 67, 120, 44, 33, 55, 50, 45, 57, 31, 29, 65, 26] + 48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 38, 30, 49, 15, 34, 45, 45, 38, 67, 120, 44, 33, 55, 50, 45, 57, 31, 29, 65, 26, 29, 42.5] frag20 = [78, 80, 90, 75, 70, 79, 70, 79, 65, 40, 50, 80, 90, 82, 0, 61, 67, 92, 85, 50, 86, 52, - 65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 89.5, 50, 50, 51] + 65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 54, 100] prog20 = [61, 80, 70, 75, 90, 70, 90, 102, 84, 78, 105, 67, 63, 68, 0, 99, 80, 66, 46, 83, 40, 73, - 80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51] + 80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 50, 53] overdrive20 = [61, 80, 47, 75, 70, 70, 95, 79, 65, 31, 50, 59, 90, 58, 0, 78, 50, 70, 62, 49, 64, - 46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51] + 46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 54, 65.5] frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62, - 65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 89.5, 50, 50, 51] + 65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 64, 100] prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83, - 80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51] + 80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 60, 53] overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64, - 56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51] + 56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 64, 65.5] char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1, - 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 0] + 0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 0, 2, 2] char_core = { 0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}], @@ -58,14 +58,15 @@ class InitData: 11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}], 12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}], 19: [{'core_id': 'core_colorful', 'amount': 30}], - 10: [{'core_id': 'core_umbral', 'amount': 30}], # TODO: check + 10: [{'core_id': 'core_umbral', 'amount': 30}], + 66: [{'core_id': 'core_chunithm', 'amount': 15}] } cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson', 'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase', 'core_umbral'] world_songs = ["babaroque", "shadesoflight", "kanagawa", "lucifer", "anokumene", "ignotus", "rabbitintheblackroom", "qualia", "redandblue", "bookmaker", "darakunosono", "espebranch", "blacklotus", "givemeanightmare", "vividtheory", "onefr", "gekka", "vexaria3", "infinityheaven3", "fairytale3", "goodtek3", "suomi", "rugie", "faintlight", "harutopia", "goodtek", "dreaminattraction", "syro", "diode", "freefall", "grimheart", "blaster", - "cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger", "bookmaker3", "laqryma3", "kyogenkigo", "hivemind", "seclusion", "quonwacca3", "bluecomet", "energysynergymatrix", "gengaozo", "lastendconductor3", "antithese3", "qualia3", "kanagawa3", "heavensdoor3", "pragmatism3", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote", "sanskia", 'altair', 'mukishitsu', 'trapcrow', 'redandblue3', 'ignotus3', 'singularity3', 'dropdead3', 'arcahv', 'freefall3', 'partyvinyl3', 'tsukinimurakumo', 'mantis', 'worldfragments', 'astrawalkthrough', 'chronicle', 'trappola3', 'letsrock'] + "cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger", "bookmaker3", "laqryma3", "kyogenkigo", "hivemind", "seclusion", "quonwacca3", "bluecomet", "energysynergymatrix", "gengaozo", "lastendconductor3", "antithese3", "qualia3", "kanagawa3", "heavensdoor3", "pragmatism3", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote", "sanskia", 'altair', 'mukishitsu', 'trapcrow', 'redandblue3', 'ignotus3', 'singularity3', 'dropdead3', 'arcahv', 'freefall3', 'partyvinyl3', 'tsukinimurakumo', 'mantis', 'worldfragments', 'astrawalkthrough', 'chronicle', 'trappola3', 'letsrock', 'shadesoflight3', 'teriqma3', 'impact3'] world_unlocks = ["scenery_chap1", "scenery_chap2", "scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"] diff --git a/latest version/database/init/packs.json b/latest version/database/init/packs.json index 00d9546..25513f3 100644 --- a/latest version/database/init/packs.json +++ b/latest version/database/init/packs.json @@ -664,5 +664,23 @@ ], "orig_price": 400, "price": 400 + }, + { + "name": "chunithm_append_2", + "items": [ + { + "type": "pack", + "id": "chunithm_append_2", + "is_available": true + }, + { + "type": "core", + "amount": 5, + "id": "core_generic", + "is_available": true + } + ], + "orig_price": 500, + "price": 500 } ] \ No newline at end of file diff --git a/latest version/database/init/singles.json b/latest version/database/init/singles.json index 3c1ad0d..818cda9 100644 --- a/latest version/database/init/singles.json +++ b/latest version/database/init/singles.json @@ -1360,5 +1360,23 @@ ], "orig_price": 100, "price": 100 + }, + { + "name": "primitivelights", + "items": [ + { + "type": "single", + "id": "primitivelights", + "is_available": true + }, + { + "type": "core", + "amount": 1, + "id": "core_generic", + "is_available": true + } + ], + "orig_price": 100, + "price": 100 } ] \ No newline at end of file diff --git a/latest version/linkplay_server/store.py b/latest version/linkplay_server/store.py index 7c1ba3d..0edd26d 100644 --- a/latest version/linkplay_server/store.py +++ b/latest version/linkplay_server/store.py @@ -61,16 +61,16 @@ def memory_clean(now): with Store.lock: clean_room_list = [] clean_player_list = [] - for token in Store.link_play_data: - room = Store.link_play_data[token]['room'] + for token, v in Store.link_play_data.items(): + room = v['room'] if now - room.timestamp >= Config.TIME_LIMIT: clean_room_list.append(room.room_id) - if now - room.players[Store.link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT: + if now - room.players[v['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT: clean_player_list.append(token) - for room_id in Store.room_id_dict: - if now - Store.room_id_dict[room_id].timestamp >= Config.TIME_LIMIT: + for room_id, v in Store.room_id_dict.items(): + if now - v.timestamp >= Config.TIME_LIMIT: clean_room_list.append(room_id) for room_id in clean_room_list: @@ -190,10 +190,10 @@ def join_room(self) -> tuple: if player_num == 4: # 满人 return '1201' - elif player_num == 0: + if player_num == 0: # 房间不存在 return '1202' - elif room.state != 2: + if room.state != 2: # 无法加入 return '1205' diff --git a/latest version/linkplay_server/udp_class.py b/latest version/linkplay_server/udp_class.py index e3df5f5..af27092 100644 --- a/latest version/linkplay_server/udp_class.py +++ b/latest version/linkplay_server/udp_class.py @@ -164,8 +164,7 @@ def is_ready(self, old_state: int, player_state: int): return False return True - else: - return False + return False def is_finish(self): # 是否全部进入结算 diff --git a/latest version/linkplay_server/udp_parser.py b/latest version/linkplay_server/udp_parser.py index 213c17b..90e0061 100644 --- a/latest version/linkplay_server/udp_parser.py +++ b/latest version/linkplay_server/udp_parser.py @@ -13,8 +13,9 @@ def __init__(self, room: Room, player_index: int = 0) -> None: self.room = room self.player_index = player_index self.s = CommandSender(self.room) + self.command: bytes = None - def get_commands(self, command): + def get_commands(self, command: bytes): self.command = command r = getattr(self, self.route[self.command[2]])() @@ -47,8 +48,6 @@ def command_01(self): self.s.random_code = self.command[16:24] self.room.command_queue.append(self.s.command_10()) - return None - def command_02(self): self.s.random_code = self.command[16:24] song_idx = bi(self.command[24:26]) @@ -81,8 +80,6 @@ def command_03(self): self.room.make_finish() self.room.command_queue.append(self.s.command_13()) - return None - def command_04(self): # 踢人 self.s.random_code = self.command[16:24] @@ -109,7 +106,6 @@ def command_06(self): self.room.song_idx = 0xffff self.room.command_queue.append(self.s.command_13()) - return None def command_07(self): self.s.random_code = self.command[16:24] @@ -117,15 +113,12 @@ def command_07(self): self.room.update_song_unlock() self.room.command_queue.append(self.s.command_14()) - return None def command_08(self): self.room.round_switch = bi(self.command[24:25]) self.s.random_code = self.command[16:24] self.room.command_queue.append(self.s.command_13()) - return None - def command_09(self): re = [] self.s.random_code = self.command[16:24] @@ -196,7 +189,7 @@ def command_09(self): # 将换房主时间提前到此刻 self.room.make_round() - if self.room.state == 4 or self.room.state == 5 or self.room.state == 6: + if self.room.state in (4, 5, 6): timestamp = round(time.time() * 1000) self.room.countdown -= timestamp - self.room.timestamp self.room.timestamp = timestamp @@ -225,10 +218,9 @@ def command_09(self): self.room.countdown = 0xffffffff flag_13 = True - if self.room.countdown <= 0: - self.room.countdown = 0 + self.room.countdown = self.room.countdown if self.room.countdown > 0 else 0 - if self.room.state == 7 or self.room.state == 8: + if self.room.state in (7, 8): if player.timer < bi(self.command[28:32]) or bi(self.command[28:32]) == 0 and player.timer != 0: player.last_timer = player.timer player.last_score = player.score @@ -270,13 +262,12 @@ def command_0a(self): self.room.command_queue.append(self.s.command_12(self.player_index)) - if self.room.state == 3 or self.room.state == 2: + if self.room.state in (2, 3): self.room.state = 1 self.room.song_idx = 0xffff # self.room.command_queue.append(self.s.command_11()) self.room.command_queue.append(self.s.command_13()) self.room.command_queue.append(self.s.command_14()) - return None def command_0b(self): # 推荐歌曲 @@ -285,5 +276,3 @@ def command_0b(self): if self.player_index != i and self.room.players[i].online == 1: self.room.players[i].extra_command_queue.append( self.s.command_0f(self.player_index, song_idx)) - - return None diff --git a/latest version/server/__init__.py b/latest version/server/__init__.py index d07d4f4..a6d5e70 100644 --- a/latest version/server/__init__.py +++ b/latest version/server/__init__.py @@ -1,6 +1,7 @@ -from core.config_manager import Config from flask import Blueprint +from core.config_manager import Config + from . import (auth, course, friend, multiplayer, others, present, purchase, score, user, world) diff --git a/latest version/server/auth.py b/latest version/server/auth.py index ded07bd..5617c9a 100644 --- a/latest version/server/auth.py +++ b/latest version/server/auth.py @@ -1,10 +1,11 @@ import base64 from functools import wraps +from flask import Blueprint, current_app, g, jsonify, request + from core.error import ArcError, NoAccess from core.sql import Connect from core.user import UserAuth, UserLogin -from flask import Blueprint, g, jsonify, request, current_app from .func import arc_try, error_return, header_check @@ -35,15 +36,21 @@ def login(): return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token}) -def auth_required(request): +@bp.route('/verify', methods=['POST']) # 邮箱验证进度查询 +@arc_try +def email_verify(): + raise ArcError('Email verification unavailable.', 151, status=404) + + +def auth_required(req): # arcaea登录验证,写成了修饰器 def decorator(view): @wraps(view) def wrapped_view(*args, **kwargs): - headers = request.headers + headers = req.headers - e = header_check(request) + e = header_check(req) if e is not None: current_app.logger.warning( f' - {e.error_code}|{e.api_error_code}: {e}') diff --git a/latest version/server/course.py b/latest version/server/course.py index adb0d88..5ecb3c4 100644 --- a/latest version/server/course.py +++ b/latest version/server/course.py @@ -1,9 +1,10 @@ +from flask import Blueprint, request + from core.constant import Constant from core.course import UserCourseList from core.item import ItemCore from core.sql import Connect from core.user import UserOnline -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return diff --git a/latest version/server/friend.py b/latest version/server/friend.py index 5dde330..df9fee1 100644 --- a/latest version/server/friend.py +++ b/latest version/server/friend.py @@ -1,6 +1,7 @@ +from flask import Blueprint, request + from core.sql import Connect from core.user import UserOnline, code_get_id -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return diff --git a/latest version/server/func.py b/latest version/server/func.py index bc2f0f7..c2d96ef 100644 --- a/latest version/server/func.py +++ b/latest version/server/func.py @@ -40,6 +40,8 @@ def error_return(e: ArcError = default_error): # 错误返回 # 124 你今天不能再使用这个IP地址创建新的账号 # 150 非常抱歉您已被限制使用此功能 # 151 目前无法使用此功能 + # 160 账户未邮箱认证,请检查邮箱 + # 161 账户认证过期,请重新注册 # 401 用户不存在 # 403 无法连接至服务器 # 501 502 -6 此物品目前无法获取 @@ -51,6 +53,7 @@ def error_return(e: ArcError = default_error): # 错误返回 # 604 你不能加自己为好友 # 903 下载量超过了限制,请24小时后重试 # 905 请在再次使用此功能前等待24小时 + # 910 重新请求验证邮件前需等待x分钟 extra: retry_at # 1001 设备数量达到上限 # 1002 此设备已使用过此功能 # 1201 房间已满 @@ -86,8 +89,7 @@ def wrapped_view(*args, **kwargs): data = view(*args, **kwargs) if data is None: return error_return() - else: - return data + return data except ArcError as e: if Config.ALLOW_WARNING_LOG: current_app.logger.warning(format_exc()) diff --git a/latest version/server/multiplayer.py b/latest version/server/multiplayer.py index d4e3f7a..7a1e6b9 100644 --- a/latest version/server/multiplayer.py +++ b/latest version/server/multiplayer.py @@ -1,8 +1,9 @@ +from flask import Blueprint, request + from core.config_manager import Config from core.error import ArcError from core.linkplay import Player, RemoteMultiPlayer, Room from core.sql import Connect -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return diff --git a/latest version/server/others.py b/latest version/server/others.py index 0d37103..e716678 100644 --- a/latest version/server/others.py +++ b/latest version/server/others.py @@ -1,13 +1,14 @@ import json from urllib.parse import parse_qs, urlparse +from flask import Blueprint, jsonify, request +from werkzeug.datastructures import ImmutableMultiDict + from core.download import DownloadList from core.error import RateLimit from core.sql import Connect from core.system import GameInfo from core.user import UserOnline -from flask import Blueprint, jsonify, request -from werkzeug.datastructures import ImmutableMultiDict from .auth import auth_required from .func import arc_try, error_return, success_return diff --git a/latest version/server/present.py b/latest version/server/present.py index a9a6e6b..85beb97 100644 --- a/latest version/server/present.py +++ b/latest version/server/present.py @@ -1,7 +1,8 @@ +from flask import Blueprint, request + from core.present import UserPresent, UserPresentList from core.sql import Connect from core.user import UserOnline -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return diff --git a/latest version/server/purchase.py b/latest version/server/purchase.py index e081d58..b45b315 100644 --- a/latest version/server/purchase.py +++ b/latest version/server/purchase.py @@ -1,12 +1,13 @@ from time import time +from flask import Blueprint, request + from core.error import InputError, ItemUnavailable, PostError from core.item import ItemFactory, Stamina6 from core.purchase import Purchase, PurchaseList from core.redeem import UserRedeem from core.sql import Connect from core.user import UserOnline -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return diff --git a/latest version/server/score.py b/latest version/server/score.py index 573f609..5c6bf5b 100644 --- a/latest version/server/score.py +++ b/latest version/server/score.py @@ -1,12 +1,13 @@ from time import time -from core.course import CoursePlay +from flask import Blueprint, request + +from core.course import CoursePlay from core.error import InputError from core.rank import RankList from core.score import UserPlay from core.sql import Connect from core.user import UserOnline -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return @@ -94,6 +95,8 @@ def song_score_post(user_id): request.form['miss_count'], request.form['health'], request.form['modifier'], int(time() * 1000), request.form['clear_type']) x.beyond_gauge = int(request.form['beyond_gauge']) x.submission_hash = request.form['submission_hash'] + if 'combo_interval_bonus' in request.form: + x.combo_interval_bonus = int(request.form['combo_interval_bonus']) if not x.is_valid: raise InputError('Invalid score.', 107) x.upload_score() diff --git a/latest version/server/user.py b/latest version/server/user.py index d290086..50ee979 100644 --- a/latest version/server/user.py +++ b/latest version/server/user.py @@ -1,11 +1,11 @@ +from flask import Blueprint, request + from core.character import UserCharacter -from core.config_manager import Config -from core.error import ArcError, NoAccess +from core.error import ArcError from core.item import ItemCore from core.save import SaveData from core.sql import Connect from core.user import User, UserLogin, UserOnline, UserRegister -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, header_check, success_return @@ -16,7 +16,6 @@ @bp.route('', methods=['POST']) # 注册接口 @arc_try def register(): - headers = request.headers error = header_check(request) if error is not None: raise error @@ -155,7 +154,7 @@ def sys_set(user_id, set_arg): user.change_favorite_character(int(value)) else: value = 'true' == value - if 'is_hide_rating' == set_arg or 'max_stamina_notification_enabled' == set_arg: + if set_arg in ('is_hide_rating', 'max_stamina_notification_enabled'): user.update_user_one_column(set_arg, value) return success_return(user.to_dict()) @@ -165,3 +164,9 @@ def sys_set(user_id, set_arg): @arc_try def user_delete(user_id): raise ArcError('Cannot delete the account.', 151, status=404) + + +@bp.route('/email/resend_verify', methods=['POST']) # 邮箱验证重发 +@arc_try +def email_resend_verify(): + raise ArcError('Email verification unavailable.', 151, status=404) diff --git a/latest version/server/world.py b/latest version/server/world.py index caa122a..8567ea0 100644 --- a/latest version/server/world.py +++ b/latest version/server/world.py @@ -1,7 +1,8 @@ +from flask import Blueprint, request + from core.sql import Connect from core.user import UserOnline from core.world import UserMap, get_world_all -from flask import Blueprint, request from .auth import auth_required from .func import arc_try, success_return diff --git a/latest version/web/index.py b/latest version/web/index.py index 5398d07..0031d01 100644 --- a/latest version/web/index.py +++ b/latest version/web/index.py @@ -422,7 +422,7 @@ def all_character(): def change_character(): # 修改角色数据 skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere', - 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror'] + 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa'] return render_template('web/changechar.html', skill_ids=skill_ids)