From e22485e4e089a5474ecd225ac3e419ff25c3677b Mon Sep 17 00:00:00 2001 From: Lost-MSth Date: Sun, 6 Nov 2022 15:06:01 +0800 Subject: [PATCH 1/5] [Bug fix] Course mode cannot start - Fix a bug due to 4da3f38f6e78f3f1cab37668e5f6e9f06991b891 that course mode cannot start --- latest version/core/score.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/latest version/core/score.py b/latest version/core/score.py index 67f0b95..a7c413d 100644 --- a/latest version/core/score.py +++ b/latest version/core/score.py @@ -155,7 +155,7 @@ def __init__(self, c=None, user=None) -> None: super().__init__() self.c = c self.user = user - self.rank = None # 成绩排名,给Ranklist用的 + self.rank = None # 成绩排名,给Ranklist用的 def select_score(self) -> None: '''查询成绩以及用户搭档信息,单次查询可用,不要集体循环查询''' @@ -269,7 +269,10 @@ def get_play_state(self) -> None: '''select * from songplay_token where token=:a ''', {'a': self.song_token}) x = self.c.fetchone() if not x: - raise NoData('No token data.') + self.is_world_mode = False + self.course_play_state = -1 + return None + # raise NoData('No token data.') self.song.set_chart(x[2], x[3]) if x[4]: self.course_play = CoursePlay(self.c, self.user, self) From 7cc17181e1a0aa5fb8b2991f8c2c3a0d756144da Mon Sep 17 00:00:00 2001 From: Lost-MSth Date: Mon, 7 Nov 2022 21:14:05 +0800 Subject: [PATCH 2/5] [Bug fix][Refactor] Course scores & Download - Fix a bug that scores in course mode cannot calculate rating - Make some optimization for downloading songs --- latest version/core/constant.py | 2 +- latest version/core/download.py | 133 ++++++++++++++++++++------------ latest version/core/score.py | 2 +- latest version/main.py | 4 +- latest version/server/course.py | 1 - latest version/server/others.py | 2 - latest version/web/index.py | 5 +- 7 files changed, 89 insertions(+), 60 deletions(-) diff --git a/latest version/core/constant.py b/latest version/core/constant.py index 1b8da8b..fba5d74 100644 --- a/latest version/core/constant.py +++ b/latest version/core/constant.py @@ -96,6 +96,6 @@ class Constant: ] DATABASE_MIGRATE_TABLES = ['user', 'friend', 'best_score', 'recent30', 'user_world', 'item', 'user_item', 'purchase', 'purchase_item', 'user_save', - 'login', 'present', 'user_present', 'present_item', 'redeem', 'user_redeem', 'redeem_item', 'api_login', 'chart', 'user_course', 'user_char'] + 'login', 'present', 'user_present', 'present_item', 'redeem', 'user_redeem', 'redeem_item', 'api_login', 'chart', 'user_course', 'user_char', 'user_role'] UPDATE_WITH_NEW_CHARACTER_DATA = Config.UPDATE_WITH_NEW_CHARACTER_DATA \ No newline at end of file diff --git a/latest version/core/download.py b/latest version/core/download.py index d542bad..6eaad48 100644 --- a/latest version/core/download.py +++ b/latest version/core/download.py @@ -99,7 +99,7 @@ def select_for_check(self) -> None: def generate_token(self) -> None: self.token_time = int(time()) self.token = md5(str(self.user.user_id) + self.song_id + - self.file_name + str(self.token_time)) + self.file_name + str(self.token_time) + str(os.urandom(8))) def insert_download_token(self) -> None: '''将数据插入数据库,让这个下载链接可用''' @@ -111,14 +111,14 @@ def url(self) -> str: '''生成下载链接''' if self.token is None: self.generate_token() - self.insert_download_token() + # self.insert_download_token() # 循环插入速度慢,改为executemany if Constant.DOWNLOAD_LINK_PREFIX: prefix = Constant.DOWNLOAD_LINK_PREFIX if prefix[-1] != '/': prefix += '/' - return prefix + self.song_id + '/' + self.file_name + '?t=' + self.token + return f'{prefix}{self.song_id}/{self.file_name}?t={self.token}' else: - return url_for('download', file_path=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: @@ -140,67 +140,100 @@ def __init__(self, c_m=None, user=None) -> None: self.downloads: list = [] self.urls: dict = {} + @staticmethod + def clear_all_cache(): + '''清除所有歌曲文件有关缓存''' + get_song_file_md5.cache_clear() + get_only_3_song_ids.cache_clear() + DownloadList.get_one_song_file_names.cache_clear() + DownloadList.get_all_song_ids.cache_clear() + def clear_download_token(self) -> None: '''清除过期下载链接''' self.c_m.execute('''delete from download_token where time None: - dir_list = os.listdir(os.path.join( - Constant.SONG_FILE_FOLDER_PATH, song_id)) - - re = {} - for i in dir_list: + def insert_download_tokens(self) -> None: + '''插入所有下载链接''' + self.c_m.executemany('''insert or replace into download_token values(?,?,?,?,?)''', [( + self.user.user_id, x.song_id, x.file_name, x.token, x.token_time) for x in self.downloads]) + + @staticmethod + @lru_cache(maxsize=2048) + def get_one_song_file_names(song_id: str) -> list: + '''获取一个歌曲文件夹下的所有合法文件名,有lru缓存''' + r = [] + for i in os.listdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id)): if os.path.isfile(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg', 'video.mp4', 'video_audio.ogg']: if song_id in get_only_3_song_ids() and i not in ['3.aff', '3.ogg']: continue - x = UserDownload(self.c_m, self.user) - # self.downloads.append(x) # 这实际上没有用 - x.song_id = song_id - x.file_name = i - if i == 'base.ogg': - if 'audio' not in re: - re['audio'] = {} - - re['audio']["checksum"] = x.hash - if self.url_flag: - re['audio']["url"] = x.url - elif i == '3.ogg': - if 'audio' not in re: - re['audio'] = {} - - if self.url_flag: - 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': - if 'additional_files' not in re: - re['additional_files'] = [] - - if self.url_flag: - re['additional_files'].append( - {"checksum": x.hash, "url": x.url, 'file_name': i}) - else: - re['additional_files'].append( - {"checksum": x.hash, 'file_name': i}) - else: - if 'chart' not in re: - re['chart'] = {} + r.append(i) + return r + + def add_one_song(self, song_id: str) -> None: - if self.url_flag: - re['chart'][i[0]] = {"checksum": x.hash, "url": x.url} - else: - re['chart'][i[0]] = {"checksum": x.hash} + re = {} + for i in self.get_one_song_file_names(song_id): + x = UserDownload(self.c_m, self.user) + self.downloads.append(x) + x.song_id = song_id + x.file_name = i + if i == 'base.ogg': + if 'audio' not in re: + re['audio'] = {} + + re['audio']["checksum"] = x.hash + if self.url_flag: + re['audio']["url"] = x.url + elif i == '3.ogg': + if 'audio' not in re: + re['audio'] = {} + + if self.url_flag: + 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': + if 'additional_files' not in re: + re['additional_files'] = [] + + if self.url_flag: + re['additional_files'].append( + {"checksum": x.hash, "url": x.url, 'file_name': i}) + else: + re['additional_files'].append( + {"checksum": x.hash, 'file_name': i}) + else: + if 'chart' not in re: + re['chart'] = {} + + if self.url_flag: + re['chart'][i[0]] = {"checksum": x.hash, "url": x.url} + else: + re['chart'][i[0]] = {"checksum": x.hash} self.urls.update({song_id: re}) + @staticmethod + @lru_cache() + def get_all_song_ids() -> list: + '''获取全歌曲文件夹列表,有lru缓存''' + return list(filter(lambda x: os.path.isdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, x)), os.listdir(Constant.SONG_FILE_FOLDER_PATH))) + def add_songs(self, song_ids: list = None) -> None: '''添加一个或多个歌曲到下载列表,若`song_ids`为空,则添加所有歌曲''' if song_ids is not None: self.song_ids = song_ids - x = self.song_ids if self.song_ids else os.listdir( - Constant.SONG_FILE_FOLDER_PATH) - for i in x: - if os.path.isdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, i)): + if not self.song_ids: + self.song_ids = self.get_all_song_ids() + for i in self.song_ids: self.add_one_song(i) + else: + for i in self.song_ids: + if os.path.isdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, i)): + self.add_one_song(i) + + if self.url_flag: + self.clear_download_token() + self.insert_download_tokens() diff --git a/latest version/core/score.py b/latest version/core/score.py index a7c413d..5f4ccb9 100644 --- a/latest version/core/score.py +++ b/latest version/core/score.py @@ -273,7 +273,7 @@ def get_play_state(self) -> None: self.course_play_state = -1 return None # raise NoData('No token data.') - self.song.set_chart(x[2], x[3]) + # self.song.set_chart(x[2], x[3]) if x[4]: self.course_play = CoursePlay(self.c, self.user, self) self.course_play.course_id = x[4] diff --git a/latest version/main.py b/latest version/main.py index d3d1dde..3b5050b 100644 --- a/latest version/main.py +++ b/latest version/main.py @@ -103,7 +103,7 @@ def download(file_path): @app.after_request def after_request(response): app.logger.info( - f'B {request.remote_addr} - - {request.method} {request.path} {response.status_code}') + f'{request.remote_addr} - - {request.method} {request.path} {response.status_code}') return response @@ -116,8 +116,8 @@ def tcp_server_run(): WSGIServer(host_port, app, log=app.logger).serve_forever() elif Config.DEPLOY_MODE == 'waitress': # waitress WSGI server - from waitress import serve import logging + from waitress import serve logger = logging.getLogger('waitress') logger.setLevel(logging.INFO) serve(app, host=Config.HOST, port=Config.PORT) diff --git a/latest version/server/course.py b/latest version/server/course.py index 7738878..b6ffdb2 100644 --- a/latest version/server/course.py +++ b/latest version/server/course.py @@ -1,6 +1,5 @@ from core.constant import Constant from core.course import UserCourseList -from core.error import ArcError from core.item import ItemCore from core.sql import Connect from core.user import UserOnline diff --git a/latest version/server/others.py b/latest version/server/others.py index d5da663..0d37103 100644 --- a/latest version/server/others.py +++ b/latest version/server/others.py @@ -35,8 +35,6 @@ def download_song(user_id): x.url_flag = json.loads(request.args.get('url', 'true')) if x.url_flag and x.is_limited: raise RateLimit('You have reached the download limit.', 903) - if x.url_flag: - x.clear_download_token() x.add_songs() return success_return(x.urls) diff --git a/latest version/web/index.py b/latest version/web/index.py index 99ecc8c..115e975 100644 --- a/latest version/web/index.py +++ b/latest version/web/index.py @@ -2,7 +2,7 @@ import time import server.arcscore -from core.download import get_only_3_song_ids, initialize_songfile +from core.download import DownloadList, initialize_songfile from core.init import FileChecker from core.rank import RankList from core.sql import Connect @@ -290,8 +290,7 @@ def update_database(): def update_song_hash(): # 更新数据库内谱面文件hash值 try: - get_only_3_song_ids.cache_clear() - get_only_3_song_ids() + DownloadList.clear_all_cache() initialize_songfile() flash('数据刷新成功 Success refresh data.') except: From b70bfd60813c5266831c7ccd920365c3680e6715 Mon Sep 17 00:00:00 2001 From: Lost-MSth Date: Mon, 7 Nov 2022 22:52:42 +0800 Subject: [PATCH 3/5] [Enhance] API user change - Add a method of API to change the user's info --- latest version/api/api_auth.py | 4 +-- latest version/api/api_code.py | 14 +++++---- latest version/api/users.py | 34 ++++++++++++++++++++- latest version/core/sql.py | 37 +++++++++++++++++++++++ latest version/core/user.py | 54 ++++++++++++++++++++++++++-------- 5 files changed, 122 insertions(+), 21 deletions(-) diff --git a/latest version/api/api_auth.py b/latest version/api/api_auth.py index 2325943..f71d7cf 100644 --- a/latest version/api/api_auth.py +++ b/latest version/api/api_auth.py @@ -70,11 +70,11 @@ def wrapped_view(*args, **kwargs): json_data = loads( b64decode(request.args['query']).decode()) else: - return view(data, *args, **kwargs) + json_data = {} for key in required_keys: if key not in json_data: - return error_return(PostError('Missing parameter: ' + key, api_error_code=-100)) + return error_return(PostError(f'Missing parameter: {key}', api_error_code=-100)) data[key] = json_data[key] for key in optional_keys: diff --git a/latest version/api/api_code.py b/latest version/api/api_code.py index 50e76ea..dcb98ac 100644 --- a/latest version/api/api_code.py +++ b/latest version/api/api_code.py @@ -6,21 +6,23 @@ CODE_MSG = { 0: '', - -1: 'See status code', + -1: 'See status code', # 基础错误 -2: 'No data', -3: 'No data or user', -4: 'No user_id', - -100: 'Wrong post data', + -5: 'No user_id or user', + -100: 'Wrong post data', # 1xx数据错误 -101: 'Wrong data type', -102: 'Wrong query parameter', -103: 'Wrong sort parameter', -104: 'Wrong sort order parameter', - -200: 'No permission', + -200: 'No permission', # 2xx用户相关错误 -201: 'Wrong username or password', -202: 'User is banned', - -203: 'Username exists', - -204: 'Email address exists', - -205: 'Too many login attempts', + -203: 'Too many login attempts', + -210: 'Username exists', + -211: 'Email address exists', + -212: 'User code exists', -999: 'Unknown error' } diff --git a/latest version/api/users.py b/latest version/api/users.py index 7a0a4e0..0fbc41f 100644 --- a/latest version/api/users.py +++ b/latest version/api/users.py @@ -1,7 +1,7 @@ from core.error import InputError, NoAccess, NoData from core.score import Potential, UserScoreList from core.sql import Connect, Query, Sql -from core.user import UserInfo, UserRegister +from core.user import UserChanger, UserInfo, UserRegister from core.api_user import APIUser from flask import Blueprint, request @@ -76,6 +76,38 @@ def users_user_get(user, user_id): return success_return(u.to_dict()) +@bp.route('/', methods=['PUT']) +@role_required(request, ['change']) +@request_json_handle(request, optional_keys=['name', 'password', 'user_code', 'ticket', 'email']) +@api_try +def users_user_put(data, user, user_id): + '''修改一个用户''' + with Connect() as c: + u = UserChanger(c, user_id) + r = {} + r['user_id'] = user_id + if 'name' in data: + u.set_name(data['name']) + r['name'] = u.name + if 'password' in data: + u.set_password(data['password']) + r['password'] = u.hash_pwd + if 'email' in data: + u.set_email(data['email']) + r['email'] = u.email + if 'user_code' in data: + u.set_user_code(data['user_code']) + r['user_code'] = u.user_code + if 'ticket' in data: + if not isinstance(data['ticket'], int): + raise InputError('Ticket must be int') + u.ticket = data['ticket'] + r['ticket'] = u.ticket + if r: + u.update_columns(d=r) + return success_return(r) + + @bp.route('//b30', methods=['GET']) @role_required(request, ['select', 'select_me']) @api_try diff --git a/latest version/core/sql.py b/latest version/core/sql.py index 6a05535..d050e68 100644 --- a/latest version/core/sql.py +++ b/latest version/core/sql.py @@ -215,6 +215,36 @@ def get_insert_sql(table_name: str, key: list = [], value_len: int = None, inser '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)) + ')' + @staticmethod + def get_update_sql(table_name: str, d: dict = {}, query: 'Query' = None) -> str: + if not d: + return None + sql_list = [] + sql = f"update {table_name} set {','.join([f'{k}=?' for k in d.keys()])}" + sql_list.extend(d.values()) + + if query is None: + return sql, sql_list + + where_key = [] + for k, v in query.query.items(): + if isinstance(v, list): + where_key.append(f"{k} in ({','.join(['?'] * len(v))})") + sql_list.extend(v) + else: + where_key.append(f'{k}=?') + sql_list.append(v) + + for k, v in query.fuzzy_query.items(): + where_key.append(f'{k} like ?') + sql_list.append(f'%{v}%') + + if where_key: + sql += ' where ' + sql += ' and '.join(where_key) + + return sql, sql_list + @staticmethod def get_delete_sql(table_name: str, query: 'Query' = None): '''拼接删除语句,query中只有query和fuzzy_query会被处理''' @@ -267,6 +297,13 @@ def insert_many(self, table_name: str, key: list, value_list: list, insert_type: self.c.executemany(self.get_insert_sql( table_name, key, len(value_list[0]), insert_type), value_list) + def update(self, table_name: str, d: dict, query: 'Query' = None) -> None: + '''单表内行update单句sql语句''' + if not d: + return + sql, sql_list = self.get_update_sql(table_name, d, query) + self.c.execute(sql, sql_list) + def delete(self, table_name: str, query: 'Query' = None) -> None: '''删除,query中只有query和fuzzy_query会被处理''' sql, sql_list = self.get_delete_sql(table_name, query) diff --git a/latest version/core/user.py b/latest version/core/user.py index 5269495..4b6ffd9 100644 --- a/latest version/core/user.py +++ b/latest version/core/user.py @@ -11,7 +11,7 @@ from .item import UserItemList from .limiter import ArcLimiter from .score import Score -from .sql import Connect +from .sql import Query, Sql from .world import Map, UserMap, UserStamina @@ -32,17 +32,17 @@ def code_get_id(c, user_code: str) -> int: class User: def __init__(self) -> None: - self.name = None - self.email = None - self.password = None - self.user_id = None - self.user_code = None + self.name: str = None + self.email: str = None + self.password: str = None + self.user_id: int = None + self.user_code: str = None self.join_date = None self.rating_ptt: int = None # 100 times - self.ticket = None - self.world_rank_score = None + self.ticket: int = None + self.world_rank_score: int = None self.ban_flag = None @property @@ -63,7 +63,7 @@ def set_name(self, name: str): if self.c.fetchone() == (0,): self.name = name else: - raise DataExist('Username exists.', 101, -203) + raise DataExist('Username exists.', 101, -210) else: raise InputError('Username is invalid.') @@ -82,10 +82,22 @@ def set_email(self, email: str): if self.c.fetchone() == (0,): self.email = email else: - raise DataExist('Email address exists.', 102, -204) + raise DataExist('Email address exists.', 102, -211) else: raise InputError('Email address is invalid.') + def set_user_code(self, user_code: str) -> None: + '''设置用户的user_code''' + if len(user_code) == 9 and user_code.isdigit(): + self.c.execute( + '''select exists(select * from user where user_code = ?)''', (user_code, )) + if self.c.fetchone() == (0,): + self.user_code = user_code + else: + raise DataExist('User code exists.', 103, -212) + else: + raise InputError('User code is invalid.') + def _build_user_code(self): # 生成9位的user_code,用的自然是随机 from random import randint @@ -225,7 +237,7 @@ def login(self, name: str = '', password: str = '', device_id: str = '', ip: str self.set_ip(ip) if not self.limiter.hit(name): - raise RateLimit('Too many login attempts.', 123) + raise RateLimit('Too many login attempts.', 123, -203) self.c.execute('''select user_id, password, ban_flag from user where name = :name''', { 'name': self.name}) @@ -283,7 +295,7 @@ def token_get_id(self): class UserInfo(User): def __init__(self, c, user_id=None) -> None: - super().__init__() + User.__init__(self) self.c = c self.user_id = user_id self.character = None @@ -733,3 +745,21 @@ def change_favorite_character(self, character_id: int) -> None: self.favorite_character = UserCharacter(self.c, character_id, self) self.c.execute('''update user set favorite_character = :a where user_id = :b''', {'a': self.favorite_character.character_id, 'b': self.user_id}) + + +class UserChanger(UserInfo, UserRegister): + + def __init__(self, c, user_id=None) -> None: + super().__init__(c, user_id) + + def update_columns(self, columns: list = None, d: dict = None) -> None: + if columns is not None: + d = {} + for column in columns: + if column == 'password': + d[column] = self.hash_pwd + else: + d[column] = self.__dict__[column] + + Sql(self.c).update('user', d, Query().from_args( + {'user_id': self.user_id})) From e1ce4d9ec4cb8b2e4c1741cbe7eebdd8fdf57112 Mon Sep 17 00:00:00 2001 From: Lost-MSth Date: Thu, 10 Nov 2022 18:00:27 +0800 Subject: [PATCH 4/5] [Enhance][Bug fix] download songlist & new skill & API null recent - Add `amane` and add support for `skill_amane` - Add a parser for `songlist` to specify downloadable files - Fix a bug that users with no recent scores cannot get `recent30` via API --- latest version/api/api_code.py | 20 +++--- latest version/api/users.py | 16 +++-- latest version/core/constant.py | 2 +- latest version/core/download.py | 83 ++++++++++++++++++----- latest version/core/init.py | 3 + latest version/core/score.py | 17 +++-- latest version/core/world.py | 15 +++- latest version/database/init/arc_data.py | 30 ++++---- latest version/database/init/singles.json | 36 ++++++++++ latest version/main.py | 4 +- latest version/web/index.py | 2 +- 11 files changed, 168 insertions(+), 60 deletions(-) diff --git a/latest version/api/api_code.py b/latest version/api/api_code.py index dcb98ac..ded765a 100644 --- a/latest version/api/api_code.py +++ b/latest version/api/api_code.py @@ -6,17 +6,17 @@ CODE_MSG = { 0: '', - -1: 'See status code', # 基础错误 + -1: 'See status code', # 基础错误 -2: 'No data', - -3: 'No data or user', - -4: 'No user_id', - -5: 'No user_id or user', - -100: 'Wrong post data', # 1xx数据错误 - -101: 'Wrong data type', - -102: 'Wrong query parameter', - -103: 'Wrong sort parameter', - -104: 'Wrong sort order parameter', - -200: 'No permission', # 2xx用户相关错误 + -3: 'No data or user', # 不确定是无数据还是无用户 + -100: 'Invalid post data', # 1xx数据错误 + -101: 'Invalid data type', + -102: 'Invalid query parameter', + -103: 'Invalid sort parameter', + -104: 'Invalid sort order parameter', + -105: 'Invalid URL parameter', + -110: 'Invalid user_id', + -200: 'No permission', # 2xx用户相关错误 -201: 'Wrong username or password', -202: 'User is banned', -203: 'Too many login attempts', diff --git a/latest version/api/users.py b/latest version/api/users.py index 0fbc41f..b247d5c 100644 --- a/latest version/api/users.py +++ b/latest version/api/users.py @@ -66,7 +66,7 @@ def users_get(data, user): def users_user_get(user, user_id): '''查询用户信息''' if user_id <= 0: - return error_return(InputError(api_error_code=-4)) + return error_return(InputError(api_error_code=-110)) # 查别人需要select权限 if user_id != user.user_id and not user.role.has_power('select'): return error_return(NoAccess('No permission', api_error_code=-1), 403) @@ -114,7 +114,7 @@ def users_user_put(data, user, user_id): def users_user_b30_get(user, user_id): '''查询用户b30''' if user_id <= 0: - return error_return(InputError(api_error_code=-4)) + return error_return(InputError(api_error_code=-110)) # 查别人需要select权限 if user_id != user.user_id and not user.role.has_power('select'): return error_return(NoAccess('No permission', api_error_code=-1), 403) @@ -123,6 +123,9 @@ def users_user_b30_get(user, user_id): x = UserScoreList(c, UserInfo(c, user_id)) x.query.limit = 30 x.select_from_user() + if not x.scores: + raise NoData( + 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]) @@ -136,7 +139,7 @@ def users_user_b30_get(user, user_id): def users_user_best_get(data, user, user_id): '''查询用户所有best成绩''' if user_id <= 0: - return error_return(InputError(api_error_code=-4)) + return error_return(InputError(api_error_code=-110)) # 查别人需要select权限 if user_id != user.user_id and not user.role.has_power('select'): return error_return(NoAccess('No permission', api_error_code=-1), 403) @@ -145,6 +148,9 @@ def users_user_best_get(data, user, user_id): x = UserScoreList(c, UserInfo(c, user_id)) x.query.from_dict(data) x.select_from_user() + if not x.scores: + raise NoData( + f'No best score data of user `{user_id}`', api_error_code=-3) r = x.to_dict_list() return success_return({'user_id': user_id, 'data': r}) @@ -156,7 +162,7 @@ def users_user_r30_get(user, user_id): '''查询用户r30''' if user_id <= 0: - return error_return(InputError(api_error_code=-4)) + return error_return(InputError(api_error_code=-110)) # 查别人需要select权限 if user_id != user.user_id and not user.role.has_power('select'): return error_return(NoAccess('No permission', api_error_code=-1), 403) @@ -173,7 +179,7 @@ def users_user_role_get(user, user_id): '''查询用户role和powers''' if user_id <= 0: - return error_return(InputError(api_error_code=-4)) + return error_return(InputError(api_error_code=-110)) if user_id == user.user_id: return success_return({'user_id': user.user_id, 'role': user.role.role_id, 'powers': [i.power_id for i in user.role.powers]}) diff --git a/latest version/core/constant.py b/latest version/core/constant.py index fba5d74..c2e8387 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.10.0.3' +ARCAEA_SERVER_VERSION = 'v2.10.1' class Constant: diff --git a/latest version/core/download.py b/latest version/core/download.py index 6eaad48..c134bc3 100644 --- a/latest version/core/download.py +++ b/latest version/core/download.py @@ -29,20 +29,69 @@ def initialize_songfile(): del x -@lru_cache() -def get_only_3_song_ids(): - '''初始化只能下载byd相关的歌曲id''' - if not os.path.isfile(Constant.SONGLIST_FILE_PATH): - return [] - only_3_song_ids = [] - data = [] - with open(Constant.SONGLIST_FILE_PATH, 'r', encoding='utf-8') as f: - data = loads(f.read())['songs'] - for x in data: - if 'remote_dl' not in x or 'remote_dl' in x and not x['remote_dl']: - if any(i['ratingClass'] == 3 for i in x['difficulties']): - only_3_song_ids.append(x['id']) - return only_3_song_ids +class SonglistParser: + '''songlist文件解析器''' + + FILE_NAMES = ['0.aff', '1.aff', '2.aff', '3.aff', + 'base.ogg', '3.ogg', 'video.mp4', 'video_audio.ogg'] + songs: dict = {} # {song_id: value, ...} + # value: bit 76543210 + # 7: video_audio.ogg + # 6: video.mp4 + # 5: 3.ogg + # 4: base.ogg + # 3: 3.aff + # 2: 2.aff + # 1: 1.aff + # 0: 0.aff + + def __init__(self, path=Constant.SONGLIST_FILE_PATH) -> None: + self.path = path + self.data: list = [] + self.parse() + + @staticmethod + def is_available_file(song_id: str, file_name: str) -> list: + '''判断文件是否允许被下载''' + if song_id not in SonglistParser.songs: + # songlist没有,则只限制文件名 + return file_name in SonglistParser.FILE_NAMES + rule = SonglistParser.songs[song_id] + for i in range(8): + if file_name == SonglistParser.FILE_NAMES[i] and rule & (1 << i) != 0: + return True + return False + + def parse_one(self, song: dict) -> dict: + '''解析单个歌曲''' + if not 'id' in song: + return None + r = 0 + if 'remote_dl' in song and song['remote_dl']: + r |= 16 + for i in song.get('difficulties', []): + if i['ratingClass'] == 3 and i.get('audioOverride', False): + r |= 32 + r |= 1 << i['ratingClass'] + else: + if any(i['ratingClass'] == 3 for i in song.get('difficulties', [])): + r |= 8 + + if 'additional_files' in song: + if 'video.mp4' in song['additional_files']: + r |= 64 + if 'video_audio.ogg' in song['additional_files']: + r |= 128 + return {song['id']: r} + + def parse(self) -> None: + '''解析songlist文件''' + if not os.path.isfile(self.path): + return + with open(self.path, 'r', encoding='utf-8') as f: + self.data = loads(f.read()).get('songs', []) + for x in self.data: + self.songs.update(self.parse_one(x)) class UserDownload: @@ -144,9 +193,9 @@ def __init__(self, c_m=None, user=None) -> None: def clear_all_cache(): '''清除所有歌曲文件有关缓存''' get_song_file_md5.cache_clear() - get_only_3_song_ids.cache_clear() DownloadList.get_one_song_file_names.cache_clear() DownloadList.get_all_song_ids.cache_clear() + SonglistParser() def clear_download_token(self) -> None: '''清除过期下载链接''' @@ -164,9 +213,7 @@ def get_one_song_file_names(song_id: str) -> list: '''获取一个歌曲文件夹下的所有合法文件名,有lru缓存''' r = [] for i in os.listdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id)): - if os.path.isfile(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg', 'video.mp4', 'video_audio.ogg']: - if song_id in get_only_3_song_ids() and i not in ['3.aff', '3.ogg']: - continue + if os.path.isfile(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id, i)) and SonglistParser.is_available_file(song_id, i): r.append(i) return r diff --git a/latest version/core/init.py b/latest version/core/init.py index b07aecc..aec8ee1 100644 --- a/latest version/core/init.py +++ b/latest version/core/init.py @@ -8,6 +8,7 @@ from core.config_manager import Config from core.constant import ARCAEA_SERVER_VERSION from core.course import Course +from core.download import SonglistParser from core.purchase import Purchase from core.sql import Connect, DatabaseMigrator, MemoryDatabase from core.user import UserRegister @@ -241,5 +242,7 @@ def update_database(old_path: str, new_path: str = Config.SQLITE_DATABASE_PATH) def check_before_run(self) -> bool: '''运行前检查,返回布尔值''' + # TODO: try MemoryDatabase() # 初始化内存数据库 + SonglistParser() # 解析songlist return self.check_folder(Config.SONG_FILE_FOLDER_PATH) & self.check_update_database() diff --git a/latest version/core/score.py b/latest version/core/score.py index 5f4ccb9..33602be 100644 --- a/latest version/core/score.py +++ b/latest version/core/score.py @@ -495,6 +495,9 @@ def select_recent_30(self) -> None: self.c.execute( '''select * from recent30 where user_id = :a''', {'a': self.user.user_id}) x = self.c.fetchone() + if not x: + raise NoData( + f'No recent30 data for user `{self.user.user_id}`', api_error_code=-3) self.r30 = [] self.s30 = [] if not x: @@ -529,11 +532,15 @@ def recent_10(self) -> float: def recent_30_to_dict_list(self) -> list: if self.r30 is None: self.select_recent_30() - return [{ - 'song_id': self.s30[i][:-1], - 'difficulty': int(self.s30[i][-1]), - 'rating': self.r30[i] - } for i in range(len(self.r30))] + r = [] + for x, y in zip(self.s30, self.r30): + if x: + r.append({ + 'song_id': x[:-1], + 'difficulty': int(x[-1]), + 'rating': y + }) + return r def recent_30_update(self, pop_index: int, rating: float, song_id_difficulty: str) -> None: self.r30.pop(pop_index) diff --git a/latest version/core/world.py b/latest version/core/world.py index 94057bb..c8aa274 100644 --- a/latest version/core/world.py +++ b/latest version/core/world.py @@ -477,7 +477,7 @@ def to_dict(self) -> dict: if self.character_bonus_progress is not None: # 猜的,为了让客户端正确显示,当然结果是没问题的 - r['base_progress'] += self.character_bonus_progress + # r['base_progress'] += self.character_bonus_progress # 肯定不是这样的 r['character_bonus_progress'] = self.character_bonus_progress if self.user_play.beyond_gauge == 0: @@ -593,7 +593,7 @@ def before_calculate(self) -> None: def after_climb(self) -> None: factory_dict = {'eto_uncap': self._eto_uncap, 'ayu_uncap': self._ayu_uncap, - 'luna_uncap': self._luna_uncap, 'skill_fatalis': self._skill_fatalis} + 'luna_uncap': self._luna_uncap, 'skill_fatalis': self._skill_fatalis, 'skill_amane': self._skill_amane} if self.character_used.skill_id_displayed in factory_dict: factory_dict[self.character_used.skill_id_displayed]() @@ -666,3 +666,14 @@ def _skill_fatalis(self) -> None: self.user.world_mode_locked_end_ts = int( time()*1000) + Constant.SKILL_FATALIS_WORLD_LOCKED_TIME self.user.update_user_one_column('world_mode_locked_end_ts') + + def _skill_amane(self) -> None: + ''' + amane技能,起始格为限速或随机,成绩小于EX时,世界模式进度减半 + 偷懒在after_climb里面,需要重爬一次 + ''' + 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.step_value = self.step_value / 2 + self.user.current_map.reclimb(self.step_value) diff --git a/latest version/database/init/arc_data.py b/latest version/database/init/arc_data.py index e627b5c..22a7b3a 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'] + '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'] 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'] + '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_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', '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, 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] 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] + 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] 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] + 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] 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] + 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] 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] + 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] 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] + 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] 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] + 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] 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] + 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] prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 105, 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] + 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] 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] + 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] 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] + 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] char_core = { 0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}], @@ -64,7 +64,7 @@ class InitData: 'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase'] 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'] + "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'] world_unlocks = ["scenery_chap1", "scenery_chap2", "scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"] diff --git a/latest version/database/init/singles.json b/latest version/database/init/singles.json index c78fbde..7cbe5ff 100644 --- a/latest version/database/init/singles.json +++ b/latest version/database/init/singles.json @@ -1216,5 +1216,41 @@ ], "orig_price": 100, "price": 100 + }, + { + "name": "freemyself", + "items": [ + { + "type": "single", + "id": "freemyself", + "is_available": true + }, + { + "type": "core", + "amount": 1, + "id": "core_generic", + "is_available": true + } + ], + "orig_price": 100, + "price": 100 + }, + { + "name": "cocorocosmetic", + "items": [ + { + "type": "single", + "id": "cocorocosmetic", + "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/main.py b/latest version/main.py index 3b5050b..f4b09b6 100644 --- a/latest version/main.py +++ b/latest version/main.py @@ -27,8 +27,7 @@ import web.index import web.login from core.constant import Constant -from core.download import (UserDownload, get_only_3_song_ids, - initialize_songfile) +from core.download import (UserDownload, initialize_songfile) from core.error import ArcError, NoAccess, RateLimit from core.init import FileChecker from core.sql import Connect @@ -191,7 +190,6 @@ def main(): app.logger.info("Start to initialize song data...") try: initialize_songfile() - get_only_3_song_ids() app.logger.info('Complete!') except: app.logger.warning('Initialization error!') diff --git a/latest version/web/index.py b/latest version/web/index.py index 115e975..ef6e694 100644 --- a/latest version/web/index.py +++ b/latest version/web/index.py @@ -426,7 +426,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'] + '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'] return render_template('web/changechar.html', skill_ids=skill_ids) From e3d5c19569798f7ff04e6c586342d4a317d21299 Mon Sep 17 00:00:00 2001 From: Lost-MSth Date: Thu, 10 Nov 2022 18:32:53 +0800 Subject: [PATCH 5/5] Update to v2.10.1 --- README.md | 58 ++++++++++++++++++++++++------------------------------- 1 file changed, 25 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index 35e2813..4e3510a 100644 --- a/README.md +++ b/README.md @@ -67,39 +67,31 @@ 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. -### **2022.11.5** -**修复一长期BUG,影响到自2022.7.4以来所有版本(表现为数据库锁以及服务端运行缓慢),请拉取或下载`master`分支最新提交** - -**Fix a long-term bug that affects all versions since Jul/4/2022 (shown as database lock and slow running). Please pull or download the latest commit from the `master` branch.** - -### Version 2.10.0 -- 适用于Arcaea 4.1.0版本 For Arcaea 4.1.0 -- 新搭档 **咲姬** 已解锁 Unlock the character **Saki**. -- 新搭档 **刹那** 已解锁 Unlock the character **Setsuna**. -- 完善了日志系统 Improve the log system. -- 现在可以利用`songlist`确保`3.aff`以外文件不被下载 Now you can use `songlist` to ensure that files other than `3.aff` should not be downloaded. [#60](https://github.com/Lost-MSth/Arcaea-server/issues/60) -- 适配v4.0.0以下版本的客户端云存档 Ensure that the clients under v4.0.0 can upload the cloud save. -- 优化数据库索引 Optimize database indices. -- 尝试确保LinkPlay服务器的线程安全,现在此功能将作为独立服务端 Try to ensure thread safety in LinkPlay server. Now this function will be served as an independent server. -- 对API接口的分数列表添加歌曲名 Add song names for getting the score list in API. -- 为下载错误增添HTTP状态码 Add HTTP status code when meeting download error. - -- 修复iOS客户端因世界模式地图数据闪退的问题 Fix a bug when world maps' data don't have some unnecessary parts the client of iOS may break down. -- 修复API接口无数据`GET`请求导致报错的问题 Fix a bug that `GET` requests without data will report an error in API. [#50](https://github.com/Lost-MSth/Arcaea-server/issues/50) -- 修复`aggregate`请求无法捕获内部错误的问题 Fix a bug that `aggregate` requests will get an error when the inner function raises an error. -- 修复因错误设置主键导致课程模式谱面无法相同的问题 Fix a bug that the charts of a course cannot be the same because of the incorrect primary keys. -- 修复无谱面数据时世界排名分计算出错的问题 Fix a bug that global ranking scores cannot be calculated if there are no chart in the database. [#61](https://github.com/Lost-MSth/Arcaea-server/issues/61) -- 修复条件满足但隐藏段位依然无法解锁的问题 Fix a bug that the hidden courses cannot appear even if their requirements are satisfied. -- 修复Finale挑战中某些无法解锁的问题 Fix a bug that something of the finale challenge cannot be unlocked. -- 修复用户物品数量无法为0的问题,此问题导致了一些购买操作异常 Fix a bug that the users' items will not be zero, which will disturb some purchase operations. -- 修复角色等级能超过最大等级的问题 Fix a bug that the level of the character can exceed the max level. -- 修复使用`以太之滴`升级角色时应答不正确的问题 Fix a bug that the response is incorrect when upgrading the characters by `generic core`. -- 修复`源韵强化`数值显示不正确的问题 Fix a bug that the `prog boost` shows the incorrect value. -- 修复世界模式奖励可能被重复发放的问题 Fix a bug that the rewards can be get repeatedly in World Mode. -- 修复世界Boss的第二管血量无法削减的问题 Fix a bug that second tube of blood of the world boss won't change. -- 修复我的排名显示不正确的问题 Fix a bug that `my rank` doesn't work correctly. -- 修复在歌曲结束后无法及时轮换房主的问题 Fix a bug that the room host will be changed late when finishing a song. - +### Version 2.10.1 +- 适用于Arcaea 4.1.4版本 For Arcaea 4.1.4 +- 新搭档 **天音** 已解锁 Unlock the character **Amane**. +- 为**天音**技能提供支持 Add support for the skill of **Amane**. +- 现在配置文件可以是含有部分选项的文件或模块 At present the setting file can be a module or a file with some of options. +- 添加`waitress`和`gevent`的部署方案支持,并支持日志记录 Add deployment mode `waitress` and `gevent`, and add support for the info log recording of them. +- 为`songlist`添加解析器以指定可下载的文件 Add a parser for `songlist` to specify downloadable files. +- 重构数据库初始化和数据迁移部分 Code refactoring for database initialization and migration. +- 限制用户下载频率将使用第三方限制器,替代数据库 Add a custom limiter and use it for limiting users' download rate instead of using database. + > 现在需要`limits`模块 + > Now `limits` module is required. +- 为登录和API登录添加限制器 Add limiter for login and API login. +- `sqlite3`数据库调整为WAL模式并增大缓存 Change journal mode to WAL and enlarge cache size for `sqlite3` database. +- 将下载token放入内存中而不是文件数据库中 Put download token in memory database instead of filesystem database. +- 加速`best_score`表多次查询,表现为歌曲排行榜查询性能提升 Accelerate multiple querying in `best_score` table, which results in performance improvement of song ranklist query. +- 优化歌曲下载部分 Make some optimization for downloading songs. +- **修复更新recent 10时可能出现的死循环问题 Fix a bug that there is a endless loop in calculating recent 10 updating.** (due to 6fcca179182775615115cdb255b3a8223831a8a0) +- 修复课题模式成绩没有rating的问题 Fix a bug that scores in course mode cannot calculate rating. +- 修正搭档数值 Fix a character's value. +- 邮箱长度最大限制提升到64 Change the email max length to 64. +- 新增API接口来获取用户身份与权限 Add a method of API for getting users' roles and powers. +- 新增API接口来修改用户信息 Add a method of API to change the user's info. +- 为API的`GET`请求添加`query`参数支持 Add support for the `query` param in API's `GET` requests. +- 修复API的`best30`接口曲目无数据导致出错的问题 Fix a bug that `best30` of API cannot have scores whose songs are not in database. +- 修复API的`recent30`接口用户成绩数量不足导致出错的问题 Fix a bug that users with no recent scores cannot get `recent30` via API. ## 运行环境与依赖 Running environment and requirements - Windows/Linux/Mac OS/Android...