Skip to content

Commit

Permalink
Merge pull request #90 from Lost-MSth/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Lost-MSth authored Jan 26, 2023
2 parents 1fde280 + fbd5d83 commit e21cf89
Show file tree
Hide file tree
Showing 28 changed files with 544 additions and 261 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ __pycache__/

# setting/config files
latest version/config/
latest version/config.py
latest version/config.py

# song data
latest version/database/songs/
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
### Version 2.10.2

- 适用于Arcaea 4.1.7版本 For Arcaea 4.1.7
- 新搭档 **红(冬日** 已解锁 Unlock the character **Kou(Winter)**.
- 新增记录数据库来记录全部的游玩历史分数 Add a log database to record all playing scores.
- 新增设置选项,可选择阻止或接受unranked成绩 Add a config option that can be used to forbid unranked scores.
- 为自定义异常添加简明的warning日志 Add brief warning logs for custom exceptions.
- 修复flask应用启动前出现异常,日志无法正确地指出异常的问题 Fix a bug that if an exception is raised before flask app runs, logger will not work well.
- 现在初始化文件中JSON文件可以是模块支持的其它编码格式 Now initial files can be other encoding types which are supported by JSON module.
- `run.bat`在报错时会停下而不是一闪而过了 Make the `run.bat` script pause when meeting an error. #82
- 新增API接口查询单谱排行 Add an API endpoint for getting the rank list of a song's chart. #81
### Version 2.10.3

- 适用于Arcaea 4.2.0版本 For Arcaea 4.2.0
- 新搭档 **拉格兰(Aria** 已解锁 Unlock the character **Lagrange(Aria)**. (Lack of its values)
- 新搭档 **忘却(Apophenia)** 已解锁 Unlock the character **Lethe(Apophenia)**.
- 新增选项取消歌曲文件哈希预计算 Add an option to disable song file hash pre-calculation.
- 新增对世界模式中地图本地限制歌曲解锁或挑战解锁以及地图中台阶上限制歌曲难度的支持 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.
- 恢复使用云存档覆盖成绩的功能 Restore the feature that cloud save can be used to cover best scores.
- 捕获`Authorization`不在请求头导致的报错 Capture error that the request does not have `Authorization` in header.
- 修复客户端版本校验中请求头不存在`AppVersion`也能通过校验的逻辑错误 Fix a bug that headers without `AppVersion` are allowed in client version checking.
- 新增增删改歌曲信息的API接口 Add some API endpoints, including creating, changing, deleting song info.

## 运行环境与依赖 Running environment and requirements

Expand Down
15 changes: 11 additions & 4 deletions latest version/api/api_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,14 @@ def wrapped_view(*args, **kwargs):
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,写成了修饰器\
parameters: \
`request`: `Request` - 当前请求\
`required_keys`: `list` - 必须的参数\
`optional_keys`: `list` - 可选的参数
`optional_keys`: `list` - 可选的参数\
`must_change`: `bool` - 当全都是可选参数时,是否必须有至少一项修改
'''

def decorator(view):
Expand All @@ -67,8 +68,11 @@ def wrapped_view(*args, **kwargs):
else:
if request.method == 'GET' and 'query' in request.args:
# 处理axios没法GET传data的问题
json_data = loads(
b64decode(request.args['query']).decode())
try:
json_data = loads(
b64decode(request.args['query']).decode())
except:
raise PostError(api_error_code=-105)
else:
json_data = {}

Expand All @@ -81,6 +85,9 @@ def wrapped_view(*args, **kwargs):
if key in json_data:
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 wrapped_view
Expand Down
4 changes: 3 additions & 1 deletion latest version/api/api_code.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from core.error import ArcError
from flask import jsonify

from core.error import ArcError

default_error = ArcError('Unknown Error')


Expand All @@ -9,6 +10,7 @@
-1: 'See status code', # 基础错误
-2: 'No data',
-3: 'No data or user', # 不确定是无数据还是无用户
-4: 'Data exist',
-100: 'Invalid post data', # 1xx数据错误
-101: 'Invalid data type',
-102: 'Invalid query parameter',
Expand Down
52 changes: 50 additions & 2 deletions latest version/api/songs.py
Original file line number Diff line number Diff line change
@@ -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.song import Song
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_code import success_return
Expand All @@ -21,6 +22,39 @@ def songs_song_get(user, song_id):
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'])
@role_required(request, ['select', 'select_song_info'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
Expand All @@ -43,6 +77,20 @@ def songs_get(data, user):
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'])
@role_required(request, ['select', 'select_song_rank', 'select_song_rank_top'])
@request_json_handle(request, optional_keys=['limit'])
Expand Down
5 changes: 2 additions & 3 deletions latest version/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def users_user_get(user, user_id):

@bp.route('/<int:user_id>', methods=['PUT'])
@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
def users_user_put(data, user, user_id):
'''修改一个用户'''
Expand All @@ -103,8 +103,7 @@ def users_user_put(data, user, user_id):
raise InputError('Ticket must be int')
u.ticket = data['ticket']
r['ticket'] = u.ticket
if r:
u.update_columns(d=r)
u.update_columns(d=r)
return success_return(r)


Expand Down
5 changes: 3 additions & 2 deletions latest version/core/api_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,11 +134,12 @@ def login(self, name: str = None, password: str = None, ip: str = None) -> None:
x = self.c.fetchone()
if x is None:
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] == '':
raise UserBan('The user `%s` is banned.' % self.name)
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]
now = int(time() * 1000)
Expand Down
2 changes: 2 additions & 0 deletions latest version/core/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ class Config:
USE_PROXY_FIX = False
USE_CORS = False

SONG_FILE_HASH_PRE_CALCULATE = True

GAME_API_PREFIX = '/join/21'

ALLOW_APPVERSION = [] # list[str]
Expand Down
2 changes: 1 addition & 1 deletion latest version/core/constant.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from .config_manager import Config

ARCAEA_SERVER_VERSION = 'v2.10.2'
ARCAEA_SERVER_VERSION = 'v2.10.3'


class Constant:
Expand Down
21 changes: 12 additions & 9 deletions latest version/core/download.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from flask import url_for

from .config_manager import Config
from .constant import Constant
from .error import NoAccess
from .limiter import ArcLimiter
Expand Down Expand Up @@ -167,7 +168,7 @@ def hash(self) -> str:

class DownloadList(UserDownload):
'''
下载列表类\
下载列表类
properties: `user` - `User`类或子类的实例
'''

Expand All @@ -184,10 +185,11 @@ def __init__(self, c_m=None, user=None) -> None:
def initialize_cache(cls) -> None:
'''初始化歌曲数据缓存,包括md5、文件目录遍历、解析songlist'''
SonglistParser()
x = cls()
x.url_flag = False
x.add_songs()
del x
if Config.SONG_FILE_HASH_PRE_CALCULATE:
x = cls()
x.url_flag = False
x.add_songs()
del x

@staticmethod
def clear_all_cache() -> None:
Expand All @@ -212,9 +214,10 @@ def insert_download_tokens(self) -> None:
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 SonglistParser.is_available_file(song_id, i):
r.append(i)
for i in os.scandir(os.path.join(Constant.SONG_FILE_FOLDER_PATH, song_id)):
file_name = i.name
if i.is_file() and SonglistParser.is_available_file(song_id, file_name):
r.append(file_name)
return r

def add_one_song(self, song_id: str) -> None:
Expand Down Expand Up @@ -265,7 +268,7 @@ def add_one_song(self, song_id: str) -> None:
@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)))
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:
'''添加一个或多个歌曲到下载列表,若`song_ids`为空,则添加所有歌曲'''
Expand Down
4 changes: 3 additions & 1 deletion latest version/core/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def __init__(self, message=None, error_code=108, api_error_code=-100, extra_data

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):
Expand Down
2 changes: 2 additions & 0 deletions latest version/core/init.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,8 @@ def check_song_file(self) -> bool:
self.logger.info("Start to initialize song data...")
try:
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!')
except Exception as e:
self.logger.error(format_exc())
Expand Down
Loading

0 comments on commit e21cf89

Please sign in to comment.