[Enhance] [Refactor] Add API about characters

- Add API endpoints about characters
- Some small changes in refactoring
This commit is contained in:
Lost-MSth
2023-03-22 22:27:21 +08:00
parent 8d856696ca
commit 1672d337ff
45 changed files with 575 additions and 375 deletions

View File

@@ -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(items.bp)
bp.register_blueprint(purchases.bp)
bp.register_blueprint(presents.bp)
bp.register_blueprint(redeems.bp)
bp.register_blueprint(characters.bp)

View File

@@ -52,11 +52,12 @@ def role_required(request, powers=[]):
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 api_try(view):
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())

View File

@@ -23,6 +23,8 @@ CODE_MSG = {
-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',

View File

@@ -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('/<int:character_id>', 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('/<int:character_id>', 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('/<int:character_id>/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('/<int:character_id>/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())

View File

@@ -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']

View File

@@ -17,21 +17,22 @@ bp = Blueprint('token', __name__, url_prefix='/token')
@api_try
def token_post(data):
'''
登录获取token\
登录获取token
{'auth': base64('<user_id>:<password>')}
'''
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'])

View File

@@ -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})

View File

@@ -55,8 +55,8 @@ class 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 @@ class APIUser(UserOnline):
'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))

View File

@@ -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 @@ class Level:
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 @@ class Level:
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 @@ class CharacterValue:
# 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 @@ class CharacterValue:
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 @@ class Character:
# 应该是只有对立这样
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 @@ class UserCharacter(Character):
'''对外显示的技能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 @@ class UserCharacter(Character):
# 获取所给用户此角色信息
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 @@ class UserCharacter(Character):
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 @@ class UserCharacter(Character):
# 切换觉醒状态
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 @@ class UserCharacter(Character):
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 @@ class UserCharacter(Character):
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 @@ class UserCharacterList:
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:

View File

@@ -1,6 +1,6 @@
from .config_manager import Config
ARCAEA_SERVER_VERSION = 'v2.11.0'
ARCAEA_SERVER_VERSION = 'v2.11.0.1'
class Constant:

View File

@@ -96,7 +96,7 @@ class 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 @@ class Course:
class UserCourse(Course):
'''
用户课题类\
用户课题类
parameter: `user` - `User`类或子类的实例
'''
@@ -200,7 +201,8 @@ class UserCourse(Course):
class UserCourseList:
'''
用户课题列表类\
用户课题列表类
parameter: `user` - `User`类或子类的实例
'''
@@ -237,8 +239,9 @@ class UserCourseList:
class CoursePlay(UserCourse):
'''
课题模式打歌类联动UserPlay\
parameter: `user` - `UserOnline`类或子类的实例\
课题模式打歌类联动UserPlay
parameter: `user` - `UserOnline`类或子类的实例
'user_play` - `UserPlay`类的实例
'''

View File

@@ -88,7 +88,8 @@ class SonglistParser:
class UserDownload:
'''
用户下载类\
用户下载类
properties: `user` - `User`类或子类的实例
'''
@@ -158,8 +159,7 @@ class UserDownload:
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 @@ class DownloadList(UserDownload):
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'] = []

View File

@@ -41,7 +41,7 @@ class DatabaseInit:
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})
@@ -129,7 +129,7 @@ class DatabaseInit:
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})

View File

@@ -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 @@ class UserItem(Item):
def select_user_item(self, user=None):
'''
查询用户item\
查询用户item
parameter: `user` - `User`类或子类的实例
'''
if user is not None:
@@ -102,8 +103,7 @@ class NormalItem(UserItem):
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 @@ class PositiveItem(UserItem):
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 @@ class CourseBanner(NormalItem):
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 @@ class ProgBoost(UserItem):
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 @@ class Stamina6(UserItem):
def user_claim_item(self, user):
'''
世界模式记忆源点或残片买体力+6\
世界模式记忆源点或残片买体力+6
顺手清一下世界模式过载状态
'''
user.select_user_about_stamina()
@@ -386,8 +394,8 @@ class ItemFactory:
class UserItemList:
'''
用户的item列表\
注意只能查在user_item里面的character不行\
用户的item列表
注意只能查在user_item里面的character不行
properties: `user` - `User`类或子类的实例
'''
@@ -420,3 +428,63 @@ class 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

View File

@@ -103,9 +103,9 @@ class RemoteMultiPlayer:
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

View File

@@ -53,8 +53,7 @@ class RefreshAllScoreRating(BaseOperation):
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 @@ class RefreshSongFileCache(BaseOperation):
class SaveUpdateScore(BaseOperation):
'''
云存档更新成绩,是覆盖式更新\
云存档更新成绩,是覆盖式更新
提供user参数时只更新该用户的成绩否则更新所有用户的成绩
'''
_name = 'save_update_score'
@@ -125,8 +124,7 @@ class SaveUpdateScore(BaseOperation):
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 @@ class SaveUpdateScore(BaseOperation):
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 @@ class SaveUpdateScore(BaseOperation):
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 @@ class SaveUpdateScore(BaseOperation):
class UnlockUserItem(BaseOperation):
'''
全解锁/锁定用户物品\
全解锁/锁定用户物品
提供user参数时只更新该用户的否则更新所有用户的
'''
_name = 'unlock_user_item'
@@ -198,7 +195,7 @@ class UnlockUserItem(BaseOperation):
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):

View File

@@ -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 @@ class Present:
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`类或子类的实例
'''

View File

@@ -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 @@ class Purchase:
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`类或子类的实例
'''

View File

@@ -7,8 +7,9 @@ from .user import UserInfo
class RankList:
'''
排行榜类\
默认limit=20limit<0认为是all\
排行榜类
默认limit=20limit<0认为是all
property: `user` - `User`类或者子类的实例
'''
@@ -85,7 +86,8 @@ class RankList:
@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 @@ class RankList:
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

View File

@@ -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 @@ class Redeem:
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 @@ class UserRedeem(Redeem):
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)

View File

@@ -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 @@ class SaveData:
'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)['']

View File

@@ -14,6 +14,8 @@ from .world import WorldPlay
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 @@ class Score:
'''分数转换为评级'''
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,16 +68,15 @@ class Score:
'''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:
@@ -112,8 +112,7 @@ class Score:
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
@@ -218,6 +217,7 @@ class UserPlay(UserScore):
self.course_play: 'CoursePlay' = 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:
@@ -302,7 +302,7 @@ class UserPlay(UserScore):
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 +374,8 @@ class UserPlay(UserScore):
'''更新此分数对应用户的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 +479,8 @@ class UserPlay(UserScore):
class Potential:
'''
用户潜力值计算处理类\
用户潜力值计算处理类
property: `user` - `User`类或子类的实例
'''
@@ -487,8 +488,8 @@ class Potential:
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 +504,7 @@ class Potential:
'''获取用户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 +579,8 @@ class Potential:
class UserScoreList:
'''
用户分数查询类\
用户分数查询类
properties: `user` - `User`类或子类的实例
'''

View File

@@ -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

View File

@@ -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 @@ class Connect:
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 @@ class Query:
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 @@ class Sql:
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 @@ class Sql:
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 @@ class Sql:
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 @@ class Sql:
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 @@ class DatabaseMigrator:
'''
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 @@ class MemoryDatabase:
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);''')

View File

@@ -13,7 +13,7 @@ class GameInfo:
"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
}

View File

@@ -2,6 +2,7 @@ import base64
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 @@ class UserRegister(User):
def _build_user_code(self):
# 生成9位的user_code用的自然是随机
from random import randint
random_times = 0
while random_times <= 1000:
@@ -148,7 +148,7 @@ class UserRegister(User):
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 @@ class UserInfo(User):
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 @@ class UserInfo(User):
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 @@ class UserInfo(User):
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 @@ class UserInfo(User):
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 @@ class UserInfo(User):
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 @@ class UserInfo(User):
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):

View File

@@ -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 @@ class Map:
class UserMap(Map):
'''
用户地图类\
用户地图类
parameters: `user` - `User`类或者子类的实例
'''
@@ -413,7 +413,8 @@ class Stamina:
class UserStamina(Stamina):
'''
用户体力类\
用户体力类
parameter: `user` - `User`类或子类的实例
'''
@@ -439,8 +440,9 @@ class UserStamina(Stamina):
class WorldPlay:
'''
世界模式打歌类处理特殊角色技能联动UserMap和UserPlay\
parameter: `user` - `UserOnline`类或子类的实例\
世界模式打歌类处理特殊角色技能联动UserMap和UserPlay
parameter: `user` - `UserOnline`类或子类的实例
'user_play` - `UserPlay`类的实例
'''
@@ -598,8 +600,8 @@ class WorldPlay:
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:
@@ -684,7 +686,7 @@ class WorldPlay:
def _skill_vita(self) -> None:
'''
vita技能overdrive随回忆率提升提升量最多为10\
vita技能overdrive随回忆率提升提升量最多为10
此处采用线性函数
'''
self.over_skill_increase = 0
@@ -745,7 +747,7 @@ class WorldPlay:
'''
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)

View File

@@ -12,7 +12,7 @@ class InitData:
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]
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]
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]
@@ -21,7 +21,7 @@ class InitData:
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]
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]
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]
@@ -30,7 +30,7 @@ class InitData:
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]
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]
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]
@@ -58,14 +58,14 @@ 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}],
}
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']
world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]

View File

@@ -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
}
]

View File

@@ -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 @@ class TCPRouter:
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'

View File

@@ -164,8 +164,7 @@ class Room:
return False
return True
else:
return False
return False
def is_finish(self):
# 是否全部进入结算

View File

@@ -13,8 +13,9 @@ class CommandParser:
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 @@ class CommandParser:
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 @@ class CommandParser:
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 @@ class CommandParser:
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 @@ class CommandParser:
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 @@ class CommandParser:
# 将换房主时间提前到此刻
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 @@ class CommandParser:
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 @@ class CommandParser:
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 @@ class CommandParser:
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

View File

@@ -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)

View File

@@ -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,15 @@ def login():
return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token})
def auth_required(request):
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}')

View File

@@ -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

View File

@@ -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

View File

@@ -86,8 +86,7 @@ def arc_try(view):
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())

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = Blueprint('user', __name__, url_prefix='/user')
@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())

View File

@@ -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

View File

@@ -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']
return render_template('web/changechar.html', skill_ids=skill_ids)