Code refactoring

- Code refactoring mainly for API
- Delete a useless option in `setting.py`
- Change some constants in Link Play mode
This commit is contained in:
Lost-MSth
2022-07-06 22:07:00 +08:00
parent 9d746eab2d
commit af3e91b3e8
43 changed files with 780 additions and 1867 deletions

View File

@@ -0,0 +1,10 @@
from flask import Blueprint
from . import users
from . import songs
from . import token
bp = Blueprint('api', __name__, url_prefix='/api/v1')
bp.register_blueprint(users.bp)
bp.register_blueprint(songs.bp)
bp.register_blueprint(token.bp)

View File

@@ -1,156 +1,75 @@
import hashlib
import base64
import time
import random
from core.sql import Connect
import functools import functools
from core.api_user import APIUser
from core.error import ArcError, NoAccess, PostError
from core.sql import Connect
from setting import Config from setting import Config
from flask import jsonify
from .api_code import error_return
class User(): def role_required(request, powers=[]):
# 用户类,当数据类型用 '''api token验证写成了修饰器'''
def __init__(self, user_id=None, role='', power=[]):
self.user_id = user_id
self.role = role
self.power = power
def login(auth: str, ip: str):
# 登录接口,返回字典和错误码
try:
auth_decode = bytes.decode(base64.b64decode(auth))
except:
return {}, -100
if not ':' in auth_decode:
return {}, -100
name, password = auth_decode.split(':', 1)
with Connect() as c:
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute('''select user_id, password from user where name = :name''', {
'name': name})
x = c.fetchone()
if x is None:
return {}, -201
if x[1] == '':
return {}, -202
if hash_pwd != x[1]:
return {}, -201
user_id = str(x[0])
now = int(time.time() * 1000)
token = hashlib.sha256(
(user_id + str(random.randint(10000, 99999)) + str(now)).encode("utf8")).hexdigest()
c.execute('''delete from api_login where user_id=?''', (user_id,))
c.execute('''insert into api_login values(?,?,?,?)''',
(user_id, token, now, ip))
return {'token': token, 'user_id': user_id}, 0
def logout(user: User):
# 登出接口,返回错误码
code = 999
with Connect() as c:
c.execute('''delete from api_login where user_id=?''', (user.user_id,))
code = 0
return code
def id_get_role_id(c, user_id):
# user_id获取role_id
role_id = 1
c.execute('''select role_id from user_role where user_id = :a''',
{'a': user_id})
x = c.fetchone()
if x is not None:
role_id = int(x[0])
return role_id
def role_id_get_role(c, role_id):
# role_id获取role
role = ''
c.execute('''select role_name from role where role_id = :a''',
{'a': role_id})
x = c.fetchone()
if x is not None:
role = x[0]
return role
def api_token_get_id(c, token):
# api的token获取user_id
user_id = None
c.execute('''select user_id from api_login where token = :token''', {
'token': token})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
def get_role_power(c, role_id):
# 获取role_id对应power返回列表
role_power = []
c.execute('''select power_name from power where power_id in (select power_id from role_power where role_id=:a)''', {
'a': role_id})
x = c.fetchall()
for i in x:
role_power.append(i[0])
return role_power
def role_required(request, power=[]):
# api token验证写成了修饰器
def decorator(view): def decorator(view):
@functools.wraps(view) @functools.wraps(view)
def wrapped_view(*args, **kwargs): def wrapped_view(*args, **kwargs):
try: try:
request.json # 检查请求json格式 request.json # 检查请求json格式
except: except:
return jsonify({'status': 400, 'code': -1, 'data': {}, 'msg': 'Payload must be a valid json'}) return error_return(PostError('Payload must be a valid json', api_error_code=-1), 400)
if not 'Token' in request.headers: if not 'Token' in request.headers:
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No token'}) return error_return(PostError('No token', api_error_code=-1), 401)
user = User() user = APIUser()
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '': if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
user.user_id = 0 user.user_id = 0
elif power == []: elif powers == []:
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'}) # 无powers则非本地权限API_TOKEN规定的无法访问
return error_return(NoAccess('No permission', api_error_code=-1), 403)
else: else:
with Connect() as c: with Connect() as c:
user.user_id = api_token_get_id( try:
c, request.headers['Token']) user.c = c
if user.user_id is None: user.select_user_id_from_api_token(
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No token'}) request.headers['Token'])
user.select_role_and_powers()
role_id = id_get_role_id(c, user.user_id) if not any([y in [x.power_name for x in user.role.powers] for y in powers]):
user.role = role_id_get_role(c, role_id) return error_return(NoAccess('No permission', api_error_code=-1), 403)
user.role_power = get_role_power(c, role_id) except ArcError as e:
return error_return(e, 401)
f = False
for i in power:
if i in user.role_power:
f = True
break
if not f:
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return view(user, *args, **kwargs) return view(user, *args, **kwargs)
return wrapped_view return wrapped_view
return decorator return decorator
def request_json_handle(request, required_keys=[], optional_keys=[]):
'''
提取post参数返回dict写成了修饰器\
parameters: \
`request`: `Request` - 当前请求\
`required_keys`: `list` - 必须的参数\
`optional_keys`: `list` - 可选的参数
'''
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
data = {}
for key in required_keys:
if key not in request.json:
return error_return(PostError('Missing parameter: ' + key, api_error_code=-100))
data[key] = request.json[key]
for key in optional_keys:
if key in request.json:
data[key] = request.json[key]
return view(data, *args, **kwargs)
return wrapped_view
return decorator

View File

@@ -1,34 +1,32 @@
from core.error import ArcError
from flask import jsonify from flask import jsonify
default_error = ArcError('Unknown Error')
def code_get_msg(code):
# api接口code获取msg返回字符串
msg = {
0: '',
-1: 'See status code',
-2: 'No data',
-3: 'No data or user',
-4: 'No user_id',
-100: 'Wrong post data',
-101: 'Wrong data type',
-102: 'Wrong query parameter',
-103: 'Wrong sort parameter',
-104: 'Wrong sort order parameter',
-201: 'Wrong username or password',
-202: 'User is banned',
-203: 'Username exists',
-204: 'Email address exists',
-999: 'Unknown error'
}
return msg[code]
def return_encode(code: int = 0, data: dict = {}, status: int = 200, msg: str = ''): CODE_MSG = {
# 构造返回返回jsonify处理过后的response_class 0: '',
if msg == '': -1: 'See status code',
msg = code_get_msg(code) -2: 'No data',
if code < 0: -3: 'No data or user',
return jsonify({'status': status, 'code': code, 'data': {}, 'msg': msg}) -4: 'No user_id',
else: -100: 'Wrong post data',
return jsonify({'status': status, 'code': code, 'data': data, 'msg': msg}) -101: 'Wrong data type',
-102: 'Wrong query parameter',
-103: 'Wrong sort parameter',
-104: 'Wrong sort order parameter',
-200: 'No permission',
-201: 'Wrong username or password',
-202: 'User is banned',
-203: 'Username exists',
-204: 'Email address exists',
-999: 'Unknown error'
}
def success_return(data: dict = {}, status: int = 200, msg: str = ''):
return jsonify({'code': 0, 'data': data, 'msg': msg}), status
def error_return(e: 'ArcError' = default_error, status: int = 200):
return jsonify({'code': e.api_error_code, 'data': {} if e.extra_data is None else e.extra_data, 'msg': CODE_MSG[e.api_error_code] if e.message is None else e.message}), status

View File

@@ -1,219 +0,0 @@
from flask import (
Blueprint, request, jsonify
)
import functools
import api.api_auth
from . import users
import api.songs
from .api_code import code_get_msg, return_encode
bp = Blueprint('api', __name__, url_prefix='/api/v1')
bp.register_blueprint(users.bp)
class Query():
# 查询类,当查询附加参数的数据类型用
def __init__(self, limit=-1, offset=0, query={}, sort=[]) -> None:
self.limit = limit
self.offset = offset
self.query = query # {'name': 'admin'}
self.sort = sort # [{'column': 'user_id', 'order': 'ASC'}, ...]
def get_query_parameter(request, query_able=[], sort_able=[]):
# 提取查询请求参数返回Query类查询参数写成修饰器
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
re = Query()
if 'limit' in request.json:
try:
re.limit = int(request.json['limit'])
except:
return jsonify({'status': 200, 'code': -101, 'data': {}, 'msg': code_get_msg(-101)})
if 'offset' in request.json:
try:
re.offset = int(request.json['offset'])
except:
return jsonify({'status': 200, 'code': -101, 'data': {}, 'msg': code_get_msg(-101)})
if 'query' in request.json:
re.query = request.json['query']
for i in re.query:
if i not in query_able:
return jsonify({'status': 200, 'code': -102, 'data': {}, 'msg': code_get_msg(-102)})
if 'sort' in request.json:
re.sort = request.json['sort']
for i in re.sort:
if 'column' not in i or i['column'] not in sort_able:
return jsonify({'status': 200, 'code': -103, 'data': {}, 'msg': code_get_msg(-103)})
if not 'order' in i:
i['order'] = 'ASC'
else:
if i['order'] not in ['ASC', 'DESC']:
return jsonify({'status': 200, 'code': -104, 'data': {}, 'msg': code_get_msg(-104)})
return view(re, *args, **kwargs)
return wrapped_view
return decorator
@bp.route('/')
def ping():
return return_encode()
@bp.route('/token', methods=['POST'])
def token_post():
# 登录获取token
# {'auth': `base64(user_id:password)`}
if 'auth' in request.json:
data, code = api.api_auth.login(
str(request.json['auth']), request.remote_addr)
return return_encode(code, data)
else:
return return_encode(-1, {}, 401, 'No authentication')
@bp.route('/token', methods=['GET'])
@api.api_auth.role_required(request, ['select_me', 'select'])
def token_get(user):
# 判断登录有效性
return return_encode()
@bp.route('/token', methods=['DELETE'])
@api.api_auth.role_required(request, ['change_me', 'select_me', 'select'])
def token_delete(user):
# 登出
return return_encode(api.api_auth.logout(user))
@bp.route('/users', methods=['GET'])
@api.api_auth.role_required(request, ['select'])
@get_query_parameter(request, ['user_id', 'name', 'user_code'], [
'user_id', 'name', 'user_code', 'join_date', 'rating_ptt', 'time_played', 'ticket', 'world_rank_score'])
def users_get(query, user):
# 查询全用户信息
data = users.get_users(query)
if not data:
return return_encode(-2)
return return_encode(0, data)
@bp.route('/users/<int:user_id>', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_get(user, user_id):
# 查询用户信息
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = users.get_user_info(user_id)
if not data:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/users/<int:user_id>/b30', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_b30_get(user, user_id):
# 查询用户b30
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = users.get_user_b30(user_id)
if data['data'] == []:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/users/<int:user_id>/best', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
@get_query_parameter(request, ['song_id', 'difficulty'], [
'song_id', 'difficulty', 'score', 'time_played', 'rating'])
def users_user_best_get(query, user, user_id):
# 查询用户所有best成绩
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = users.get_user_best(user_id, query)
if data['data'] == []:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/users/<int:user_id>/r30', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_r30_get(user, user_id):
# 查询用户r30
if user_id <= 0:
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = users.get_user_r30(user_id)
if data['data'] == []:
return return_encode(-3)
return return_encode(0, data)
@bp.route('/songs/<string:song_id>', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
def songs_song_get(user, song_id):
# 查询歌曲信息
data = api.songs.get_song_info(song_id)
if not data:
return return_encode(-2)
return return_encode(0, data)
@bp.route('/songs', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
@get_query_parameter(request, ['sid', 'name_en', 'name_jp', 'pakset', 'artist'], [
'sid', 'name_en', 'name_jp', 'pakset', 'artist', 'date', 'rating_pst', 'rating_prs', 'rating_ftr', 'rating_byn'])
def songs_get(query, user):
# 查询全歌曲信息
data = api.songs.get_songs(query)
if not data:
return return_encode(-2)
return return_encode(0, data)

View File

@@ -0,0 +1,2 @@
class Constant:
QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort']

View File

@@ -1,57 +1,48 @@
from core.sql import Connect from core.error import ArcError, NoData
from core.sql import Sql from core.song import Song
from core.sql import Connect, Query, Sql
from flask import Blueprint, request
from .api_auth import request_json_handle, role_required
from .api_code import error_return, success_return
from .constant import Constant
bp = Blueprint('songs', __name__, url_prefix='/songs')
def get_song_info(song_id): @bp.route('/<string:song_id>', methods=['GET'])
# 查询指定歌曲信息,返回字典 @role_required(request, ['select', 'select_song_info'])
r = {} def songs_song_get(user, song_id):
'''查询歌曲信息'''
with Connect('') as c: with Connect() as c:
c.execute('''select * from chart where song_id=:a''', {'a': song_id}) try:
x = c.fetchone() s = Song(c, song_id).select()
if x: return success_return(s.to_dict())
r = {'song_id': x[0], except ArcError as e:
'name': {'name_en': x[1], return error_return(e)
'name_jp': x[2]}, return error_return()
'pakset': x[5],
'artist': x[6],
'date': x[9],
'rating_pst': x[13]/10,
'rating_prs': x[14]/10,
'rating_ftr': x[15]/10,
'rating_byn': x[16]/10,
'difficultly_pst': x[17]/2,
'difficultly_prs': x[18]/2,
'difficultly_ftr': x[19]/2,
'difficultly_byn': x[20]/2
}
return r
def get_songs(query=None): @bp.route('', methods=['GET'])
# 查询全部歌曲信息,返回字典列表 @role_required(request, ['select', 'select_song_info'])
r = [] @request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
def songs_get(data, user):
with Connect('') as c: '''查询全歌曲信息'''
x = Sql.select(c, 'chart', [], query) A = ['song_id', 'name']
B = ['song_id', 'name', 'rating_pst',
if x: 'rating_prs', 'rating_ftr', 'rating_byn']
with Connect() as c:
try:
query = Query(A, A, B).from_data(data)
x = Sql(c).select('chart', query=query)
r = []
for i in x: for i in x:
r.append({'sid': i[0], r.append(Song(c).from_list(i))
'name': {'name_en': i[1],
'name_jp': i[2]},
'pakset': i[5],
'artist': i[6],
'date': i[9],
'rating_pst': i[13]/10,
'rating_prs': i[14]/10,
'rating_ftr': i[15]/10,
'rating_byn': i[16]/10,
'difficultly_pst': i[17]/2,
'difficultly_prs': i[18]/2,
'difficultly_ftr': i[19]/2,
'difficultly_byn': i[20]/2
})
return r if not r:
raise NoData(api_error_code=-2)
return success_return([x.to_dict() for x in r])
except ArcError as e:
return error_return(e)
return error_return()

View File

@@ -0,0 +1,57 @@
from base64 import b64decode
from core.api_user import APIUser
from core.error import ArcError, PostError
from core.sql import Connect
from flask import Blueprint, request
from .api_auth import request_json_handle, role_required
from .api_code import error_return, success_return
bp = Blueprint('token', __name__, url_prefix='/token')
@bp.route('', methods=['POST'])
@request_json_handle(request, required_keys=['auth'])
def token_post(data):
'''
登录获取token\
{'auth': base64('<user_id>:<password>')}
'''
try:
auth_decode = bytes.decode(b64decode(data['auth']))
except:
return error_return(PostError(api_error_code=-100))
if not ':' in auth_decode:
return error_return(PostError(api_error_code=-100))
name, password = auth_decode.split(':', 1)
with Connect() as c:
try:
user = APIUser(c)
user.login(name, password, request.remote_addr)
return success_return({'token': user.token, 'user_id': user.user_id})
except ArcError as e:
return error_return(e)
return error_return()
@bp.route('', methods=['GET'])
@role_required(request, ['select_me', 'select'])
def token_get(user):
'''判断登录有效性'''
return success_return()
@bp.route('', methods=['DELETE'])
@role_required(request, ['change_me', 'select_me', 'select'])
def token_delete(user):
'''登出'''
with Connect() as c:
try:
user.c = c
user.logout()
return success_return()
except ArcError as e:
return error_return(e)
return error_return()

View File

@@ -1,138 +1,150 @@
from flask import ( from core.error import ArcError, InputError, NoAccess, NoData
Blueprint, request from core.score import Potential, UserScoreList
) from core.sql import Connect, Query, Sql
from core.user import UserInfo, UserRegister
from flask import Blueprint, request
from .api_code import return_encode from .api_auth import request_json_handle, role_required
from .api_auth import role_required from .api_code import error_return, success_return
from core.user import UserRegister from .constant import Constant
from core.error import ArcError, PostError
from core.sql import Connect
from core.sql import Sql
import time
import web.webscore
import server.info
bp = Blueprint('users', __name__, url_prefix='/users') bp = Blueprint('users', __name__, url_prefix='/users')
@bp.route('', methods=['POST']) @bp.route('', methods=['POST'])
@role_required(request, ['change']) @role_required(request, ['change'])
def users_post(user): @request_json_handle(request, ['name', 'password', 'email'])
# 注册用户 def users_post(data, _):
'''注册一个用户'''
with Connect() as c: with Connect() as c:
new_user = UserRegister(c) new_user = UserRegister(c)
try: try:
if 'name' in request.json: new_user.set_name(data['name'])
new_user.set_name(request.json['name']) new_user.set_password(data['password'])
else: new_user.set_email(data['email'])
raise PostError('No name provided.')
if 'password' in request.json:
new_user.set_password(request.json['password'])
else:
raise PostError('No password provided.')
if 'email' in request.json:
new_user.set_email(request.json['email'])
else:
raise PostError('No email provided.')
new_user.register() new_user.register()
return success_return({'user_id': new_user.user_id, 'user_code': new_user.user_code})
except ArcError as e: except ArcError as e:
return return_encode(e.api_error_code) return error_return(e)
return error_return()
return return_encode(0, {'user_id': new_user.user_id, 'user_code': new_user.user_code})
def get_users(query=None): @bp.route('', methods=['GET'])
# 获取全用户信息,返回字典列表 @role_required(request, ['select'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
r = [] def users_get(data, user):
'''查询全用户信息'''
A = ['user_id', 'name', 'user_code']
B = ['user_id', 'name', 'user_code', 'join_date',
'rating_ptt', 'time_played', 'ticket', 'world_rank_score']
with Connect() as c: with Connect() as c:
x = Sql.select(c, 'user', [], query) try:
query = Query(A, A, B).from_data(data)
if x: x = Sql(c).select('user', query=query)
r = []
for i in x: for i in x:
if i[23] != -1: r.append(UserInfo(c).from_list(i))
character_id = i[23]
else:
character_id = i[6]
r.append({
'user_id': i[0],
'name': i[1],
'join_date': i[3],
'user_code': i[4],
'rating_ptt': i[5]/100,
'character_id': character_id,
'is_char_uncapped': i[8] == 1,
'is_char_uncapped_override': i[9] == 1,
'is_hide_rating': i[10],
'ticket': i[26]
})
return r if not r:
raise NoData(api_error_code=-2)
return success_return([{
'user_id': x.user_id,
'name': x.name,
'join_date': x.join_date,
'user_code': x.user_code,
'rating_ptt': x.rating_ptt/100,
'character_id': x.character.character_id,
'is_char_uncapped': x.character.is_uncapped,
'is_char_uncapped_override': x.character.is_uncapped_override,
'is_hide_rating': x.is_hide_rating,
'ticket': x.ticket
} for x in r])
except ArcError as e:
return error_return(e)
return error_return()
def get_user_info(user_id): @bp.route('/<int:user_id>', methods=['GET'])
# 获取用户信息返回字典其实就是调用user/me信息 @role_required(request, ['select', 'select_me'])
def users_user_get(user, user_id):
'''查询用户信息'''
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)
r = {}
with Connect() as c: with Connect() as c:
r = server.info.get_user_me(c, user_id) try:
u = UserInfo(c, user_id)
return r return success_return(u.to_dict())
except ArcError as e:
return error_return(e)
return error_return()
def get_user_b30(user_id): @bp.route('/<int:user_id>/b30', methods=['GET'])
# 获取用户b30信息返回字典 @role_required(request, ['select', 'select_me'])
def users_user_b30_get(user, user_id):
'''查询用户b30'''
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)
r = []
with Connect() as c: with Connect() as c:
r = web.webscore.get_user_score(c, user_id, 30) try:
x = UserScoreList(c, UserInfo(c, user_id))
bestptt = 0 x.query.limit = 30
for i in r: x.select_from_user()
if i['rating']: r = x.to_dict_list()
bestptt += i['rating'] rating_sum = sum([i.rating for i in x.scores])
if 'time_played' in i: return success_return({'user_id': user_id, 'b30_ptt': rating_sum / 30, 'data': r})
i['time_played'] = int(time.mktime(time.strptime( except ArcError as e:
i['time_played'], '%Y-%m-%d %H:%M:%S'))) return error_return(e)
return error_return()
return {'user_id': user_id, 'b30_ptt': bestptt / 30, 'data': r}
def get_user_best(user_id, query=None): @bp.route('/<int:user_id>/best', methods=['GET'])
# 获取用户b30信息返回字典 @role_required(request, ['select', 'select_me'])
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
def users_user_best_get(data, user, user_id):
'''查询用户所有best成绩'''
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)
r = []
with Connect() as c: with Connect() as c:
x = Sql.select(c, 'best_score', [], query) try:
if x: x = UserScoreList(c, UserInfo(c, user_id))
for i in x: x.query.from_data(data)
r.append({ x.select_from_user()
"song_id": i[1], r = x.to_dict_list()
"difficulty": i[2], return success_return({'user_id': user_id, 'data': r})
"score": i[3], except ArcError as e:
"shiny_perfect_count": i[4], return error_return(e)
"perfect_count": i[5], return error_return()
"near_count": i[6],
"miss_count": i[7],
"health": i[8],
"modifier": i[9],
"time_played": i[10],
"best_clear_type": i[11],
"clear_type": i[12],
"rating": i[13]
})
return {'user_id': user_id, 'data': r}
def get_user_r30(user_id): @bp.route('/<int:user_id>/r30', methods=['GET'])
# 获取用户r30信息返回字典 @role_required(request, ['select', 'select_me'])
def users_user_r30_get(user, user_id):
'''查询用户r30'''
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)
r = []
with Connect() as c: with Connect() as c:
r, r10_ptt = web.webscore.get_user_recent30(c, user_id) try:
p = Potential(c, UserInfo(c, user_id))
return {'user_id': user_id, 'r10_ptt': r10_ptt, 'data': r} return success_return({'user_id': user_id, 'r10_ptt': p.recent_10 / 10, 'data': p.recent_30_to_dict_list()})
except ArcError as e:
return error_return(e)
return error_return()

View File

@@ -0,0 +1,134 @@
from hashlib import sha256
from os import urandom
from time import time
from .error import NoAccess, NoData, UserBan
from .user import UserOnline
class Power:
def __init__(self, c=None):
self.c = c
self.power_id: int = None
self.power_name: str = None
self.caption: str = None
@classmethod
def from_dict(cls, d: dict, c=None) -> 'Power':
p = cls(c)
p.power_id = d['power_id']
p.power_name = d['power_name']
p.caption = d['caption']
return p
def select_from_name(self, power_name: str) -> 'Power':
pass
class Role:
def __init__(self, c=None):
self.c = c
self.role_id: int = None
self.role_name: str = None
self.caption: str = None
self.powers: list = None
def has_power(self, power_name: str) -> bool:
'''判断role是否有power'''
for i in self.powers:
if i.power_name == power_name:
return True
return False
def select_from_id(self, role_id: int = None) -> 'Role':
'''用role_id查询role'''
if role_id is not None:
self.role_id = role_id
self.c.execute('''select role_name, caption from role where role_id = :a''',
{'a': self.role_id})
x = self.c.fetchone()
if x is None:
raise NoData('The role `%s` does not exist.' %
self.role_id, api_error_code=-200)
self.role_name = x[0]
self.caption = x[1]
return self
def select_powers(self) -> None:
'''查询role的全部powers'''
self.powers = []
self.c.execute('''select * from power where power_id in (select power_id from role_power where role_id=:a)''', {
'a': self.role_id})
x = self.c.fetchall()
for i in x:
self.powers.append(Power.from_dict(
{'power_id': i[0], 'power_name': i[1], 'caption': i[2]}, self.c))
class APIUser(UserOnline):
def __init__(self, c=None, user_id=None) -> None:
super().__init__(c, user_id)
self.api_token: str = None
self.role: 'Role' = None
self.ip: str = None
def select_role(self) -> None:
'''查询user的role'''
self.c.execute('''select role_id from user_role where user_id = :a''',
{'a': self.user_id})
x = self.c.fetchone()
self.role = Role(self.c)
if x is None:
# 默认role为user
self.role.role_id = 1
else:
self.role.role_id = int(x[0])
self.role.select_from_id()
def select_role_and_powers(self) -> None:
'''查询user的role以及role的powers'''
self.select_role()
self.role.select_powers()
def select_user_id_from_api_token(self, api_token: str = None) -> None:
if api_token is not None:
self.api_token = api_token
self.c.execute('''select user_id from api_login where token = :token''', {
'token': self.api_token})
x = self.c.fetchone()
if x is None:
raise NoAccess('No token', api_error_code=-1)
self.user_id = x[0]
def logout(self) -> None:
self.c.execute(
'''delete from api_login where user_id=?''', (self.user_id,))
def login(self, name: str = None, password: str = None, ip: str = None) -> None:
if name is not None:
self.name = name
if password is not None:
self.password = password
if ip is not None:
self.ip = ip
self.c.execute('''select user_id, password from user where name = :a''', {
'a': self.name})
x = self.c.fetchone()
if x is None:
raise NoData('The user `%s` does not exist.' %
self.name, api_error_code=-201)
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)
self.user_id = x[0]
now = int(time() * 1000)
self.token = sha256(
(str(self.user_id) + str(now)).encode("utf8") + urandom(8)).hexdigest()
self.logout()
self.c.execute('''insert into api_login values(?,?,?,?)''',
(self.user_id, self.token, now, self.ip))

View File

@@ -120,7 +120,6 @@ class Character:
def skill_id_displayed(self) -> str: def skill_id_displayed(self) -> str:
return None return None
@property
def uncap_cores_to_dict(self): def uncap_cores_to_dict(self):
return [x.to_dict() for x in self.uncap_cores] return [x.to_dict() for x in self.uncap_cores]
@@ -210,13 +209,12 @@ class UserCharacter(Character):
self.select_character_core() self.select_character_core()
@property def to_dict(self) -> dict:
def to_dict(self):
if self.char_type is None: if self.char_type is None:
self.select_character_info(self.user) self.select_character_info(self.user)
r = {"is_uncapped_override": self.is_uncapped_override, r = {"is_uncapped_override": self.is_uncapped_override,
"is_uncapped": self.is_uncapped, "is_uncapped": self.is_uncapped,
"uncap_cores": self.uncap_cores_to_dict, "uncap_cores": self.uncap_cores_to_dict(),
"char_type": self.char_type, "char_type": self.char_type,
"skill_id_uncap": self.skill.skill_id_uncap, "skill_id_uncap": self.skill.skill_id_uncap,
"skill_requires_uncap": self.skill.skill_requires_uncap, "skill_requires_uncap": self.skill.skill_requires_uncap,

View File

@@ -25,6 +25,7 @@ class Constant:
WORLD_MAP_FOLDER_PATH = './database/map/' WORLD_MAP_FOLDER_PATH = './database/map/'
SONG_FILE_FOLDER_PATH = './database/songs/' SONG_FILE_FOLDER_PATH = './database/songs/'
SQLITE_DATABASE_PATH = './database/arcaea_database.db'
DOWNLOAD_TIMES_LIMIT = Config.DOWNLOAD_TIMES_LIMIT DOWNLOAD_TIMES_LIMIT = Config.DOWNLOAD_TIMES_LIMIT
DOWNLOAD_TIME_GAP_LIMIT = Config.DOWNLOAD_TIME_GAP_LIMIT DOWNLOAD_TIME_GAP_LIMIT = Config.DOWNLOAD_TIME_GAP_LIMIT

View File

@@ -34,7 +34,7 @@ class PostError(ArcError):
class UserBan(ArcError): class UserBan(ArcError):
# 用户封禁 # 用户封禁
def __init__(self, message=None, error_code=121, api_error_code=-999, extra_data=None) -> None: def __init__(self, message=None, error_code=121, api_error_code=-202, extra_data=None) -> None:
super().__init__(message, error_code, api_error_code, extra_data) super().__init__(message, error_code, api_error_code, extra_data)

View File

@@ -9,8 +9,8 @@ from .user import UserInfo
def get_song_unlock(client_song_map: dict) -> bytes: def get_song_unlock(client_song_map: dict) -> bytes:
'''处理可用歌曲bit返回bytes''' '''处理可用歌曲bit返回bytes'''
user_song_unlock = [0] * 512 user_song_unlock = [0] * Constant.LINK_PLAY_UNLOCK_LENGTH
for i in range(0, 1024, 2): for i in range(0, Constant.LINK_PLAY_UNLOCK_LENGTH*2, 2):
x = 0 x = 0
y = 0 y = 0
if str(i) in client_song_map: if str(i) in client_song_map:

View File

@@ -113,7 +113,7 @@ class UserPresentList:
self.presents: list = None self.presents: list = None
def to_dict(self) -> list: def to_dict_list(self) -> list:
return [x.to_dict() for x in self.presents] return [x.to_dict() for x in self.presents]
def select_user_presents(self) -> None: def select_user_presents(self) -> None:

View File

@@ -38,7 +38,6 @@ class Purchase:
return self.price return self.price
return self.orig_price return self.orig_price
@property
def to_dict(self) -> dict: def to_dict(self) -> dict:
price = self.price_displayed price = self.price_displayed
r = { r = {
@@ -143,9 +142,8 @@ class PurchaseList:
self.user = user self.user = user
self.purchases: list = [] self.purchases: list = []
@property def to_dict_list(self) -> list:
def to_dict(self) -> list: return [x.to_dict() for x in self.purchases]
return [x.to_dict for x in self.purchases]
def select_from_type(self, item_type: str) -> 'PurchaseList': def select_from_type(self, item_type: str) -> 'PurchaseList':
self.c.execute('''select purchase_name from purchase_item where type = :a''', { self.c.execute('''select purchase_name from purchase_item where type = :a''', {

View File

@@ -1,7 +1,7 @@
from .user import UserInfo
from .song import Chart
from .score import UserScore
from .constant import Constant from .constant import Constant
from .score import UserScore
from .song import Chart
from .user import UserInfo
class RankList: class RankList:
@@ -18,9 +18,8 @@ class RankList:
self.limit: int = 20 self.limit: int = 20
self.user = None self.user = None
@property
def to_dict_list(self) -> list: def to_dict_list(self) -> list:
return [x.to_dict for x in self.list] return [x.to_dict() for x in self.list]
def select_top(self) -> None: def select_top(self) -> None:
''' '''

View File

@@ -18,7 +18,6 @@ class SaveData:
self.story_data = [] self.story_data = []
self.createdAt = 0 self.createdAt = 0
@property
def to_dict(self): def to_dict(self):
return { return {
"user_id": self.user.user_id, "user_id": self.user.user_id,

View File

@@ -3,6 +3,7 @@ from time import time
from .constant import Constant from .constant import Constant
from .error import NoData, StaminaNotEnough from .error import NoData, StaminaNotEnough
from .song import Chart from .song import Chart
from .sql import Query, Sql
from .util import md5 from .util import md5
from .world import WorldPlay from .world import WorldPlay
@@ -122,7 +123,6 @@ class Score:
self.rating = self.calculate_rating(self.song.chart_const, self.score) self.rating = self.calculate_rating(self.song.chart_const, self.score)
return self.rating return self.rating
@property
def to_dict(self) -> dict: def to_dict(self) -> dict:
return { return {
"rating": self.rating, "rating": self.rating,
@@ -158,20 +158,29 @@ class UserScore(Score):
raise NoData('No score data.') raise NoData('No score data.')
self.user.select_user_about_character() self.user.select_user_about_character()
self.from_list(x)
def from_list(self, x: list) -> 'UserScore':
if self.song.song_id is None:
self.song.song_id = x[1]
if self.song.difficulty is None:
self.song.difficulty = x[2]
self.set_score(x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[12]) self.set_score(x[3], x[4], x[5], x[6], x[7], x[8], x[9], x[10], x[12])
self.best_clear_type = int(x[11]) self.best_clear_type = int(x[11])
self.rating = float(x[13]) self.rating = float(x[13])
@property return self
def to_dict(self) -> dict:
r = super().to_dict def to_dict(self, has_user_info: bool = True) -> dict:
r['user_id'] = self.user.user_id r = super().to_dict()
r['name'] = self.user.name
r['best_clear_type'] = self.best_clear_type r['best_clear_type'] = self.best_clear_type
r['is_skill_sealed'] = self.user.is_skill_sealed if has_user_info:
character = self.user.character_displayed r['user_id'] = self.user.user_id
r['is_char_uncapped'] = character.is_uncapped_displayed r['name'] = self.user.name
r['character'] = character.character_id r['is_skill_sealed'] = self.user.is_skill_sealed
character = self.user.character_displayed
r['is_char_uncapped'] = character.is_uncapped_displayed
r['character'] = character.character_id
if self.rank: if self.rank:
r['rank'] = self.rank r['rank'] = self.rank
return r return r
@@ -196,14 +205,13 @@ class UserPlay(UserScore):
self.ptt: Potential = None # 临时用来计算用户ptt的 self.ptt: Potential = None # 临时用来计算用户ptt的
self.world_play: 'WorldPlay' = None self.world_play: 'WorldPlay' = None
@property
def to_dict(self) -> dict: def to_dict(self) -> dict:
if self.is_world_mode is None: if self.is_world_mode is None:
return {} return {}
elif not self.is_world_mode: elif not self.is_world_mode:
return {'global_rank': self.user.global_rank, 'user_rating': self.user.rating_ptt} return {'global_rank': self.user.global_rank, 'user_rating': self.user.rating_ptt}
else: else:
r = self.world_play.to_dict r = self.world_play.to_dict()
r['user_rating'] = self.user.rating_ptt r['user_rating'] = self.user.rating_ptt
r['global_rank'] = self.user.global_rank r['global_rank'] = self.user.global_rank
return r return r
@@ -384,6 +392,8 @@ class Potential:
self.s30: list = None self.s30: list = None
self.songs_selected: list = None self.songs_selected: list = None
self.b30: list = None
@property @property
def value(self) -> float: def value(self) -> float:
'''计算用户潜力值''' '''计算用户潜力值'''
@@ -432,6 +442,15 @@ class Potential:
i += 1 i += 1
return rating_sum return rating_sum
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))]
def recent_30_update(self, pop_index: int, rating: float, song_id_difficulty: str) -> None: def recent_30_update(self, pop_index: int, rating: float, song_id_difficulty: str) -> None:
self.r30.pop(pop_index) self.r30.pop(pop_index)
self.s30.pop(pop_index) self.s30.pop(pop_index)
@@ -449,3 +468,32 @@ class Potential:
sql_list.append(self.user.user_id) sql_list.append(self.user.user_id)
self.c.execute(sql, sql_list) self.c.execute(sql, sql_list)
class UserScoreList:
'''
用户分数查询类\
properties: `user` - `User`类或子类的实例
'''
def __init__(self, c=None, user=None):
self.c = c
self.user = user
self.scores: list = None
self.query: 'Query' = Query(['user_id', 'song_id', 'difficulty'], ['song_id'], [
'rating', 'difficulty', 'song_id', 'score', 'time_played'])
def to_dict_list(self) -> list:
return [x.to_dict(has_user_info=False) for x in self.scores]
def select_from_user(self, user=None) -> None:
'''获取用户的best_score数据'''
if user is not None:
self.user = user
self.query.query_append({'user_id': self.user.user_id})
self.query.sort += [{'column': 'rating', 'order': 'DESC'}]
print(self.query.sort)
x = Sql(self.c).select('best_score', query=self.query)
self.scores = [UserScore(self.c, self.user).from_list(i) for i in x]

View File

@@ -9,6 +9,12 @@ class Chart:
self.set_chart(song_id, difficulty) self.set_chart(song_id, difficulty)
self.defnum: int = None self.defnum: int = None
def to_dict(self) -> dict:
return {
'difficulty': self.difficulty,
'chart_const': self.chart_const
}
@property @property
def chart_const(self) -> float: def chart_const(self) -> float:
return self.defnum / 10 if self.defnum else -1 return self.defnum / 10 if self.defnum else -1
@@ -37,6 +43,37 @@ class Song:
self.name: str = None self.name: str = None
self.charts: dict = None self.charts: dict = None
def to_dict(self) -> dict:
return {
'song_id': self.song_id,
'name': self.name,
'charts': [chart.to_dict() for chart in self.charts]
}
def from_list(self, x: list) -> 'Song':
if self.song_id is None:
self.song_id = x[0]
self.name = x[1]
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)]
self.charts[0].defnum = x[2]
self.charts[1].defnum = x[3]
self.charts[2].defnum = x[4]
self.charts[3].defnum = x[5]
return self
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(self, song_id: str = None) -> 'Song':
if song_id is not None:
self.song_id = song_id
self.c.execute('''select * from chart where song_id=:a''', {
'a': self.song_id})
x = self.c.fetchone()
if x is None:
raise NoData('The song `%s` does not exist.' % self.song_id)
return self.from_list(x)

View File

@@ -1,12 +1,17 @@
import sqlite3 import sqlite3
from flask import current_app
import traceback import traceback
from certifi import where
from flask import current_app
from .constant import Constant
from .error import InputError
class Connect(): class Connect:
# 数据库连接类,上下文管理 # 数据库连接类,上下文管理
def __init__(self, file_path='./database/arcaea_database.db'): def __init__(self, file_path=Constant.SQLITE_DATABASE_PATH):
""" """
数据库连接默认连接arcaea_database.db\ 数据库连接默认连接arcaea_database.db\
接受:文件路径\ 接受:文件路径\
@@ -34,15 +39,126 @@ class Connect():
return True return True
class Sql(): class Query:
'''查询参数类'''
@staticmethod def __init__(self, query_able: list = None, quzzy_query_able: list = None, sort_able: list = None) -> None:
def select(c, table_name, target_column=[], query=None): self.query_able: list = query_able
# 执行查询单句sql语句返回fetchall数据 self.quzzy_query_able: list = quzzy_query_able
# 使用准确查询,且在单表内 self.sort_able: list = sort_able
self.__limit: int = -1
self.__offset: int = 0
self.__query: dict = {} # {'name': 'admin'}
self.__fuzzy_query: dict = {} # {'name': 'dmi'}
# [{'column': 'user_id', 'order': 'ASC'}, ...]
self.__sort: list = []
@property
def limit(self) -> int:
return self.__limit
@limit.setter
def limit(self, limit: int) -> None:
if not isinstance(limit, int):
raise InputError(api_error_code=-101)
self.__limit = limit
@property
def offset(self) -> int:
return self.__offset
@offset.setter
def offset(self, offset: int) -> None:
if not isinstance(offset, int):
raise InputError(api_error_code=-101)
self.__offset = offset
@property
def query(self) -> dict:
return self.__query
@query.setter
def query(self, query: dict) -> None:
self.__query = {}
self.query_append(query)
def query_append(self, query: dict) -> None:
if not isinstance(query, dict):
raise InputError(api_error_code=-101)
if self.query_able is not None and query and not set(query).issubset(set(self.query_able)):
raise InputError(api_error_code=-102)
if not self.__query:
self.__query = query
else:
self.__query.update(query)
@property
def fuzzy_query(self) -> dict:
return self.__fuzzy_query
@fuzzy_query.setter
def fuzzy_query(self, fuzzy_query: dict) -> None:
self.__fuzzy_query = {}
self.fuzzy_query_append(fuzzy_query)
def fuzzy_query_append(self, fuzzy_query: dict) -> None:
if not isinstance(fuzzy_query, dict):
raise InputError(api_error_code=-101)
if self.quzzy_query_able is not None and fuzzy_query and not set(fuzzy_query).issubset(set(self.quzzy_query_able)):
raise InputError(api_error_code=-102)
if not self.__fuzzy_query:
self.__fuzzy_query = fuzzy_query
else:
self.__fuzzy_query.update(fuzzy_query)
@property
def sort(self) -> list:
return self.__sort
@sort.setter
def sort(self, sort: list) -> None:
if not isinstance(sort, list):
raise InputError(api_error_code=-101)
if self.sort_able is not None and sort:
for x in sort:
if not isinstance(x, dict):
raise InputError(api_error_code=-101)
if 'column' not in x or x['column'] not in self.sort_able:
raise InputError(api_error_code=-103)
if 'order' not in x:
x['order'] = 'ASC'
else:
if x['order'] not in ['ASC', 'DESC']:
raise InputError(api_error_code=-104)
self.__sort = sort
def set_value(self, limit=-1, offset=0, query={}, fuzzy_query={}, sort=[]) -> None:
self.limit = limit
self.offset = offset
self.query = query
self.fuzzy_query = fuzzy_query
self.sort = sort
def from_data(self, d: dict) -> 'Query':
self.set_value(d.get('limit', -1), d.get('offset', 0),
d.get('query', {}), d.get('fuzzy_query', {}), d.get('sort', []))
return self
class Sql:
'''
数据库增查删改类
'''
def __init__(self, c=None) -> None:
self.c = c
def select(self, table_name: str, target_column: 'list' = [], query: 'Query' = None) -> list:
'''单表内行查询单句sql语句返回fetchall数据'''
sql = 'select ' sql = 'select '
sql_dict = {} sql_list = []
if len(target_column) >= 2: if len(target_column) >= 2:
sql += target_column[0] sql += target_column[0]
for i in range(1, len(target_column)): for i in range(1, len(target_column)):
@@ -53,34 +169,48 @@ class Sql():
else: else:
sql += '* from ' + table_name sql += '* from ' + table_name
where_field = [] if query is None:
where_value = [] self.c.execute(sql)
if query: return self.c.fetchall()
for i in query.query:
where_field.append(i)
where_value.append(query.query[i])
if where_field and where_value: where_key = []
where_like_key = []
for i in query.query:
where_key.append(i)
sql_list.append(query.query[i])
for i in query.fuzzy_query:
where_like_key.append(i)
sql_list.append('%' + query.fuzzy_query[i] + '%')
if where_key or where_like_key:
sql += ' where ' sql += ' where '
sql += where_field[0] + '=:' + where_field[0] if where_key:
sql_dict[where_field[0]] = where_value[0] sql += where_key[0] + '=?'
if len(where_field) >= 2: if len(where_key) >= 2:
for i in range(1, len(where_field)): for i in range(1, len(where_key)):
sql_dict[where_field[i]] = where_value[i] sql += ' and ' + where_key[i] + '=?'
sql += ' and ' + where_field[i] + '=:' + where_field[i] if where_like_key:
for i in where_like_key:
sql += ' and ' + i + ' like ?'
else:
sql += where_like_key[0] + ' like ?'
if query and query.sort: if len(where_like_key) >= 2:
for i in range(1, len(where_key)):
sql += ' and ' + where_key[i] + ' like ?'
if query.sort:
sql += ' order by ' + \ sql += ' order by ' + \
query.sort[0]['column'] + ' ' + query.sort[0]['order'] query.sort[0]['column'] + ' ' + query.sort[0]['order']
for i in range(1, len(query.sort)): for i in range(1, len(query.sort)):
sql += ', ' + query.sort[i]['column'] + \ sql += ', ' + query.sort[i]['column'] + \
' ' + query.sort[i]['order'] ' ' + query.sort[i]['order']
if query and query.limit >= 0: if query.limit >= 0:
sql += ' limit :limit offset :offset' sql += ' limit ? offset ?'
sql_dict['limit'] = query.limit sql_list.append(query.limit)
sql_dict['offset'] = query.offset sql_list.append(query.offset)
c.execute(sql, sql_dict) self.c.execute(sql, sql_list)
return self.c.fetchall()
return c.fetchall()

View File

@@ -45,12 +45,16 @@ class User:
self.world_rank_score = None self.world_rank_score = None
self.ban_flag = None self.ban_flag = None
@property
def hash_pwd(self) -> str:
'''`password`的SHA-256值'''
return hashlib.sha256(self.password.encode("utf8")).hexdigest()
class UserRegister(User): class UserRegister(User):
def __init__(self, c) -> None: def __init__(self, c) -> None:
super().__init__() super().__init__()
self.c = c self.c = c
self.hash_pwd = None
def set_name(self, name: str): def set_name(self, name: str):
if 3 <= len(name) <= 16: if 3 <= len(name) <= 16:
@@ -67,7 +71,6 @@ class UserRegister(User):
def set_password(self, password: str): def set_password(self, password: str):
if 8 <= len(password) <= 32: if 8 <= len(password) <= 32:
self.password = password self.password = password
self.hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
else: else:
raise InputError('Password is invalid.') raise InputError('Password is invalid.')
@@ -146,7 +149,6 @@ class UserLogin(User):
self.c = c self.c = c
self.device_id = None self.device_id = None
self.ip = None self.ip = None
self.hash_pwd = None
self.token = None self.token = None
self.now = 0 self.now = 0
@@ -155,7 +157,6 @@ class UserLogin(User):
def set_password(self, password: str): def set_password(self, password: str):
self.password = password self.password = password
self.hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
def set_device_id(self, device_id: str): def set_device_id(self, device_id: str):
self.device_id = device_id self.device_id = device_id
@@ -282,7 +283,7 @@ class UserInfo(User):
self.is_skill_sealed = False self.is_skill_sealed = False
self.is_hide_rating = False self.is_hide_rating = False
self.recent_score = Score() self.recent_score = Score()
self.favorite_character = -1 self.favorite_character = None
self.max_stamina_notification_enabled = False self.max_stamina_notification_enabled = False
self.prog_boost = 0 self.prog_boost = 0
@@ -411,7 +412,7 @@ class UserInfo(User):
y = self.c.fetchone() y = self.c.fetchone()
best_clear_type = y[0] if y is not None else self.recent_score.clear_type best_clear_type = y[0] if y is not None else self.recent_score.clear_type
r = self.recent_score.to_dict r = self.recent_score.to_dict()
r["best_clear_type"] = best_clear_type r["best_clear_type"] = best_clear_type
return [r] return [r]
@@ -440,7 +441,7 @@ class UserInfo(User):
return { return {
"is_aprilfools": Config.IS_APRILFOOLS, "is_aprilfools": Config.IS_APRILFOOLS,
"curr_available_maps": self.curr_available_maps_list, "curr_available_maps": self.curr_available_maps_list,
"character_stats": [x.to_dict for x in self.characters.characters], "character_stats": [x.to_dict() for x in self.characters.characters],
"friends": self.friends, "friends": self.friends,
"settings": { "settings": {
"favorite_character": favorite_character_id, "favorite_character": favorite_character_id,
@@ -473,14 +474,12 @@ class UserInfo(User):
"global_rank": self.global_rank "global_rank": self.global_rank
} }
def select_user(self) -> None: def from_list(self, x: list) -> 'UserInfo':
# 查user表所有信息 '''从数据库user表全部数据获取信息'''
self.c.execute(
'''select * from user where user_id = :x''', {'x': self.user_id})
x = self.c.fetchone()
if not x: if not x:
raise NoData('No user.', 108, -3) return None
if self.user_id is None:
self.user_id = x[0]
self.name = x[1] self.name = x[1]
self.join_date = int(x[3]) self.join_date = int(x[3])
self.user_code = x[4] self.user_code = x[4]
@@ -512,6 +511,18 @@ class UserInfo(User):
self.stamina = UserStamina(self.c, self) self.stamina = UserStamina(self.c, self)
self.stamina.set_value(x[32], x[33]) self.stamina.set_value(x[32], x[33])
return self
def select_user(self) -> None:
# 查user表所有信息
self.c.execute(
'''select * from user where user_id = :x''', {'x': self.user_id})
x = self.c.fetchone()
if not x:
raise NoData('No user.', 108, -3)
self.from_list(x)
def select_user_about_current_map(self) -> None: def select_user_about_current_map(self) -> None:
self.c.execute('''select current_map from user where user_id = :a''', self.c.execute('''select current_map from user where user_id = :a''',
{'a': self.user_id}) {'a': self.user_id})

View File

@@ -54,7 +54,6 @@ class Step:
self.speed_limit_value: int = None self.speed_limit_value: int = None
self.plus_stamina_value: int = None self.plus_stamina_value: int = None
@property
def to_dict(self) -> dict: def to_dict(self) -> dict:
r = { r = {
'position': self.position, 'position': self.position,
@@ -152,7 +151,7 @@ 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,
'steps': [s.to_dict for s in self.steps], 'steps': [s.to_dict() for s in self.steps],
} }
def from_dict(self, raw_dict: dict) -> 'Map': def from_dict(self, raw_dict: dict) -> 'Map':
@@ -208,7 +207,6 @@ class UserMap(Map):
return rewards return rewards
@property
def rewards_for_climbing_to_dict(self) -> list: def rewards_for_climbing_to_dict(self) -> list:
rewards = [] rewards = []
for i in range(self.prev_position, self.curr_position+1): for i in range(self.prev_position, self.curr_position+1):
@@ -441,11 +439,10 @@ class WorldPlay:
self.overdrive_extra: float = None self.overdrive_extra: float = None
self.character_bonus_progress: float = None self.character_bonus_progress: float = None
@property
def to_dict(self) -> dict: def to_dict(self) -> dict:
arcmap: 'UserMap' = self.user.current_map arcmap: 'UserMap' = self.user.current_map
r = { r = {
"rewards": arcmap.rewards_for_climbing_to_dict, "rewards": arcmap.rewards_for_climbing_to_dict(),
"exp": self.character_used.level.exp, "exp": self.character_used.level.exp,
"level": self.character_used.level.level, "level": self.character_used.level.level,
"base_progress": self.base_step_value, "base_progress": self.base_step_value,
@@ -484,7 +481,7 @@ class WorldPlay:
if self.user_play.beyond_gauge == 0: if self.user_play.beyond_gauge == 0:
r["user_map"]["steps"] = [ r["user_map"]["steps"] = [
x.to_dict for x in arcmap.steps_for_climbing] x.to_dict() for x in arcmap.steps_for_climbing]
else: else:
r["user_map"]["steps"] = len(arcmap.steps_for_climbing) r["user_map"]["steps"] = len(arcmap.steps_for_climbing)

View File

@@ -7,7 +7,7 @@ from multiprocessing import Process, set_start_method
from flask import Flask, request, send_from_directory from flask import Flask, request, send_from_directory
import api.api_main import api
import server import server
import server.init import server.init
import web.index import web.index
@@ -28,7 +28,7 @@ app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY)
app.config['SESSION_TYPE'] = 'filesystem' app.config['SESSION_TYPE'] = 'filesystem'
app.register_blueprint(web.login.bp) app.register_blueprint(web.login.bp)
app.register_blueprint(web.index.bp) app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.bp) app.register_blueprint(api.bp)
app.register_blueprint(server.bp) app.register_blueprint(server.bp)

View File

@@ -1,224 +0,0 @@
import os
import hashlib
from flask import url_for
import sqlite3
from server.sql import Connect
import time
from setting import Config
time_limit = Config.DOWNLOAD_TIMES_LIMIT # 每个玩家24小时下载次数限制
time_gap_limit = Config.DOWNLOAD_TIME_GAP_LIMIT # 下载链接有效秒数
def get_file_md5(file_path):
# 计算文件MD5
if not os.path.isfile(file_path):
return None
myhash = hashlib.md5()
with open(file_path, 'rb') as f:
while True:
b = f.read(8096)
if not b:
break
myhash.update(b)
return myhash.hexdigest()
def get_url(file_path, **kwargs):
# 获取下载地址
t = ''
if 't' in kwargs:
t = kwargs['t']
if Config.DOWNLOAD_LINK_PREFIX:
prefix = Config.DOWNLOAD_LINK_PREFIX
if prefix[-1] != '/':
prefix += '/'
return prefix + file_path + '?t=' + t
else:
return url_for('download', file_path=file_path, t=t, _external=True)
def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True):
# 获取一首歌的下载链接,返回字典
dir_list = os.listdir(os.path.join(file_dir, song_id))
re = {}
now = int(time.time())
c.execute('''delete from download_token where user_id=:a and song_id=:b''', {
'a': user_id, 'b': song_id})
for i in dir_list:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg']:
token = hashlib.md5(
(str(user_id) + song_id + i + str(now)).encode(encoding='UTF-8')).hexdigest()
if i == 'base.ogg':
if 'audio' not in re:
re['audio'] = {}
c.execute(
'''select md5 from songfile where song_id=:a and file_type=-1''', {'a': song_id})
x = c.fetchone()
if x:
checksum = x[0]
else:
checksum = get_file_md5(os.path.join(
file_dir, song_id, 'base.ogg'))
re['audio']["checksum"] = checksum
if url_flag:
re['audio']["url"] = get_url(
file_path=song_id+'/base.ogg', t=token)
elif i == '3.ogg':
if 'audio' not in re:
re['audio'] = {}
c.execute(
'''select md5 from songfile where song_id=:a and file_type=-2''', {'a': song_id})
x = c.fetchone()
if x:
checksum = x[0]
else:
checksum = get_file_md5(os.path.join(
file_dir, song_id, '3.ogg'))
if url_flag:
re['audio']['3'] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/3.ogg', t=token)}
else:
re['audio']['3'] = {"checksum": checksum}
else:
if 'chart' not in re:
re['chart'] = {}
c.execute(
'''select md5 from songfile where song_id=:a and file_type=:b''', {'a': song_id, 'b': int(i[0])})
x = c.fetchone()
if x:
checksum = x[0]
else:
checksum = get_file_md5(os.path.join(file_dir, song_id, i))
if url_flag:
re['chart'][i[0]] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/'+i, t=token)}
else:
re['chart'][i[0]] = {"checksum": checksum}
if url_flag:
c.execute('''insert into download_token values(:a,:b,:c,:d,:e)''', {
'a': user_id, 'b': song_id, 'c': i, 'd': token, 'e': now})
return {song_id: re}
def get_all_songs(user_id, file_dir='./database/songs', url_flag=True):
# 获取所有歌的下载链接,返回字典
dir_list = os.listdir(file_dir)
re = {}
with Connect() as c:
for i in dir_list:
if os.path.isdir(os.path.join(file_dir, i)):
re.update(get_one_song(c, user_id, i, file_dir, url_flag))
return re
def get_some_songs(user_id, song_ids):
# 获取一些歌的下载链接,返回字典
re = {}
with Connect() as c:
for song_id in song_ids:
re.update(get_one_song(c, user_id, song_id))
return re
def is_token_able_download(t, path):
# token是否可以下载返回错误码0即可以
errorcode = 108
with Connect() as c:
c.execute('''select * from download_token where token = :t limit 1''',
{'t': t})
x = c.fetchone()
now = int(time.time())
if x and now - x[4] <= time_gap_limit and x[1]+'/'+x[2] == path:
c.execute(
'''select count(*) from user_download where user_id = :a''', {'a': x[0]})
y = c.fetchone()
if y and y[0] <= time_limit:
c.execute('''insert into user_download values(:a,:b,:c)''', {
'a': x[0], 'b': x[3], 'c': now})
errorcode = 0
else:
errorcode = 903
else:
errorcode = 108
return errorcode
def is_able_download(user_id):
# 是否可以下载,返回布尔值
f = False
with Connect() as c:
now = int(time.time())
c.execute(
'''delete from user_download where user_id = :a and time <= :b''', {'a': user_id, 'b': now - 24*3600})
c.execute(
'''select count(*) from user_download where user_id = :a''', {'a': user_id})
y = c.fetchone()
if y and y[0] <= time_limit:
f = True
else:
f = False
return f
def initialize_one_songfile(c, song_id, file_dir='./database/songs'):
# 计算并添加歌曲md5到表中无返回
dir_list = os.listdir(os.path.join(file_dir, song_id))
for i in dir_list:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg']:
if i == 'base.ogg':
c.execute('''insert into songfile values(:a,-1,:c)''', {
'a': song_id, 'c': get_file_md5(os.path.join(file_dir, song_id, 'base.ogg'))})
elif i == '3.ogg':
c.execute('''insert into songfile values(:a,-2,:c)''', {
'a': song_id, 'c': get_file_md5(os.path.join(file_dir, song_id, '3.ogg'))})
else:
c.execute('''insert into songfile values(:a,:b,:c)''', {
'a': song_id, 'b': int(i[0]), 'c': get_file_md5(os.path.join(file_dir, song_id, i))})
return
def initialize_songfile(file_dir='./database/songs'):
# 初始化歌曲数据的md5表返回错误信息
error = None
try:
dir_list = os.listdir(file_dir)
except:
error = 'OS error!'
return error
try:
conn = sqlite3.connect('./database/arcaea_database.db')
c = conn.cursor()
except:
error = 'Database error!'
return error
try:
c.execute('''delete from songfile''')
for i in dir_list:
if os.path.isdir(os.path.join(file_dir, i)):
initialize_one_songfile(c, i, file_dir)
except:
error = 'Initialization error!'
finally:
conn.commit()
conn.close()
return error

View File

@@ -1,125 +0,0 @@
from .sql import Connect
import base64
def get_song_unlock(client_song_map):
# 处理可用歌曲bit返回bytes
user_song_unlock = [0] * 512
for i in range(0, 1024, 2):
x = 0
y = 0
if str(i) in client_song_map:
if client_song_map[str(i)][0]:
x += 1
if client_song_map[str(i)][1]:
x += 2
if client_song_map[str(i)][2]:
x += 4
if client_song_map[str(i)][3]:
x += 8
if str(i+1) in client_song_map:
if client_song_map[str(i+1)][0]:
y += 1
if client_song_map[str(i+1)][1]:
y += 2
if client_song_map[str(i+1)][2]:
y += 4
if client_song_map[str(i+1)][3]:
y += 8
user_song_unlock[i // 2] = y*16 + x
return bytes(user_song_unlock)
def create_room(conn, user_id, client_song_map):
# 创建房间,返回错误码和房间与用户信息
error_code = 108
with Connect() as c:
c.execute('''select name from user where user_id=?''', (user_id,))
x = c.fetchone()
if x is not None:
name = x[0]
song_unlock = get_song_unlock(client_song_map)
conn.send((1, name, song_unlock))
if conn.poll(10):
data = conn.recv()
else:
data = (-1,)
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
'roomId': str(data[2]),
'token': str(data[3]),
'key': (base64.b64encode(data[4])).decode(),
'playerId': str(data[5]),
'userId': user_id,
'orderedAllowedSongs': (base64.b64encode(song_unlock)).decode()
}
return error_code, None
def join_room(conn, user_id, client_song_map, room_code):
# 加入房间,返回错误码和房间与用户信息
error_code = 108
with Connect() as c:
c.execute('''select name from user where user_id=?''', (user_id,))
x = c.fetchone()
if x is not None:
name = x[0]
song_unlock = get_song_unlock(client_song_map)
conn.send((2, name, song_unlock, room_code))
if conn.poll(10):
data = conn.recv()
else:
data = (-1,)
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
'roomId': str(data[2]),
'token': str(data[3]),
'key': (base64.b64encode(data[4])).decode(),
'playerId': str(data[5]),
'userId': user_id,
'orderedAllowedSongs': (base64.b64encode(data[6])).decode()
}
else:
error_code = data[0]
return error_code, None
def update_room(conn, user_id, token):
# 更新房间,返回错误码和房间与用户信息
error_code = 108
conn.send((3, int(token)))
if conn.poll(10):
data = conn.recv()
else:
data = (-1,)
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
'roomId': str(data[2]),
'token': token,
'key': (base64.b64encode(data[3])).decode(),
'playerId': str(data[4]),
'userId': user_id,
'orderedAllowedSongs': (base64.b64encode(data[5])).decode()
}
else:
error_code = data[0]
return error_code, None

View File

@@ -1,23 +0,0 @@
def buy_item(c, user_id, price):
# 购买接口,返回成功与否标识符和剩余源点数
c.execute('''select ticket from user where user_id = :a''',
{'a': user_id})
ticket = c.fetchone()
if ticket:
ticket = ticket[0]
else:
ticket = 0
if ticket < price:
return False, ticket
c.execute('''update user set ticket = :b where user_id = :a''',
{'a': user_id, 'b': ticket-price})
return True, ticket - price

View File

@@ -1,98 +1,4 @@
from core.sql import Connect from core.sql import Connect
from setting import Config
def b2int(x):
# int与布尔值转换
if x:
return 1
else:
return 0
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def get_score(c, user_id, song_id, difficulty):
# 根据user_id、song_id、难度得到该曲目最好成绩返回字典
c.execute('''select * from best_score where user_id = :a and song_id = :b and difficulty = :c''',
{'a': user_id, 'b': song_id, 'c': difficulty})
x = c.fetchone()
if x is not None:
c.execute('''select name, character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, favorite_character from user where user_id = :a''', {
'a': user_id})
y = c.fetchone()
if y is not None:
character = y[1]
is_char_uncapped = int2b(y[3])
if y[5] != -1:
character = y[5]
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id=:a and character_id=:b''', {
'a': user_id, 'b': character})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id=:a and character_id=:b''', {
'a': user_id, 'b': character})
z = c.fetchone()
if z:
if z[1] == 0:
is_char_uncapped = int2b(z[0])
else:
is_char_uncapped = False
else:
if y[4] == 1:
is_char_uncapped = False
return {
"user_id": x[0],
"song_id": x[1],
"difficulty": x[2],
"score": x[3],
"shiny_perfect_count": x[4],
"perfect_count": x[5],
"near_count": x[6],
"miss_count": x[7],
"health": x[8],
"modifier": x[9],
"time_played": x[10],
"best_clear_type": x[11],
"clear_type": x[12],
"name": y[0],
"character": character,
"is_skill_sealed": int2b(y[2]),
"is_char_uncapped": is_char_uncapped
}
else:
return {}
else:
return {}
def arc_score_top(song_id, difficulty, limit=20):
# 得到top分数表默认最多20个如果是负数则全部查询
r = []
with Connect() as c:
if limit >= 0:
c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit''', {
'song_id': song_id, 'difficulty': difficulty, 'limit': limit})
else:
c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC''', {
'song_id': song_id, 'difficulty': difficulty})
x = c.fetchall()
if x != []:
rank = 0
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
return r
def calculate_rating(defnum, score): def calculate_rating(defnum, score):
@@ -113,7 +19,7 @@ def refresh_all_score_rating():
# 刷新所有best成绩的rating # 刷新所有best成绩的rating
error = 'Unknown error.' error = 'Unknown error.'
with Connect('') as c: with Connect() as c:
c.execute( c.execute(
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''') '''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
x = c.fetchall() x = c.fetchall()

View File

@@ -1,56 +0,0 @@
import json
from .config import Constant
from setting import Config
import time
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def calc_stamina(max_stamina_ts, curr_stamina):
# 计算体力,返回剩余体力数值
stamina = int(Constant.MAX_STAMINA - (max_stamina_ts -
int(time.time()*1000)) / Constant.STAMINA_RECOVER_TICK)
if stamina >= Constant.MAX_STAMINA:
if curr_stamina >= Constant.MAX_STAMINA:
stamina = curr_stamina
else:
stamina = Constant.MAX_STAMINA
if stamina < 0:
stamina = 0
return stamina
def get_world_info(map_id):
# 读取json文件内容返回字典
world_info = {}
with open('./database/map/'+map_id+'.json', 'r') as f:
world_info = json.load(f)
return world_info
def get_available_maps():
# 获取当前可用图(用户设定的),返回字典列表
re = []
for i in Config.AVAILABLE_MAP:
info = get_world_info(i)
del info['steps']
try:
del info['is_locked']
del info['curr_position']
del info['curr_capture']
except:
pass
re.append(info)
return re

View File

@@ -1,131 +0,0 @@
from setting import Config
from .config import Constant
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def calc_char_value(level, value1, value20, value30):
# 计算搭档数值的核心函数,返回浮点数
def calc_char_value_20(level, stata, statb, lva=1, lvb=20):
# 计算1~20级搭档数值的核心函数返回浮点数来自https://redive.estertion.win/arcaea/calc/
n = [0, 0, 0.0005831753900000081, 0.004665403120000065, 0.015745735529959858, 0.03732322495992008, 0.07289692374980007, 0.12596588423968, 0.2000291587694801, 0.29858579967923987, 0.42513485930893946,
0.5748651406910605, 0.7014142003207574, 0.7999708412305152, 0.8740341157603029, 0.9271030762501818, 0.962676775040091, 0.9842542644700301, 0.9953345968799998, 0.9994168246100001, 1]
e = n[lva] - n[lvb]
a = stata - statb
r = a / e
d = stata - n[lva] * r
return d + r * n[level]
def calc_char_value_30(level, stata, statb, lva=20, lvb=30):
# 计算21~30级搭档数值返回浮点数
return (level - lva) * (statb - stata) / (lvb - lva) + stata
if level < 1 or level > 30:
return 0
elif 1 <= level <= 20:
return calc_char_value_20(level, value1, value20)
else:
return calc_char_value_30(level, value20, value30)
def get_char_core(c, character_id):
# 得到对应角色觉醒所需的核心,返回字典列表
r = []
c.execute(
'''select item_id, amount from char_item where character_id = ? and type="core"''', (character_id,))
x = c.fetchall()
if x:
for i in x:
r.append({'core_type': i[0], 'amount': i[1]})
return r
def get_user_character(c, user_id):
# 得到用户拥有的角色列表,返回列表
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select * from user_char_full a,character b where a.user_id = :user_id and a.character_id=b.character_id''',
{'user_id': user_id})
else:
c.execute('''select * from user_char a,character b where a.user_id = :user_id and a.character_id=b.character_id''',
{'user_id': user_id})
x = c.fetchall()
if not x and not Config.CHARACTER_FULL_UNLOCK:
# 添加初始角色
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 0, 1, 0, 0, 0))
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 1, 1, 0, 0, 0))
c.execute('''select * from user_char a,character b where a.user_id = :user_id and a.character_id=b.character_id''',
{'user_id': user_id})
x = c.fetchall()
if not x:
return []
r = []
for i in x:
char = {
"is_uncapped_override": int2b(i[5]),
"is_uncapped": int2b(i[4]),
"uncap_cores": get_char_core(c, i[1]),
"char_type": i[22],
"skill_id_uncap": i[21],
"skill_requires_uncap": int2b(i[20]),
"skill_unlock_level": i[19],
"skill_id": i[18],
"overdrive": calc_char_value(i[2], i[11], i[14], i[17]),
"prog": calc_char_value(i[2], i[10], i[13], i[16]),
"frag": calc_char_value(i[2], i[9], i[12], i[15]),
"level_exp": Constant.LEVEL_STEPS[i[2]],
"exp": i[3],
"level": i[2],
"name": i[7],
"character_id": i[1]
}
if i[1] == 21:
char["voice"] = [0, 1, 2, 3, 100, 1000, 1001]
r.append(char)
return r
def calc_level_up(c, user_id, character_id, exp, exp_addition):
# 计算角色升级,返回当前经验和等级
exp += exp_addition
if exp >= Constant.LEVEL_STEPS[20]: # 未觉醒溢出
c.execute('''select is_uncapped from user_char where user_id=? and character_id=?''',
(user_id, character_id))
x = c.fetchone()
if x and x[0] == 0:
return Constant.LEVEL_STEPS[20], 20
a = []
b = []
for i in Constant.LEVEL_STEPS:
a.append(i)
b.append(Constant.LEVEL_STEPS[i])
if exp >= b[-1]: # 溢出
return b[-1], a[-1]
if exp < b[0]: # 向下溢出,是异常状态
return 0, 1
i = len(a) - 1
while exp < b[i]:
i -= 1
return exp, a[i]

View File

@@ -1,14 +0,0 @@
class Constant:
MAX_STAMINA = 12
STAMINA_RECOVER_TICK = 1800000
CORE_EXP = 250
LEVEL_STEPS = {1: 0, 2: 50, 3: 100, 4: 150, 5: 200, 6: 300, 7: 450, 8: 650, 9: 900, 10: 1200, 11: 1600, 12: 2100, 13: 2700, 14: 3400, 15: 4200, 16: 5100,
17: 6100, 18: 7200, 19: 8500, 20: 10000, 21: 11500, 22: 13000, 23: 14500, 24: 16000, 25: 17500, 26: 19000, 27: 20500, 28: 22000, 29: 23500, 30: 25000}
ETO_UNCAP_BONUS_PROGRESS = 7
LUNA_UNCAP_BONUS_PROGRESS = 7
AYU_UNCAP_BONUS_PROGRESS = 5

View File

@@ -1,195 +0,0 @@
import server.arcworld
import server.arcpurchase
import server.character
import server.item
from setting import Config
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def get_recent_score(c, user_id):
# 得到用户最近一次的成绩,返回列表
c.execute('''select * from user where user_id = :x''', {'x': user_id})
x = c.fetchone()
if x is not None:
if x[11] is not None:
c.execute('''select best_clear_type from best_score where user_id=:u and song_id=:s and difficulty=:d''', {
'u': user_id, 's': x[11], 'd': x[12]})
y = c.fetchone()
if y is not None:
best_clear_type = y[0]
else:
best_clear_type = x[21]
return [{
"rating": x[22],
"modifier": x[19],
"time_played": x[20],
"health": x[18],
"best_clear_type": best_clear_type,
"clear_type": x[21],
"miss_count": x[17],
"near_count": x[16],
"perfect_count": x[15],
"shiny_perfect_count": x[14],
"score": x[13],
"difficulty": x[12],
"song_id": x[11]
}]
return []
def get_user_friend(c, user_id):
# 得到用户的朋友列表,返回列表
c.execute('''select user_id_other from friend where user_id_me = :user_id''', {
'user_id': user_id})
x = c.fetchall()
s = []
if x != [] and x[0][0] is not None:
for i in x:
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
{'x': i[0], 'y': user_id})
if c.fetchone() == (1,):
is_mutual = True
else:
is_mutual = False
c.execute('''select * from user where user_id = :x''', {'x': i[0]})
y = c.fetchone()
if y is not None:
character = y[6]
is_char_uncapped = int2b(y[8])
is_char_uncapped_override = int2b(y[9])
if y[23] != -1:
character = y[23]
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id=:a and character_id=:b''', {
'a': i[0], 'b': character})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id=:a and character_id=:b''', {
'a': i[0], 'b': character})
z = c.fetchone()
if z:
is_char_uncapped = int2b(z[0])
is_char_uncapped_override = int2b(z[1])
rating = y[5]
if int2b(y[10]):
rating = -1
s.append({
"is_mutual": is_mutual,
"is_char_uncapped_override": is_char_uncapped_override,
"is_char_uncapped": is_char_uncapped,
"is_skill_sealed": int2b(y[7]),
"rating": rating,
"join_date": int(y[3]),
"character": character,
"recent_score": get_recent_score(c, i[0]),
"name": y[1],
"user_id": i[0]
})
s.sort(key=lambda item: item["recent_score"][0]["time_played"] if len(
item["recent_score"]) > 0 else 0, reverse=True)
return s
def get_user_me(c, user_id):
# 构造user/me的数据返回字典
c.execute('''select * from user where user_id = :x''', {'x': user_id})
x = c.fetchone()
r = {}
if x is not None:
user_character = server.character.get_user_character(c, user_id)
# 下面没有使用get_user_characters函数是为了节省一次查询
characters = []
for i in user_character:
characters.append(i['character_id'])
character_id = x[6]
if character_id not in characters:
character_id = 0
c.execute(
'''update user set character_id=0 where user_id=?''', (user_id,))
favorite_character_id = x[23]
if favorite_character_id not in characters: # 这是考虑有可能favourite_character设置了用户未拥有的角色
favorite_character_id = -1
c.execute(
'''update user set favorite_character=-1 where user_id=?''', (user_id,))
# 计算世界排名
c.execute(
'''select world_rank_score from user where user_id=?''', (user_id,))
y = c.fetchone()
if y and y[0]:
c.execute(
'''select count(*) from user where world_rank_score > ?''', (y[0],))
y = c.fetchone()
if y and y[0] + 1 <= Config.WORLD_RANK_MAX:
world_rank = y[0] + 1
else:
world_rank = 0
else:
world_rank = 0
# 源点强化
prog_boost = 0
if x[27] and x[27] != 0:
prog_boost = 300
# 体力计算
next_fragstam_ts = -1
max_stamina_ts = 1586274871917
stamina = 12
if x[31]:
next_fragstam_ts = x[31]
if x[32]:
max_stamina_ts = x[32]
if x[33]:
stamina = x[33]
r = {"is_aprilfools": Config.IS_APRILFOOLS,
"curr_available_maps": server.arcworld.get_available_maps(),
"character_stats": user_character,
"friends": get_user_friend(c, user_id),
"settings": {
"favorite_character": favorite_character_id,
"is_hide_rating": int2b(x[10]),
"max_stamina_notification_enabled": int2b(x[24])
},
"user_id": user_id,
"name": x[1],
"user_code": x[4],
"display_name": x[1],
"ticket": x[26],
"character": character_id,
"is_locked_name_duplicate": False,
"is_skill_sealed": int2b(x[7]),
"current_map": x[25],
"prog_boost": prog_boost,
"next_fragstam_ts": next_fragstam_ts,
"max_stamina_ts": max_stamina_ts,
"stamina": server.arcworld.calc_stamina(max_stamina_ts, stamina),
"world_unlocks": server.item.get_user_items(c, user_id, 'world_unlock'),
"world_songs": server.item.get_user_items(c, user_id, 'world_song'),
"singles": server.item.get_user_items(c, user_id, 'single'),
"packs": server.item.get_user_items(c, user_id, 'pack'),
"characters": characters,
"cores": server.item.get_user_cores(c, user_id),
"recent_score": get_recent_score(c, user_id),
"max_friend": 50,
"rating": x[5],
"join_date": int(x[3]),
"global_rank": world_rank
}
return r

View File

@@ -1,106 +0,0 @@
from setting import Config
def get_user_items(c, user_id, item_type):
# 得到用户的物品,返回列表,不包含数量信息
if item_type == 'world_song' and Config.WORLD_SONG_FULL_UNLOCK or item_type == 'world_unlock' and Config.WORLD_SCENERY_FULL_UNLOCK:
c.execute('''select item_id from item where type=?''', (item_type,))
else:
c.execute('''select item_id from user_item where user_id = :user_id and type = :item_type''',
{'user_id': user_id, 'item_type': item_type})
x = c.fetchall()
if not x:
return []
re = []
for i in x:
re.append(i[0])
return re
def get_user_cores(c, user_id):
# 得到用户的core返回字典列表
r = []
c.execute(
'''select item_id, amount from user_item where user_id = ? and type="core"''', (user_id,))
x = c.fetchall()
if x:
for i in x:
if i[1]:
amount = i[1]
else:
amount = 0
r.append({'core_type': i[0], 'amount': amount})
return r
def claim_user_item(c, user_id, item_id, item_type, amount=1):
# 处理用户物品,包括添加和删除操作,返回成功与否布尔值
# 这个接口允许memory变成负数所以不能用来购买
try:
amount = int(amount)
except:
return False
if item_type not in ['memory', 'character']:
c.execute('''select is_available from item where item_id=? and type=?''',
(item_id, item_type))
x = c.fetchone()
if x:
if x[0] == 0:
return False
else:
return False
if item_type in ['core', 'anni5tix']:
c.execute(
'''select amount from user_item where user_id=? and item_id=? and type=?''', (user_id, item_id, item_type))
x = c.fetchone()
if x:
if x[0] + amount < 0: # 数量不足
return False
c.execute('''update user_item set amount=? where user_id=? and item_id=? and type=?''',
(x[0]+amount, user_id, item_id, item_type))
else:
if amount < 0: # 添加数量错误
return False
c.execute('''insert into user_item values(?,?,?,?)''',
(user_id, item_id, item_type, amount))
elif item_type == 'memory':
c.execute('''select ticket from user where user_id=?''', (user_id,))
x = c.fetchone()
if x is not None:
c.execute('''update user set ticket=? where user_id=?''',
(x[0]+amount, user_id))
else:
return False
elif item_type == 'character':
if not item_id.isdigit():
c.execute(
'''select character_id from character where name=?''', (item_id,))
x = c.fetchone()
if x:
character_id = x[0]
else:
return False
else:
character_id = int(item_id)
c.execute(
'''select exists(select * from user_char where user_id=? and character_id=?)''', (user_id, character_id))
if c.fetchone() == (0,):
c.execute('''insert into user_char values(?,?,1,0,0,0)''',
(user_id, character_id))
elif item_type in ['world_song', 'world_unlock', 'single', 'pack']:
c.execute('''select exists(select * from user_item where user_id=? and item_id=? and type=?)''',
(user_id, item_id, item_type))
if c.fetchone() == (0,):
c.execute('''insert into user_item values(:a,:b,:c,1)''',
{'a': user_id, 'b': item_id, 'c': item_type})
return True

View File

@@ -18,7 +18,7 @@ def present_info(user_id):
x = UserPresentList(c, UserOnline(c, user_id)) x = UserPresentList(c, UserOnline(c, user_id))
x.select_user_presents() x.select_user_presents()
return success_return(x.to_dict()) return success_return(x.to_dict_list())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()

View File

@@ -21,7 +21,7 @@ def bundle_pack(user_id):
try: try:
x = PurchaseList(c, UserOnline(c, user_id) x = PurchaseList(c, UserOnline(c, user_id)
).select_from_type('pack') ).select_from_type('pack')
return success_return(x.to_dict) return success_return(x.to_dict_list())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()
@@ -34,7 +34,7 @@ def get_single(user_id):
try: try:
x = PurchaseList(c, UserOnline(c, user_id) x = PurchaseList(c, UserOnline(c, user_id)
).select_from_type('single') ).select_from_type('single')
return success_return(x.to_dict) return success_return(x.to_dict_list())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()

View File

@@ -63,7 +63,7 @@ def song_score_post(user_id):
if not x.is_valid: if not x.is_valid:
raise InputError('Invalid score.', 107) raise InputError('Invalid score.', 107)
x.upload_score() x.upload_score()
return success_return(x.to_dict) return success_return(x.to_dict())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()
@@ -78,7 +78,7 @@ def song_score_top(user_id):
rank_list.song.set_chart(request.args.get( rank_list.song.set_chart(request.args.get(
'song_id'), request.args.get('difficulty')) 'song_id'), request.args.get('difficulty'))
rank_list.select_top() rank_list.select_top()
return success_return(rank_list.to_dict_list) return success_return(rank_list.to_dict_list())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()
@@ -93,7 +93,7 @@ def song_score_me(user_id):
rank_list.song.set_chart(request.args.get( rank_list.song.set_chart(request.args.get(
'song_id'), request.args.get('difficulty')) 'song_id'), request.args.get('difficulty'))
rank_list.select_me(UserOnline(c, user_id)) rank_list.select_me(UserOnline(c, user_id))
return success_return(rank_list.to_dict_list) return success_return(rank_list.to_dict_list())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()
@@ -108,7 +108,7 @@ def song_score_friend(user_id):
rank_list.song.set_chart(request.args.get( rank_list.song.set_chart(request.args.get(
'song_id'), request.args.get('difficulty')) 'song_id'), request.args.get('difficulty'))
rank_list.select_friend(UserOnline(c, user_id)) rank_list.select_friend(UserOnline(c, user_id))
return success_return(rank_list.to_dict_list) return success_return(rank_list.to_dict_list())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()

View File

@@ -1,160 +0,0 @@
from server.sql import Connect
from setting import Config
from .config import Constant
import server.info
import server.character
def b2int(x):
# int与布尔值转换
if x:
return 1
else:
return 0
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
return False
else:
return True
def change_char(user_id, character_id, skill_sealed):
# 角色改变,包括技能封印的改变,返回成功与否的布尔值
re = False
with Connect() as c:
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
x = c.fetchone()
if x:
is_uncapped = x[0]
is_uncapped_override = x[1]
else:
return False
if skill_sealed == 'false':
skill_sealed = False
else:
skill_sealed = True
c.execute('''update user set is_skill_sealed = :a, character_id = :b, is_char_uncapped = :c, is_char_uncapped_override = :d where user_id = :e''', {
'a': b2int(skill_sealed), 'b': character_id, 'c': is_uncapped, 'd': is_uncapped_override, 'e': user_id})
re = True
return re
def change_char_uncap(user_id, character_id):
# 角色觉醒改变,返回字典
r = None
with Connect() as c:
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
else:
c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id = :a and character_id = :b''',
{'a': user_id, 'b': character_id})
x = c.fetchone()
if x is not None and x[0] == 1:
c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
'a': b2int(x[1] == 0), 'b': user_id})
if not Config.CHARACTER_FULL_UNLOCK:
c.execute('''update user_char set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': b2int(x[1] == 0), 'b': user_id, 'c': character_id})
c.execute(
'''select * from user_char a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''', (user_id, character_id))
else:
c.execute('''update user_char_full set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': b2int(x[1] == 0), 'b': user_id, 'c': character_id})
c.execute(
'''select * from user_char_full a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''', (user_id, character_id))
y = c.fetchone()
if y is not None:
r = {
"is_uncapped_override": int2b(y[5]),
"is_uncapped": int2b(y[4]),
"uncap_cores": server.character.get_char_core(c, y[1]),
"char_type": y[22],
"skill_id_uncap": y[21],
"skill_requires_uncap": int2b(y[20]),
"skill_unlock_level": y[19],
"skill_id": y[18],
"overdrive": server.character.calc_char_value(y[2], y[11], y[14], y[17]),
"prog": server.character.calc_char_value(y[2], y[10], y[13], y[16]),
"frag": server.character.calc_char_value(y[2], y[9], y[12], y[15]),
"level_exp": Constant.LEVEL_STEPS[y[2]],
"exp": y[3],
"level": y[2],
"name": y[7],
"character_id": y[1]
}
return r
def arc_sys_set(user_id, value, set_arg):
# 三个设置PTT隐藏、体力满通知、最爱角色无返回
with Connect() as c:
if 'favorite_character' in set_arg:
value = int(value)
c.execute('''update user set favorite_character = :a where user_id = :b''', {
'a': value, 'b': user_id})
else:
if value == 'false':
value = False
else:
value = True
if 'is_hide_rating' in set_arg:
c.execute('''update user set is_hide_rating = :a where user_id = :b''', {
'a': b2int(value), 'b': user_id})
if 'max_stamina_notification_enabled' in set_arg:
c.execute('''update user set max_stamina_notification_enabled = :a where user_id = :b''', {
'a': b2int(value), 'b': user_id})
return None
def arc_add_friend(user_id, friend_id):
# 加好友返回好友列表或者是错误码602、604
if user_id == friend_id:
return 604
r = None
with Connect() as c:
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
{'x': user_id, 'y': friend_id})
if c.fetchone() == (0,):
c.execute('''insert into friend values(:a, :b)''',
{'a': user_id, 'b': friend_id})
r = server.info.get_user_friend(c, user_id)
else:
r = 602
return r
def arc_delete_friend(user_id, friend_id):
# 删好友,返回好友列表
r = None
with Connect() as c:
c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
{'x': user_id, 'y': friend_id})
if c.fetchone() == (1,):
c.execute('''delete from friend where user_id_me = :x and user_id_other = :y''',
{'x': user_id, 'y': friend_id})
r = server.info.get_user_friend(c, user_id)
return r

View File

@@ -1,83 +0,0 @@
import sqlite3
from flask import current_app
import traceback
class Connect():
# 数据库连接类,上下文管理
def __init__(self, file_path='./database/arcaea_database.db'):
"""
数据库连接默认连接arcaea_database.db
接受:文件路径
返回sqlite3连接操作对象
"""
self.file_path = file_path
def __enter__(self):
self.conn = sqlite3.connect(self.file_path)
self.c = self.conn.cursor()
return self.c
def __exit__(self, exc_type, exc_val, exc_tb):
if self.conn:
self.conn.commit()
self.conn.close()
if exc_type is not None:
current_app.logger.error(
traceback.format_exception(exc_type, exc_val, exc_tb))
return True
class Sql():
@staticmethod
def select(c, table_name, target_column=[], query=None):
# 执行查询单句sql语句返回fetchall数据
# 使用准确查询,且在单表内
sql = 'select '
sql_dict = {}
if len(target_column) >= 2:
sql += target_column[0]
for i in range(1, len(target_column)):
sql += ',' + target_column[i]
sql += ' from ' + table_name
elif len(target_column) == 1:
sql += target_column[0] + ' from ' + table_name
else:
sql += '* from ' + table_name
where_field = []
where_value = []
if query:
for i in query.query:
where_field.append(i)
where_value.append(query.query[i])
if where_field and where_value:
sql += ' where '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 2:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
if query and query.sort:
sql += ' order by ' + \
query.sort[0]['column'] + ' ' + query.sort[0]['order']
for i in range(1, len(query.sort)):
sql += ', ' + query.sort[i]['column'] + \
' ' + query.sort[i]['order']
if query and query.limit >= 0:
sql += ' limit :limit offset :offset'
sql_dict['limit'] = query.limit
sql_dict['offset'] = query.offset
c.execute(sql, sql_dict)
return c.fetchall()

View File

@@ -80,7 +80,7 @@ def toggle_uncap(user_id, character_id):
character = UserCharacter(c, character_id) character = UserCharacter(c, character_id)
character.change_uncap_override(user) character.change_uncap_override(user)
character.select_character_info(user) character.select_character_info(user)
return success_return({'user_id': user.user_id, 'character': [character.to_dict]}) return success_return({'user_id': user.user_id, 'character': [character.to_dict()]})
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()
@@ -96,7 +96,7 @@ def character_first_uncap(user_id, character_id):
character = UserCharacter(c, character_id) character = UserCharacter(c, character_id)
character.select_character_info(user) character.select_character_info(user)
character.character_uncap(user) character.character_uncap(user)
return success_return({'user_id': user.user_id, 'character': [character.to_dict], 'cores': user.cores}) return success_return({'user_id': user.user_id, 'character': [character.to_dict()], 'cores': user.cores})
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()
@@ -130,7 +130,7 @@ def cloud_get(user_id):
user.user_id = user_id user.user_id = user_id
save = SaveData(c) save = SaveData(c)
save.select_all(user) save.select_all(user)
return success_return(save.to_dict) return success_return(save.to_dict())
except ArcError as e: except ArcError as e:
return error_return(e) return error_return(e)
return error_return() return error_return()

View File

@@ -252,13 +252,3 @@ class Config():
''' '''
-------------------- --------------------
''' '''
'''
--------------------
是否使用最好的 10 条记录(而不是最近的 30 条记录中较好的 10 条)来计算 PTT
Calculate PTT with best 10 instead of recent best 10
'''
USE_B10_AS_R10 = False
'''
--------------------
'''

View File

@@ -1,3 +1,6 @@
from .udp_config import Config
def b(value, length=1): def b(value, length=1):
return value.to_bytes(length=length, byteorder='little') return value.to_bytes(length=length, byteorder='little')
@@ -35,7 +38,7 @@ class Player:
self.last_timestamp = 0 self.last_timestamp = 0
self.extra_command_queue = [] self.extra_command_queue = []
self.song_unlock = b'\x00' * 512 self.song_unlock = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH
self.start_command_num = 0 self.start_command_num = 0
@@ -58,7 +61,7 @@ class Room:
self.song_idx = 0xffff self.song_idx = 0xffff
self.last_song_idx = 0xffff self.last_song_idx = 0xffff
self.song_unlock = b'\x00' * 512 self.song_unlock = b'\x00' * Config.LINK_PLAY_UNLOCK_LENGTH
self.host_id = 0 self.host_id = 0
self.players = [Player(), Player(), Player(), Player()] self.players = [Player(), Player(), Player(), Player()]
@@ -120,12 +123,12 @@ class Room:
def update_song_unlock(self): def update_song_unlock(self):
# 更新房间可用歌曲 # 更新房间可用歌曲
r = bi(b'\xff' * 512) r = bi(b'\xff' * Config.LINK_PLAY_UNLOCK_LENGTH)
for i in self.players: for i in self.players:
if i.player_id != 0: if i.player_id != 0:
r &= bi(i.song_unlock) r &= bi(i.song_unlock)
self.song_unlock = b(r, 512) self.song_unlock = b(r, Config.LINK_PLAY_UNLOCK_LENGTH)
def is_ready(self, old_state: int, player_state: int): def is_ready(self, old_state: int, player_state: int):
# 是否全部准备就绪 # 是否全部准备就绪

View File

@@ -5,4 +5,7 @@ class Config:
COUNTDOWM_TIME = 3999 COUNTDOWM_TIME = 3999
PLAYER_PRE_TIMEOUT = 3000000
PLAYER_TIMEOUT = 20000000 PLAYER_TIMEOUT = 20000000
LINK_PLAY_UNLOCK_LENGTH = 512

View File

@@ -176,7 +176,7 @@ class CommandParser:
t = self.room.players[i] t = self.room.players[i]
if t.player_id != 0: if t.player_id != 0:
if t.last_timestamp != 0: if t.last_timestamp != 0:
if t.online == 1 and x.timestamp - t.last_timestamp >= 3000000: if t.online == 1 and x.timestamp - t.last_timestamp >= Config.PLAYER_PRE_TIMEOUT:
t.online = 0 t.online = 0
self.room.command_queue_length += 1 self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i)) self.room.command_queue.append(x.command_12(i))

View File

@@ -3,6 +3,7 @@ import time
import server.arcscore import server.arcscore
from core.download import initialize_songfile from core.download import initialize_songfile
from core.rank import RankList
from core.sql import Connect from core.sql import Connect
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
@@ -215,25 +216,30 @@ def single_chart_top():
difficulty = request.form['difficulty'] difficulty = request.form['difficulty']
if difficulty.isdigit(): if difficulty.isdigit():
difficulty = int(difficulty) difficulty = int(difficulty)
else:
difficulty = 0
error = None error = None
x = None x = None
with Connect('') as c: with Connect() as c:
song_name = '%'+song_name+'%' song_name = '%'+song_name+'%'
c.execute('''select song_id, name from chart where song_id like :a limit 1''', c.execute('''select song_id, name from chart where song_id like :a limit 1''',
{'a': song_name}) {'a': song_name})
x = c.fetchone() x = c.fetchone()
if x: if x:
song_id = x[0] y = RankList(c)
posts = server.arcscore.arc_score_top(song_id, difficulty, -1) y.song.set_chart(x[0], difficulty)
for i in posts: y.limit = -1
i['time_played'] = time.strftime('%Y-%m-%d %H:%M:%S', y.select_top()
time.localtime(i['time_played'])) posts = y.to_dict_list()
else: for i in posts:
error = '查询为空 No song.' i['time_played'] = time.strftime('%Y-%m-%d %H:%M:%S',
time.localtime(i['time_played']))
else:
error = '查询为空 No song.'
if not error: if not error:
return render_template('web/singlecharttop.html', posts=posts, song_name_en=x[1], song_id=song_id, difficulty=difficulty) return render_template('web/singlecharttop.html', posts=posts, song_name_en=x[1], song_id=x[0], difficulty=difficulty)
else: else:
flash(error) flash(error)