7 Commits

Author SHA1 Message Date
Lost-MSth
e21cf89b1b Merge pull request #90 from Lost-MSth/dev
Dev
2023-01-26 21:19:32 +08:00
Lost-MSth
fbd5d83626 Update to v2.10.3 [Enhance] map local restrict
- Add support for locally restricting songs or challenges in the map of world mode
2023-01-26 21:15:09 +08:00
Lost-MSth
9fbdcd5edb [Refactor][Enhance] unlock items & Steps' difficulty restrict
- Refactor some codes about unlocking or locking some users' packs and singles
- Add support for restricting songs' difficulty in the map's steps of world mode
2023-01-24 16:45:09 +08:00
Lost-MSth
88d949fc18 [Bug fix] block headers without app version
- Fix a bug that headers without `AppVersion` are allowed in client version checking.
2023-01-22 22:09:15 +08:00
Lost-MSth
9636722709 [Enhance] Add API about songs
- Add some API endpoints, including creating, changing, deleting song info.
2023-01-22 18:39:15 +08:00
Lost-MSth
9c90d6ef89 [Enhance] Add an option about file hash pre-calc
- Add an option to disable song file hash pre-calculation
2023-01-14 18:32:53 +08:00
Lost-MSth
af03a48134 [Enhance] Cloud save update scores & capture auth error
- Restore the feature that cloud save can be used to cover best scores
- Capture error that the request does not have `Authorization` in header
2023-01-13 17:50:01 +08:00
28 changed files with 555 additions and 272 deletions

5
.gitignore vendored
View File

@@ -17,4 +17,7 @@ __pycache__/
# setting/config files # setting/config files
latest version/config/ latest version/config/
latest version/config.py latest version/config.py
# song data
latest version/database/songs/

View File

@@ -73,17 +73,17 @@ 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. > Tips: When updating, please keep the original database in case of data loss.
### Version 2.10.2 ### Version 2.10.3
- 适用于Arcaea 4.1.7版本 For Arcaea 4.1.7 - 适用于Arcaea 4.2.0版本 For Arcaea 4.2.0
- 新搭档 **红(冬日** 已解锁 Unlock the character **Kou(Winter)**. - 新搭档 **拉格兰Aria** 已解锁 Unlock the character **Lagrange(Aria)**. (Lack of its values)
-增记录数据库来记录全部的游玩历史分数 Add a log database to record all playing scores. -搭档 **忘却Apophenia** 已解锁 Unlock the character **Lethe(Apophenia)**.
- 新增设置选项可选择阻止或接受unranked成绩 Add a config option that can be used to forbid unranked scores. - 新增选项取消歌曲文件哈希预计算 Add an option to disable song file hash pre-calculation.
- 为自定义异常添加简明的warning日志 Add brief warning logs for custom exceptions. - 新增对世界模式中地图本地限制歌曲解锁或挑战解锁以及地图中台阶上限制歌曲难度的支持 Add support for restricting songs' difficulty in the map's steps of world mode and locally restricting unlocking songs or challenges in the map of world mode.
- 修复flask应用启动前出现异常日志无法正确地指出异常的问题 Fix a bug that if an exception is raised before flask app runs, logger will not work well. - 恢复使用云存档覆盖成绩的功能 Restore the feature that cloud save can be used to cover best scores.
- 现在初始化文件中JSON文件可以是模块支持的其它编码格式 Now initial files can be other encoding types which are supported by JSON module. - 捕获`Authorization`不在请求头导致的报错 Capture error that the request does not have `Authorization` in header.
- `run.bat`在报错时会停下而不是一闪而过了 Make the `run.bat` script pause when meeting an error. #82 - 修复客户端版本校验中请求头不存在`AppVersion`也能通过校验的逻辑错误 Fix a bug that headers without `AppVersion` are allowed in client version checking.
- 新增API接口查询单谱排行 Add an API endpoint for getting the rank list of a song's chart. #81 - 新增增删改歌曲信息的API接口 Add some API endpoints, including creating, changing, deleting song info.
## 运行环境与依赖 Running environment and requirements ## 运行环境与依赖 Running environment and requirements

View File

@@ -48,13 +48,14 @@ def role_required(request, powers=[]):
return decorator return decorator
def request_json_handle(request, required_keys=[], optional_keys=[]): def request_json_handle(request, required_keys: list = [], optional_keys: list = [], must_change: bool = False):
''' '''
提取post参数返回dict写成了修饰器\ 提取post参数返回dict写成了修饰器\
parameters: \ parameters: \
`request`: `Request` - 当前请求\ `request`: `Request` - 当前请求\
`required_keys`: `list` - 必须的参数\ `required_keys`: `list` - 必须的参数\
`optional_keys`: `list` - 可选的参数 `optional_keys`: `list` - 可选的参数\
`must_change`: `bool` - 当全都是可选参数时,是否必须有至少一项修改
''' '''
def decorator(view): def decorator(view):
@@ -67,8 +68,11 @@ def request_json_handle(request, required_keys=[], optional_keys=[]):
else: else:
if request.method == 'GET' and 'query' in request.args: if request.method == 'GET' and 'query' in request.args:
# 处理axios没法GET传data的问题 # 处理axios没法GET传data的问题
json_data = loads( try:
b64decode(request.args['query']).decode()) json_data = loads(
b64decode(request.args['query']).decode())
except:
raise PostError(api_error_code=-105)
else: else:
json_data = {} json_data = {}
@@ -81,6 +85,9 @@ def request_json_handle(request, required_keys=[], optional_keys=[]):
if key in json_data: if key in json_data:
data[key] = json_data[key] data[key] = json_data[key]
if must_change and not data:
return error_return(PostError('No change', api_error_code=-100))
return view(data, *args, **kwargs) return view(data, *args, **kwargs)
return wrapped_view return wrapped_view

View File

@@ -1,6 +1,7 @@
from core.error import ArcError
from flask import jsonify from flask import jsonify
from core.error import ArcError
default_error = ArcError('Unknown Error') default_error = ArcError('Unknown Error')
@@ -9,6 +10,7 @@ CODE_MSG = {
-1: 'See status code', # 基础错误 -1: 'See status code', # 基础错误
-2: 'No data', -2: 'No data',
-3: 'No data or user', # 不确定是无数据还是无用户 -3: 'No data or user', # 不确定是无数据还是无用户
-4: 'Data exist',
-100: 'Invalid post data', # 1xx数据错误 -100: 'Invalid post data', # 1xx数据错误
-101: 'Invalid data type', -101: 'Invalid data type',
-102: 'Invalid query parameter', -102: 'Invalid query parameter',

View File

@@ -1,8 +1,9 @@
from core.error import NoData, InputError from flask import Blueprint, request
from core.error import DataExist, InputError, NoData
from core.rank import RankList from core.rank import RankList
from core.song import Song from core.song import Song
from core.sql import Connect, Query, Sql from core.sql import Connect, Query, Sql
from flask import Blueprint, request
from .api_auth import api_try, request_json_handle, role_required from .api_auth import api_try, request_json_handle, role_required
from .api_code import success_return from .api_code import success_return
@@ -21,6 +22,39 @@ def songs_song_get(user, song_id):
return success_return(s.to_dict()) return success_return(s.to_dict())
@bp.route('/<string:song_id>', methods=['PUT'])
@role_required(request, ['change'])
@request_json_handle(request, optional_keys=['name', 'charts'], must_change=True)
@api_try
def songs_song_put(data, user, song_id):
'''修改歌曲信息'''
with Connect() as c:
s = Song(c, song_id).select()
if 'name' in data:
s.name = str(data['name'])
if 'charts' in data:
for i in data['charts']:
if 'difficulty' in i and 'chart_const' in i:
s.charts[i['difficulty']].defnum = round(
i['chart_const'] * 10)
s.update()
return success_return(s.to_dict())
@bp.route('/<string:song_id>', methods=['DELETE'])
@role_required(request, ['change'])
@api_try
def songs_song_delete(user, song_id):
'''删除歌曲信息'''
with Connect() as c:
s = Song(c, song_id)
if not s.select_exists():
raise NoData(f'No such song: `{song_id}`')
s.delete()
return success_return()
@bp.route('', methods=['GET']) @bp.route('', methods=['GET'])
@role_required(request, ['select', 'select_song_info']) @role_required(request, ['select', 'select_song_info'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS) @request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
@@ -43,6 +77,20 @@ def songs_get(data, user):
return success_return([x.to_dict() for x in r]) return success_return([x.to_dict() for x in r])
@bp.route('', methods=['POST'])
@role_required(request, ['change'])
@request_json_handle(request, ['song_id', 'charts'], ['name'])
@api_try
def songs_post(data, user):
'''添加歌曲信息'''
with Connect() as c:
s = Song(c).from_dict(data)
if s.select_exists():
raise DataExist(f'Song `{s.song_id}` already exists')
s.insert()
return success_return(s.to_dict())
@bp.route('/<string:song_id>/<int:difficulty>/rank', methods=['GET']) @bp.route('/<string:song_id>/<int:difficulty>/rank', methods=['GET'])
@role_required(request, ['select', 'select_song_rank', 'select_song_rank_top']) @role_required(request, ['select', 'select_song_rank', 'select_song_rank_top'])
@request_json_handle(request, optional_keys=['limit']) @request_json_handle(request, optional_keys=['limit'])

View File

@@ -78,7 +78,7 @@ def users_user_get(user, user_id):
@bp.route('/<int:user_id>', methods=['PUT']) @bp.route('/<int:user_id>', methods=['PUT'])
@role_required(request, ['change']) @role_required(request, ['change'])
@request_json_handle(request, optional_keys=['name', 'password', 'user_code', 'ticket', 'email']) @request_json_handle(request, optional_keys=['name', 'password', 'user_code', 'ticket', 'email'], must_change=True)
@api_try @api_try
def users_user_put(data, user, user_id): def users_user_put(data, user, user_id):
'''修改一个用户''' '''修改一个用户'''
@@ -103,8 +103,7 @@ def users_user_put(data, user, user_id):
raise InputError('Ticket must be int') raise InputError('Ticket must be int')
u.ticket = data['ticket'] u.ticket = data['ticket']
r['ticket'] = u.ticket r['ticket'] = u.ticket
if r: u.update_columns(d=r)
u.update_columns(d=r)
return success_return(r) return success_return(r)

View File

@@ -134,11 +134,12 @@ class APIUser(UserOnline):
x = self.c.fetchone() x = self.c.fetchone()
if x is None: if x is None:
raise NoData('The user `%s` does not exist.' % raise NoData('The user `%s` does not exist.' %
self.name, api_error_code=-201) self.name, api_error_code=-201, status=401)
if x[1] == '': if x[1] == '':
raise UserBan('The user `%s` is banned.' % self.name) raise UserBan('The user `%s` is banned.' % self.name)
if self.hash_pwd != x[1]: if self.hash_pwd != x[1]:
raise NoAccess('The password is incorrect.', api_error_code=-201) raise NoAccess('The password is incorrect.',
api_error_code=-201, status=401)
self.user_id = x[0] self.user_id = x[0]
now = int(time() * 1000) now = int(time() * 1000)

View File

@@ -10,6 +10,8 @@ class Config:
USE_PROXY_FIX = False USE_PROXY_FIX = False
USE_CORS = False USE_CORS = False
SONG_FILE_HASH_PRE_CALCULATE = True
GAME_API_PREFIX = '/join/21' GAME_API_PREFIX = '/join/21'
ALLOW_APPVERSION = [] # list[str] ALLOW_APPVERSION = [] # list[str]

View File

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

View File

@@ -5,6 +5,7 @@ from time import time
from flask import url_for from flask import url_for
from .config_manager import Config
from .constant import Constant from .constant import Constant
from .error import NoAccess from .error import NoAccess
from .limiter import ArcLimiter from .limiter import ArcLimiter
@@ -167,7 +168,7 @@ class UserDownload:
class DownloadList(UserDownload): class DownloadList(UserDownload):
''' '''
下载列表类\ 下载列表类
properties: `user` - `User`类或子类的实例 properties: `user` - `User`类或子类的实例
''' '''
@@ -184,10 +185,11 @@ class DownloadList(UserDownload):
def initialize_cache(cls) -> None: def initialize_cache(cls) -> None:
'''初始化歌曲数据缓存包括md5、文件目录遍历、解析songlist''' '''初始化歌曲数据缓存包括md5、文件目录遍历、解析songlist'''
SonglistParser() SonglistParser()
x = cls() if Config.SONG_FILE_HASH_PRE_CALCULATE:
x.url_flag = False x = cls()
x.add_songs() x.url_flag = False
del x x.add_songs()
del x
@staticmethod @staticmethod
def clear_all_cache() -> None: def clear_all_cache() -> None:
@@ -212,9 +214,10 @@ class DownloadList(UserDownload):
def get_one_song_file_names(song_id: str) -> list: def get_one_song_file_names(song_id: str) -> list:
'''获取一个歌曲文件夹下的所有合法文件名有lru缓存''' '''获取一个歌曲文件夹下的所有合法文件名有lru缓存'''
r = [] r = []
for i in os.listdir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id)): for i in os.scandir(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 SonglistParser.is_available_file(song_id, i): file_name = i.name
r.append(i) if i.is_file() and SonglistParser.is_available_file(song_id, file_name):
r.append(file_name)
return r return r
def add_one_song(self, song_id: str) -> None: def add_one_song(self, song_id: str) -> None:
@@ -265,7 +268,7 @@ class DownloadList(UserDownload):
@lru_cache() @lru_cache()
def get_all_song_ids() -> list: def get_all_song_ids() -> list:
'''获取全歌曲文件夹列表有lru缓存''' '''获取全歌曲文件夹列表有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))) return [i.name for i in os.scandir(Constant.SONG_FILE_FOLDER_PATH) if i.is_dir()]
def add_songs(self, song_ids: list = None) -> None: def add_songs(self, song_ids: list = None) -> None:
'''添加一个或多个歌曲到下载列表,若`song_ids`为空,则添加所有歌曲''' '''添加一个或多个歌曲到下载列表,若`song_ids`为空,则添加所有歌曲'''

View File

@@ -19,7 +19,9 @@ class InputError(ArcError):
class DataExist(ArcError): class DataExist(ArcError):
'''数据存在''' '''数据存在'''
pass
def __init__(self, message=None, error_code=108, api_error_code=-4, extra_data=None, status=200) -> None:
super().__init__(message, error_code, api_error_code, extra_data, status)
class NoData(ArcError): class NoData(ArcError):

View File

@@ -286,6 +286,8 @@ class FileChecker:
self.logger.info("Start to initialize song data...") self.logger.info("Start to initialize song data...")
try: try:
DownloadList.initialize_cache() DownloadList.initialize_cache()
if not Config.SONG_FILE_HASH_PRE_CALCULATE:
self.logger.info('Song file hash pre-calculate is disabled.')
self.logger.info('Complete!') self.logger.info('Complete!')
except Exception as e: except Exception as e:
self.logger.error(format_exc()) self.logger.error(format_exc())

View File

@@ -1,18 +1,23 @@
from .sql import Connect, Sql
from .score import Score
from .download import DownloadList from .download import DownloadList
from .save import SaveData
from .score import Score
from .sql import Connect, Sql
from .user import User
class BaseOperation: class BaseOperation:
name: str = None _name: str = None
def __init__(self): def __init__(self, *args, **kwargs):
pass pass
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs) -> None:
return self.run(*args, **kwargs) return self.run(*args, **kwargs)
def run(self, *args, **kwargs): def set_params(self, *args, **kwargs) -> None:
pass
def run(self, *args, **kwargs) -> None:
raise NotImplementedError raise NotImplementedError
@@ -20,7 +25,7 @@ class RefreshAllScoreRating(BaseOperation):
''' '''
刷新所有成绩的评分 刷新所有成绩的评分
''' '''
name = 'refresh_all_score_rating' _name = 'refresh_all_score_rating'
def run(self): def run(self):
# 追求效率不用Song类尽量不用对象 # 追求效率不用Song类尽量不用对象
@@ -59,9 +64,181 @@ class RefreshAllScoreRating(BaseOperation):
class RefreshSongFileCache(BaseOperation): class RefreshSongFileCache(BaseOperation):
''' '''
刷新歌曲文件缓存包括文件hash缓存重建、文件目录重遍历、songlist重解析 刷新歌曲文件缓存包括文件hash缓存重建、文件目录重遍历、songlist重解析
注意在设置里预先计算关闭的情况下文件hash不会计算
''' '''
name = 'refresh_song_file_cache' _name = 'refresh_song_file_cache'
def run(self): def run(self):
DownloadList.clear_all_cache() DownloadList.clear_all_cache()
DownloadList.initialize_cache() DownloadList.initialize_cache()
class SaveUpdateScore(BaseOperation):
'''
云存档更新成绩,是覆盖式更新\
提供user参数时只更新该用户的成绩否则更新所有用户的成绩
'''
_name = 'save_update_score'
def __init__(self, user=None):
self.user = user
def set_params(self, user_id: int = None, *args, **kwargs):
if user_id is not None:
self.user = User()
self.user.user_id = int(user_id)
def run(self, user=None):
'''
parameter:
`user` - `User`类或子类的实例
'''
if user is not None:
self.user = user
if self.user is not None and self.user.user_id is not None:
self._one_user_update()
else:
self._all_update()
def _one_user_update(self):
with Connect() as c:
save = SaveData(c)
save.select_scores(self.user)
clear_state = {f'{i["song_id"]}{i["difficulty"]}': i['clear_type']
for i in save.clearlamps_data}
song_id_1 = [i['song_id'] for i in save.scores_data]
song_id_2 = [i['song_id'] for i in save.clearlamps_data]
song_id = list(set(song_id_1 + song_id_2))
c.execute(
f'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart where song_id in ({','.join(['?']*len(song_id))})''', song_id)
x = c.fetchall()
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4]]
for i in x} # chart const * 10
new_scores = []
for i in save.scores_data:
rating = 0
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
y = f'{i["song_id"]}{i["difficulty"]}'
if y in clear_state:
clear_type = clear_state[y]
else:
clear_type = 0
new_scores.append((self.user.user_id, i['song_id'], i['difficulty'], i['score'], i['shiny_perfect_count'], i['perfect_count'],
i['near_count'], i['miss_count'], i['health'], i['modifier'], i['time_played'], clear_type, clear_type, rating))
c.executemany(
'''insert or replace into best_score values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', new_scores)
def _all_update(self):
with Connect() as c:
c.execute(
f'''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''')
for y in c.fetchall():
user = User()
user.user_id = y[0]
save = SaveData(c)
save.select_scores(user)
clear_state = {f'{i["song_id"]}{i["difficulty"]}': i['clear_type']
for i in save.clearlamps_data}
new_scores = []
for i in save.scores_data:
rating = 0
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
y = f'{i["song_id"]}{i["difficulty"]}'
if y in clear_state:
clear_type = clear_state[y]
else:
clear_type = 0
new_scores.append((user.user_id, i['song_id'], i['difficulty'], i['score'], i['shiny_perfect_count'], i['perfect_count'],
i['near_count'], i['miss_count'], i['health'], i['modifier'], i['time_played'], clear_type, clear_type, rating))
c.executemany(
'''insert or replace into best_score values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)''', new_scores)
class UnlockUserItem(BaseOperation):
'''
全解锁/锁定用户物品\
提供user参数时只更新该用户的否则更新所有用户的
'''
_name = 'unlock_user_item'
ALLOW_TYPES = ['single', 'pack', 'world_song',
'course_banner', 'world_unlock']
def __init__(self, user=None, method: str = 'unlock', item_types: list = ['single', 'pack']):
self.user = user
self.set_params(method=method, item_types=item_types)
def set_params(self, user_id: int = None, method: str = 'unlock', item_types: list = ['single', 'pack'], *args, **kwargs):
if user_id is not None:
self.user = User()
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]):
self.item_types = item_types
def run(self):
if self.user is not None and self.user.user_id is not None:
if self.method == 'unlock':
self._one_user_insert()
else:
self._one_user_delete()
else:
if self.method == 'unlock':
self._all_insert()
else:
self._all_delete()
def _one_user_insert(self):
with Connect() as c:
c.execute(
f'''select item_id, type from item where type in ({','.join(['?'] * len(self.item_types))})''', self.item_types)
sql_list = [(self.user.user_id, i[0], i[1])
for i in c.fetchall()]
c.executemany(
'''insert or ignore into user_item values (?, ?, ?, 1)''', sql_list)
def _all_insert(self):
with Connect() as c:
c.execute('''select user_id from user''')
x = c.fetchall()
c.execute(
f'''select item_id, type from item where type in ({','.join(['?'] * len(self.item_types))})''', self.item_types)
y = c.fetchall()
sql_list = [(i[0], j[0], j[1])
for i in x for j in y]
c.executemany(
'''insert or ignore into user_item values (?, ?, ?, 1)''', sql_list)
def _one_user_delete(self):
with Connect() as c:
c.execute(
f'''delete from user_item where user_id = ? and type in ({','.join(['?'] * len(self.item_types))})''', (self.user.user_id, *self.item_types))
def _all_delete(self):
with Connect() as c:
c.execute(
f'''delete from user_item where type in ({','.join(['?'] * len(self.item_types))})''', self.item_types)

View File

@@ -22,6 +22,8 @@ class Purchase:
self.items: list = [] self.items: list = []
# TODO: "discount_reason": "extend"
@property @property
def price_displayed(self) -> int: def price_displayed(self) -> int:
''' '''

View File

@@ -3,7 +3,7 @@ from time import time
from .config_manager import Config from .config_manager import Config
from .constant import Constant from .constant import Constant
from .error import InputError from .error import InputError, NoData
from .util import md5 from .util import md5
@@ -54,6 +54,20 @@ class SaveData:
} }
} }
def select_scores(self, user) -> None:
'''
parameter: `user` - `User`类或子类的实例
'''
self.user = user
self.c.execute('''select scores_data, clearlamps_data from user_save where user_id=:a''',
{'a': user.user_id})
x = self.c.fetchone()
if not x:
raise NoData(f'User `{user.user_id}` has no cloud save data')
self.scores_data: list = json.loads(x[0])[""]
self.clearlamps_data: list = json.loads(x[1])[""]
def select_all(self, user) -> None: def select_all(self, user) -> None:
''' '''
parameter: `user` - `User`类或子类的实例 parameter: `user` - `User`类或子类的实例

View File

@@ -102,7 +102,7 @@ class Score:
return True return True
@staticmethod @staticmethod
def calculate_rating(defnum: int, score: int) -> float: def calculate_rating(defnum: float, score: int) -> float:
'''计算rating谱面定数小于等于0视为Unrank返回值会为-1这里的defnum = Chart const''' '''计算rating谱面定数小于等于0视为Unrank返回值会为-1这里的defnum = Chart const'''
if not defnum or defnum <= 0: if not defnum or defnum <= 0:
# 谱面没定数或者定数小于等于0被视作Unrank # 谱面没定数或者定数小于等于0被视作Unrank

View File

@@ -43,7 +43,7 @@ class Chart:
class Song: class Song:
def __init__(self, c=None, song_id=None) -> None: def __init__(self, c=None, song_id: str = None) -> None:
self.c = c self.c = c
self.song_id: str = song_id self.song_id: str = song_id
self.name: str = None self.name: str = None
@@ -68,10 +68,39 @@ class Song:
self.charts[3].defnum = x[5] self.charts[3].defnum = x[5]
return self return self
def from_dict(self, d: dict) -> 'Song':
self.song_id = d['song_id']
self.name = d.get('name', '')
self.charts = [Chart(self.c, self.song_id, 0), Chart(self.c, self.song_id, 1), Chart(
self.c, self.song_id, 2), Chart(self.c, self.song_id, 3)]
for i in range(4):
self.charts[i].defnum = -10
for chart in d['charts']:
self.charts[chart['difficulty']].defnum = round(
chart['chart_const'] * 10)
return self
def delete(self) -> None:
self.c.execute(
'''delete from chart where song_id=?''', (self.song_id,))
def update(self) -> None:
'''全部更新'''
self.c.execute(
'''update chart set name=?, rating_pst=?, rating_prs=?, rating_ftr=?, rating_byn=? where song_id=?''', (self.name, self.charts[0].defnum, self.charts[1].defnum, self.charts[2].defnum, self.charts[3].defnum, self.song_id))
def insert(self) -> None: def insert(self) -> None:
self.c.execute( self.c.execute(
'''insert into chart values (?,?,?,?,?,?)''', (self.song_id, self.name, self.charts[0].defnum, self.charts[1].defnum, self.charts[2].defnum, self.charts[3].defnum)) '''insert into chart values (?,?,?,?,?,?)''', (self.song_id, self.name, self.charts[0].defnum, self.charts[1].defnum, self.charts[2].defnum, self.charts[3].defnum))
def select_exists(self, song_id: str = None) -> bool:
if song_id is not None:
self.song_id = song_id
self.c.execute(
'''select exists(select * from chart where song_id=?)''', (self.song_id,))
return bool(self.c.fetchone()[0])
def select(self, song_id: str = None) -> 'Song': def select(self, song_id: str = None) -> 'Song':
if song_id is not None: if song_id is not None:
self.song_id = song_id self.song_id = song_id
@@ -80,6 +109,6 @@ class Song:
'a': self.song_id}) 'a': self.song_id})
x = self.c.fetchone() x = self.c.fetchone()
if x is None: if x is None:
raise NoData('The song `%s` does not exist.' % self.song_id) raise NoData(f'The song `{self.song_id}` does not exist.')
return self.from_list(x) return self.from_list(x)

View File

@@ -243,8 +243,9 @@ class UserLogin(User):
'name': self.name}) 'name': self.name})
x = self.c.fetchone() x = self.c.fetchone()
if x is None: if x is None:
raise NoData('Username does not exist.', 104) raise NoData(f'Username `{self.name}` does not exist.', 104)
self.user_id = x[0]
self.now = int(time.time() * 1000) self.now = int(time.time() * 1000)
if x[2] is not None and x[2] != '': if x[2] is not None and x[2] != '':
# 自动封号检查 # 自动封号检查
@@ -255,14 +256,14 @@ class UserLogin(User):
if x[1] == '': if x[1] == '':
# 账号封禁 # 账号封禁
raise UserBan('The account has been banned.', 106) raise UserBan(
f'The account `{self.user_id}` has been banned.', 106)
if x[1] != self.hash_pwd: if x[1] != self.hash_pwd:
raise NoAccess('Wrong password.', 104) raise NoAccess('Wrong password.', 104)
self.user_id = str(x[0])
self.token = base64.b64encode(hashlib.sha256( self.token = base64.b64encode(hashlib.sha256(
(self.user_id + str(self.now)).encode("utf8") + urandom(8)).digest()).decode() (str(self.user_id) + str(self.now)).encode("utf8") + urandom(8)).digest()).decode()
self.c.execute( self.c.execute(
'''select login_device from login where user_id = :user_id''', {"user_id": self.user_id}) '''select login_device from login where user_id = :user_id''', {"user_id": self.user_id})

View File

@@ -13,13 +13,11 @@ def md5(code: str) -> str:
def get_file_md5(file_path: str) -> str: def get_file_md5(file_path: str) -> str:
'''计算文件MD5''' '''计算文件MD5,假设是文件'''
if not os.path.isfile(file_path):
return None
myhash = hashlib.md5() myhash = hashlib.md5()
with open(file_path, 'rb') as f: with open(file_path, 'rb') as f:
while True: while True:
b = f.read(8096) b = f.read(8192)
if not b: if not b:
break break
myhash.update(b) myhash.update(b)

View File

@@ -50,6 +50,7 @@ class Step:
self.restrict_id: str = None self.restrict_id: str = None
self.restrict_ids: list = [] self.restrict_ids: list = []
self.restrict_type: str = None self.restrict_type: str = None
self.restrict_difficulty: int = None
self.step_type: list = None self.step_type: list = None
self.speed_limit_value: int = None self.speed_limit_value: int = None
self.plus_stamina_value: int = None self.plus_stamina_value: int = None
@@ -61,12 +62,14 @@ class Step:
} }
if self.items: if self.items:
r['items'] = [i.to_dict() for i in self.items] r['items'] = [i.to_dict() for i in self.items]
if self.restrict_id:
r['restrict_id'] = self.restrict_id
if self.restrict_ids:
r['restrict_ids'] = self.restrict_ids
if self.restrict_type: if self.restrict_type:
r['restrict_type'] = self.restrict_type r['restrict_type'] = self.restrict_type
if self.restrict_id:
r['restrict_id'] = self.restrict_id
if self.restrict_ids:
r['restrict_ids'] = self.restrict_ids
if self.restrict_difficulty is not None:
r['restrict_difficulty'] = self.restrict_difficulty
if self.step_type: if self.step_type:
r['step_type'] = self.step_type r['step_type'] = self.step_type
if self.speed_limit_value: if self.speed_limit_value:
@@ -82,6 +85,7 @@ class Step:
self.restrict_id = d.get('restrict_id') self.restrict_id = d.get('restrict_id')
self.restrict_ids = d.get('restrict_ids') self.restrict_ids = d.get('restrict_ids')
self.restrict_type = d.get('restrict_type') self.restrict_type = d.get('restrict_type')
self.restrict_difficulty = d.get('restrict_difficulty')
self.step_type = d.get('step_type') self.step_type = d.get('step_type')
self.speed_limit_value = d.get('speed_limit_value') self.speed_limit_value = d.get('speed_limit_value')
self.plus_stamina_value = d.get('plus_stamina_value') self.plus_stamina_value = d.get('plus_stamina_value')
@@ -102,7 +106,7 @@ class Map:
self.available_from: int = None self.available_from: int = None
self.available_to: int = None self.available_to: int = None
self.is_repeatable: bool = None self.is_repeatable: bool = None
self.require_id: str = None self.require_id: 'str | list[str]' = None
self.require_type: str = None self.require_type: str = None
self.require_value: int = None self.require_value: int = None
self.coordinate: str = None self.coordinate: str = None
@@ -111,6 +115,9 @@ class Map:
self.steps: list = [] self.steps: list = []
self.__rewards: list = None self.__rewards: list = None
self.require_localunlock_songid: str = None
self.require_localunlock_challengeid: str = None
@property @property
def rewards(self) -> list: def rewards(self) -> list:
if self.__rewards is None: if self.__rewards is None:
@@ -151,6 +158,8 @@ class Map:
'custom_bg': self.custom_bg, 'custom_bg': self.custom_bg,
'stamina_cost': self.stamina_cost, 'stamina_cost': self.stamina_cost,
'step_count': self.step_count, 'step_count': self.step_count,
'require_localunlock_songid': self.require_localunlock_songid,
'require_localunlock_challengeid': self.require_localunlock_challengeid,
'steps': [s.to_dict() for s in self.steps], 'steps': [s.to_dict() for s in self.steps],
} }
@@ -170,6 +179,8 @@ class Map:
self.coordinate = raw_dict.get('coordinate') self.coordinate = raw_dict.get('coordinate')
self.custom_bg = raw_dict.get('custom_bg', '') self.custom_bg = raw_dict.get('custom_bg', '')
self.stamina_cost = raw_dict.get('stamina_cost') self.stamina_cost = raw_dict.get('stamina_cost')
self.require_localunlock_songid = raw_dict.get('require_localunlock_songid', '')
self.require_localunlock_challengeid = raw_dict.get('require_localunlock_challengeid', '')
self.steps = [Step().from_dict(s) for s in raw_dict.get('steps')] self.steps = [Step().from_dict(s) for s in raw_dict.get('steps')]
return self return self

View File

@@ -1,24 +1,24 @@
class InitData: 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', char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', 'hikari&tairitsu(reunion)', 'Tairitsu(Axium)', 'Tairitsu(Grievous Lady)', 'stella', 'Hikari & Fisica', 'ilith', 'eto', 'luna', 'shirabe', 'Hikari(Zero)', 'Hikari(Fracture)', 'Hikari(Summer)', 'Tairitsu(Summer)', 'Tairitsu & Trin',
'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)'] 'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)']
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', skill_id = ['gauge_easy', '', '', '', 'note_mirror', 'skill_reunion', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter'] 'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror']
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee', 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, skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0,
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0, 0, 0] 0, 0, 0, 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]
frag1 = [55, 55, 60, 50, 47, 79, 47, 57, 41, 22, 50, 54, 60, 56, 78, 42, 41, 61, 52, 50, 52, 32, 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] 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, 0, 57]
prog1 = [35, 55, 47, 50, 60, 70, 60, 70, 58, 45, 70, 45, 42, 46, 61, 67, 49, 44, 28, 45, 24, 46, 52, 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] 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, 0, 70]
overdrive1 = [35, 55, 25, 50, 47, 70, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18, overdrive1 = [35, 55, 25, 50, 47, 70, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18,
48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 38, 30, 49, 15, 34, 45, 45, 38, 67, 120, 44, 33, 55, 50] 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, 0, 57]
frag20 = [78, 80, 90, 75, 70, 79, 70, 79, 65, 40, 50, 80, 90, 82, 0, 61, 67, 92, 85, 50, 86, 52, 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] 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]
@@ -64,7 +64,7 @@ class InitData:
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase'] '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", 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'] "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']
world_unlocks = ["scenery_chap1", "scenery_chap2", world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"] "scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]

View File

@@ -327,11 +327,10 @@
"is_available": true "is_available": true
} }
], ],
"price": 0, "price": 700,
"orig_price": 700, "orig_price": 700,
"discount_from": 1646784000000, "discount_from": 1646784000000,
"discount_to": 1647388799000, "discount_to": 1647388799000
"discount_reason": "extend"
}, },
{ {
"name": "alice", "name": "alice",
@@ -595,6 +594,11 @@
"id": "finale", "id": "finale",
"is_available": true "is_available": true
}, },
{
"type": "pack",
"id": "epilogue",
"is_available": true
},
{ {
"type": "core", "type": "core",
"amount": 5, "amount": 5,
@@ -622,5 +626,25 @@
], ],
"orig_price": 500, "orig_price": 500,
"price": 500 "price": 500
},
{
"name": "extend_2",
"items": [
{
"type": "pack",
"id": "extend_2",
"is_available": true
},
{
"type": "core",
"amount": 7,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 700,
"price": 700,
"discount_from": 1646784000000,
"discount_to": 1647388799000
} }
] ]

View File

@@ -28,8 +28,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "yourvoiceso", "name": "yourvoiceso",
@@ -48,8 +48,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "crosssoul", "name": "crosssoul",
@@ -68,8 +68,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "impurebird", "name": "impurebird",
@@ -88,8 +88,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "auxesia", "name": "auxesia",
@@ -128,8 +128,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "yozakurafubuki", "name": "yozakurafubuki",
@@ -148,8 +148,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "surrender", "name": "surrender",
@@ -168,8 +168,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "metallicpunisher", "name": "metallicpunisher",
@@ -224,8 +224,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "callmyname", "name": "callmyname",
@@ -244,8 +244,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "fallensquare", "name": "fallensquare",
@@ -264,8 +264,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "dropdead", "name": "dropdead",
@@ -304,8 +304,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "astraltale", "name": "astraltale",
@@ -324,8 +324,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "phantasia", "name": "phantasia",
@@ -380,8 +380,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "dottodot", "name": "dottodot",
@@ -400,8 +400,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "dreadnought", "name": "dreadnought",
@@ -420,8 +420,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "mirzam", "name": "mirzam",
@@ -440,8 +440,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "heavenlycaress", "name": "heavenlycaress",
@@ -460,8 +460,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "filament", "name": "filament",
@@ -480,8 +480,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "avantraze", "name": "avantraze",
@@ -518,8 +518,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "saikyostronger", "name": "saikyostronger",
@@ -556,8 +556,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "einherjar", "name": "einherjar",
@@ -576,8 +576,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "laqryma", "name": "laqryma",
@@ -596,8 +596,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "amygdata", "name": "amygdata",
@@ -616,8 +616,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "altale", "name": "altale",
@@ -636,8 +636,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "feelssoright", "name": "feelssoright",
@@ -656,8 +656,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "scarletcage", "name": "scarletcage",
@@ -712,8 +712,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "badtek", "name": "badtek",
@@ -732,8 +732,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "maliciousmischance", "name": "maliciousmischance",
@@ -788,8 +788,8 @@
], ],
"price": 100, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1646784000000,
"discount_to": 1615852799000 "discount_to": 1647388799000
}, },
{ {
"name": "xeraphinite", "name": "xeraphinite",
@@ -807,7 +807,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "xanatos", "name": "xanatos",
@@ -825,7 +827,9 @@
} }
], ],
"price": 100, "price": 100,
"orig_price": 100 "orig_price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "attraqtia", "name": "attraqtia",
@@ -843,7 +847,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "gimmedablood", "name": "gimmedablood",
@@ -879,7 +885,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "theultimacy", "name": "theultimacy",
@@ -897,7 +905,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "rekkaresonanc", "name": "rekkaresonanc",
@@ -945,7 +955,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "eveninginscarlet", "name": "eveninginscarlet",
@@ -981,7 +993,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "goldenslaughterer", "name": "goldenslaughterer",
@@ -999,7 +1013,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "redolentshape", "name": "redolentshape",
@@ -1017,7 +1033,9 @@
} }
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100,
"discount_from": 1646784000000,
"discount_to": 1647388799000
}, },
{ {
"name": "summerfireworks", "name": "summerfireworks",
@@ -1056,29 +1074,11 @@
"price": 100 "price": 100
}, },
{ {
"name": "internetoverdose", "name": "kissinglucifer",
"items": [ "items": [
{ {
"type": "single", "type": "single",
"id": "internetoverdose", "id": "kissinglucifer",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "macromod",
"items": [
{
"type": "single",
"id": "macromod",
"is_available": true "is_available": true
}, },
{ {
@@ -1110,11 +1110,11 @@
"price": 100 "price": 100
}, },
{ {
"name": "kissinglucifer", "name": "macromod",
"items": [ "items": [
{ {
"type": "single", "type": "single",
"id": "kissinglucifer", "id": "macromod",
"is_available": true "is_available": true
}, },
{ {
@@ -1128,11 +1128,11 @@
"price": 100 "price": 100
}, },
{ {
"name": "headbonkache", "name": "internetoverdose",
"items": [ "items": [
{ {
"type": "single", "type": "single",
"id": "headbonkache", "id": "internetoverdose",
"is_available": true "is_available": true
}, },
{ {
@@ -1163,6 +1163,24 @@
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100
}, },
{
"name": "headbonkache",
"items": [
{
"type": "single",
"id": "headbonkache",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{ {
"name": "picopicotranslation", "name": "picopicotranslation",
"items": [ "items": [
@@ -1218,11 +1236,11 @@
"price": 100 "price": 100
}, },
{ {
"name": "freemyself", "name": "cocorocosmetic",
"items": [ "items": [
{ {
"type": "single", "type": "single",
"id": "freemyself", "id": "cocorocosmetic",
"is_available": true "is_available": true
}, },
{ {
@@ -1236,11 +1254,11 @@
"price": 100 "price": 100
}, },
{ {
"name": "cocorocosmetic", "name": "freemyself",
"items": [ "items": [
{ {
"type": "single", "type": "single",
"id": "cocorocosmetic", "id": "freemyself",
"is_available": true "is_available": true
}, },
{ {
@@ -1270,5 +1288,23 @@
], ],
"orig_price": 100, "orig_price": 100,
"price": 100 "price": 100
},
{
"name": "nullapophenia",
"items": [
{
"type": "single",
"id": "nullapophenia",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
} }
] ]

View File

@@ -105,6 +105,12 @@ if Config.DEPLOY_MODE == 'waitress':
f'{request.remote_addr} - - {request.method} {request.path} {response.status_code}') f'{request.remote_addr} - - {request.method} {request.path} {response.status_code}')
return response return response
# @app.before_request
# def before_request():
# print(request.path)
# print(request.headers)
# print(request.data)
def tcp_server_run(): def tcp_server_run():
if Config.DEPLOY_MODE == 'gevent': if Config.DEPLOY_MODE == 'gevent':

View File

@@ -16,10 +16,9 @@ bp = Blueprint('auth', __name__, url_prefix='/auth')
@arc_try @arc_try
def login(): def login():
headers = request.headers headers = request.headers
if 'AppVersion' in headers: # 版本检查 if Config.ALLOW_APPVERSION: # 版本检查
if Config.ALLOW_APPVERSION: if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION:
if headers['AppVersion'] not in Config.ALLOW_APPVERSION: raise NoAccess('Invalid app version.', 1203)
raise NoAccess('Wrong app version.', 1203)
request.form['grant_type'] request.form['grant_type']
with Connect() as c: with Connect() as c:
@@ -45,15 +44,17 @@ def auth_required(request):
headers = request.headers headers = request.headers
if 'AppVersion' in headers: # 版本检查 if Config.ALLOW_APPVERSION: # 版本检查
if Config.ALLOW_APPVERSION: if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION:
if headers['AppVersion'] not in Config.ALLOW_APPVERSION: return error_return(NoAccess('Invalid app version.', 1203))
return error_return(NoAccess('Wrong app version.', 1203))
with Connect() as c: with Connect() as c:
try: try:
user = UserAuth(c) user = UserAuth(c)
user.token = headers['Authorization'][7:] token = headers.get('Authorization')
if not token:
raise NoAccess('No token.', -4)
user.token = token[7:]
user_id = user.token_get_id() user_id = user.token_get_id()
g.user = user g.user = user
except ArcError as e: except ArcError as e:

View File

@@ -16,10 +16,10 @@ bp = Blueprint('user', __name__, url_prefix='/user')
@bp.route('', methods=['POST']) # 注册接口 @bp.route('', methods=['POST']) # 注册接口
@arc_try @arc_try
def register(): def register():
if 'AppVersion' in request.headers: # 版本检查 headers = request.headers
if Config.ALLOW_APPVERSION: if Config.ALLOW_APPVERSION: # 版本检查
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION: if 'AppVersion' not in headers or headers['AppVersion'] not in Config.ALLOW_APPVERSION:
raise NoAccess('Wrong app version.', 1203) raise NoAccess('Invalid app version.', 1203)
with Connect() as c: with Connect() as c:
new_user = UserRegister(c) new_user = UserRegister(c)

View File

@@ -2,9 +2,10 @@ import os
import time import time
from core.init import FileChecker from core.init import FileChecker
from core.operation import RefreshAllScoreRating, RefreshSongFileCache from core.operation import RefreshAllScoreRating, RefreshSongFileCache, SaveUpdateScore, UnlockUserItem
from core.rank import RankList from core.rank import RankList
from core.sql import Connect from core.sql import Connect
from core.user import User
from flask import Blueprint, flash, redirect, render_template, request, url_for from flask import Blueprint, flash, redirect, render_template, request, url_for
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
@@ -421,7 +422,7 @@ def all_character():
def change_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', 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'] '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']
return render_template('web/changechar.html', skill_ids=skill_ids) return render_template('web/changechar.html', skill_ids=skill_ids)
@@ -605,7 +606,7 @@ def edit_user_purchase():
if 'name' not in request.form and 'user_code' not in request.form: if 'name' not in request.form and 'user_code' not in request.form:
flag = False flag = False
if method == '0': if method == '0':
web.system.unlock_all_user_item(c) UnlockUserItem().run()
else: else:
c.execute( c.execute(
'''delete from user_item where type in ('pack', 'single')''') '''delete from user_item where type in ('pack', 'single')''')
@@ -631,7 +632,9 @@ def edit_user_purchase():
user_id = user_id[0] user_id = user_id[0]
if method == '0': if method == '0':
web.system.unlock_user_item(c, user_id) x = UnlockUserItem()
x.set_params(user_id=user_id)
x.run()
else: else:
c.execute('''delete from user_item where type in ('pack', 'single') and user_id = :user_id''', { c.execute('''delete from user_item where type in ('pack', 'single') and user_id = :user_id''', {
'user_id': user_id}) 'user_id': user_id})
@@ -937,7 +940,7 @@ def update_user_save():
# 全修改 # 全修改
if 'name' not in request.form and 'user_code' not in request.form: if 'name' not in request.form and 'user_code' not in request.form:
flag = False flag = False
web.system.update_all_save(c) SaveUpdateScore().run()
flash("全部用户存档同步成功 Successfully update all users' saves.") flash("全部用户存档同步成功 Successfully update all users' saves.")
else: else:
@@ -957,7 +960,9 @@ def update_user_save():
user_id = c.fetchone() user_id = c.fetchone()
if user_id: if user_id:
user_id = user_id[0] user_id = user_id[0]
web.system.update_one_save(c, user_id) user = User()
user.user_id = user_id
SaveUpdateScore(user).run()
flash("用户存档同步成功 Successfully update the user's saves.") flash("用户存档同步成功 Successfully update the user's saves.")
else: else:

View File

@@ -40,41 +40,6 @@ def update_user_char(c):
(j[0], i[0], i[1], exp, i[2], 0)) (j[0], i[0], i[1], exp, i[2], 0))
def unlock_all_user_item(c):
# 解锁所有用户购买
c.execute('''select user_id from user''')
x = c.fetchall()
c.execute('''select item_id, type from purchase_item''')
y = c.fetchall()
if x and y:
for i in x:
for j in y:
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
'a': i[0], 'b': j[0], 'c': j[1]})
if c.fetchone() == (0,) and j[1] != 'character':
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
'a': i[0], 'b': j[0], 'c': j[1]})
return
def unlock_user_item(c, user_id):
# 解锁用户购买
c.execute('''select item_id, type from purchase_item''')
y = c.fetchall()
for j in y:
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
'a': user_id, 'b': j[0], 'c': j[1]})
if c.fetchone() == (0,) and j[1] != 'character':
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
'a': user_id, 'b': j[0], 'c': j[1]})
return
def get_all_item(): def get_all_item():
# 所有物品数据查询 # 所有物品数据查询
with Connect() as c: with Connect() as c:
@@ -132,61 +97,6 @@ def get_all_purchase():
return re return re
def update_one_save(c, user_id):
# 同步指定用户存档
# 注意best_score表不比较直接覆盖
return
# c.execute('''select scores_data, clearlamps_data from user_save where user_id=:a''', {
# 'a': user_id})
# x = c.fetchone()
# if x:
# scores = json.loads(x[0])[""]
# clearlamps = json.loads(x[1])[""]
# clear_song_id_difficulty = []
# clear_state = []
# for i in clearlamps:
# clear_song_id_difficulty.append(i['song_id']+str(i['difficulty']))
# clear_state.append(i['clear_type'])
# for i in scores:
# rating = server.arcscore.get_one_ptt(
# i['song_id'], i['difficulty'], i['score'])
# if rating < 0:
# rating = 0
# try:
# index = clear_song_id_difficulty.index(
# i['song_id'] + str(i['difficulty']))
# except:
# index = -1
# if index != -1:
# clear_type = clear_state[index]
# else:
# clear_type = 0
# c.execute('''delete from best_score where user_id=:a and song_id=:b and difficulty=:c''', {
# 'a': user_id, 'b': i['song_id'], 'c': i['difficulty']})
# c.execute('''insert into best_score values(:a, :b, :c, :d, :e, :f, :g, :h, :i, :j, :k, :l, :m, :n)''', {
# 'a': user_id, 'b': i['song_id'], 'c': i['difficulty'], 'd': i['score'], 'e': i['shiny_perfect_count'], 'f': i['perfect_count'], 'g': i['near_count'], 'h': i['miss_count'], 'i': i['health'], 'j': i['modifier'], 'k': i['time_played'], 'l': clear_type, 'm': clear_type, 'n': rating})
# ptt = server.arcscore.get_user_ptt(c, user_id) # 更新PTT
# c.execute('''update user set rating_ptt=:a where user_id=:b''', {
# 'a': ptt, 'b': user_id})
# return
def update_all_save(c):
# 同步所有用户存档
c.execute('''select user_id from user_save''')
x = c.fetchall()
if x:
for i in x:
update_one_save(c, i[0])
return
def add_one_present(present_id, expire_ts, description, item_id, item_type, item_amount): def add_one_present(present_id, expire_ts, description, item_id, item_type, item_amount):
# 添加一个奖励 # 添加一个奖励