52 Commits
v2.5 ... v2.8.5

Author SHA1 Message Date
Lost-MSth
96fbd26342 Update to v2.8.5 2022-04-28 18:29:26 +08:00
Lost-MSth
aa77a4c903 Update requirements 2022-04-16 18:37:11 +08:00
Lost-MSth
6075bda5d8 Merge pull request #49 from Young-Lord/patch-1
Modify the import of link_play module
2022-04-16 18:35:23 +08:00
LY
409c2c032b Modify the location of the import of link_play
... to reduce unnecessary imports (both link_play and cryptography)
2022-04-16 18:29:40 +08:00
Lost-MSth
e8c15f5069 Add a register api
- Add a register api
- Change requirements, which flask version should be over 2.0
2022-04-10 21:02:46 +08:00
Lost-MSth
35c34f25d5 Try to fix a bug in Unix
multiprocess bug of python in Unix operating system:
The subprocess will not start when using 'fork' mode. This will make the UDP server down.
2022-04-05 22:12:23 +08:00
Lost-MSth
4b8c5a520e Fix a bug about core
A typing error
2022-04-02 12:53:42 +08:00
Lost-MSth
1235733ddf Update to v2.8.6 2022-04-01 13:37:38 +08:00
Lost-MSth
cb6425a0d1 Update to v2.8.3 without release
- Unlock the character **linka**.
- Update the song database.
2022-03-24 16:30:46 +08:00
Lost-MSth
3a1c731e24 Update to v2.8.2 without release
- Try to add support for Anniversary 5 ticket
- Update the song database

> Well, there may be bugs with Anniversary 5 ticket. Remember it can only be used when the pack is on sale.
2022-03-09 18:44:00 +08:00
Lost-MSth
5de274fdf5 Update to v2.8.1 2022-02-20 23:36:44 +08:00
Lost-MSth
b60457c38b Ready to update to v2.8.1
Wait for chart constants and character values.
2022-02-17 21:16:57 +08:00
Lost-MSth
35b954e549 Fix two bugs
- Fix a bug when downloading a beyond chart which has another audio file.

- Fix a safety problem when downloading.
2022-02-14 16:35:36 +08:00
Lost-MSth
ceebba4664 Fix a bug
Fix a bug that the high version of ios client cannot log in.
2022-01-27 19:26:13 +08:00
Lost-MSth
5bc9b9a3dc Fix a bug
Fix a bug about link play not getting the right address.
If you use reverse proxy, this may be helpful.
2022-01-24 22:38:43 +08:00
Lost-MSth
d9a543bc5a Change something about constant table and aggregate 2022-01-24 21:44:37 +08:00
Lost-MSth
4666c9c153 Merge pull request #35 from Young-Lord/master
加入aggregate
2022-01-24 21:28:44 +08:00
Lost-MSth
32bcbb0ccd Change something about aggregate 2022-01-24 21:22:38 +08:00
Young-Lord
34497d0638 Remove requests from requirements 2022-01-22 10:42:42 +08:00
Young-Lord
23bf3c020f Change the implement of experimental agreegate API 2022-01-22 10:40:06 +08:00
Young-Lord
e1fc1bbcd1 Minor fix 2022-01-22 07:35:57 +08:00
Young-Lord
355ec3557f Add experimental aggregate API 2022-01-21 22:55:23 +08:00
Young-Lord
cc4ac192e7 Update .gitignore 2022-01-21 22:53:22 +08:00
Lost-MSth
5068c7215c Merge pull request #34 from Lost-MSth/test
Update to v2.8
2022-01-20 16:43:31 +08:00
Lost-MSth
99f05cabb7 Update README.md 2022-01-20 16:42:33 +08:00
Lost-MSth
1a097bc4d0 Update to v2.8 2022-01-20 16:39:57 +08:00
Lost-MSth
849f4f7260 Add Link Play 2022-01-19 17:52:32 +08:00
Lost-MSth
add81ee639 Update README.md 2022-01-18 15:39:27 +08:00
Lost-MSth
b927ad23f8 Update to v2.7.2 without release
- Unlock the character **marija**
- Update the song database
2021-12-26 21:29:12 +08:00
Lost-MSth
8bc74b36c7 Update to v2.7.1 without release
- Unlock the character **shirahime**
- Fix the values of two characters
- Update the song database

> It will take more time for me to make the Link Play system. I am not sure how much time it will take, because it is a new challenge for me.
2021-12-13 00:26:01 +08:00
Lost-MSth
7dc8bfea9a Fix a bug
#32
Fix a bug about register
2021-11-21 13:57:09 +08:00
Lost-MSth
d0be49fe20 Update to v2.7.0 2021-11-14 21:35:34 +08:00
Lost-MSth
b494b93c14 Update to v2.6.6 without release
- Fix a bug about purchase discount
- Fix a bug about database synchronization, which may make 'api_login' table empty
- For Arcaea 3.8.8
- Update a logout api
2021-10-22 20:00:22 +08:00
Lost-MSth
a523ff5aae Update README.md
Add some download links.

close #28
2021-10-01 16:24:56 +08:00
Lost-MSth
acbe9fc4a1 Merge pull request #31 from superDXZ/master
Try to add some download links.
2021-10-01 16:19:53 +08:00
superDXZ
9422bf7182 Update README.md
Found another download site
2021-10-01 02:49:06 +08:00
superDXZ
53acad4ffb Update README.md
Found another download site
2021-10-01 02:46:39 +08:00
Lost-MSth
13048e57f4 Update to v2.6.5 without release
- Change something to make that purchase things can have some cores.
- Fix the download link of Arcaea client in README

> This is a small update.
2021-09-30 19:02:10 +08:00
Lost-MSth
39d3f073ba Update to v2.6.4 without release 2021-09-08 17:35:10 +08:00
Lost-MSth
2cf6595478 Update to v2.6.3 2021-08-25 14:31:06 +08:00
Lost-MSth
7c3bc99570 Update to v2.6.2 2021-08-11 18:45:32 +08:00
Lost-MSth
168e5f0b12 Update to v2.6.1 2021-07-21 18:14:26 +08:00
Lost-MSth
9286caf900 Update to v2.6 2021-07-01 18:46:16 +08:00
Lost-MSth
b0647bedb6 Merge pull request #24 from MBRjun/patch-1
Update run.bat

To deal with the problem when you run *run.bat* with Administrator power and in another path.
2021-06-20 22:07:52 +08:00
MBRjun
f49a868707 Update run.bat 2021-06-20 22:04:41 +08:00
MBRjun
28bca25092 Update run.bat
cd %~dp0 用于cd到当前目录
使用管理员权限或Administrator账户运行脚本默认路径为%windir%\system32
2021-06-20 21:47:02 +08:00
Lost-MSth
0227d2b44a Merge pull request #22 from Menci/sort_friend_list
Sort friend list
2021-06-17 13:03:54 +08:00
Lost-MSth
0d95b886bc Merge pull request #23 from Menci/use_b10_as_r10
Add USE_B10_AS_R10 option
2021-06-17 12:53:33 +08:00
Menci
9b61acd163 Sort friend list 2021-06-17 03:04:39 +08:00
Menci
be134bbc6e Add USE_B10_AS_R10 option 2021-06-16 19:02:04 +00:00
Lost-MSth
c8d0463b9e Fix a small bug
Fix a bug about double slashes in URL, which will influence character uncap, uncap override and level up with EXP cores when you use a special game API prefix, like '/latte' or '/latte/1'.
It means that the length of game API prefix is not 2 in the end (not '/*** . . . **/**') and the bug will happen.
2021-06-14 18:04:16 +08:00
Lost-MSth
871c2082a8 Fix two small bugs
1. Rank list numbers in my world rank may disappear.
2.  Characters in rank list may be locked to uncapping state.
2021-06-10 22:27:29 +08:00
59 changed files with 6645 additions and 2566 deletions

4
.gitignore vendored
View File

@@ -1 +1,5 @@
*.log
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

View File

@@ -21,6 +21,7 @@ This procedure is mainly used for study and research, and shall not be used for
- PTT
- 世界排名 Global rank
- 排名 Rank
- Link Play
- 好友系统 Friends
- 数据同步 Data synchronization
- 爬梯 Climbing steps
@@ -53,7 +54,9 @@ It is just so interesting. What it can do is under exploration.
## 下载 Download
[这里 Here](https://github.com/Lost-MSth/Arcaea-server/releases)
[Arcaea](https://konmai.cn/#arcaea)
[Arcaea-CN official](https://arcaea.lowiro.com/zh)
[Arcaea-lowi.ro](https://lowi.ro)
[Arcaea-RhyDown](https://rhydown.com)
## 更新日志 Update log
只保留最新版本 Only keep the latest version.
@@ -62,42 +65,43 @@ 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.5
- 适用于Arcaea 3.6.2版本 For Arcaea 3.6.2
### Version 2.8.5
- 适用于Arcaea 3.12.8版本 For Arcaea 3.12.8
- 更新了歌曲数据库 Update the song database.
- 新增世界排名 Add global rank.
- 新增世界模式的奖励获取 Add reward acquisition of new world mode.
- 完善角色系统,包括: Improve the character system, including
- 角色升级 Level up
- 角色觉醒 Upcapping
- 角色核心 Character cores
- 世界模式下的数值计算 Numerical calculation in world mode
- 某些场景下的角色解锁 Character unlocking in some cases
- 修复一个导致无法升级角色的拼写错误 Fix a typing error, which makes giving characters Exp wrong.
- 尝试强制指定多进程启动方式为spawn这可能对UNIX系统中UDP服务器的启动有所帮助 Try to forcibly specify the multiprocess startup mode as spawn, which may be helpful for the startup of UDP server in UNIX system.
- 添加了注册API对外接口 Add an external API interface of user register.
- 重构一些代码,顺手修复了登陆时因多设备登录封号的用户没有正确显示错误提示的问题 Refactoring some codes and in passing fix the problem that users which has been banned because of multiple devices do not show error messages correctly when logging in.
> 注意:
> - 现在Flask最低版本要求提高到2.0
> - 服务端可能不再完全支持低版本客户端
> - 对3.12.6c版本,愚人节开关打开时点击`网络`按钮会闪退,原因不明
>
> Note:
> - Now the version of Flask which is required should be up to 2.0.
> - The server may no longer fully support lower version clients.
> - For version 3.12.6c, when the switch of April Fool's Day is on, clicking the `Network` button will make the client break down. The reason is not clear now.
- 新增了一些设置选项和后台功能 Add some setting options and background functions.
- 正在构建API接口 Building API interface.
- 打算重新写后台界面 Plan to rewrite the background interface.
- 修复了一些Bug Fix some bugs.
- 登录时获取信息的并行计算导致的数据库锁错误 Database lock error caused by parallel computing of getting information during login
> 提醒:由于时间仓促,本版本可能存在较多问题与漏洞,请谨慎使用
> Tips: Due to lack of time, this version may have many bugs, please use it with caution.
## 运行环境与依赖 Running environment and requirements
- Windows/Linux/Mac OS/Android
- Windows/Linux/Mac OS/Android...
- Python 3
- Flask module
- Flask module >= 2.0, Cryptography module
- Charles, IDA, proxy app... (optional)
<!--
## 环境搭建 Environment construction
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA)
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Environment-construction)
-->
## 使用说明 Instruction for use
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E)
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Instruction-for-use)
-->
## 注意 Attentions
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E6%B3%A8%E6%84%8F)

View File

View File

@@ -1,5 +1,7 @@
import hashlib
import base64
import time
import random
from server.sql import Connect
import functools
from setting import Config
@@ -14,14 +16,50 @@ class User():
self.power = power
def login():
# 登录接口
return {'token': 1}, 0
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():
# 登出接口
pass
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):
@@ -86,7 +124,7 @@ def role_required(request, power=[]):
return jsonify({'status': 400, 'code': -1, 'data': {}, 'msg': 'Payload must be a valid json'})
if not 'Token' in request.headers:
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No Token'})
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No token'})
user = User()
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
@@ -98,7 +136,7 @@ def role_required(request, power=[]):
user.user_id = api_token_get_id(
c, request.headers['Token'])
if user.user_id is None:
return jsonify({'status': 404, 'code': -1, 'data': {}, 'msg': ''})
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No token'})
role_id = id_get_role_id(c, user.user_id)
user.role = role_id_get_role(c, role_id)

View File

@@ -1,15 +1,34 @@
from flask import jsonify
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',
'-101': 'Wrong data type',
'-102': 'Wrong query parameter',
'-103': 'Wrong sort parameter',
'-104': 'Wrong sort order parameter'
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[str(code)]
return msg[code]
def return_encode(code: int = 0, data: dict = {}, status: int = 200, msg: str = ''):
# 构造返回返回jsonify处理过后的response_class
if msg == '':
msg = code_get_msg(code)
if code < 0:
return jsonify({'status': status, 'code': code, 'data': {}, 'msg': msg})
else:
return jsonify({'status': status, 'code': code, 'data': data, 'msg': msg})

View File

@@ -1,102 +1,113 @@
from flask import (
Blueprint, request, jsonify
)
import functools
import api.api_auth
import api.users
from . import users
import api.songs
from api.api_code import code_get_msg
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=[]):
# 提取查询请求参数,返回四个参数和code
# 提取查询请求参数,返回Query类查询参数写成修饰器
limit = -1
offset = 0
query = {} # {'name': 'admin'}
sort = [] # [{'column': 'user_id', 'order': 'ASC'}, ...]
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
if 'limit' in request.json:
try:
limit = int(request.json['limit'])
except:
return -1, 0, {}, {}, -101
if 'offset' in request.json:
try:
offset = int(request.json['offset'])
except:
return -1, 0, {}, {}, -101
if 'query' in request.json:
query = request.json['query']
for i in query:
if i not in query_able:
return -1, 0, {}, {}, -102
if 'sort' in request.json:
sort = request.json['sort']
for i in sort:
if 'column' not in i or i['column'] not in sort_able:
return -1, 0, {}, {}, -103
if not 'order' in i:
i['order'] = 'ASC'
else:
if i['order'] not in ['ASC', 'DESC']:
return -1, 0, {}, {}, -104
re = Query()
return limit, offset, query, sort, 0
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 jsonify({'status': 200, 'code': 0, 'data': {}, 'msg': ''})
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(
request.json['auth'], request.remote_addr)
if code < 0:
return jsonify({'status': 200, 'code': code, 'data': {}, 'msg': code_get_msg(code)})
else:
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
str(request.json['auth']), request.remote_addr)
return return_encode(code, data)
else:
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': 'No authentication'})
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 jsonify({'status': 200, 'code': 0, 'data': {}, 'msg': ''})
return return_encode()
@bp.route('/token', methods=['DELETE'])
@api.api_auth.role_required(request, ['change_me', 'select_me', 'select'])
def token_delete(user):
# 登出
return jsonify({'status': 200, 'code': 0, 'data': {}, 'msg': ''})
return return_encode(api.api_auth.logout(user))
@bp.route('/users', methods=['GET'])
@api.api_auth.role_required(request, ['select'])
def users_get(user):
@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):
# 查询全用户信息
limit, offset, query, sort, code = get_query_parameter(request, ['user_id', 'name', 'user_code'], [
'user_id', 'name', 'user_code', 'join_date', 'rating_ptt', 'time_played', 'ticket', 'world_rank_score'])
if code < 0:
return jsonify({'status': 200, 'code': code, 'data': {}, 'msg': code_get_msg(code)})
data = api.users.get_users(limit, offset, query, sort)
data = users.get_users(query)
if not data:
return jsonify({'status': 200, 'code': -2, 'data': {}, 'msg': code_get_msg(-2)})
return return_encode(-2)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)
@bp.route('/users/<int:user_id>', methods=['GET'])
@@ -108,17 +119,17 @@ def users_user_get(user, user_id):
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-4)})
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_info(user_id)
data = users.get_user_info(user_id)
if not data:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return return_encode(-3)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)
@bp.route('/users/<int:user_id>/b30', methods=['GET'])
@@ -126,48 +137,39 @@ def users_user_get(user, user_id):
def users_user_b30_get(user, user_id):
# 查询用户b30
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-4)})
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_b30(user_id)
data = users.get_user_b30(user_id)
if data['data'] == []:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return return_encode(-3)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)
@bp.route('/users/<int:user_id>/best', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_best_get(user, user_id):
@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 == 'me':
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-4)})
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return return_encode(-1, {}, 403, 'No permission')
limit, offset, query, sort, code = get_query_parameter(request, ['song_id', 'difficulty'], [
'song_id', 'difficulty', 'score', 'time_played', 'rating'])
if code < 0:
return jsonify({'status': 200, 'code': code, 'data': {}, 'msg': code_get_msg(code)})
data = api.users.get_user_best(user_id, limit, offset, query, sort)
data = users.get_user_best(user_id, query)
if data['data'] == []:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return return_encode(-3)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)
@bp.route('/users/<int:user_id>/r30', methods=['GET'])
@@ -175,21 +177,18 @@ def users_user_best_get(user, user_id):
def users_user_r30_get(user, user_id):
# 查询用户r30
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-4)})
return return_encode(-4)
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_r30(user_id)
data = users.get_user_r30(user_id)
if data['data'] == []:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return return_encode(-3)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)
@bp.route('/songs/<string:song_id>', methods=['GET'])
@@ -200,24 +199,21 @@ def songs_song_get(user, song_id):
data = api.songs.get_song_info(song_id)
if not data:
return jsonify({'status': 200, 'code': -2, 'data': {}, 'msg': code_get_msg(-2)})
return return_encode(-2)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)
@bp.route('/songs', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
def songs_get(user):
@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):
# 查询全歌曲信息
limit, offset, query, sort, code = 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'])
if code < 0:
return jsonify({'status': 200, 'code': code, 'data': {}, 'msg': code_get_msg(code)})
data = api.songs.get_songs(limit, offset, query, sort)
data = api.songs.get_songs(query)
if not data:
return jsonify({'status': 200, 'code': -2, 'data': {}, 'msg': code_get_msg(-2)})
return return_encode(-2)
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
return return_encode(0, data)

View File

@@ -30,12 +30,12 @@ def get_song_info(song_id):
return r
def get_songs(limit=-1, offset=0, query={}, sort=[]):
def get_songs(query=None):
# 查询全部歌曲信息,返回字典列表
r = []
with Connect('./database/arcsong.db') as c:
x = Sql.select(c, 'songs', [], limit, offset, query, sort)
x = Sql.select(c, 'songs', [], query)
if x:
for i in x:

View File

@@ -1,16 +1,55 @@
from flask import (
Blueprint, request
)
from .api_code import code_get_msg, return_encode
from .api_auth import role_required
from core.user import UserRegister
from core.error import ArcError, PostError
from server.sql import Connect
from server.sql import Sql
import time
import web.webscore
import server.info
bp = Blueprint('users', __name__, url_prefix='/users')
def get_users(limit=-1, offset=0, query={}, sort=[]):
@bp.route('', methods=['POST'])
@role_required(request, ['change'])
def users_post(user):
# 注册用户
with Connect() as c:
new_user = UserRegister(c)
try:
if 'name' in request.json:
new_user.set_name(request.json['name'])
else:
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()
except ArcError as e:
return return_encode(e.api_error_code)
return return_encode(0, {'user_id': new_user.user_id, 'user_code': new_user.user_code})
def get_users(query=None):
# 获取全用户信息,返回字典列表
r = []
with Connect() as c:
x = Sql.select(c, 'user', [], limit, offset, query, sort)
x = Sql.select(c, 'user', [], query)
if x:
for i in x:
@@ -62,12 +101,12 @@ def get_user_b30(user_id):
return {'user_id': user_id, 'b30_ptt': bestptt / 30, 'data': r}
def get_user_best(user_id, limit=-1, offset=0, query={}, sort=[]):
def get_user_best(user_id, query=None):
# 获取用户b30信息返回字典
r = []
with Connect() as c:
x = Sql.select(c, 'best_score', [], limit, offset, query, sort)
x = Sql.select(c, 'best_score', [], query)
if x:
for i in x:
r.append({

View File

View File

@@ -0,0 +1,213 @@
from setting import Config
from .error import ArcError, NoData
from .constant import Constant
from .item import Item
class Level:
max_level = None
mid_level = 20
min_level = 1
level = None
exp = None
def __init__(self) -> None:
pass
@property
def level_exp(self):
return Constant.LEVEL_STEPS[self.level]
class Skill:
skill_id = None
skill_id_uncap = None
skill_unlock_level = None
skill_requires_uncap = None
def __init__(self) -> None:
pass
class Core(Item):
item_type = 'core'
amount = None
def __init__(self, core_type: str = '', amount: int = 0) -> None:
super().__init__()
self.item_id = core_type
self.amount = amount
def to_dict(self):
return {'core_type': self.item_id, 'amount': self.amount}
class CharacterValue:
start = None
mid = None
end = None
def __init__(self) -> None:
pass
@staticmethod
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]
@staticmethod
def _calc_char_value_30(level, stata, statb, lva=20, lvb=30):
# 计算21~30级搭档数值返回浮点数
return (level - lva) * (statb - stata) / (lvb - lva) + stata
def set_parameter(self, start: float = 0, mid: float = 0, end: float = 0):
self.start = start
self.mid = mid
self.end = end
def get_value(self, level: Level):
if level.min_level <= level.level <= level.mid_level:
return self._calc_char_value_20(level.level, self.start, self.mid)
elif level.mid_level < level.level <= level.max_level:
return self._calc_char_value_30(level.level, self.mid, self.end)
else:
return 0
class Character:
character_id = None
name = None
char_type = None
is_uncapped = None
is_uncapped_override = None
skill = Skill()
level = Level()
frag = CharacterValue()
prog = CharacterValue()
overdrive = CharacterValue()
uncap_cores = None
def __init__(self) -> None:
pass
@property
def uncap_cores_to_dict(self):
return [x.to_dict() for x in self.uncap_cores]
class UserCharacter(Character):
def __init__(self, c, character_id=None) -> None:
super().__init__()
self.c = c
self.character_id = character_id
def select_character_core(self):
# 获取此角色所需核心
self.c.execute(
'''select item_id, amount from char_item where character_id = ? and type="core"''', (self.character_id,))
x = self.c.fetchall()
if x:
self.uncap_cores = []
for i in x:
self.uncap_cores.append(Core(i[0], i[1]))
def select_character_uncap_condition(self, user):
# parameter: user - User类或子类的实例
# 获取此角色的觉醒信息
if Config.CHARACTER_FULL_UNLOCK:
self.c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id = :a and character_id = :b''',
{'a': user.user_id, 'b': self.character_id})
else:
self.c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''',
{'a': user.user_id, 'b': self.character_id})
x = self.c.fetchone()
if not x:
raise NoData('The character of the user does not exist.')
self.is_uncapped = x[0] == 1
self.is_uncapped_override = x[1] == 1
def select_character_info(self, user):
# parameter: user - User类或子类的实例
# 获取所给用户此角色信息
if not Config.CHARACTER_FULL_UNLOCK:
self.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.user_id, self.character_id))
else:
self.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.user_id, self.character_id))
y = self.c.fetchone()
if y is None:
raise NoData('The character of the user does not exist.')
self.name = y[7]
self.char_type = y[22]
self.is_uncapped = y[4] == 1
self.is_uncapped_override = y[5] == 1
self.level.level = y[2]
self.level.exp = y[3]
self.level.max_level = y[8]
self.frag.set_parameter(y[9], y[12], y[15])
self.prog.set_parameter(y[10], y[13], y[16])
self.overdrive.set_parameter(y[11], y[14], y[17])
self.skill.skill_id = y[18]
self.skill.skill_id_uncap = y[21]
self.skill.skill_unlock_level = y[19]
self.skill.skill_requires_uncap = y[20] == 1
self.select_character_core()
@property
def to_dict(self):
return {"is_uncapped_override": self.is_uncapped_override,
"is_uncapped": self.is_uncapped,
"uncap_cores": self.uncap_cores_to_dict,
"char_type": self.char_type,
"skill_id_uncap": self.skill.skill_id_uncap,
"skill_requires_uncap": self.skill.skill_requires_uncap,
"skill_unlock_level": self.skill.skill_unlock_level,
"skill_id": self.skill.skill_id,
"overdrive": self.overdrive.get_value(self.level),
"prog": self.overdrive.get_value(self.level),
"frag": self.overdrive.get_value(self.level),
"level_exp": self.level.level_exp,
"exp": self.level.exp,
"level": self.level.level,
"name": self.name,
"character_id": self.character_id
}
def change_uncap_override(self, user):
# parameter: user - User类或子类的实例
# 切换觉醒状态
if not Config.CHARACTER_FULL_UNLOCK:
self.c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''',
{'a': user.user_id, 'b': self.character_id})
else:
self.c.execute('''select is_uncapped, is_uncapped_override from user_char_full where user_id = :a and character_id = :b''',
{'a': user.user_id, 'b': self.character_id})
x = self.c.fetchone()
if x is None or x[0] == 0:
raise ArcError('Unknown Error')
self.c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
'a': 1 if x[1] == 0 else 0, 'b': user.user_id})
if not Config.CHARACTER_FULL_UNLOCK:
self.c.execute('''update user_char set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': 1 if x[1] == 0 else 0, 'b': user.user_id, 'c': self.character_id})
else:
self.c.execute('''update user_char_full set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
'a': 1 if x[1] == 0 else 0, 'b': user.user_id, 'c': self.character_id})
def character_uncap(self):
# 觉醒角色
pass

View File

@@ -0,0 +1,16 @@
class Constant:
BAN_TIME = [1, 3, 7, 15, 31]
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

@@ -0,0 +1,51 @@
class ArcError(Exception):
api_error_code = -999
error_code = 108
message = None
extra_data = None
def __init__(self, message=None, error_code=None, api_error_code=None, extra_data=None) -> None:
self.message = message
if error_code:
self.error_code = error_code
if api_error_code:
self.api_error_code = api_error_code
if extra_data:
self.extra_data = extra_data
def __str__(self) -> str:
return repr(self.message)
class InputError(ArcError):
# 输入类型错误
def __init__(self, message=None, error_code=None, api_error_code=-100, extra_data=None) -> None:
super().__init__(message, error_code, api_error_code, extra_data)
class DataExist(ArcError):
# 数据存在
pass
class NoData(ArcError):
# 数据不存在
def __init__(self, message=None, error_code=None, api_error_code=-2, extra_data=None) -> None:
super().__init__(message, error_code, api_error_code, extra_data)
class PostError(ArcError):
# 缺少输入
def __init__(self, message=None, error_code=None, api_error_code=-100, extra_data=None) -> None:
super().__init__(message, error_code, api_error_code, extra_data)
class UserBan(ArcError):
# 用户封禁
def __init__(self, message=None, error_code=121, api_error_code=None, extra_data=None) -> None:
super().__init__(message, error_code, api_error_code, extra_data)
class NoAccess(ArcError):
# 无权限
pass

View File

@@ -0,0 +1,7 @@
class Item:
item_id = None
item_type = None
is_available = None
def __init__(self) -> None:
pass

View File

@@ -0,0 +1,83 @@
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()

280
latest version/core/user.py Normal file
View File

@@ -0,0 +1,280 @@
from .error import ArcError, InputError, DataExist, NoAccess, NoData, UserBan
from .constant import Constant
from .character import UserCharacter
from setting import Config
import hashlib
import base64
import time
from os import urandom
class User:
name = None
email = None
password = None
user_id = None
user_code = None
def __init__(self) -> None:
pass
class UserRegister(User):
hash_pwd = None
def __init__(self, c) -> None:
super().__init__()
self.c = c
def set_name(self, name: str):
if 3 <= len(name) <= 16:
self.c.execute(
'''select exists(select * from user where name = :name)''', {'name': name})
if self.c.fetchone() == (0,):
self.name = name
else:
raise DataExist('Username exists.', 101, -203)
else:
raise InputError('Username is invalid.')
def set_password(self, password: str):
if 8 <= len(password) <= 32:
self.password = password
self.hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
else:
raise InputError('Password is invalid.')
def set_email(self, email: str):
# 邮箱格式懒得多判断
if 4 <= len(email) <= 32 and '@' in email and '.' in email:
self.c.execute(
'''select exists(select * from user where email = :email)''', {'email': email})
if self.c.fetchone() == (0,):
self.email = email
else:
raise DataExist('Email address exists.', 102, -204)
else:
raise InputError('Email address is invalid.')
def _build_user_code(self):
# 生成9位的user_code用的自然是随机
from random import randint
random_times = 0
while random_times <= 1000:
random_times += 1
user_code = ''.join([str(randint(0, 9)) for _ in range(9)])
self.c.execute('''select exists(select * from user where user_code = :user_code)''',
{'user_code': user_code})
if self.c.fetchone() == (0,):
break
if random_times <= 1000:
self.user_code = user_code
else:
raise ArcError('No available user code.')
def _build_user_id(self):
# 生成user_id往后加1
self.c.execute('''select max(user_id) from user''')
x = self.c.fetchone()
if x[0] is not None:
self.user_id = x[0] + 1
else:
self.user_id = 2000001
def _insert_user_char(self):
# 为用户添加初始角色
self.c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(self.user_id, 0, 1, 0, 0, 0))
self.c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(self.user_id, 1, 1, 0, 0, 0))
self.c.execute(
'''select character_id, max_level, is_uncapped from character''')
x = self.c.fetchall()
if x:
for i in x:
exp = 25000 if i[1] == 30 else 10000
self.c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
(self.user_id, i[0], i[1], exp, i[2], 0))
def register(self):
now = int(time.time() * 1000)
self._build_user_code()
self._build_user_id()
self._insert_user_char()
self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email)
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email)
''', {'user_code': self.user_code, 'user_id': self.user_id, 'join_date': now, 'name': self.name, 'password': self.hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': self.email})
self.c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': self.user_id})
class UserLogin(User):
# 密码和token的加密方式为 SHA-256
device_id = None
ip = None
hash_pwd = None
token = None
now = 0
def __init__(self, c) -> None:
super().__init__()
self.c = c
def set_name(self, name: str):
self.name = name
def set_password(self, password: str):
self.password = password
self.hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
def set_device_id(self, device_id: str):
self.device_id = device_id
def set_ip(self, ip: str):
self.ip = ip
def _arc_auto_ban(self):
# 多设备自动封号机制,返回封号时长
self.c.execute('''delete from login where user_id=?''',
(self.user_id, ))
self.c.execute(
'''select ban_flag from user where user_id=?''', (self.user_id,))
x = self.c.fetchone()
if x and x[0] != '' and x[0] is not None:
last_ban_time = int(x[0].split(':', 1)[0])
i = 0
while i < len(Constant.BAN_TIME) - 1 and Constant.BAN_TIME[i] <= last_ban_time:
i += 1
ban_time = Constant.BAN_TIME[i]
else:
ban_time = Constant.BAN_TIME[0]
ban_flag = ':'.join(
(str(ban_time), str(self.now + ban_time * 86400000)))
self.c.execute('''update user set ban_flag=? where user_id=?''',
(ban_flag, self.user_id))
return ban_time * 86400000
def _check_device(self, device_list):
should_delete_num = len(
device_list) + 1 - Config.LOGIN_DEVICE_NUMBER_LIMIT
if not Config.ALLOW_LOGIN_SAME_DEVICE:
if self.device_id in device_list: # 对相同设备进行删除
self.c.execute('''delete from login where login_device=:a and user_id=:b''', {
'a': self.device_id, 'b': self.user_id})
should_delete_num = len(
device_list) + 1 - device_list.count(self.device_id) - Config.LOGIN_DEVICE_NUMBER_LIMIT
if should_delete_num >= 1: # 删掉多余token
if not Config.ALLOW_LOGIN_SAME_DEVICE and Config.ALLOW_BAN_MULTIDEVICE_USER_AUTO: # 自动封号检查
self.c.execute(
'''select count(*) from login where user_id=? and login_time>?''', (self.user_id, self.now-86400000))
if self.c.fetchone()[0] >= Config.LOGIN_DEVICE_NUMBER_LIMIT:
remaining_ts = self._arc_auto_ban()
raise UserBan('Too many devices logging in during 24 hours.', 105, extra_data={
'remaining_ts': remaining_ts})
self.c.execute('''delete from login where rowid in (select rowid from login where user_id=:user_id limit :a);''',
{'user_id': self.user_id, 'a': int(should_delete_num)})
def login(self, name: str = '', password: str = '', device_id: str = '', ip: str = ''):
if name:
self.set_name(name)
if password:
self.set_password(password)
if device_id:
self.set_device_id(device_id)
if ip:
self.set_ip(ip)
self.c.execute('''select user_id, password, ban_flag from user where name = :name''', {
'name': self.name})
x = self.c.fetchone()
if x is None:
raise NoData('Username does not exist.', 104)
self.now = int(time.time() * 1000)
if x[2] is not None and x[2] != '':
# 自动封号检查
ban_timestamp = int(x[2].split(':', 1)[1])
if ban_timestamp > self.now:
raise UserBan('Too many devices logging in during 24 hours.', 105, extra_data={
'remaining_ts': ban_timestamp-self.now})
if x[1] == '':
# 账号封禁
raise UserBan('The account has been banned.', 106)
if x[1] != self.hash_pwd:
raise NoAccess('Wrong password.', 104)
self.user_id = str(x[0])
self.token = base64.b64encode(hashlib.sha256(
(self.user_id + str(self.now)).encode("utf8") + urandom(8)).digest()).decode()
self.c.execute(
'''select login_device from login where user_id = :user_id''', {"user_id": self.user_id})
y = self.c.fetchall()
if y:
self._check_device([i[0] if i[0] else '' for i in y])
self.c.execute('''insert into login values(:access_token, :user_id, :time, :ip, :device_id)''', {
'user_id': self.user_id, 'access_token': self.token, 'device_id': self.device_id, 'time': self.now, 'ip': self.ip})
class UserAuth(User):
token = None
def __init__(self, c) -> None:
super().__init__()
self.c = c
def token_get_id(self):
# 用token获取id没有考虑不同用户token相同情况说不定会有bug
self.c.execute('''select user_id from login where access_token = :token''', {
'token': self.token})
x = self.c.fetchone()
if x is not None:
self.user_id = x[0]
else:
raise NoAccess('Wrong token.', -4)
return self.user_id
def code_get_id(self):
# 用user_code获取id
self.c.execute('''select user_id from user where user_code = :a''',
{'a': self.user_code})
x = self.c.fetchone()
if x is not None:
self.user_id = x[0]
else:
raise NoData('No user.', 401, -3)
return self.user_id
class UserOnline(User):
character = None
def __init__(self, c, user_id=None) -> None:
super().__init__()
self.c = c
self.user_id = user_id
def change_character(self, character_id: int, skill_sealed: bool = False):
# 用户角色改变,包括技能封印的改变
self.character = UserCharacter(self.c, character_id)
self.character.select_character_uncap_condition(self)
self.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': 1 if skill_sealed else 0, 'b': self.character.character_id, 'c': self.character.is_uncapped, 'd': self.character.is_uncapped_override, 'e': self.user_id})

View File

Binary file not shown.

View File

@@ -4,7 +4,7 @@ import json
# 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用
ARCAEA_SERVER_VERSION = 'v2.5'
ARCAEA_SERVER_VERSION = 'v2.8.5'
def main(path='./'):
@@ -44,7 +44,11 @@ def main(path='./'):
ticket int,
prog_boost int,
email text,
world_rank_score int
world_rank_score int,
ban_flag text,
next_fragstam_ts int,
max_stamina_ts int,
stamina int
);''')
c.execute('''create table if not exists login(access_token text,
user_id int,
@@ -219,11 +223,13 @@ def main(path='./'):
price int,
orig_price int,
discount_from int,
discount_to int
discount_to int,
discount_reason text
);''')
c.execute('''create table if not exists purchase_item(purchase_name text,
item_id text,
type text,
amount int,
primary key(purchase_name, item_id, type)
);''')
c.execute('''create table if not exists user_save(user_id int primary key,
@@ -238,26 +244,36 @@ def main(path='./'):
);''')
c.execute('''create table if not exists present(present_id text primary key,
expire_ts int,
items text,
description text
);''')
c.execute('''create table if not exists user_present(user_id int,
present_id text,
primary key(user_id, present_id)
);''')
c.execute('''create table if not exists present_item(present_id text,
item_id text,
type text,
amount int,
primary key(present_id, item_id, type)
);''')
c.execute('''create table if not exists songfile(song_id text,
file_type int,
md5 text,
primary key(song_id, file_type)
);''')
c.execute('''create table if not exists redeem(code text primary key,
items text,
type int
);''')
c.execute('''create table if not exists user_redeem(user_id int,
code text,
primary key(user_id, code)
);''')
c.execute('''create table if not exists redeem_item(code text,
item_id text,
type text,
amount int,
primary key(code, item_id, type)
);''')
c.execute('''create table if not exists role(role_id int primary key,
role_name text,
@@ -284,46 +300,46 @@ def main(path='./'):
# 搭档初始化
char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', '', 'Tairitsu(Axium)', 'Tairitsu(Grievous Lady)', 'stella', 'Hikari & Fisica', 'ilith', 'eto', 'luna', 'shirabe', 'Hikari(Zero)', 'Hikari(Fracture)', 'Hikari(Summer)', 'Tairitsu(Summer)', 'Tairitsu & Trin',
'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange']
'ayu', 'Eto & Luna', 'yume', 'Seine & Hikari', 'saya', 'Tairitsu & Chuni Penguin', 'Chuni Penguin', 'haruna', 'nono', 'MTA-XXX', 'MDA-21', 'kanae', 'Hikari(Fantasia)', 'Tairitsu(Sonata)', 'sia', 'DORO*C', 'Tairitsu(Tempest)', 'brillante', 'Ilith(Summer)', 'etude', 'Alice & Tenniel', 'Luna & Mia', 'areus', 'seele', 'isabelle', 'mir', 'lagrange', 'linka', 'nami', 'Saya & Elizabeth', 'lily', 'kanae(midsummer)', 'alice&tenniel(minuet)', 'tairitsu(elegy)', 'marija', 'vita']
skill_id = ['gauge_easy', '', '', '', 'note_mirror', '', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange']
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', '', '', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', '', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', '', '', 'visual_ghost_skynotes', 'skill_vita']
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', '', '', 'shirabe_entry_fee',
'', '', '', '', '', '', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0,
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0]
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0]
frag1 = [55, 55, 60, 50, 47, 0, 47, 57, 41, 22, 50, 54, 60, 56, 78, 42, 41, 61, 52, 50, 52, 32,
42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42]
42, 55, 45, 58, 43, 0.5, 68, 50, 62, 45, 45, 52, 44, 27, 59, 0, 45, 50, 50, 47, 47, 61, 43, 42, 38, 25, 58, 50, 61, 45, 45, 38, 34]
prog1 = [35, 55, 47, 50, 60, 0, 60, 70, 58, 45, 70, 45, 42, 46, 61, 67, 49, 44, 28, 45, 24, 46, 52,
59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42]
59, 62, 33, 58, 25, 63, 69, 50, 45, 45, 51, 34, 70, 62, 70, 45, 32, 32, 61, 47, 47, 37, 42, 50, 50, 45, 41, 61, 45, 45, 58, 50]
overdrive1 = [35, 55, 25, 50, 47, 0, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18,
48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80]
48, 65, 45, 55, 44, 25, 46, 44, 33, 45, 45, 37, 25, 27, 50, 20, 45, 63, 21, 47, 61, 47, 65, 80, 38, 30, 49, 15, 34, 45, 45, 38, 67]
frag20 = [78, 80, 90, 75, 70, 0, 70, 79, 65, 40, 50, 80, 90, 82, 0, 61, 67, 92, 85, 50, 86, 52,
65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80]
65, 85, 67, 88, 64, 0.5, 95, 70, 95, 50, 80, 87, 71, 50, 85, 0, 80, 75, 50, 70, 70, 90, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51]
prog20 = [61, 80, 70, 75, 90, 0, 90, 102, 84, 78, 105, 67, 63, 68, 0, 99, 80, 66, 46, 83, 40, 73,
80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80]
80, 90, 93, 50, 86, 78, 89, 98, 75, 80, 50, 64, 55, 100, 90, 110, 80, 50, 74, 90, 70, 70, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75]
overdrive20 = [61, 80, 47, 75, 70, 0, 95, 79, 65, 31, 50, 59, 90, 58, 0, 78, 50, 70, 62, 49, 64,
46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80]
46, 73, 95, 67, 84, 70, 78, 69, 70, 50, 80, 80, 63, 25, 50, 72, 55, 50, 95, 55, 70, 90, 70, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100]
frag30 = [88, 90, 100, 75, 80, 0, 70, 79, 65, 40, 50, 80, 90, 92, 0, 61, 67, 92, 85, 50, 86, 62,
65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80]
frag30 = [88, 90, 100, 75, 80, 0, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62,
65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51]
prog30 = [71, 90, 80, 75, 100, 0, 90, 102, 84, 78, 105, 67, 63, 78, 0, 99, 80, 66, 46, 83, 40, 83,
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80]
prog30 = [71, 90, 80, 75, 100, 0, 90, 102, 84, 78, 105, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75]
overdrive30 = [71, 90, 57, 75, 80, 0, 95, 79, 65, 31, 50, 59, 90, 68, 0, 78, 50, 70, 62, 49, 64,
56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80]
overdrive30 = [71, 90, 57, 75, 80, 0, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64,
56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100]
char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1,
0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0]
0, 0, 0, 0, 0, 0, 0, 2, 2, 0, 0, 0, 0, 0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2]
char_core = {
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
@@ -338,13 +354,16 @@ def main(path='./'):
29: [{'core_id': 'core_chunithm', 'amount': 15}],
36: [{'core_id': 'core_chunithm', 'amount': 15}],
42: [{'core_id': 'core_chunithm', 'amount': 15}],
43: [{'core_id': 'core_chunithm', 'amount': 15}]
43: [{'core_id': 'core_chunithm', 'amount': 15}],
11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}],
12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
19: [{'core_id': 'core_colorful', 'amount': 30}]
}
for i in range(0, 46):
for i in range(0, 55):
skill_requires_uncap = 1 if i == 2 else 0
if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21, 42, 43]:
if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21, 42, 43, 11, 12, 19]:
sql = '''insert into character values(?,?,30,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
@@ -354,26 +373,36 @@ def main(path='./'):
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
c.execute('''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)''', (99,
'shirahime', 38, 33, 28, 66, 58, 50, 66, 58, 50, 'frags_preferred_song', 0, 0, '', 0))
for i in char_core:
for j in char_core[i]:
c.execute('''insert into char_item values(?,?,'core',?)''',
(i, j['core_id'], j['amount']))
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic']
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful']
for i in cores:
c.execute('''insert into item values(?,"core",1,'')''', (i,))
world_songs = ["babaroque", "shadesoflight", "kanagawa", "lucifer", "anokumene", "ignotus", "rabbitintheblackroom", "qualia", "redandblue", "bookmaker", "darakunosono", "espebranch", "blacklotus", "givemeanightmare", "vividtheory", "onefr", "gekka", "vexaria3", "infinityheaven3", "fairytale3", "goodtek3", "suomi", "rugie", "faintlight", "harutopia", "goodtek", "dreaminattraction", "syro", "diode", "freefall", "grimheart", "blaster",
"cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger"]
"cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take", "lifeispiano", "vandalism", "nexttoyou3", "lostcivilization3", "turbocharger", "bookmaker3", "laqryma3", "kyogenkigo", "hivemind", "seclusion", "quonwacca3", "bluecomet", "energysynergymatrix", "gengaozo", "lastendconductor3", "antithese3", "qualia3", "kanagawa3", "heavensdoor3", "pragmatism3", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote"]
for i in world_songs:
c.execute('''insert into item values(?,"world_song",1,'')''', (i,))
world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5"]
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6"]
for i in world_unlocks:
c.execute('''insert into item values(?,"world_unlock",1,'')''', (i,))
c.execute('''insert into item values(?,?,?,?)''',
('fragment', 'fragment', 1, ''))
c.execute('''insert into item values(?,?,?,?)''',
('memory', 'memory', 1, ''))
c.execute('''insert into item values(?,?,?,?)''',
('anni5tix', 'anni5tix', 1, ''))
def insert_items(c, items):
# 物品数据导入
for i in items:
@@ -385,17 +414,28 @@ def main(path='./'):
discount_to = -1
else:
discount_to = i['discount_to']
c.execute('''insert into purchase values(?,?,?,?,?)''',
(i['name'], i['price'], i['orig_price'], discount_from, discount_to))
if 'discount_reason' not in i:
discount_reason = ''
else:
discount_reason = i['discount_reason']
c.execute('''insert into purchase values(?,?,?,?,?,?)''',
(i['name'], i['price'], i['orig_price'], discount_from, discount_to, discount_reason))
for j in i['items']:
if "_id" not in j:
_id = ''
else:
_id = j['_id']
c.execute('''insert into item values(?,?,?,?)''',
(j['id'], j['type'], j['is_available'], _id))
c.execute('''insert into purchase_item values(?,?,?)''',
(i['name'], j['id'], j['type']))
c.execute(
'''select exists(select * from item where item_id=?)''', (j['id'],))
if c.fetchone() == (0,):
c.execute('''insert into item values(?,?,?,?)''',
(j['id'], j['type'], j['is_available'], _id))
if 'amount' in j:
amount = j['amount']
else:
amount = 1
c.execute('''insert into purchase_item values(?,?,?,?)''',
(i['name'], j['id'], j['type'], amount))
# item初始化
f = open(path+'singles.json', 'r')

View File

@@ -0,0 +1,376 @@
{
"map_id": "byd_bookmaker",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "bookmaker2",
"require_type": "chart_unlock",
"coordinate": "-650,-650",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 300,
"character_affinity": [
23,
34,
47
],
"affinity_multiplier": [
1.3,
5,
3.8
],
"step_count": 31,
"custom_bg": "",
"curr_position": 3,
"curr_capture": 35.81730523472207,
"is_locked": false,
"steps": [
{
"map_id": "byd_bookmaker",
"position": 0,
"capture": 10
},
{
"map_id": "byd_bookmaker",
"position": 1,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 2,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 3,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
],
"restrict_ids": [
"bookmaker",
"viciousheroism",
"battlenoone",
"galaxyfriends",
"rekkaresonance"
],
"restrict_type": "song_id"
},
{
"map_id": "byd_bookmaker",
"position": 4,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 5,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 6,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 7,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 8,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 9,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 10,
"capture": 10,
"items": [
{
"type": "core",
"id": "core_generic",
"amount": 1
}
]
},
{
"map_id": "byd_bookmaker",
"position": 11,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 12,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 13,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 14,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 15,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 16,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 17,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 18,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 19,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 20,
"capture": 10,
"items": [
{
"type": "core",
"id": "core_generic",
"amount": 1
}
]
},
{
"map_id": "byd_bookmaker",
"position": 21,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 22,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 23,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 24,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 25,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 26,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 27,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 28,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 29,
"capture": 10,
"items": [
{
"type": "fragment",
"amount": 30
}
]
},
{
"map_id": "byd_bookmaker",
"position": 30,
"capture": 0,
"items": [
{
"id": "bookmaker3",
"type": "world_song"
}
]
}
]
}

View File

@@ -1,250 +1,574 @@
[{
"name": "core",
"items": [{
"type": "pack",
"id": "core",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "shiawase",
"items": [{
"type": "pack",
"id": "shiawase",
"is_available": true
}, {
"type": "character",
"id": "kou",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "dynamix",
"items": [{
"type": "pack",
"id": "dynamix",
"is_available": true
}, {
"type": "character",
"id": "sapphire",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "mirai",
"items": [{
"type": "pack",
"id": "mirai",
"is_available": true
}, {
"type": "character",
"id": "lethe",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "yugamu",
"items": [{
"type": "pack",
"id": "yugamu",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "lanota",
"items": [{
"type": "pack",
"id": "lanota",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "nijuusei",
"items": [{
"type": "pack",
"id": "nijuusei",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "rei",
"items": [{
"type": "pack",
"id": "rei",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "tonesphere",
"items": [{
"type": "pack",
"id": "tonesphere",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "groovecoaster",
"items": [{
"type": "pack",
"id": "groovecoaster",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "zettai",
"items": [{
"type": "pack",
"id": "zettai",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "chunithm",
"items": [{
"type": "pack",
"id": "chunithm",
"is_available": true
}],
"price": 300,
"orig_price": 300
}, {
"name": "prelude",
"items": [{
"type": "pack",
"id": "prelude",
"is_available": true
}],
"price": 400,
"orig_price": 400,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "omatsuri",
"items": [{
"type": "pack",
"id": "omatsuri",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "vs",
"items": [{
"type": "pack",
"id": "vs",
"is_available": true
}],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "extend",
"items": [{
"type": "pack",
"id": "extend",
"is_available": true
}],
"price": 700,
"orig_price": 700,
"discount_from": 1615248000000,
"discount_to": 1615852799000
}, {
"name": "alice",
"items": [{
"type": "pack",
"id": "alice",
"is_available": true
}],
"orig_price": 500,
"price": 500
}, {
"name": "alice_append_1",
"items": [{
"type": "pack",
"id": "alice_append_1",
"is_available": true
}],
"orig_price": 300,
"price": 300
}, {
"name": "ongeki",
"items": [{
"type": "pack",
"id": "ongeki",
"is_available": true
}],
"orig_price": 400,
"price": 400
}, {
"name": "maimai",
"items": [{
"type": "pack",
"id": "maimai",
"is_available": true
}],
"orig_price": 400,
"price": 400
}, {
"name": "chunithm_append_1",
"items": [{
"type": "pack",
"id": "chunithm_append_1",
"is_available": true
}],
"orig_price": 300,
"price": 300
}, {
"name": "observer_append_1",
"items": [{
"type": "pack",
"id": "observer_append_1",
"is_available": true
}],
"orig_price": 300,
"price": 300
}, {
"name": "observer",
"items": [{
"type": "pack",
"id": "observer",
"is_available": true
}],
"orig_price": 500,
"price": 500
}]
[
{
"name": "core",
"items": [
{
"type": "pack",
"id": "core",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "shiawase",
"items": [
{
"type": "pack",
"id": "shiawase",
"is_available": true
},
{
"type": "character",
"id": "kou",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "dynamix",
"items": [
{
"type": "pack",
"id": "dynamix",
"is_available": true
},
{
"type": "character",
"id": "sapphire",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "mirai",
"items": [
{
"type": "pack",
"id": "mirai",
"is_available": true
},
{
"type": "character",
"id": "lethe",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "yugamu",
"items": [
{
"type": "pack",
"id": "yugamu",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "lanota",
"items": [
{
"type": "pack",
"id": "lanota",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "nijuusei",
"items": [
{
"type": "pack",
"id": "nijuusei",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "rei",
"items": [
{
"type": "pack",
"id": "rei",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "tonesphere",
"items": [
{
"type": "pack",
"id": "tonesphere",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "groovecoaster",
"items": [
{
"type": "pack",
"id": "groovecoaster",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "zettai",
"items": [
{
"type": "pack",
"id": "zettai",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "chunithm",
"items": [
{
"type": "pack",
"id": "chunithm",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"price": 300,
"orig_price": 300
},
{
"name": "prelude",
"items": [
{
"type": "pack",
"id": "prelude",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"price": 400,
"orig_price": 400,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "omatsuri",
"items": [
{
"type": "pack",
"id": "omatsuri",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "vs",
"items": [
{
"type": "pack",
"id": "vs",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 500,
"orig_price": 500,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "extend",
"items": [
{
"type": "pack",
"id": "extend",
"is_available": true
},
{
"type": "core",
"amount": 7,
"id": "core_generic",
"is_available": true
}
],
"price": 0,
"orig_price": 700,
"discount_from": 1615248000000,
"discount_to": 1615852799000
},
{
"name": "alice",
"items": [
{
"type": "pack",
"id": "alice",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "alice_append_1",
"items": [
{
"type": "pack",
"id": "alice_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "ongeki",
"items": [
{
"type": "pack",
"id": "ongeki",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "maimai",
"items": [
{
"type": "pack",
"id": "maimai",
"is_available": true
},
{
"type": "core",
"amount": 4,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 400,
"price": 400
},
{
"name": "chunithm_append_1",
"items": [
{
"type": "pack",
"id": "chunithm_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "observer_append_1",
"items": [
{
"type": "pack",
"id": "observer_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "observer",
"items": [
{
"type": "pack",
"id": "observer",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "observer_append_2",
"items": [
{
"type": "pack",
"id": "observer_append_2",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "wacca",
"items": [
{
"type": "pack",
"id": "wacca",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "nijuusei_append_1",
"items": [
{
"type": "pack",
"id": "nijuusei_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
},
{
"name": "dividedheart",
"items": [
{
"type": "pack",
"id": "dividedheart",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 500,
"price": 500
},
{
"name": "musedash",
"items": [
{
"type": "pack",
"id": "musedash",
"is_available": true
},
{
"type": "character",
"id": "marija",
"is_available": true
},
{
"type": "core",
"amount": 5,
"id": "core_generic",
"is_available": true
}
],
"price": 400,
"orig_price": 400
},
{
"name": "lanota_append_1",
"items": [
{
"type": "pack",
"id": "lanota_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,9 @@
# encoding: utf-8
from flask import Flask, request, jsonify, make_response, send_from_directory
from flask import Flask, json, request, jsonify, send_from_directory
from logging.config import dictConfig
from setting import Config
import base64
import server
import server.auth
import server.info
import server.setme
@@ -16,8 +16,14 @@ import server.arcdownload
import server.arcpurchase
import server.init
import server.character
import server.arclinkplay
import os
import sys
from multiprocessing import Process, Pipe, set_start_method
from urllib.parse import parse_qs, urlparse
from werkzeug.datastructures import ImmutableMultiDict
app = Flask(__name__)
@@ -30,63 +36,9 @@ app.config['SESSION_TYPE'] = 'filesystem'
app.register_blueprint(web.login.bp)
app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.bp)
app.register_blueprint(server.bp)
log_dict = {
'version': 1,
'root': {
'level': 'INFO',
'handlers': ['wsgi', 'error_file']
},
'handlers': {
'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
},
"error_file": {
"class": "logging.handlers.RotatingFileHandler",
"maxBytes": 1024 * 1024,
"backupCount": 1,
"encoding": "utf-8",
"level": "ERROR",
"formatter": "default",
"filename": "./log/error.log"
}
},
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
}
}
}
if Config.ALLOW_LOG_INFO:
log_dict['root']['handlers'] = ['wsgi', 'info_file', 'error_file']
log_dict['handlers']['info_file'] = {
"class": "logging.handlers.RotatingFileHandler",
"maxBytes": 1024 * 1024,
"backupCount": 1,
"encoding": "utf-8",
"level": "INFO",
"formatter": "default",
"filename": "./log/info.log"
}
dictConfig(log_dict)
if not server.init.check_before_run(app):
app.logger.error('Something wrong. The server will not run.')
input('Press ENTER key to exit.')
sys.exit()
app.logger.info("Start to initialize data in 'songfile' table...")
try:
error = server.arcdownload.initialize_songfile()
except:
error = 'Something wrong.'
if error:
app.logger.warning(error)
else:
app.logger.info('Complete!')
conn1, conn2 = Pipe()
def add_url_prefix(url, strange_flag=False):
@@ -113,25 +65,25 @@ def add_url_prefix(url, strange_flag=False):
return prefix + r
def error_return(error_code): # 错误返回
def error_return(error_code, extra={}): # 错误返回
# -7 处理交易时发生了错误
# -5 所有的曲目都已经下载完毕
# -4 您的账号已在别处登录
# -3 无法连接至服务器
# 2 Arcaea服务器正在维护
# 5 请更新Arcaea到最新版本
# 9 新版本请等待几分钟
# 100 无法在此ip地址下登录游戏
# 101 用户名占用
# 102 电子邮箱已注册
# 103 已有一个账号由此设备创建
# 104 用户名密码错误
# 105 24小时内登入两台设备
# 106 账户冻结
# 106 121 账户冻结
# 107 你没有足够的体力
# 113 活动已结束
# 114 该活动已结束,您的成绩不会提交
# 115 请输入有效的电子邮箱地址
# 120 封号警告
# 121 账户冻结
# 122 账户暂时冻结
# 123 账户被限制
# 124 你今天不能再使用这个IP地址创建新的账号
@@ -150,22 +102,40 @@ def error_return(error_code): # 错误返回
# 905 请在再次使用此功能前等待24小时
# 1001 设备数量达到上限
# 1002 此设备已使用过此功能
# 1201 房间已满
# 1202 房间号码无效
# 1203 请将Arcaea更新至最新版本
# 1205 此房间目前无法加入
# 9801 下载歌曲时发生问题,请再试一次
# 9802 保存歌曲时发生问题,请检查设备空间容量
# 9803 下载已取消
# 9905 没有在云端发现任何数据
# 9907 更新数据时发生了问题
# 9908 服务器只支持最新的版本请更新Arcaea
# 其它 发生未知错误
if extra:
return jsonify({
"success": False,
"error_code": error_code,
"extra": extra
})
else:
return jsonify({
"success": False,
"error_code": error_code
})
def success_return(value):
return jsonify({
"success": False,
"error_code": error_code
"success": True,
"value": value
})
@app.route('/')
def hello():
return "Hello World!"
# 自定义路径
@app.route('/favicon.ico', methods=['GET']) # 图标
@@ -177,104 +147,81 @@ def favicon():
return app.send_static_file('favicon.ico')
@app.route(add_url_prefix('/auth/login'), methods=['POST']) # 登录接口
def login():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return jsonify({"success": False, "error_code": 5})
headers = request.headers
id_pwd = headers['Authorization']
id_pwd = base64.b64decode(id_pwd[6:]).decode()
name, password = id_pwd.split(':', 1)
if 'DeviceId' in headers:
device_id = headers['DeviceId']
else:
device_id = 'low_version'
token, error_code = server.auth.arc_login(
name, password, device_id, request.remote_addr)
if not error_code:
r = {"success": True, "token_type": "Bearer"}
r['access_token'] = token
return jsonify(r)
else:
return error_return(error_code)
@app.route(add_url_prefix('/user/'), methods=['POST']) # 注册接口
def register():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return jsonify({"success": False, "error_code": 5})
name = request.form['name']
password = request.form['password']
email = request.form['email']
if 'device_id' in request.form:
device_id = request.form['device_id']
else:
device_id = 'low_version'
user_id, token, error_code = server.auth.arc_register(
name, password, device_id, email, request.remote_addr)
if user_id is not None:
r = {"success": True, "value": {
'user_id': user_id, 'access_token': token}}
return jsonify(r)
else:
return error_return(error_code)
# 集成式请求,没想到什么好办法处理,就先这样写着
@app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
@app.route(add_url_prefix('/purchase/bundle/pack'), methods=['GET']) # 曲包信息
@server.auth.auth_required(request)
def aggregate(user_id):
calls = request.args.get('calls')
if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断我猜get的参数就两种
r = server.info.arc_aggregate_small(user_id)
else:
r = server.info.arc_aggregate_big(user_id)
return jsonify(r)
def bundle_pack(user_id):
return success_return(server.info.get_purchase_pack(user_id))
@app.route(add_url_prefix('/user/me/character'), methods=['POST']) # 角色切换
@app.route(add_url_prefix('/game/info'), methods=['GET']) # 系统信息
def game_info():
return success_return(server.info.get_game_info())
@app.route(add_url_prefix('/present/me'), methods=['GET']) # 用户奖励信息
@server.auth.auth_required(request)
def character_change(user_id):
character_id = request.form['character']
skill_sealed = request.form['skill_sealed']
def present_info(user_id):
return success_return(server.info.get_user_present(user_id))
flag = server.setme.change_char(user_id, character_id, skill_sealed)
if flag:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"character": character_id
}
})
else:
@app.route(add_url_prefix('/compose/aggregate'), methods=['GET']) # 集成式请求
def aggregate():
try:
#global request
finally_response = {'success': True, 'value': []}
#request_ = request
get_list = json.loads(request.args.get('calls'))
if len(get_list) > 10:
# 请求太多驳回
return error_return(108)
for i in get_list:
endpoint = i['endpoint']
url = add_url_prefix(endpoint)
request.args = ImmutableMultiDict(
{key: value[0] for key, value in parse_qs(urlparse(url).query).items()})
resp_t = map_dict[urlparse(endpoint).path]()
if hasattr(resp_t, "response"):
resp_t = resp_t.response[0].decode().rstrip('\n')
resp = json.loads(resp_t)
if hasattr(resp, 'get') and resp.get('success') is False:
finally_response = {'success': False, 'error_code': 7, 'extra': {
"id": i['id'], 'error_code': resp.get('error_code')}}
if "extra" in resp:
finally_response['extra']['extra'] = resp['extra']
#request = request_
return jsonify(finally_response)
finally_response['value'].append(
{'id': i.get('id'), 'value': resp['value'] if hasattr(resp, 'get') else resp})
#request = request_
return jsonify(finally_response)
except KeyError:
return error_return(108)
# 角色觉醒切换
@app.route(add_url_prefix('/<path:path>/toggle_uncap', True), methods=['POST'])
# # 集成式请求,没想到什么好办法处理,就先这样写着
# @app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
# @server.auth.auth_required(request)
# def aggregate(user_id):
# calls = request.args.get('calls')
# if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断我猜get的参数就两种
# r = server.info.arc_aggregate_small(user_id)
# else:
# r = server.info.arc_aggregate_big(user_id)
# return jsonify(r)
@app.route(add_url_prefix('/user/me'), methods=['GET']) # 用户信息
@server.auth.auth_required(request)
def character_uncap(user_id, path):
while '//' in path:
path = path.replace('//', '/')
character_id = int(path[21:])
r = server.setme.change_char_uncap(user_id, character_id)
if r is not None:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"character": [r]
}
})
def user_me(user_id):
r = server.info.get_user_me_c(user_id)
if r:
return success_return(r)
else:
return error_return(108)
@@ -283,9 +230,7 @@ def character_uncap(user_id, path):
@app.route(add_url_prefix('/<path:path>/uncap', True), methods=['POST'])
@server.auth.auth_required(request)
def character_first_uncap(user_id, path):
while '//' in path:
path = path.replace('//', '/')
character_id = int(path[21:])
character_id = int(path[path.find('character')+10:])
r = server.character.char_uncap(user_id, character_id)
if r is not None:
return jsonify({
@@ -300,9 +245,7 @@ def character_first_uncap(user_id, path):
@app.route(add_url_prefix('/<path:path>/exp', True), methods=['POST'])
@server.auth.auth_required(request)
def character_exp(user_id, path):
while '//' in path:
path = path.replace('//', '/')
character_id = int(path[21:])
character_id = int(path[path.find('character')+10:])
amount = int(request.form['amount'])
r = server.character.char_use_core(user_id, character_id, amount)
if r is not None:
@@ -457,15 +400,14 @@ def score_token():
@server.auth.auth_required(request)
def score_token_world(user_id):
args = request.args
server.arcworld.play_world_song(user_id, args)
return jsonify({
"success": True,
"value": {
"stamina": 12,
"max_stamina_ts": 1599547603825,
"token": "13145201919810"
}
})
r = server.arcworld.play_world_song(user_id, args)
if r:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
@app.route(add_url_prefix('/user/me/save'), methods=['GET']) # 从云端同步
@@ -536,6 +478,28 @@ def claim_present(user_id, present_id):
return error_return(108)
# 购买体力
@app.route(add_url_prefix('/purchase/me/stamina/<buy_stamina_type>'), methods=['POST'])
@server.auth.auth_required(request)
def purchase_stamina(user_id, buy_stamina_type):
if buy_stamina_type == 'fragment':
r, error_code = server.arcworld.buy_stamina_by_fragment(user_id)
else:
return error_return(108)
if error_code:
return error_return(error_code)
else:
if r:
return jsonify({
"success": True,
"value": r
})
else:
return error_return(108)
# 购买world模式boost
@app.route(add_url_prefix('/purchase/me/item'), methods=['POST'])
@server.auth.auth_required(request)
@@ -551,6 +515,16 @@ def prog_boost(user_id):
"success": True,
"value": {'ticket': ticket}
}
elif request.form['item_id'] == 'stamina6':
r, error_code = server.arcworld.buy_stamina_by_ticket(user_id)
if error_code:
return error_return(error_code)
re = {
"success": True,
"value": r
}
return jsonify(re)
@@ -567,10 +541,11 @@ def pack(user_id):
# 单曲购买信息获取
@app.route(add_url_prefix('/purchase/bundle/single'), methods=['GET'])
def single():
@server.auth.auth_required(request)
def single(user_id):
return jsonify({
"success": True,
"value": server.arcpurchase.get_single_purchase()
"value": server.arcpurchase.get_single_purchase(user_id)
})
@@ -587,12 +562,13 @@ def world_all(user_id):
})
@app.route(add_url_prefix('/world/map/me/'), methods=['POST']) # 进入地图
@app.route(add_url_prefix('/world/map/me'), methods=['POST']) # 进入地图
@server.auth.auth_required(request)
def world_in(user_id):
map_id = request.form['map_id']
flag = server.arcworld.unlock_user_world(user_id, map_id)
return jsonify({
"success": True,
"success": flag,
"value": server.arcworld.get_user_world(user_id, map_id)
})
@@ -616,10 +592,11 @@ def world_one(user_id, map_id):
@server.auth.auth_required(request)
def download_song(user_id):
song_ids = request.args.getlist('sid')
if server.arcdownload.is_able_download(user_id):
url_flag = json.loads(request.args.get('url', 'true'))
if server.arcdownload.is_able_download(user_id) or not url_flag:
re = {}
if not song_ids:
re = server.arcdownload.get_all_songs(user_id)
re = server.arcdownload.get_all_songs(user_id, url_flag=url_flag)
else:
re = server.arcdownload.get_some_songs(user_id, song_ids)
@@ -635,7 +612,7 @@ def download_song(user_id):
def download(file_path):
try:
t = request.args.get('t')
message = server.arcdownload.is_token_able_download(t)
message = server.arcdownload.is_token_able_download(t, file_path)
if message == 0:
path = os.path.join('./database/songs', file_path)
if os.path.isfile(path) and not('../' in path or '..\\' in path):
@@ -648,27 +625,191 @@ def download(file_path):
return error_return(108)
# 创建房间
@app.route(add_url_prefix('/multiplayer/me/room/create'), methods=['POST'])
@server.auth.auth_required(request)
def room_create(user_id):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
client_song_map = request.json['clientSongMap']
error_code, value = server.arclinkplay.create_room(
conn1, user_id, client_song_map)
if error_code == 0:
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
# 加入房间
@app.route(add_url_prefix('/multiplayer/me/room/join/<room_code>'), methods=['POST'])
@server.auth.auth_required(request)
def room_join(user_id, room_code):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
client_song_map = request.json['clientSongMap']
error_code, value = server.arclinkplay.join_room(
conn1, user_id, client_song_map, room_code)
if error_code == 0:
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
@app.route(add_url_prefix('/multiplayer/me/update'), methods=['POST']) # 更新房间
@server.auth.auth_required(request)
def multiplayer_update(user_id):
if not Config.UDP_PORT or Config.UDP_PORT == '':
return error_return(151), 404
token = request.json['token']
error_code, value = server.arclinkplay.update_room(conn1, user_id, token)
if error_code == 0:
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
"value": value
})
else:
return error_return(error_code), 400
@app.route(add_url_prefix('/user/me/request_delete'), methods=['POST']) # 删除账号
@server.auth.auth_required(request)
def user_delete(user_id):
return error_return(151), 404
# 三个设置,写在最后降低优先级
@app.route(add_url_prefix('/<path:path>', True), methods=['POST'])
@server.auth.auth_required(request)
def sys_set(user_id, path):
set_arg = path[10:]
set_arg = path[5:]
value = request.form['value']
server.setme.arc_sys_set(user_id, value, set_arg)
r = server.info.arc_aggregate_small(user_id)
r['value'] = r['value'][0]['value']
return jsonify(r)
r = server.info.get_user_me_c(user_id)
if r:
return success_return(r)
else:
return error_return(108)
map_dict = {'/user/me': user_me,
'/purchase/bundle/pack': bundle_pack,
'/serve/download/me/song': download_song,
'/game/info': game_info,
'/present/me': present_info,
'/world/map/me': world_all,
'/score/song/friend': song_score_friend}
def main():
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
log_dict = {
'version': 1,
'root': {
'level': 'INFO',
'handlers': ['wsgi', 'error_file']
},
'handlers': {
'wsgi': {
'class': 'logging.StreamHandler',
'stream': 'ext://flask.logging.wsgi_errors_stream',
'formatter': 'default'
},
"error_file": {
"class": "logging.handlers.RotatingFileHandler",
"maxBytes": 1024 * 1024,
"backupCount": 1,
"encoding": "utf-8",
"level": "ERROR",
"formatter": "default",
"filename": "./log/error.log"
}
},
'formatters': {
'default': {
'format': '[%(asctime)s] %(levelname)s in %(module)s: %(message)s'
}
}
}
if Config.ALLOW_LOG_INFO:
log_dict['root']['handlers'] = ['wsgi', 'info_file', 'error_file']
log_dict['handlers']['info_file'] = {
"class": "logging.handlers.RotatingFileHandler",
"maxBytes": 1024 * 1024,
"backupCount": 1,
"encoding": "utf-8",
"level": "INFO",
"formatter": "default",
"filename": "./log/info.log"
}
dictConfig(log_dict)
if not server.init.check_before_run(app):
app.logger.error('Something wrong. The server will not run.')
input('Press ENTER key to exit.')
sys.exit()
app.logger.info("Start to initialize data in 'songfile' table...")
try:
error = server.arcdownload.initialize_songfile()
except:
error = 'Something wrong.'
if error:
app.logger.warning(error)
else:
app.run(Config.HOST, Config.PORT)
app.logger.info('Complete!')
if Config.UDP_PORT and Config.UDP_PORT != '':
from udpserver.udp_main import link_play
process = [Process(target=link_play, args=(
conn2, Config.HOST, int(Config.UDP_PORT)))]
[p.start() for p in process]
app.logger.info("UDP server is running...")
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
app.run(Config.HOST, Config.PORT)
[p.join() for p in process]
else:
if Config.SSL_CERT and Config.SSL_KEY:
app.run(Config.HOST, Config.PORT, ssl_context=(
Config.SSL_CERT, Config.SSL_KEY))
else:
app.run(Config.HOST, Config.PORT)
if __name__ == '__main__':
set_start_method("spawn")
main()

View File

@@ -0,0 +1,2 @@
flask>=2.0
cryptography

View File

@@ -1 +1,3 @@
cd /d %~dp0
:: Develop server
python -B main.py

View File

@@ -0,0 +1,8 @@
from flask import Blueprint
from setting import Config
from . import user
from . import auth
bp = Blueprint('server', __name__, url_prefix=Config.GAME_API_PREFIX)
bp.register_blueprint(user.bp)
bp.register_blueprint(auth.bp)

View File

@@ -51,11 +51,14 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True
'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']:
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()
@@ -65,11 +68,29 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True
checksum = get_file_md5(os.path.join(
file_dir, song_id, 'base.ogg'))
re['audio']["checksum"] = checksum
if url_flag:
re['audio'] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/base.ogg', t=token)}
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:
re['audio'] = {"checksum": checksum}
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'] = {}
@@ -116,7 +137,7 @@ def get_some_songs(user_id, song_ids):
return re
def is_token_able_download(t):
def is_token_able_download(t, path):
# token是否可以下载返回错误码0即可以
errorcode = 108
with Connect() as c:
@@ -124,7 +145,7 @@ def is_token_able_download(t):
{'t': t})
x = c.fetchone()
now = int(time.time())
if x and now - x[4] <= time_gap_limit:
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()
@@ -162,10 +183,13 @@ 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']:
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))})

View File

@@ -0,0 +1,125 @@
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

@@ -2,7 +2,6 @@ from server.sql import Connect
import server.item
import server.character
import time
import json
def int2b(x):
@@ -13,7 +12,7 @@ def int2b(x):
return True
def get_purchase(c, type='pack'):
def get_purchase(c, user_id, type='pack'):
# 读取packs内容返回字典列表
c.execute(
'''select * from purchase where purchase_name in (select purchase_name from purchase_item where type = :a)''', {'a': type})
@@ -25,15 +24,34 @@ def get_purchase(c, type='pack'):
for i in x:
items = []
c.execute(
'''select a.* from item a, purchase_item b where a.item_id=b.item_id and a.type=b.type and b.purchase_name=:name''', {'name': i[0]})
'''select a.*, b.amount from item a, purchase_item b where a.item_id=b.item_id and a.type=b.type and b.purchase_name=:name''', {'name': i[0]})
y = c.fetchall()
t = None
if y:
for j in y:
items.append({
"type": j[1],
"id": j[0],
"is_available": int2b(j[2])
})
if j[3]:
amount = j[3]
else:
amount = 1
if i[0] == j[0]:
# 物品排序,否则客户端报错
t = {
"type": j[1],
"id": j[0],
"is_available": int2b(j[2]),
'amount': amount
}
else:
items.append({
"type": j[1],
"id": j[0],
"is_available": int2b(j[2]),
"amount": amount
})
if t is not None:
# 放到列表头
items = [t, items]
r = {"name": i[0],
"items": items,
@@ -42,19 +60,27 @@ def get_purchase(c, type='pack'):
if i[3] > 0:
r['discount_from'] = i[3]
if i[4] > 0:
r['discount_to'] = i[4]
if i[4] > 0:
r['discount_to'] = i[4]
if i[5] == 'anni5tix' and i[3] <= int(time.time() * 1000) <= i[4]:
c.execute(
'''select amount from user_item where user_id=? and item_id="anni5tix"''', (user_id,))
z = c.fetchone()
if z and z[0] >= 1:
r['discount_reason'] = 'anni5tix'
r['price'] = 0
re.append(r)
return re
def get_single_purchase():
def get_single_purchase(user_id):
# main里面没开数据库这里写一下代替
re = []
with Connect() as c:
re = get_purchase(c, type='single')
re = get_purchase(c, user_id, 'single')
return re
@@ -78,6 +104,25 @@ def buy_item(c, user_id, price):
return True, ticket - price
def buy_item_with_anni5tix(c, user_id):
# 兑换券购买接口,返回成功与否标志
c.execute('''select amount from user_item where user_id = :a and item_id = "anni5tix"''',
{'a': user_id})
amount = c.fetchone()
if amount:
amount = amount[0]
else:
return False
if amount <= 0:
return False
c.execute('''update user_item set amount = :b where user_id = :a and item_id = "anni5tix"''',
{'a': user_id, 'b': amount-1})
return True
def buy_thing(user_id, purchase_id):
# 购买物品接口,返回字典
success_flag = False
@@ -87,7 +132,7 @@ def buy_thing(user_id, purchase_id):
characters = []
with Connect() as c:
c.execute('''select price, orig_price, discount_from, discount_to from purchase where purchase_name=:a''',
c.execute('''select price, orig_price, discount_from, discount_to, discount_reason from purchase where purchase_name=:a''',
{'a': purchase_id})
x = c.fetchone()
price = 0
@@ -97,6 +142,7 @@ def buy_thing(user_id, purchase_id):
orig_price = x[1]
discount_from = x[2]
discount_to = x[3]
discount_reason = x[4]
else:
return {
"success": False,
@@ -104,18 +150,24 @@ def buy_thing(user_id, purchase_id):
}
c.execute(
'''select item_id, type from purchase_item where purchase_name=:a''', {'a': purchase_id})
'''select item_id, type, amount from purchase_item where purchase_name=:a''', {'a': purchase_id})
x = c.fetchall()
if x:
now = int(time.time() * 1000)
if not(discount_from <= now <= discount_to):
price = orig_price
elif discount_reason == 'anni5tix' and buy_item_with_anni5tix(c, user_id):
price = 0
flag, ticket = buy_item(c, user_id, price)
if flag:
for i in x:
server.item.claim_user_item(c, user_id, i[0], i[1])
if i[2]:
amount = i[2]
else:
amount = 1
server.item.claim_user_item(c, user_id, i[0], i[1], amount)
success_flag = True
else:
@@ -165,10 +217,22 @@ def get_user_present(c, user_id):
if x:
for i in x:
if now <= int(i[1]):
c.execute(
'''select * from present_item where present_id=?''', (i[0],))
y = c.fetchall()
items = []
if y:
for j in y:
if j is not None:
items.append({
"type": j[2],
"id": j[1],
"amount": j[3]
})
re.append({'expire_ts': i[1],
'description': i[3],
'description': i[2],
'present_id': i[0],
'items': json.loads(i[2])
'items': items
})
return re
@@ -189,16 +253,16 @@ def claim_user_present(user_id, present_id):
now = int(time.time() * 1000)
if now <= int(x[1]):
# 处理memory
items = json.loads(x[2])
for i in items:
if i['id'] == 'memory':
c.execute('''select ticket from user where user_id=:a''', {
'a': user_id})
ticket = int(c.fetchone()[0])
ticket += int(i['amount'])
c.execute('''update user set ticket=:b where user_id=:a''', {
'a': user_id, 'b': ticket})
c.execute(
'''select * from present_item where present_id=?''', (x[0],))
y = c.fetchall()
flag = True
if y:
for j in y:
if j is not None:
flag = flag and server.item.claim_user_item(
c, user_id, j[1], j[2], j[3])
else:
# 过期
flag = False
@@ -216,12 +280,12 @@ def claim_user_redeem(user_id, code):
if not x:
return 0, 504
if x[2] == 0: # 一次性
if x[1] == 0: # 一次性
c.execute(
'''select exists(select * from user_redeem where code=:a)''', {'a': code})
if c.fetchone() == (1,):
return 0, 505
elif x[2] == 1: # 每个玩家一次
elif x[1] == 1: # 每个玩家一次
c.execute('''select exists(select * from user_redeem where code=:a and user_id=:b)''',
{'a': code, 'b': user_id})
if c.fetchone() == (1,):
@@ -230,17 +294,17 @@ def claim_user_redeem(user_id, code):
c.execute('''insert into user_redeem values(:b,:a)''',
{'a': code, 'b': user_id})
items = json.loads(x[1])
for i in items:
if i['type'] == 'fragment':
fragment = i['amount']
if i['type'] == 'memory':
c.execute('''select ticket from user where user_id=:a''', {
'a': user_id})
ticket = int(c.fetchone()[0])
ticket += int(i['amount'])
c.execute('''update user set ticket=:b where user_id=:a''', {
'a': user_id, 'b': ticket})
error_code = None
c.execute('''select * from redeem_item where code=?''', (code,))
x = c.fetchall()
flag = True
if x:
for i in x:
if i[2] == 'fragment':
fragment += i[3]
else:
flag = flag and server.item.claim_user_item(
c, user_id, i[1], i[2], i[3])
if flag:
error_code = None
return fragment, error_code

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,14 @@
import json
from server.sql import Connect
from .config import Constant
from setting import Config
import server.item
import server.character
import server.info
import server.arcpurchase
import os
import time
import random
def int2b(x):
@@ -14,6 +19,23 @@ def int2b(x):
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_name(file_dir='./database/map'):
# 获取所有地图名称,返回列表
L = []
@@ -45,7 +67,10 @@ def get_user_world_info(user_id, map_id):
info['curr_capture'] = x[3]
info['is_locked'] = int2b(x[4])
else:
c.execute('''insert into user_world values(:a,:b,0,0,0)''', {
info['curr_position'] = 0
info['curr_capture'] = 0
info['is_locked'] = True
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
return info
@@ -67,8 +92,8 @@ def get_current_map(user_id):
def get_world_all(user_id):
# 读取所有地图信息并处理,返回字典列表
re = []
worlds = get_world_name()
with Connect() as c:
worlds = get_world_name()
for map_id in worlds:
info = get_world_info(map_id)
steps = info['steps']
@@ -87,7 +112,10 @@ def get_world_all(user_id):
info['curr_capture'] = x[3]
info['is_locked'] = int2b(x[4])
else:
c.execute('''insert into user_world values(:a,:b,0,0,0)''', {
info['curr_position'] = 0
info['curr_capture'] = 0
info['is_locked'] = True
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
re.append(info)
@@ -95,6 +123,23 @@ def get_world_all(user_id):
return re
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
def get_user_world(user_id, map_id):
# 获取用户图信息,返回字典
re = {}
@@ -106,7 +151,7 @@ def get_user_world(user_id, map_id):
"user_id": user_id,
"curr_position": 0,
"curr_capture": 0,
"is_locked": False,
"is_locked": True,
"map_id": map_id
}
if x:
@@ -114,12 +159,40 @@ def get_user_world(user_id, map_id):
re['curr_capture'] = x[3]
re['is_locked'] = int2b(x[4])
else:
c.execute('''insert into user_world values(:a,:b,0,0,0)''', {
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
return re
def unlock_user_world(user_id, map_id):
# 解锁用户的图,返回成功与否布尔值
with Connect() as c:
c.execute(
'''select is_locked from user_world where map_id=? and user_id=?''', (map_id, user_id))
x = c.fetchone()
if x:
is_locked = x[0]
else:
is_locked = 1
c.execute('''insert into user_world values(:a,:b,0,0,1)''', {
'a': user_id, 'b': map_id})
if is_locked == 1:
map_info = get_world_info(map_id)
if 'require_type' in map_info and map_info['require_type'] != '':
if map_info['require_type'] in ['pack', 'single']:
c.execute('''select exists(select * from user_item where user_id=? and item_id=? and type=?)''',
(user_id, map_info['require_id'], map_info['require_type']))
if c.fetchone() == (0,):
return False
c.execute(
'''update user_world set is_locked=0 where user_id=? and map_id=?''', (user_id, map_id))
return True
def change_user_current_map(user_id, map_id):
# 改变用户当前图
with Connect() as c:
@@ -129,7 +202,8 @@ def change_user_current_map(user_id, map_id):
def play_world_song(user_id, args):
# 声明是世界模式的打歌,并且记录加成信息
# 声明是世界模式的打歌,并且记录加成信息,返回字典
r = {}
with Connect() as c:
stamina_multiply = 1
fragment_multiply = 100
@@ -150,7 +224,34 @@ def play_world_song(user_id, args):
c.execute('''insert into world_songplay values(:a,:b,:c,:d,:e,:f)''', {
'a': user_id, 'b': args['song_id'], 'c': args['difficulty'], 'd': stamina_multiply, 'e': fragment_multiply, 'f': prog_boost_multiply})
return None
c.execute('''select current_map from user where user_id = :a''', {
'a': user_id})
map_id = c.fetchone()[0]
info = get_world_info(map_id)
# 体力计算
c.execute(
'''select max_stamina_ts, stamina from user where user_id=?''', (user_id,))
x = c.fetchone()
max_stamina_ts = x[0] if x and x[0] is not None else 0
stamina = x[1] if x and x[1] is not None else 12
now = int(time.time() * 1000)
# 体力不足
if calc_stamina(max_stamina_ts, stamina) < info['stamina_cost']:
return {}
stamina = calc_stamina(max_stamina_ts, stamina) - \
info['stamina_cost'] * stamina_multiply
max_stamina_ts = now + Constant.STAMINA_RECOVER_TICK * \
(Constant.MAX_STAMINA - stamina)
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
r = {
"stamina": stamina,
"max_stamina_ts": max_stamina_ts,
"token": "13145201919810"
}
return r
def climb_step(user_id, map_id, step, prev_capture, prev_position):
@@ -251,7 +352,7 @@ def climb_step(user_id, map_id, step, prev_capture, prev_position):
return rewards, steps, curr_position, curr_capture, info
def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gauge, stamina_multiply=1, fragment_multiply=100, prog_boost_multiply=0):
def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gauge, health, stamina_multiply=1, fragment_multiply=100, prog_boost_multiply=0):
# 成绩上传后世界模式更新,返回字典
step_times = stamina_multiply * fragment_multiply / \
@@ -263,48 +364,98 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
c.execute('''delete from world_songplay where user_id=:a and song_id=:b and difficulty=:c''', {
'a': user_id, 'b': song_id, 'c': difficulty})
c.execute('''select character_id from user where user_id=?''', (user_id,))
x = c.fetchone()
character_id = x[0] if x else 0
c.execute('''select frag1,prog1,overdrive1,frag20,prog20,overdrive20,frag30,prog30,overdrive30 from character where character_id=?''', (character_id,))
c.execute(
'''select character_id, max_stamina_ts, stamina, is_skill_sealed, is_char_uncapped, is_char_uncapped_override from user where user_id=?''', (user_id,))
x = c.fetchone()
character_id = x[0] if x and x[0] is not None else 0
max_stamina_ts = x[1] if x and x[1] is not None else 0
stamina = x[2] if x and x[2] is not None else 12
is_skill_sealed = x[3] if x and x[3] is not None else 1
skill = False
skill_uncap = False
level = 1
exp = 0
frag = 50
prog = 50
overdrive = 50
if not is_skill_sealed:
if x:
skill = True
if x[4] is not None and x[4] == 1:
skill_uncap = True
if x[5] is not None and x[5] == 1:
skill_uncap = False
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select level, exp from user_char_full where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
else:
c.execute('''select level, exp from user_char where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
y = c.fetchone()
if y:
level = y[0]
exp = y[1]
else:
level = 1
exp = 0
c.execute('''select frag1,prog1,overdrive1,frag20,prog20,overdrive20,frag30,prog30,overdrive30,skill_id,skill_id_uncap from character where character_id=?''', (character_id,))
x = c.fetchone()
if x:
flag = server.character.calc_char_value(level, x[0], x[3], x[6])
prog = server.character.calc_char_value(level, x[1], x[4], x[7])
overdrive = server.character.calc_char_value(level, x[2], x[5], x[8])
else:
flag = 0
prog = 0
overdrive = 0
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select level, exp from user_char_full where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
else:
c.execute('''select level, exp from user_char where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
y = c.fetchone()
if y:
level = y[0]
exp = y[1]
else:
level = 1
exp = 0
if x:
frag = server.character.calc_char_value(level, x[0], x[3], x[6])
prog = server.character.calc_char_value(level, x[1], x[4], x[7])
overdrive = server.character.calc_char_value(
level, x[2], x[5], x[8])
if x[9] is not None and x[9] != '' and skill:
skill = x[9]
else:
skill = None
if x[10] is not None and x[9] != '' and skill_uncap:
skill_uncap = x[10]
else:
skill_uncap = None
else:
frag = 0
prog = 0
overdrive = 0
skill = None
skill_uncap = None
skill_special = ''
if skill_uncap is not None and skill_uncap and skill_uncap in ['eto_uncap', 'luna_uncap', 'ayu_uncap', 'skill_vita']:
skill_special = skill_uncap
elif skill is not None and skill and skill in ['eto_uncap', 'luna_uncap', 'ayu_uncap', 'skill_vita']:
skill_special = skill
c.execute('''select current_map from user where user_id = :a''', {
'a': user_id})
map_id = c.fetchone()[0]
if beyond_gauge == 0: # 是否是beyond挑战
prog_tempest = 0
if not is_skill_sealed and character_id == 35:
# 风暴对立
if Config.CHARACTER_FULL_UNLOCK:
prog_tempest = 60
else:
c.execute(
'''select sum(level) from user_char where user_id=?''', (user_id,))
prog_tempest = int(x[0]) / 10 if x else 0
if prog_tempest > 60:
prog_tempest = 60
elif prog_tempest < 0:
prog_tempest = 0
base_step = 2.5 + 2.45*rating**0.5
step = base_step * (prog/50) * step_times
step = base_step * (prog + prog_tempest) / 50 * step_times
else:
info = get_world_info(map_id)
if clear_type == 0:
base_step = 8/9 + (rating/1.3)**0.5
base_step = 25/28 + (rating)**0.5 * 0.43
else:
base_step = 8/3 + (rating/1.3)**0.5
base_step = 75/28 + (rating)**0.5 * 0.43
if character_id in info['character_affinity']:
affinity_multiplier = info['affinity_multiplier'][info['character_affinity'].index(
@@ -312,18 +463,79 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
else:
affinity_multiplier = 1
step = base_step * (prog/50) * step_times * affinity_multiplier
if skill_special == 'skill_vita':
# vita技能overdrive随回忆率提升提升量最多为10
# 此处采用线性函数
overdrive_extra = 0
if 0 < health <= 100:
overdrive_extra = health / 10
overdrive += overdrive_extra
step = base_step * overdrive / 50 * \
step_times * affinity_multiplier
c.execute('''select * from user_world where user_id = :a and map_id =:b''',
{'a': user_id, 'b': map_id})
y = c.fetchone()
if y[4] == 1: # 图不可用
return {}
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2])
# Eto、Luna、Ayu的技能
character_bonus_progress = None
if skill_special == 'eto_uncap':
# eto觉醒技能获得残片奖励时世界模式进度加7
fragment_flag = False
for i in rewards:
for j in i['items']:
if j['type'] == 'fragment':
fragment_flag = True
break
if fragment_flag:
break
if fragment_flag:
character_bonus_progress = Constant.ETO_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
elif skill_special == 'luna_uncap':
# luna觉醒技能限制格开始时世界模式进度加7
if 'restrict_id' in steps[0] and 'restrict_type' in steps[0] and steps[0]['restrict_type'] != '' and steps[0]['restrict_id'] != '':
character_bonus_progress = Constant.LUNA_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
elif skill_special == 'ayu_uncap':
# ayu觉醒技能世界模式进度+5或-5但不会小于0
if random.random() >= 0.5:
character_bonus_progress = Constant.AYU_UNCAP_BONUS_PROGRESS
else:
character_bonus_progress = -Constant.AYU_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
if step < 0:
character_bonus_progress += step / step_times
step = 0
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
for i in rewards: # 物品分发
for j in i['items']:
amount = j['amount'] if 'amount' in j else 1
item_id = j['id'] if 'id' in j else ''
server.item.claim_user_item(c, user_id, item_id, j['type'], amount)
if 'step_type' in steps[-1]:
if 'plusstamina' in steps[-1]['step_type'] and 'plus_stamina_value' in steps[-1]:
# 体力格子
max_stamina_ts, stamina = add_stamina(
c, user_id, int(steps[-1]['plus_stamina_value']))
# 角色升级
if not Config.CHARACTER_FULL_UNLOCK:
exp, level = server.character.calc_level_up(
@@ -331,62 +543,45 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
else:
exp = server.character.LEVEL_STEPS[level]
exp = Constant.LEVEL_STEPS[level]
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health']
},
"char_stats": {
"character_id": character_id,
"frag": frag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": calc_stamina(max_stamina_ts, stamina),
"max_stamina_ts": max_stamina_ts
}
if beyond_gauge == 0:
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health'],
"steps": steps
},
"char_stats": {
"character_id": character_id,
"frag": flag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": 12,
"max_stamina_ts": 1586274871917
}
re["user_map"]["steps"] = steps
else:
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health'],
"step_count": len(steps)
},
"char_stats": {
"character_id": character_id,
"frag": flag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": 12,
"max_stamina_ts": 1586274871917
}
re["user_map"]["steps"] = len(steps)
if character_id == 35 and not is_skill_sealed:
re['char_stats']['prog_tempest'] = prog_tempest
re['char_stats']['prog'] += prog_tempest
if character_bonus_progress is not None:
re['character_bonus_progress'] = character_bonus_progress
if stamina_multiply != 1:
re['stamina_multiply'] = stamina_multiply
@@ -401,3 +596,77 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
'a': curr_position, 'b': curr_capture, 'c': user_id, 'd': map_id})
return re
def add_stamina(c, user_id, add_stamina):
# 增添体力返回max_stamina_ts和stamina
now = int(time.time() * 1000)
c.execute(
'''select max_stamina_ts, stamina from user where user_id=?''', (user_id,))
x = c.fetchone()
if x and x[0] is not None and x[1] is not None:
stamina = calc_stamina(x[0], x[1]) + add_stamina
max_stamina_ts = now - \
(stamina-Constant.MAX_STAMINA) * \
Constant.STAMINA_RECOVER_TICK
else:
max_stamina_ts = now
stamina = Constant.MAX_STAMINA
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
return max_stamina_ts, stamina
def buy_stamina_by_fragment(user_id):
# 残片买体力,返回字典和错误码
r = {}
with Connect() as c:
c.execute(
'''select next_fragstam_ts from user where user_id=?''', (user_id,))
x = c.fetchone()
if x:
now = int(time.time() * 1000)
next_fragstam_ts = x[0] if x[0] else 0
if now < next_fragstam_ts:
return {}, 905
next_fragstam_ts = now + 24*3600*1000
max_stamina_ts, stamina = add_stamina(c, user_id, 6)
c.execute('''update user set next_fragstam_ts=?, max_stamina_ts=?, stamina=? where user_id=?''',
(next_fragstam_ts, max_stamina_ts, stamina, user_id))
r = {
"user_id": user_id,
"stamina": stamina,
"max_stamina_ts": max_stamina_ts,
"next_fragstam_ts": next_fragstam_ts
}
return r, None
def buy_stamina_by_ticket(user_id):
# 源点买体力,返回字典和错误码
r = {}
with Connect() as c:
flag, ticket = server.arcpurchase.buy_item(c, user_id, 50)
if flag:
max_stamina_ts, stamina = add_stamina(c, user_id, 6)
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
r = {
"user_id": user_id,
"stamina": stamina,
"max_stamina_ts": max_stamina_ts,
"ticket": ticket
}
else:
return None, 501
return r, None

View File

@@ -1,172 +1,43 @@
import hashlib
import time
from server.sql import Connect
from flask import Blueprint, request, jsonify
import functools
import base64
from core.error import ArcError, NoAccess
from core.user import UserAuth, UserLogin
from core.sql import Connect
from .func import error_return
from setting import Config
from flask import jsonify
def arc_login(name: str, password: str, device_id: str, ip: str): # 登录判断
# 查询数据库中的user表验证账号密码返回并记录token多返回个error code
# token采用user_id和时间戳连接后hash生成真的是瞎想的没用bear
# 密码和token的加密方式为 SHA-256
bp = Blueprint('auth', __name__, url_prefix='/auth')
error_code = 108
token = None
@bp.route('/login', methods=['POST']) # 登录接口
def login():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return error_return(NoAccess('Wrong app version.', 1203))
headers = request.headers
request.form['grant_type']
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 not None:
if x[1] == '':
# 账号封禁
error_code = 106
elif x[1] == hash_pwd:
user_id = str(x[0])
now = int(time.time() * 1000)
token = hashlib.sha256(
(user_id + str(now)).encode("utf8")).hexdigest()
c.execute(
'''select login_device from login where user_id = :user_id''', {"user_id": user_id})
y = c.fetchall()
if y:
device_list = []
for i in y:
if i[0]:
device_list.append(i[0])
else:
device_list.append('')
should_delete_num = len(
device_list) + 1 - Config.LOGIN_DEVICE_NUMBER_LIMIT
if not Config.ALLOW_LOGIN_SAME_DEVICE:
if device_id in device_list: # 对相同设备进行删除
c.execute('''delete from login where login_device=:a and user_id=:b''', {
'a': device_id, 'b': user_id})
should_delete_num = len(
device_list) + 1 - device_list.count(device_id) - Config.LOGIN_DEVICE_NUMBER_LIMIT
if should_delete_num >= 1: # 删掉多余token
c.execute('''delete from login where rowid in (select rowid from login where user_id=:user_id limit :a);''',
{'user_id': user_id, 'a': int(should_delete_num)})
c.execute('''insert into login values(:access_token, :user_id, :time, :ip, :device_id)''', {
'user_id': user_id, 'access_token': token, 'device_id': device_id, 'time': now, 'ip': ip})
error_code = None
try:
id_pwd = headers['Authorization']
id_pwd = base64.b64decode(id_pwd[6:]).decode()
name, password = id_pwd.split(':', 1)
if 'DeviceId' in headers:
device_id = headers['DeviceId']
else:
# 密码错误
error_code = 104
else:
# 用户名错误
error_code = 104
device_id = 'low_version'
return token, error_code
user = UserLogin(c)
user.login(name, password, device_id, request.remote_addr)
return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token})
except ArcError as e:
return error_return(e)
def arc_register(name: str, password: str, device_id: str, email: str, ip: str): # 注册
# 账号注册记录hash密码、用户名和邮箱生成user_id和user_code自动登录返回token
# token和密码的处理同登录部分
def build_user_code(c):
# 生成9位的user_code用的自然是随机
import random
flag = True
while flag:
user_code = ''.join([str(random.randint(0, 9)) for i in range(9)])
c.execute('''select exists(select * from user where user_code = :user_code)''',
{'user_code': user_code})
if c.fetchone() == (0,):
flag = False
return user_code
def build_user_id(c):
# 生成user_id往后加1
c.execute('''select max(user_id) from user''')
x = c.fetchone()
if x[0] is not None:
return x[0] + 1
else:
return 2000001
def insert_user_char(c, user_id):
# 为用户添加初始角色
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 character_id, max_level, is_uncapped from character''')
x = c.fetchall()
if x:
for i in x:
exp = 25000 if i[1] == 30 else 10000
c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
(user_id, i[0], i[1], exp, i[2], 0))
user_id = None
token = None
error_code = 108
with Connect() as c:
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute(
'''select exists(select * from user where name = :name)''', {'name': name})
if c.fetchone() == (0,):
c.execute(
'''select exists(select * from user where email = :email)''', {'email': email})
if c.fetchone() == (0,):
user_code = build_user_code(c)
user_id = build_user_id(c)
now = int(time.time() * 1000)
c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email)
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email)
''', {'user_code': user_code, 'user_id': user_id, 'join_date': now, 'name': name, 'password': hash_pwd, 'memories': Config.DEFAULT_MEMORIES, 'email': email})
c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': user_id})
token = hashlib.sha256(
(str(user_id) + str(now)).encode("utf8")).hexdigest()
c.execute('''insert into login values(:access_token, :user_id, :time, :ip, :device_id)''', {
'user_id': user_id, 'access_token': token, 'device_id': device_id, 'time': now, 'ip': ip})
insert_user_char(c, user_id)
error_code = 0
else:
error_code = 102
else:
error_code = 101
return user_id, token, error_code
def token_get_id(token: str):
# 用token获取id没有考虑不同用户token相同情况说不定会有bug
user_id = None
with Connect() as c:
c.execute('''select user_id from login where access_token = :token''', {
'token': token})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
def code_get_id(user_code):
# 用user_code获取id
user_id = None
with Connect() as c:
c.execute('''select user_id from user where user_code = :a''',
{'a': user_code})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
return error_return()
def auth_required(request):
@@ -175,23 +46,22 @@ def auth_required(request):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
user_id = None
headers = request.headers
if 'AppVersion' in headers: # 版本检查
if Config.ALLOW_APPVERSION:
if headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return jsonify({"success": False, "error_code": 5})
return error_return(NoAccess('Wrong app version.', 1203))
if 'Authorization' in headers:
token = headers['Authorization']
token = token[7:]
user_id = token_get_id(token)
with Connect() as c:
try:
user = UserAuth(c)
user.token = headers['Authorization'][7:]
return view(user.token_get_id(), *args, **kwargs)
except ArcError as e:
return error_return(e)
if user_id is not None:
return view(user_id, *args, **kwargs)
else:
return jsonify({"success": False, "error_code": 108})
return error_return()
return wrapped_view
return decorator

View File

@@ -1,11 +1,10 @@
from setting import Config
from server.sql import Connect
from .config import Constant
import server.info
import server.item
import server.setme
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}
def int2b(x):
# int与布尔值转换
@@ -17,7 +16,7 @@ def int2b(x):
def get_level_steps():
# 返回level_steps字典数组
return [{'level': i, 'level_exp': LEVEL_STEPS[i]} for i in LEVEL_STEPS]
return [{'level': i, 'level_exp': Constant.LEVEL_STEPS[i]} for i in Constant.LEVEL_STEPS]
def calc_char_value(level, value1, value20, value30):
@@ -113,7 +112,7 @@ def get_user_character(c, user_id):
"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": LEVEL_STEPS[i[2]],
"level_exp": Constant.LEVEL_STEPS[i[2]],
"exp": i[3],
"level": i[2],
"name": i[7],
@@ -150,13 +149,13 @@ def get_one_character(c, user_id, character_id):
"overdrive": calc_char_value(x[2], x[11], x[14], x[17]),
"prog": calc_char_value(x[2], x[10], x[13], x[16]),
"frag": calc_char_value(x[2], x[9], x[12], x[15]),
"level_exp": LEVEL_STEPS[x[2]],
"level_exp": Constant.LEVEL_STEPS[x[2]],
"exp": x[3],
"level": x[2],
"name": x[7],
"character_id": x[1]
}
if x[1] == 21:
if x[1] == 21 or x[1] == 46:
r["voice"] = [0, 1, 2, 3, 100, 1000, 1001]
return r
@@ -167,18 +166,18 @@ def calc_level_up(c, user_id, character_id, exp, exp_addition):
exp += exp_addition
if exp >= LEVEL_STEPS[20]: # 未觉醒溢出
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 LEVEL_STEPS[20], 20
return Constant.LEVEL_STEPS[20], 20
a = []
b = []
for i in LEVEL_STEPS:
for i in Constant.LEVEL_STEPS:
a.append(i)
b.append(LEVEL_STEPS[i])
b.append(Constant.LEVEL_STEPS[i])
if exp >= b[-1]: # 溢出
return b[-1], a[-1]
@@ -212,11 +211,11 @@ def char_use_core(user_id, character_id, amount):
x = c.fetchone()
if x:
exp, level = calc_level_up(
c, user_id, character_id, x[0], amount*250)
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
server.item.claim_user_item(
c, user_id, 'core_generic', 'core', -amount)
c, user_id, character_id, x[0], amount*Constant.CORE_EXP)
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
server.item.claim_user_item(
c, user_id, 'core_generic', 'core', -amount)
r = {'character': [get_one_character(c, user_id, character_id)]}
r['cores'] = server.item.get_user_cores(c, user_id)
@@ -244,10 +243,10 @@ def char_uncap(user_id, character_id):
break
if success:
for i in x:
server.item.claim_user_item(c, user_id, i[1], i[2], -i[3])
c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''',
(user_id, character_id))
for i in x:
server.item.claim_user_item(c, user_id, i[1], i[2], -i[3])
r = {'character': [get_one_character(c, user_id, character_id)]}
r['cores'] = server.item.get_user_cores(c, user_id)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
from flask import jsonify
from core.error import ArcError
default_error = ArcError('Unknown Error')
def error_return(e: ArcError = default_error): # 错误返回
# -7 处理交易时发生了错误
# -5 所有的曲目都已经下载完毕
# -4 您的账号已在别处登录
# -3 无法连接至服务器
# 2 Arcaea服务器正在维护
# 9 新版本请等待几分钟
# 100 无法在此ip地址下登录游戏
# 101 用户名占用
# 102 电子邮箱已注册
# 103 已有一个账号由此设备创建
# 104 用户名密码错误
# 105 24小时内登入两台设备
# 106 121 账户冻结
# 107 你没有足够的体力
# 113 活动已结束
# 114 该活动已结束,您的成绩不会提交
# 115 请输入有效的电子邮箱地址
# 120 封号警告
# 122 账户暂时冻结
# 123 账户被限制
# 124 你今天不能再使用这个IP地址创建新的账号
# 150 非常抱歉您已被限制使用此功能
# 151 目前无法使用此功能
# 401 用户不存在
# 403 无法连接至服务器
# 501 502 -6 此物品目前无法获取
# 504 无效的序列码
# 505 此序列码已被使用
# 506 你已拥有了此物品
# 601 好友列表已满
# 602 此用户已是好友
# 604 你不能加自己为好友
# 903 下载量超过了限制请24小时后重试
# 905 请在再次使用此功能前等待24小时
# 1001 设备数量达到上限
# 1002 此设备已使用过此功能
# 1201 房间已满
# 1202 房间号码无效
# 1203 请将Arcaea更新至最新版本
# 1205 此房间目前无法加入
# 9801 下载歌曲时发生问题,请再试一次
# 9802 保存歌曲时发生问题,请检查设备空间容量
# 9803 下载已取消
# 9905 没有在云端发现任何数据
# 9907 更新数据时发生了问题
# 9908 服务器只支持最新的版本请更新Arcaea
# 其它 发生未知错误
r = {"success": False, "error_code": e.error_code}
if e.extra_data:
r['extra'] = e.extra_data
return jsonify(r)
def success_return(value):
return jsonify({
"success": True,
"value": value
})

View File

@@ -6,6 +6,7 @@ import server.character
import server.item
import time
from setting import Config
from .config import Constant
def int2b(x):
@@ -100,6 +101,8 @@ def get_user_friend(c, user_id):
"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
@@ -122,11 +125,12 @@ def get_user_me(c, user_id):
'''update user set character_id=0 where user_id=?''', (user_id,))
favorite_character_id = x[23]
if favorite_character_id not in characters:
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()
@@ -141,12 +145,24 @@ def get_user_me(c, user_id):
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": [],
"curr_available_maps": server.arcworld.get_available_maps(),
"character_stats": user_character,
"friends": get_user_friend(c, user_id),
"settings": {
@@ -164,9 +180,9 @@ def get_user_me(c, user_id):
"is_skill_sealed": int2b(x[7]),
"current_map": x[25],
"prog_boost": prog_boost,
"next_fragstam_ts": -1,
"max_stamina_ts": 1586274871917,
"stamina": 12,
"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'),
@@ -183,6 +199,38 @@ def get_user_me(c, user_id):
return r
def get_user_me_c(user_id):
# user/me调用上边没开数据库这里开一下
with Connect() as c:
return get_user_me(c, user_id)
def get_purchase_pack(user_id):
# 返回曲包数据
with Connect() as c:
return server.arcpurchase.get_purchase(c, user_id)
def get_game_info():
# 返回游戏基本信息
r = {
"max_stamina": Constant.MAX_STAMINA,
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
"core_exp": Constant.CORE_EXP,
"curr_ts": int(time.time()*1000),
"level_steps": server.character.get_level_steps(),
"world_ranking_enabled": True,
"is_byd_chapter_unlocked": True
}
return r
def get_user_present(user_id):
# 返回奖励信息
with Connect() as c:
return server.arcpurchase.get_user_present(c, user_id)
def arc_aggregate_small(user_id):
# 返回用户数据
r = {"success": False}
@@ -213,19 +261,19 @@ def arc_aggregate_big(user_id):
"value": get_user_me(c, user_id)
}, {
"id": 1,
"value": server.arcpurchase.get_purchase(c, 'pack')
"value": server.arcpurchase.get_purchase(c, user_id)
}, {
"id": 2,
"value": id_2
}, {
"id": 3,
"value": {
"max_stamina": 12,
"stamina_recover_tick": 1800000,
"core_exp": 250,
"curr_ts": int(time.time())*1000,
"max_stamina": Constant.MAX_STAMINA,
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
"core_exp": Constant.CORE_EXP,
"curr_ts": int(time.time()*1000),
"level_steps": server.character.get_level_steps(),
"world_ranking_enabled": False,
"world_ranking_enabled": True,
"is_byd_chapter_unlocked": True
}
}, {

View File

@@ -55,20 +55,20 @@ def claim_user_item(c, user_id, item_id, item_type, amount=1):
else:
return False
if item_type == 'core':
if item_type in ['core', 'anni5tix']:
c.execute(
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (user_id, item_id))
'''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="core"''',
(x[0]+amount, user_id, item_id))
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(?,?,"core",?)''',
(user_id, item_id, amount))
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,))

View File

@@ -1,5 +1,6 @@
from server.sql import Connect
from setting import Config
from .config import Constant
import server.info
import server.character
@@ -90,7 +91,7 @@ def change_char_uncap(user_id, character_id):
"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": server.character.LEVEL_STEPS[y[2]],
"level_exp": Constant.LEVEL_STEPS[y[2]],
"exp": y[3],
"level": y[2],
"name": y[7],

View File

@@ -30,10 +30,11 @@ class Connect():
return True
class Sql():
@staticmethod
def select(c, table_name, target_column=[], limit=-1, offset=0, query={}, sort=[]):
def select(c, table_name, target_column=[], query=None):
# 执行查询单句sql语句返回fetchall数据
# 使用准确查询,且在单表内
@@ -51,9 +52,10 @@ class Sql():
where_field = []
where_value = []
for i in query:
where_field.append(i)
where_value.append(query[i])
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 '
@@ -64,16 +66,17 @@ class Sql():
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
if sort:
sql += ' order by ' + sort[0]['column'] + ' ' + sort[0]['order']
if len(sort) >= 2:
for i in range(1, len(sort)):
sql += ', ' + sort[i]['column'] + ' ' + sort[i]['order']
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 limit >= 0:
if query and query.limit >= 0:
sql += ' limit :limit offset :offset'
sql_dict['limit'] = limit
sql_dict['offset'] = offset
sql_dict['limit'] = query.limit
sql_dict['offset'] = query.offset
c.execute(sql, sql_dict)

View File

@@ -0,0 +1,73 @@
from flask import Blueprint, request
from core.error import ArcError, NoAccess
from core.sql import Connect
from core.user import UserRegister, UserLogin, User, UserOnline
from core.character import UserCharacter
from .func import error_return, success_return
from .auth import auth_required
from setting import Config
bp = Blueprint('user', __name__, url_prefix='/user')
@bp.route('', methods=['POST']) # 注册接口
def register():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return error_return(NoAccess('Wrong app version.', 1203))
with Connect() as c:
try:
new_user = UserRegister(c)
new_user.set_name(request.form['name'])
new_user.set_password(request.form['password'])
new_user.set_email(request.form['email'])
if 'device_id' in request.form:
device_id = request.form['device_id']
else:
device_id = 'low_version'
new_user.register()
# 注册后自动登录
user = UserLogin(c)
user.login(new_user.name, new_user.password,
device_id, request.remote_addr)
return success_return({'user_id': user.user_id, 'access_token': user.token})
except ArcError as e:
return error_return(e)
return error_return()
@bp.route('/me/character', methods=['POST']) # 角色切换
@auth_required(request)
def character_change(user_id):
with Connect() as c:
try:
user = UserOnline(c, user_id)
user.change_character(
int(request.form['character']), request.form['skill_sealed'] == 'true')
return success_return({'user_id': user.user_id, 'character': user.character.character_id})
except ArcError as e:
return error_return(e)
return error_return()
# 角色觉醒切换
@bp.route('/me/character/<int:character_id>/toggle_uncap', methods=['POST'])
@auth_required(request)
def toggle_uncap(user_id, character_id):
with Connect() as c:
try:
user = User()
user.user_id = user_id
character = UserCharacter(c)
character.character_id = character_id
character.change_uncap_override(user)
character.select_character_info(user)
return success_return({'user_id': user.user_id, 'character': [character.to_dict]})
except ArcError as e:
return error_return(e)
return error_return()

View File

@@ -8,7 +8,7 @@ class Config():
主机的地址和端口号
Host and port of your server
'''
HOST = '192.168.1.101'
HOST = '0.0.0.0'
PORT = '80'
'''
--------------------
@@ -19,7 +19,7 @@ class Config():
游戏API地址前缀
Game API's URL prefix
'''
GAME_API_PREFIX = '/blockchain/14'
GAME_API_PREFIX = '/years/19'
'''
--------------------
'''
@@ -30,7 +30,29 @@ class Config():
Allowed game versions
If it is blank, all are allowed.
'''
ALLOW_APPVERSION = ['3.5.3', '3.5.3c', '3.6.2', '3.6.2c']
ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '3.12.8', '3.12.8c']
'''
--------------------
'''
'''
--------------------
联机功能的端口号,若为空,则默认不开启联机功能
Port of your link play server
If it is blank, link play will be unavailable.
'''
UDP_PORT = '10900'
'''
--------------------
'''
'''
--------------------
联机功能地址,留空则自动获取
Link Play address
If left blank, it will be obtained automatically.
'''
LINK_PLAY_HOST = '' # ***.com
'''
--------------------
'''
@@ -53,7 +75,7 @@ class Config():
愚人节模式开关
Switch of April Fool's Day
'''
IS_APRILFOOLS = True
IS_APRILFOOLS = False
'''
--------------------
'''
@@ -68,6 +90,16 @@ class Config():
--------------------
'''
'''
--------------------
世界模式当前活动图设置
Current available maps in world mode
'''
AVAILABLE_MAP = [] # Ex. ['test', 'test2']
'''
--------------------
'''
'''
--------------------
Web后台管理页面的用户名和密码
@@ -135,10 +167,17 @@ class Config():
LOGIN_DEVICE_NUMBER_LIMIT = 1
'''
是否允许同设备多应用共存登录
请注意这个选项设置为True时下一个选项将自动变为False
If logging in from multiple applications on the same device is allowed
Note that when this option is set to True, the next option automatically becomes False
'''
ALLOW_LOGIN_SAME_DEVICE = False
'''
24小时内登陆设备数超过最大允许设备数量时是否自动封号1天、3天、7天、15天、31天
When the number of login devices exceeds the maximum number of devices allowed to log in Arcaea within 24 hours, whether the account will be automatically banned (1 day, 3 days, 7 days, 15 days, 31 days)
'''
ALLOW_BAN_MULTIDEVICE_USER_AUTO = True
'''
--------------------
'''
@@ -162,6 +201,18 @@ class Config():
--------------------
'''
'''
--------------------
数据库更新时,是否采用最新的角色数据,如果你想采用最新的官方角色数据
注意:如果是,旧的数据将丢失;如果否,某些角色的数据变动将无法同步
If using the latest character data when updating database. If you want to only keep newest official character data, please set it `True`.
Note: If `True`, the old data will be lost; If `False`, the data changes of some characters will not be synchronized.
'''
UPDATE_WITH_NEW_CHARACTER_DATA = True
'''
--------------------
'''
'''
--------------------
是否全解锁搭档
@@ -185,7 +236,7 @@ class Config():
'''
--------------------
是否全解锁世界场景
If unlocking all world scenerys is enabled
If unlocking all world sceneries is enabled
'''
WORLD_SCENERY_FULL_UNLOCK = True
'''
@@ -201,3 +252,13 @@ class Config():
'''
--------------------
'''
'''
--------------------
是否使用最好的 10 条记录(而不是最近的 30 条记录中较好的 10 条)来计算 PTT
Calculate PTT with best 10 instead of recent best 10
'''
USE_B10_AS_R10 = False
'''
--------------------
'''

View File

@@ -20,4 +20,4 @@
{% endfor %} {% block content %}{% endblock %}
</section>
<footer id="footer" class="footer">Made by Lost@2020-2021</footer>
<footer id="footer" class="footer">Made by Lost@2020-2022</footer>

View File

@@ -20,10 +20,28 @@
<span class="char-num">{{present['description']}}</span>
<br />
<span>Items: </span>
<span class="char-num" style='white-space:pre'>{{present['items']}}</span>
{% if present['items'] %}<br />
{% for x in present['items'] %}
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Amount: </span>
<span class="char-num">{{x['amount']}}</span>
<br />
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
</div>
{% if not loop.last %}

View File

@@ -1,6 +1,6 @@
{% extends 'base.html' %}
{% block header %}
<h1>{% block title %}All items{% endblock %}</h1>
<h1>{% block title %}All purchases{% endblock %}</h1>
{% endblock %}
{% block content %}
@@ -27,23 +27,31 @@
<span>Discount to: </span>
<span class="char-num">{{item['discount_to']}}</span>
<br />
<span>Whether it is allowed to purchase with exchange certificate: </span>
<span class="char-num">{{item['discount_reason']}}</span>
<br />
{% if item['items'] %}<br />
{% for x in item['items'] %}
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Amount: </span>
<span class="char-num">{{x['amount']}}</span>
<br />
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
</div>
{% if not loop.last %}

View File

@@ -22,9 +22,28 @@
</span>
<br />
<span>Items: </span>
<span class="char-num" style='white-space:pre'>{{redeem['items']}}</span>
{% if redeem['items'] %}<br />
{% for x in redeem['items'] %}
<span>Item id: </span>
<span class="char-num">{{x['item_id']}}</span>
<br />
<span>Item type: </span>
<span class="char-num">{{x['type']}}</span>
<br />
<span>Amount: </span>
<span class="char-num">{{x['amount']}}</span>
<br />
{% if not loop.last %}
<br />
{% endif %}
{% endfor %}
{% endif %}
<div><a href="/web/redeem/{{redeem['code']}}">使用者 Players who have used it.</a></div>
</div>

View File

@@ -14,15 +14,26 @@
<label for="expire_ts">Expire time</label>
<input type="datetime-local" name="expire_ts" id="expire_ts" required>
<br />
<div>Items:</div>
<label for="fragment">Fragments</label>
<input name="fragment" id="fragment">
<label for="ticket">Memories</label>
<input name="ticket" id="ticket">
<div class="content">两种奖励只能有一个,另一个请留空。如果都填写,以记忆源点优先。</div>
<div class="content">There can only be one of the two kinds of present. Please leave the other one blank. If you
fill both, the memories will be given priority.</div>
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single song</option>
<option value='world_song'>World song</option>
<option value='world_unlock'>World unlock scene</option>
<option value='character'>Character</option>
<option value='core'>Character core</option>
<option value='fragment'>Fragment</option>
<option value='memory'>Memory</option>
<option value='anni5tix'>Anniversary 5 ticket</option>
</select>
</div>
<label for="amount">Amount</label>
<input name="amount" id="amount" value="1" required>
<br />
<div class="content">时间填写是一个HTML5控件</div>
<div class="content">Time filling is an HTML5 control.</div>
<input type="submit" value="Add">

View File

@@ -16,6 +16,10 @@
<input type="datetime-local" name="discount_from" id="discount_from">
<label for="discount_to">Discount to</label>
<input type="datetime-local" name="discount_to" id="discount_to">
<div>是否允许使用兑换券购买 Whether it is allowed to purchase with Anniversary 5 ticket:
<label><input type="radio" name="discount_reason" value="0">No</label>
<label><input type="radio" name="discount_reason" value="anni5tix">Yes</label>
</div>
<div class="content">时间填写是一个HTML5控件</div>
<div class="content">Time filling is an HTML5 control.</div>
<br />

View File

@@ -13,12 +13,15 @@
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single</option>
<option value='pack'>Pack</option>
<option value='single' selected>Single song</option>
<option value='pack'>Song pack</option>
<option value='world_song'>World song</option>
<option value='character'>Character</option>
<option value='core'>Core</option>
</select>
</div>
<label for="amount">Amount</label>
<input name="amount" id="amount" value="1" required>
<br />
<input type="submit" value="Add">
</form>
@@ -36,6 +39,7 @@
<option value='pack'>Pack</option>
<option value='world_song'>World song</option>
<option value='character'>Character</option>
<option value='core'>Core</option>
</select>
</div>
<br />

View File

@@ -11,27 +11,41 @@
<input name="code" id="code">
<div>Or</div>
<div>随机批量生成 Batch generation at random</div>
<label for="amount">Amount</label>
<input name="amount" id="amount">
<label for="redeem_amount">Redeem code amount</label>
<input name="redeem_amount" id="redeem_amount">
<br />
<div>Type:
<div>Redeem code type:
<br />
<br />
<label><input type="radio" name="type" value="0" checked>仅可使用一次 It can only be used once.</label>
<label><input type="radio" name="redeem_type" value="0" checked>仅可使用一次 It can only be used once.</label>
<br />
<label><input type="radio" name="type" value="1">每个用户仅可以使用一次 Each user can only use it once.</label>
<label><input type="radio" name="redeem_type" value="1">每个用户仅可以使用一次 Each user can only use it once.</label>
</div>
<br />
<div>Items:</div>
<label for="fragment">Fragments</label>
<input name="fragment" id="fragment">
<label for="ticket">Memories</label>
<input name="ticket" id="ticket">
<div class="content">两种奖励可以都有,不需要请留空。</div>
<div class="content">You can have both kinds of items. Please leave it blank if you don't need it.</div>
<label for="item_id">ID of the item</label>
<input name="item_id" id="item_id" required>
<label for="type">Type</label>
<div>
<select name="type">
<option value='single' selected>Single song</option>
<option value='pack'>Song pack</option>
<option value='world_song'>World song</option>
<option value='world_unlock'>World unlock scene</option>
<option value='character'>Character</option>
<option value='core'>Character core</option>
<option value='fragment'>Fragment</option>
<option value='memory'>Memory</option>
<option value='anni5tix'>Anniversary 5 ticket</option>
</select>
</div>
<label for="amount">Amount</label>
<input name="amount" id="amount" value="1" required>
<br />
<div class="content">兑换码长度为10~20。</div>
<div class="content">The length of redeem code is 10 ~ 20.</div>
<div class="content">随机生成数量不得超过1000。</div>

View File

@@ -10,10 +10,10 @@
<input type="submit" value="Submit">
</form>
<div class="content">
这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的角色列表。<br />
这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的角色列表。<br />
可上传文件: arcaea_database.db和arcsong.db<br />
新数据库不存在的数据会被添加,存在的重复数据也会被改变。<br /><br />
Here you can synchronize the old version of the database to the new version of the database and refresh the list of
Here you can synchronize the old version of the database to the new version of the database and refresh the list of full
characters owned by players.<br />
Uploadable files: arcaea_database.db & arcsong.db<br />
Data that does not exist in the new database will be added and the existing duplicate data will also be changed.

View File

View File

@@ -0,0 +1,24 @@
import os
from cryptography.hazmat.primitives.ciphers import (
Cipher, algorithms, modes
)
def encrypt(key, plaintext, associated_data):
iv = os.urandom(12)
encryptor = Cipher(
algorithms.AES(key),
modes.GCM(iv, min_tag_length=12),
).encryptor()
encryptor.authenticate_additional_data(associated_data)
ciphertext = encryptor.update(plaintext) + encryptor.finalize()
return (iv, ciphertext, encryptor.tag)
def decrypt(key, associated_data, iv, ciphertext, tag):
decryptor = Cipher(
algorithms.AES(key),
modes.GCM(iv, tag, min_tag_length=12),
).decryptor()
decryptor.authenticate_additional_data(associated_data)
return decryptor.update(ciphertext) + decryptor.finalize()

View File

@@ -0,0 +1,174 @@
def b(value, length=1):
return value.to_bytes(length=length, byteorder='little')
def bi(value):
return int.from_bytes(value, byteorder='little')
class Player:
def __init__(self) -> None:
self.player_id = 0
self.player_name = b'\x45\x6d\x70\x74\x79\x50\x6c\x61\x79\x65\x72\x00\x00\x00\x00\x00'
self.token = 0
self.character_id = 0xff
self.last_character_id = 0xff
self.is_uncapped = 0
self.difficulty = 0xff
self.last_difficulty = 0xff
self.score = 0
self.last_score = 0
self.timer = 0
self.last_timer = 0
self.cleartype = 0
self.last_cleartype = 0
self.best_score_flag = 0
self.best_player_flag = 0
self.finish_flag = 0
self.player_state = 1
self.download_percent = 0
self.online = 0
self.last_timestamp = 0
self.extra_command_queue = []
self.song_unlock = b'\x00' * 512
self.start_command_num = 0
def set_player_name(self, player_name: str):
self.player_name = player_name.encode('ascii')
if len(self.player_name) > 16:
self.player_name = self.player_name[:16]
else:
self.player_name += b'\x00' * (16 - len(self.player_name))
class Room:
def __init__(self) -> None:
self.room_id = 0
self.room_code = 'AAAA00'
self.countdown = 0xffffffff
self.timestamp = 0
self.state = 0
self.song_idx = 0xffff
self.last_song_idx = 0xffff
self.song_unlock = b'\x00' * 512
self.host_id = 0
self.players = [Player(), Player(), Player(), Player()]
self.player_num = 0
self.interval = 1000
self.times = 100
self.round_switch = 0
self.command_queue = []
self.command_queue_length = 0
def get_players_info(self):
# 获取所有玩家信息
re = b''
for i in self.players:
re += b(i.player_id, 8) + b(i.character_id) + b(i.is_uncapped) + b(i.difficulty) + b(i.score, 4) + \
b(i.timer, 4) + b(i.cleartype) + b(i.player_state) + \
b(i.download_percent) + b(i.online) + b'\x00' + i.player_name
return re
def get_player_last_score(self):
# 获取上次曲目玩家分数返回bytes
if self.last_song_idx == 0xffff:
return b'\xff\xff\x00\x00\x00\x00\x00\x00\x00' * 4
re = b''
for i in range(4):
player = self.players[i]
if player.player_id != 0:
re += b(player.last_character_id) + b(player.last_difficulty) + b(player.last_score, 4) + b(
player.last_cleartype) + b(player.best_score_flag) + b(player.best_player_flag)
else:
re += b'\xff\xff\x00\x00\x00\x00\x00\x00\x00'
return re
def make_round(self):
# 轮换房主
for i in range(4):
if self.players[i].player_id == self.host_id:
for j in range(1, 4):
if self.players[(i + j) % 4].player_id != 0:
self.host_id = self.players[(i + j) % 4].player_id
break
break
def delete_player(self, player_index: int):
# 删除某个玩家
self.player_num -= 1
if self.players[player_index].player_id == self.host_id:
self.make_round()
self.players[player_index].online = 0
self.players[player_index] = Player()
self.update_song_unlock()
def update_song_unlock(self):
# 更新房间可用歌曲
r = bi(b'\xff' * 512)
for i in self.players:
if i.player_id != 0:
r &= bi(i.song_unlock)
self.song_unlock = b(r, 512)
def is_ready(self, old_state: int, player_state: int):
# 是否全部准备就绪
if self.state == old_state:
for i in self.players:
if i.player_id != 0 and (i.player_state != player_state or i.online == 0):
return False
return True
else:
return False
def is_finish(self):
# 是否全部进入结算
for i in self.players:
if i.player_id != 0 and (i.finish_flag == 0 or i.online == 0):
return False
return True
def make_finish(self):
# 结算
self.state = 8
self.last_song_idx = self.song_idx
max_score = 0
max_score_i = []
for i in range(4):
player = self.players[i]
if player.player_id != 0:
player.finish_flag = 0
player.last_timer = player.timer
player.last_score = player.score
player.last_cleartype = player.cleartype
player.last_character_id = player.character_id
player.last_difficulty = player.difficulty
player.best_player_flag = 0
if player.last_score > max_score:
max_score = player.last_score
max_score_i = [i]
elif player.last_score == max_score:
max_score_i.append(i)
for i in max_score_i:
self.players[i].best_player_flag = 1

View File

@@ -0,0 +1,8 @@
class Config:
TIME_LIMIT = 3600000
COMMAND_INTERVAL = 1000000
COUNTDOWM_TIME = 3999
PLAYER_TIMEOUT = 30000000

View File

@@ -0,0 +1,223 @@
import socketserver
import threading
import os
import random
import time
# import binascii
from . import aes
from .udp_parser import CommandParser
from .udp_class import Room, Player, bi
from .udp_config import Config
# token: {'key': key, 'room': Room, 'player_index': player_index, 'player_id': player_id}
link_play_data = {}
room_id_dict = {} # 'room_id': Room
room_code_dict = {} # 'room_code': Room
player_dict = {} # 'player_id' : Player
clean_timer = 0
def random_room_code():
# 随机生成房间号
re = ''
for _ in range(4):
re += chr(random.randint(65, 90))
for _ in range(2):
re += str(random.randint(0, 9))
return re
def clear_player(token):
# 清除玩家信息和token
del player_dict[link_play_data[token]['player_id']]
del link_play_data[token]
def clear_room(room):
# 清除房间信息
room_id = room.room_id
room_code = room.room_code
del room_id_dict[room_id]
del room_code_dict[room_code]
del room
def memory_clean(now):
# 内存清理
clean_room_list = []
clean_player_list = []
for token in link_play_data:
room = link_play_data[token]['room']
if now - room.timestamp >= Config.TIME_LIMIT:
clean_room_list.append(room.room_id)
if now - room.players[link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT:
clean_player_list.append(token)
for room_id in room_id_dict:
if now - room_id_dict[room_id].timestamp >= Config.TIME_LIMIT:
clean_room_list.append(room_id)
for room_id in clean_room_list:
if room_id in room_id_dict:
clear_room(room_id_dict[room_id])
for token in clean_player_list:
clear_player(token)
class UDPhandler(socketserver.BaseRequestHandler):
def handle(self):
client_msg, server = self.request
token = client_msg[:8]
iv = client_msg[8:20]
tag = client_msg[20:32]
ciphertext = client_msg[32:]
if int.from_bytes(token, byteorder='little') in link_play_data:
user = link_play_data[bi(token)]
else:
return None
plaintext = aes.decrypt(user['key'], b'', iv, ciphertext, tag)
# print(binascii.b2a_hex(plaintext))
commands = CommandParser(
user['room'], user['player_index']).get_commands(plaintext)
if user['room'].players[user['player_index']].player_id == 0:
clear_player(bi(token))
temp = []
for i in commands:
if i[:3] == b'\x06\x16\x12':
temp.append(i)
commands = temp
# 处理不能正确被踢的问题
for i in commands:
iv, ciphertext, tag = aes.encrypt(user['key'], i, b'')
# print(binascii.b2a_hex(i))
server.sendto(token + iv + tag[:12] +
ciphertext, self.client_address)
def server_run(ip, port):
server = socketserver.ThreadingUDPServer((ip, port), UDPhandler)
server.serve_forever()
def data_swap(conn):
clean_timer = 0
while True:
try:
data = conn.recv()
except EOFError:
break
now = round(time.time() * 1000)
if now - clean_timer >= Config.TIME_LIMIT:
clean_timer = now
memory_clean(now)
if data[0] == 1:
# 开房
key = os.urandom(16)
room_id = bi(os.urandom(8))
while room_id in room_id_dict and room_id == 0:
room_id = bi(os.urandom(8))
room = Room()
room.room_id = room_id
room_id_dict[room_id] = room
player_id = bi(os.urandom(3))
while player_id in player_dict and player_id == 0:
player_id = bi(os.urandom(3))
player = Player()
player.player_id = player_id
player.set_player_name(data[1])
player_dict[player_id] = player
player.song_unlock = data[2]
room.song_unlock = data[2]
room.host_id = player_id
room.players[0] = player
room.player_num = 1
room_code = random_room_code()
while room_code in room_code_dict:
room_code = random_room_code()
room.room_code = room_code
room_code_dict[room_code] = room
token = room_id
player.token = token
link_play_data[token] = {'key': key,
'room': room,
'player_index': 0,
'player_id': player_id}
conn.send((0, room_code, room_id, token, key, player_id))
elif data[0] == 2:
room_code = data[3].upper()
if room_code not in room_code_dict:
# 房间号错误
conn.send((1202, ))
else:
room = room_code_dict[room_code]
if room.player_num == 4:
# 满人
conn.send((1201, ))
elif room.state != 2:
# 无法加入
conn.send((1205, ))
else:
key = os.urandom(16)
token = bi(os.urandom(8))
player_id = bi(os.urandom(3))
while player_id in player_dict and player_id == 0:
player_id = bi(os.urandom(3))
player = Player()
player.player_id = player_id
player.set_player_name(data[1])
player.token = token
player_dict[player_id] = player
player.song_unlock = data[2]
room.update_song_unlock()
for i in range(4):
if room.players[i].player_id == 0:
room.players[i] = player
player_index = i
break
link_play_data[token] = {'key': key,
'room': room,
'player_index': player_index,
'player_id': player_id}
conn.send((0, room_code, room.room_id,
token, key, player_id, room.song_unlock))
elif data[0] == 3:
token = data[1]
if token in link_play_data:
r = link_play_data[token]
conn.send((0, r['room'].room_code, r['room'].room_id, r['key'],
r['room'].players[r['player_index']].player_id, r['room'].song_unlock))
else:
conn.send((108, ))
def link_play(conn, ip: str, port: int):
try:
server = threading.Thread(target=server_run, args=(ip, port))
data_exchange = threading.Thread(target=data_swap, args=(conn,))
server.start()
data_exchange.start()
except:
pass

View File

@@ -0,0 +1,333 @@
from operator import irshift
from .udp_sender import CommandSender
from .udp_class import bi, Room
from .udp_config import Config
import time
class CommandParser:
def __init__(self, room: Room, player_index: int = 0) -> None:
self.room = room
self.player_index = player_index
def get_commands(self, command):
self.command = command
l = {b'\x06\x16\x01': self.command_01,
b'\x06\x16\x02': self.command_02,
b'\x06\x16\x03': self.command_03,
b'\x06\x16\x04': self.command_04,
b'\x06\x16\x05': self.command_05,
b'\x06\x16\x06': self.command_06,
b'\x06\x16\x07': self.command_07,
b'\x06\x16\x08': self.command_08,
b'\x06\x16\x09': self.command_09,
b'\x06\x16\x0a': self.command_0a,
b'\x06\x16\x0b': self.command_0b
}
r = l[command[:3]]()
re = []
flag_13 = False
for i in range(max(bi(self.command[12:16]), self.room.players[self.player_index].start_command_num), self.room.command_queue_length):
if self.room.command_queue[i][:3] == b'\x06\x16\x13':
if flag_13:
break
flag_13 = True
re.append(self.room.command_queue[i])
if self.room.players[self.player_index].extra_command_queue:
re += self.room.players[self.player_index].extra_command_queue
self.room.players[self.player_index].extra_command_queue = []
if r:
re += r
return re
def command_01(self):
# 给房主
player_id = bi(self.command[24:32])
for i in self.room.players:
if i.player_id == player_id and i.online == 1:
self.room.host_id = player_id
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_10())
return None
def command_02(self):
x = CommandSender(self.room)
x.random_code = self.command[16:24]
song_idx = bi(self.command[24:26])
flag = 2
if self.room.state == 2:
flag = 0
self.room.state = 3
self.room.song_idx = song_idx
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_11())
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return [x.command_0d(flag)]
def command_03(self):
# 尝试进入结算
x = CommandSender(self.room)
x.random_code = self.command[16:24]
player = self.room.players[self.player_index]
player.score = bi(self.command[24:28])
player.cleartype = self.command[28]
player.difficulty = self.command[29]
player.best_score_flag = self.command[30]
player.finish_flag = 1
player.last_timestamp -= Config.COMMAND_INTERVAL
self.room.last_song_idx = self.room.song_idx
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(self.player_index))
if self.room.is_finish():
self.room.make_finish()
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return None
def command_04(self):
# 踢人
x = CommandSender(self.room)
x.random_code = self.command[16:24]
player_id = bi(self.command[24:32])
flag = 2
if self.room.players[self.player_index].player_id == self.room.host_id and player_id != self.room.host_id:
for i in range(4):
if self.room.players[i].player_id == player_id:
flag = 1
self.room.delete_player(i)
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i))
self.room.update_song_unlock()
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_14())
break
return [x.command_0d(flag)]
def command_05(self):
pass
def command_06(self):
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.state = 1
self.room.song_idx = 0xffff
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return None
def command_07(self):
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.players[self.player_index].song_unlock = self.command[24:536]
self.room.update_song_unlock()
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_14())
return None
def command_08(self):
self.room.round_switch = bi(self.command[24:25])
x = CommandSender(self.room)
x.random_code = self.command[16:24]
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return None
def command_09(self):
re = []
x = CommandSender(self.room)
x.random_code = self.command[16:24]
player = self.room.players[self.player_index]
if bi(self.command[12:16]) == 0:
player.online = 1
self.room.state = 1
self.room.update_song_unlock()
player.start_command_num = self.room.command_queue_length
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_15())
else:
if x.timestamp - player.last_timestamp >= Config.COMMAND_INTERVAL:
re.append(x.command_0c())
player.last_timestamp = x.timestamp
# 离线判断
for i in range(4):
if i != self.player_index:
t = self.room.players[i]
if t.player_id != 0:
if t.last_timestamp != 0:
if t.online == 1 and x.timestamp - t.last_timestamp >= 5000000:
t.online = 0
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i))
elif t.online == 0 and x.timestamp - t.last_timestamp >= Config.PLAYER_TIMEOUT:
self.room.delete_player(i)
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(i))
flag_11 = False
flag_12 = False
flag_13 = False
if player.online == 0:
flag_12 = True
player.online = 1
if self.room.is_ready(1, 1):
flag_13 = True
self.room.state = 2
if player.player_state != self.command[32]:
flag_12 = True
player.player_state = self.command[32]
if player.difficulty != self.command[33] and player.player_state != 5 and player.player_state != 6 and player.player_state != 7 and player.player_state != 8:
flag_12 = True
player.difficulty = self.command[33]
if player.cleartype != self.command[34] and player.player_state != 7 and player.player_state != 8:
flag_12 = True
player.cleartype = self.command[34]
if player.download_percent != self.command[35]:
flag_12 = True
player.download_percent = self.command[35]
if player.character_id != self.command[36]:
flag_12 = True
player.character_id = self.command[36]
if player.is_uncapped != self.command[37]:
flag_12 = True
player.is_uncapped = self.command[37]
if self.room.state == 3 and player.score != bi(self.command[24:28]):
flag_12 = True
player.score = bi(self.command[24:28])
if self.room.is_ready(3, 4):
flag_13 = True
self.room.countdown = Config.COUNTDOWM_TIME
self.room.timestamp = round(time.time() * 1000)
self.room.state = 4
if self.room.state == 4 or self.room.state == 5 or self.room.state == 6:
timestamp = round(time.time() * 1000)
self.room.countdown -= timestamp - self.room.timestamp
self.room.timestamp = timestamp
if self.room.state == 4 and self.room.countdown <= 0:
# 此处不清楚
self.room.state = 5
self.room.countdown = 5999
flag_11 = True
flag_13 = True
if self.room.state == 5 and self.room.is_ready(5, 6):
self.room.state = 6
flag_13 = True
if self.room.state == 5 and self.room.is_ready(5, 7):
self.room.state = 7
self.room.countdown = 0xffffffff
flag_13 = True
if self.room.state == 5 and self.room.countdown <= 0:
print('我怎么知道这是啥')
if self.room.state == 6 and self.room.countdown <= 0:
# 此处不清楚
self.room.state = 7
self.room.countdown = 0xffffffff
flag_13 = True
if self.room.countdown <= 0:
self.room.countdown = 0
if self.room.state == 7 or self.room.state == 8:
if player.timer < bi(self.command[28:32]) or bi(self.command[28:32]) == 0 and player.timer != 0:
player.last_timer = player.timer
player.last_score = player.score
player.timer = bi(self.command[28:32])
player.score = bi(self.command[24:28])
if player.timer != 0 or self.room.state != 8:
for i in self.room.players:
i.extra_command_queue.append(
x.command_0e(self.player_index))
if self.room.is_ready(8, 1):
flag_13 = True
self.room.state = 1
self.room.song_idx = 0xffff
if self.room.round_switch == 1:
self.room.make_round()
for i in self.room.players:
i.timer = 0
i.score = 0
if self.room.is_finish():
# 有人退房导致的结算
self.room.make_finish()
flag_13 = True
if flag_11:
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_11())
if flag_12:
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(self.player_index))
if flag_13:
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
return re
def command_0a(self):
# 退出房间
self.room.delete_player(self.player_index)
x = CommandSender(self.room)
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_12(self.player_index))
if self.room.state == 3:
self.room.state = 1
self.room.song_idx = 0xffff
# self.room.command_queue_length += 1
# self.room.command_queue.append(x.command_11())
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_13())
self.room.command_queue_length += 1
self.room.command_queue.append(x.command_14())
return None
def command_0b(self):
# 推荐歌曲
song_idx = bi(self.command[16:18])
x = CommandSender(self.room)
for i in range(4):
if self.player_index != i and self.room.players[i].online == 1:
self.room.players[i].extra_command_queue.append(
x.command_0f(self.player_index, song_idx))
return None

View File

@@ -0,0 +1,46 @@
import time
from .udp_class import Room, b
class CommandSender:
def __init__(self, room: Room = Room()) -> None:
self.room = room
self.timestamp = round(time.time() * 1000000)
self.random_code = b'\x11\x11\x11\x11\x00\x00\x00\x00'
def command_0c(self):
return b'\x06\x16\x0c\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b'\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b\x0b'
def command_0d(self, code: int):
return b'\x06\x16\x0d\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(code) + b'\x07\x07\x07\x07\x07\x07\x07'
def command_0e(self, player_index: int):
# 分数广播
player = self.room.players[player_index]
return b'\x06\x16\x0e\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + b(player.player_id, 8) + b(player.character_id) + b(player.is_uncapped) + b(player.difficulty) + b(player.score, 4) + b(player.timer, 4) + b(player.cleartype) + b(player.player_state) + b(player.download_percent) + b'\x01' + b(player.last_score, 4) + b(player.last_timer, 4) + b(player.online)
def command_0f(self, player_index: int, song_idx: int):
# 歌曲推荐
player = self.room.players[player_index]
return b'\x06\x16\x0f\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + b(player.player_id, 8) + b(song_idx, 2) + b'\x06\x06\x06\x06\x06\x06'
def command_10(self):
# 房主宣告
return b'\x06\x16\x10\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.host_id, 8)
def command_11(self):
return b'\x06\x16\x11\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + self.room.get_players_info() + b'\x08\x08\x08\x08\x08\x08\x08\x08'
def command_12(self, player_index: int):
player = self.room.players[player_index]
return b'\x06\x16\x12\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(player_index) + b(player.player_id, 8) + b(player.character_id) + b(player.is_uncapped) + b(player.difficulty) + b(player.score, 4) + b(player.timer, 4) + b(player.cleartype) + b(player.player_state) + b(player.download_percent) + b(player.online)
def command_13(self):
return b'\x06\x16\x13\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + b(self.room.host_id, 8) + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b(self.room.song_idx, 2) + b(self.room.interval, 2) + b(self.room.times, 7) + self.room.get_player_last_score() + b(self.room.last_song_idx, 2) + b(self.room.round_switch, 1) + b'\x01'
def command_14(self):
return b'\x06\x16\x14\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.random_code + self.room.song_unlock + b'\x08\x08\x08\x08\x08\x08\x08\x08'
def command_15(self):
return b'\x06\x16\x15\x09' + b(self.room.room_id, 8) + b(self.room.command_queue_length, 4) + self.room.get_players_info() + self.room.song_unlock + b(self.room.host_id, 8) + b(self.room.state) + b(self.room.countdown, 4) + b(self.timestamp, 8) + b(self.room.song_idx, 2) + b(self.room.interval, 2) + b(self.room.times, 7) + self.room.get_player_last_score() + b(self.room.last_song_idx, 2) + b(self.room.round_switch, 1) + b'\x09\x09\x09\x09\x09\x09\x09\x09\x09'

View File

View File

@@ -1,5 +1,5 @@
from flask import (
Blueprint, flash, g, redirect, render_template, request, url_for
Blueprint, flash, redirect, render_template, request, url_for
)
from web.login import login_required
from werkzeug.utils import secure_filename
@@ -418,7 +418,7 @@ def all_character():
def change_character():
# 修改角色数据
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange']
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita']
return render_template('web/changechar.html', skill_ids=skill_ids)
@@ -604,7 +604,8 @@ def edit_user_purchase():
if method == '0':
web.system.unlock_all_user_item(c)
else:
c.execute('''delete from user_item''')
c.execute(
'''delete from user_item where type in ('pack', 'single')''')
flash("全部用户购买信息修改成功 Successfully edit all the users' purchase information.")
@@ -629,8 +630,8 @@ def edit_user_purchase():
if method == '0':
web.system.unlock_user_item(c, user_id)
else:
c.execute('''delete from user_item where user_id=:a''', {
'a': user_id})
c.execute('''delete from user_item where type in ('pack', 'single') and user_id = :user_id''', {
'user_id': user_id})
flash('用户购买信息修改成功 Successfully edit the user purchase information.')
else:
@@ -763,6 +764,7 @@ def change_purchase():
orig_price = request.form['orig_price']
discount_from = request.form['discount_from']
discount_to = request.form['discount_to']
discount_reason = request.form['discount_reason']
if price:
price = int(price)
@@ -776,12 +778,16 @@ def change_purchase():
discount_from = int(time.mktime(time.strptime(
discount_from, "%Y-%m-%dT%H:%M"))) * 1000
else:
discount_from = None
discount_from = -1
if discount_to:
discount_to = int(time.mktime(time.strptime(
discount_to, "%Y-%m-%dT%H:%M"))) * 1000
else:
discount_to = None
discount_to = -1
if not discount_reason:
discount_reason = ''
except:
error = '数据错误 Wrong data.'
flash(error)
@@ -791,8 +797,8 @@ def change_purchase():
c.execute(
'''select exists(select * from purchase where purchase_name=:a)''', {'a': purchase_name})
if c.fetchone() == (0,):
c.execute('''insert into purchase values(?,?,?,?,?)''',
(purchase_name, price, orig_price, discount_from, discount_to))
c.execute('''insert into purchase values(?,?,?,?,?,?)''',
(purchase_name, price, orig_price, discount_from, discount_to, discount_reason))
flash('购买项目添加成功 Successfully add the purchase.')
else:
@@ -846,6 +852,7 @@ def change_purchase_item():
purchase_name = request.form['purchase_name']
item_id = request.form['item_id']
item_type = request.form['type']
amount = int(request.form['amount'])
except:
error = '数据错误 Wrong data.'
flash(error)
@@ -861,8 +868,8 @@ def change_purchase_item():
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (1,):
c.execute('''insert into purchase_item values(?,?,?)''',
(purchase_name, item_id, item_type))
c.execute('''insert into purchase_item values(?,?,?,?)''',
(purchase_name, item_id, item_type, amount))
flash('''购买项目的物品添加成功 Successfully add the purchase's item.''')
else:
error = '''物品不存在 The item does not exist.'''
@@ -975,16 +982,19 @@ def all_present():
if x:
posts = []
for i in x:
items = json.loads(i[2])
items_string = ''
for j in items:
items_string = items_string + '\n' + \
str(j['id']) + ': ' + str(j['amount'])
items = []
c.execute(
'''select * from present_item where present_id=?''', (i[0],))
y = c.fetchall()
for j in y:
if j is not None:
items.append(
{'item_id': j[1], 'type': j[2], 'amount': j[3]})
posts.append({'present_id': i[0],
'expire_ts': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(int(i[1])//1000)),
'items': items_string,
'description': i[3]
'items': items,
'description': i[2]
})
else:
error = '没有奖励数据 No present data.'
@@ -1010,13 +1020,15 @@ def add_present():
present_id = request.form['present_id']
expire_ts = request.form['expire_ts']
description = request.form['description']
fragment = request.form['fragment']
ticket = request.form['ticket']
item_id = request.form['item_id']
item_type = request.form['type']
item_amount = request.form['amount']
try:
if ticket:
ticket = int(ticket)
if fragment:
fragment = int(fragment)
if item_amount:
item_amount = int(item_amount)
else:
item_amount = 1
expire_ts = int(time.mktime(time.strptime(
expire_ts, "%Y-%m-%dT%H:%M"))) * 1000
except:
@@ -1028,18 +1040,8 @@ def add_present():
if len(description) >= 256:
description = description[:200]
items = []
if ticket:
items.append({'type': 'memory', 'id': 'memory', 'amount': ticket})
if fragment:
items.append(
{'type': 'fragment', 'id': 'fragment', 'amount': fragment})
if items == []:
flash('奖励为空 No items.')
return redirect(url_for('index.change_present'))
message = web.system.add_one_present(
present_id, expire_ts, description, json.dumps(items))
present_id, expire_ts, description, item_id, item_type, item_amount)
if message:
flash(message)
@@ -1128,15 +1130,18 @@ def all_redeem():
if x:
posts = []
for i in x:
items = json.loads(i[1])
items_string = ''
for j in items:
items_string = items_string + '\n' + \
str(j['id']) + ': ' + str(j['amount'])
items = []
c.execute(
'''select * from redeem_item where code=?''', (i[0],))
y = c.fetchall()
for j in y:
if j is not None:
items.append(
{'item_id': j[1], 'type': j[2], 'amount': j[3]})
posts.append({'code': i[0],
'items': items_string,
'type': i[2]
'items': items,
'type': i[1]
})
else:
error = '没有兑换码数据 No redeem code data.'
@@ -1159,48 +1164,39 @@ def change_redeem():
@login_required
def add_redeem():
# 添加兑换码数据
print(request.form)
code = request.form['code']
amount = request.form['amount']
redeem_type = request.form['type']
fragment = request.form['fragment']
ticket = request.form['ticket']
redeem_amount = request.form['redeem_amount']
redeem_type = request.form['redeem_type']
item_id = request.form['item_id']
item_type = request.form['type']
item_amount = request.form['amount']
try:
if amount:
amount = int(amount)
if ticket:
ticket = int(ticket)
if fragment:
fragment = int(fragment)
if item_amount:
item_amount = int(item_amount)
else:
item_amount = 1
if redeem_amount:
redeem_amount = int(redeem_amount)
except:
flash('数据错误 Wrong data.')
return redirect(url_for('index.change_redeem'))
items = []
if ticket:
items.append({'type': 'memory', 'id': 'memory', 'amount': ticket})
if fragment:
items.append(
{'type': 'fragment', 'id': 'fragment', 'amount': fragment})
if items == []:
flash('奖励为空 No items.')
return redirect(url_for('index.change_redeem'))
if code and not amount:
if code and not redeem_amount:
if len(code) > 20 or len(code) < 10:
flash('兑换码长度不合适 Inappropriate length of redeem code.')
return redirect(url_for('index.change_redeem'))
message = web.system.add_one_redeem(
code, redeem_type, json.dumps(items))
elif amount and not code:
if amount <= 0 or amount > 1000:
code, redeem_type, item_id, item_type, item_amount)
elif redeem_amount and not code:
if redeem_amount <= 0 or redeem_amount > 1000:
flash('数量错误 Wrong amount.')
return redirect(url_for('index.change_redeem'))
message = web.system.add_some_random_redeem(
amount, redeem_type, json.dumps(items))
elif amount and code:
redeem_amount, redeem_type, item_id, item_type, item_amount)
elif redeem_amount and code:
flash('只能使用一种添加方式 Only one add method can be used.')
return redirect(url_for('index.change_redeem'))
else:

View File

@@ -5,6 +5,7 @@ import json
import server.arcscore
import hashlib
from random import Random
from setting import Config
def int2b(x):
@@ -177,7 +178,7 @@ def update_user_char(c):
def update_database():
# 将old数据库不存在数据加入到新数据库上并删除old数据库
# 对于arcaea_datebase.db更新一些表并用character数据更新user_char
# 对于arcaea_datebase.db更新一些表并用character数据更新user_char_full
# 对于arcsong.db更新songs
if os.path.isfile("database/old_arcaea_database.db") and os.path.isfile("database/arcaea_database.db"):
with Connect('./database/old_arcaea_database.db') as c1:
@@ -196,15 +197,21 @@ def update_database():
update_one_table(c1, c2, 'login')
update_one_table(c1, c2, 'present')
update_one_table(c1, c2, 'user_present')
update_one_table(c1, c2, 'present_item')
update_one_table(c1, c2, 'redeem')
update_one_table(c1, c2, 'user_redeem')
update_one_table(c1, c2, 'redeem_item')
update_one_table(c1, c2, 'role')
update_one_table(c1, c2, 'user_role')
update_one_table(c1, c2, 'power')
update_one_table(c1, c2, 'role_power')
update_one_table(c1, c2, 'api_auth')
update_one_table(c1, c2, 'api_login')
update_one_table(c1, c2, 'user_char')
if not Config.UPDATE_WITH_NEW_CHARACTER_DATA:
update_one_table(c1, c2, 'character')
update_user_char(c2) # 更新user_char_full
os.remove('database/old_arcaea_database.db')
@@ -224,14 +231,16 @@ def unlock_all_user_item(c):
c.execute('''select user_id from user''')
x = c.fetchall()
c.execute('''select item_id, type from item''')
c.execute('''select item_id, type from purchase_item''')
y = c.fetchall()
c.execute('''delete from user_item''')
if x and y:
for i in x:
for j in y:
c.execute('''insert into user_item values(:a,:b,:c)''', {
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
'a': i[0], 'b': j[0], 'c': j[1]})
if c.fetchone() == (0,) and j[1] != 'character':
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
'a': i[0], 'b': j[0], 'c': j[1]})
return
@@ -239,14 +248,14 @@ def unlock_all_user_item(c):
def unlock_user_item(c, user_id):
# 解锁用户购买
c.execute('''select item_id, type from item''')
c.execute('''select item_id, type from purchase_item''')
y = c.fetchall()
for j in y:
c.execute('''select exists(select * from user_item where user_id=:a and item_id=:b and type=:c)''', {
'a': user_id, 'b': j[0], 'c': j[1]})
if c.fetchone() == (0,):
c.execute('''insert into user_item values(:a,:b,:c)''', {
if c.fetchone() == (0,) and j[1] != 'character':
c.execute('''insert into user_item values(:a,:b,:c,1)''', {
'a': user_id, 'b': j[0], 'c': j[1]})
return
@@ -279,6 +288,7 @@ def get_all_purchase():
discount_from = None
discount_to = None
discount_reason = 'Yes' if i[5] == 'anni5tix' else 'No'
if i[3] and i[3] >= 0:
discount_from = time.strftime(
@@ -293,13 +303,15 @@ def get_all_purchase():
items = []
if y:
for j in y:
items.append({'item_id': j[1], 'type': j[2]})
items.append(
{'item_id': j[1], 'type': j[2], 'amount': j[3]})
re.append({'purchase_name': i[0],
'price': i[1],
'orig_price': i[2],
'discount_from': discount_from,
'discount_to': discount_to,
'discount_reason': discount_reason,
'items': items
})
@@ -360,7 +372,7 @@ def update_all_save(c):
return
def add_one_present(present_id, expire_ts, description, items):
def add_one_present(present_id, expire_ts, description, item_id, item_type, item_amount):
# 添加一个奖励
message = None
@@ -368,9 +380,16 @@ def add_one_present(present_id, expire_ts, description, items):
c.execute(
'''select exists(select * from present where present_id=:a)''', {'a': present_id})
if c.fetchone() == (0,):
c.execute('''insert into present values(:a,:b,:c,:d)''', {
'a': present_id, 'b': expire_ts, 'c': items, 'd': description})
message = '添加成功 Successfully add it.'
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (1,):
c.execute('''insert into present values(:a,:b,:c)''', {
'a': present_id, 'b': expire_ts, 'c': description})
c.execute('''insert into present_item values(?,?,?,?)''',
(present_id, item_id, item_type, item_amount))
message = '添加成功 Successfully add it.'
else:
message = '物品不存在 The item does not exist.'
else:
message = '奖励已存在 The present exists.'
@@ -389,6 +408,8 @@ def delete_one_present(present_id):
{'a': present_id})
c.execute('''delete from user_present where present_id =:a''', {
'a': present_id})
c.execute('''delete from present_item where present_id =:a''', {
'a': present_id})
message = '删除成功 Successfully delete it.'
else:
message = '奖励不存在 The present does not exist.'
@@ -431,7 +452,7 @@ def deliver_all_user_present(c, present_id):
return
def add_one_redeem(code, redeem_type, items):
def add_one_redeem(code, redeem_type, item_id, item_type, item_amount):
# 添加一个兑换码
message = None
@@ -439,28 +460,41 @@ def add_one_redeem(code, redeem_type, items):
c.execute(
'''select exists(select * from redeem where code=:a)''', {'a': code})
if c.fetchone() == (0,):
c.execute('''insert into redeem values(:a,:b,:c)''', {
'a': code, 'b': items, 'c': redeem_type})
message = '添加成功 Successfully add it.'
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (1,):
c.execute('''insert into redeem values(:a,:c)''', {
'a': code, 'c': redeem_type})
c.execute('''insert into redeem_item values(?,?,?,?)''',
(code, item_id, item_type, item_amount))
message = '添加成功 Successfully add it.'
else:
message = '物品不存在 The item does not exist.'
else:
message = '兑换码已存在 The redeem code exists.'
return message
def add_some_random_redeem(amount, redeem_type, items):
def add_some_random_redeem(amount, redeem_type, item_id, item_type, item_amount):
# 随机生成一堆10位的兑换码
message = None
with Connect() as c:
c.execute(
'''select exists(select * from item where item_id=? and type=?)''', (item_id, item_type))
if c.fetchone() == (0,):
return '物品不存在 The item does not exist.'
i = 1
while i <= amount:
code = random_str()
c.execute(
'''select exists(select * from redeem where code=:a)''', {'a': code})
if c.fetchone() == (0,):
c.execute('''insert into redeem values(:a,:b,:c)''',
{'a': code, 'b': items, 'c': redeem_type})
c.execute('''insert into redeem values(:a,:c)''',
{'a': code, 'c': redeem_type})
c.execute('''insert into redeem_item values(?,?,?,?)''',
(code, item_id, item_type, item_amount))
i += 1
message = '添加成功 Successfully add it.'
@@ -479,6 +513,8 @@ def delete_one_redeem(code):
c.execute('''delete from redeem where code = :a''', {'a': code})
c.execute(
'''delete from user_redeem where code =:a''', {'a': code})
c.execute(
'''delete from redeem_item where code =:a''', {'a': code})
message = '删除成功 Successfully delete it.'
else:
message = '兑换码不存在 The redeem code does not exist.'