16 Commits
v2.3 ... v2.4

Author SHA1 Message Date
Lost-MSth
8a3cec05b7 Update to v2.4 2021-05-14 14:06:58 +08:00
Lost-MSth
e3d81436d0 Waiting for updating
+ new character
+ new byd map
+ new items
+ some new configs
+ two new operations in background
+ record email while registering
+ record ip while logging in
+ checking something before running and updating database automatically

building something about API

fix bugs:
about purchasing system
about hiding ptt
about login different accounts with same device
about some details

This is only a pre updating. Many things have been changed. It takes time to find some ways.
2021-05-12 00:45:13 +08:00
Lost-MSth
941a79ccc7 Update to v2.3.2 2021-04-09 14:42:14 +08:00
Lost-MSth
62902a561c Add an option 2021-04-05 14:45:50 +08:00
Lost-MSth
1660bc5899 Merge branch 'master' of https://github.com/Lost-MSth/Arcaea-server 2021-04-05 13:20:42 +08:00
Lost-MSth
d577246d28 Revert "Revert "Fix some bugs""
This reverts commit 9fd626468e.
2021-04-05 13:20:36 +08:00
Lost-MSth
9fd626468e Revert "Fix some bugs"
This reverts commit c51a1b4e85.
2021-04-05 13:19:32 +08:00
Lost-MSth
c51a1b4e85 Fix some bugs
Fix the fanmade cloud save (add a switch in setting)

Add SSL switch

Fix 'my rank' in game, if there is not enough people behind you, now it will show 20 people as expected
2021-04-05 13:02:56 +08:00
Lost-MSth
0e42123417 Update README.md
Well, it is not 4.1 now.
2021-04-02 22:03:35 +08:00
Lost-MSth
e216e6144b Update README.md 2021-04-01 17:05:53 +08:00
Lost-MSth
b70c435a4a Update to v2.3.1 2021-04-01 17:02:56 +08:00
Lost-MSth
eb03d2be93 Update README.md 2021-03-14 22:37:59 +08:00
Lost-MSth
fcb5d264ef Add something and fix something
Add character values.
Do something about #16.
Fix a problem about relative path.
2021-03-14 22:33:13 +08:00
Lost-MSth
d617951e1f Fix a bug(Follow last commit)
Ctrl c and Ctrl v,
A bug appears!
2021-03-09 20:09:58 +08:00
Lost-MSth
febf931f1a Fix something
I don't know if it is a bug and I haven't test it.

Maybe it makes low version arcaea can log in.
2021-03-09 19:47:54 +08:00
Lost-MSth
981961d6a1 Update README.md 2021-03-09 14:20:54 +08:00
29 changed files with 2762 additions and 1565 deletions

View File

@@ -39,8 +39,9 @@ This procedure is mainly used for study and research, and shall not be used for
- 服务器日志 Server log - 服务器日志 Server log
没有以下 We don't have 没有以下 We don't have
- 角色数值 Character characteristic value
- 服务器安全性保证 Server security assurance - 服务器安全性保证 Server security assurance
- 世界模式下的搭档升级、觉醒,以及某些奖励的获取 Partner upgrading, uncapping, and receiving some rewards in the world mode
- 世界排名 Global rank
可能有问题 There may be problems 可能有问题 There may be problems
- Recent 30 - Recent 30
@@ -60,24 +61,29 @@ It is just so interesting. What it can do is under exploration.
## 更新日志 Update log ## 更新日志 Update log
只保留最新版本 Only keep the latest version. 只保留最新版本 Only keep the latest version.
> 提醒:更新时请注意保留原先的数据库,以防数据丢失 > 提醒:更新时请注意保留原先的数据库,以防数据丢失
> >
> Tips: When updating, please keep the original database in case of data loss. > Tips: When updating, please keep the original database in case of data loss.
### Version 2.3 ### Version 2.4
- 适用于Arcaea 3.5.2版本 For Arcaea 3.5.2 - 适用于Arcaea 3.6.0版本 For Arcaea 3.6.0
- 更新了歌曲数据库 Update the song database. - 更新了歌曲数据库 Update the song database.
-增服务器日志 Add server log. -搭档 **拉格兰(拉格朗日!)** 已解锁 Unlock the new character **Lagrange**.
- 优化了代码结构 Optimize the code structure. - 新增游戏版本校验 Add game version verification.
- 修复了一些Bug提升服务器安全性 Fix some bugs and improve server security. - 新增注册时邮箱记录 Add email recording while registering.
- 新增登录时IP地址记录 Add IP address recording at login time.
- 新增了一些设置选项和后台功能 Add some setting options and background functions.
- 新增数据库自动更新功能 Add database automatic update function.
- 正在构建API接口 Building API interface.
- 修复了一些Bug Fix some bugs.
## 运行环境与依赖 Running environment and requirements ## 运行环境与依赖 Running environment and requirements
- Windows/Linux/Mac OS/Android - Windows/Linux/Mac OS/Android
- Python 3 - Python 3
- Flask module - Flask module
- Charles (optional) - Charles, IDA, proxy app... (optional)
<!--
## 环境搭建 Environment construction ## 环境搭建 Environment construction
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E7%8E%AF%E5%A2%83%E6%90%AD%E5%BB%BA) [中文](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) [English](https://github.com/Lost-MSth/Arcaea-server/wiki/Environment-construction)
@@ -85,11 +91,13 @@ It is just so interesting. What it can do is under exploration.
## 使用说明 Instruction for use ## 使用说明 Instruction for use
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E) [中文](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) [English](https://github.com/Lost-MSth/Arcaea-server/wiki/Instruction-for-use)
-->
## 注意 Attentions ## 注意 Attentions
[中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E6%B3%A8%E6%84%8F) [中文](https://github.com/Lost-MSth/Arcaea-server/wiki/%E6%B3%A8%E6%84%8F)
[English](https://github.com/Lost-MSth/Arcaea-server/wiki/Attentions) [English](https://github.com/Lost-MSth/Arcaea-server/wiki/Attentions)
## 鸣谢 Thanks ## 鸣谢 Thanks
歌曲数据库来自 Using song database from 歌曲数据库来自 Using song database from
[BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases) [BotArcAPI releases](https://github.com/TheSnowfield/BotArcAPI/releases)

View File

@@ -0,0 +1,119 @@
import hashlib
import time
from server.sql import Connect
import functools
from setting import Config
from flask import jsonify
class User():
# 用户类,当数据类型用
def __init__(self, user_id=None, role='', power=[]):
self.user_id = user_id
self.role = role
self.power = power
def login():
# 登录接口
return {'token': 1}, 0
def logout():
# 登出接口
pass
def id_get_role_id(c, user_id):
# user_id获取role_id
role_id = 1
c.execute('''select role_id from user_role where user_id = :a''',
{'a': user_id})
x = c.fetchone()
if x is not None:
role_id = int(x[0])
return role_id
def role_id_get_role(c, role_id):
# role_id获取role
role = ''
c.execute('''select role_name from role where role_id = :a''',
{'a': role_id})
x = c.fetchone()
if x is not None:
role = x[0]
return role
def api_token_get_id(c, token):
# api的token获取user_id
user_id = None
c.execute('''select user_id from api_login where token = :token''', {
'token': token})
x = c.fetchone()
if x is not None:
user_id = x[0]
return user_id
def get_role_power(c, role_id):
# 获取role_id对应power返回列表
role_power = []
c.execute('''select power_name from power where power_id in (select power_id from role_power where role_id=:a)''', {
'a': role_id})
x = c.fetchall()
for i in x:
role_power.append(i[0])
return role_power
def role_required(request, power=[]):
# api token验证写成了修饰器
def decorator(view):
@functools.wraps(view)
def wrapped_view(*args, **kwargs):
try:
request.json # 检查请求json格式
except:
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'})
user = User()
if power == []:
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
user.user_id = 0
else:
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
else:
with Connect() as c:
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': ''})
role_id = id_get_role_id(c, user.user_id)
user.role = role_id_get_role(c, role_id)
user.role_power = get_role_power(c, role_id)
f = False
for i in power:
if i in user.role_power:
f = True
break
if not f:
return jsonify({'status': 403, 'code': -1, 'data': {}, 'msg': 'No permission'})
return view(user, *args, **kwargs)
return wrapped_view
return decorator

View File

@@ -0,0 +1,15 @@
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'
}
return msg[str(code)]

View File

@@ -0,0 +1,223 @@
from flask import (
Blueprint, request, jsonify
)
import api.api_auth
import api.users
import api.songs
from api.api_code import code_get_msg
bp = Blueprint('api', __name__, url_prefix='/api/v1')
def get_query_parameter(request, query_able=[], sort_able=[]):
# 提取查询请求参数返回四个参数和code
limit = -1
offset = 0
query = {} # {'name': 'admin'}
sort = [] # [{'column': 'user_id', 'order': 'ASC'}, ...]
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
return limit, offset, query, sort, 0
@bp.route('/')
def ping():
return jsonify({'status': 200, 'code': 0, 'data': {}, 'msg': ''})
@bp.route('/token', methods=['POST'])
def token_post():
# 登录获取token
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': ''})
else:
return jsonify({'status': 401, 'code': -1, 'data': {}, 'msg': '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': ''})
@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': ''})
@bp.route('/users', methods=['GET'])
@api.api_auth.role_required(request, ['select'])
def users_get(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'])
if code < 0:
return jsonify({'status': 200, 'code': code, 'data': {}, 'msg': code_get_msg(code)})
data = api.users.get_users(limit, offset, query, sort)
if not data:
return jsonify({'status': 200, 'code': -2, 'data': {}, 'msg': code_get_msg(-2)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
@bp.route('/users/<int:user_id>', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_get(user, user_id):
# 查询用户信息
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-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'})
data = api.users.get_user_info(user_id)
if not data:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
@bp.route('/users/<int:user_id>/b30', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_b30_get(user, user_id):
# 查询用户b30
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-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'})
data = api.users.get_user_b30(user_id)
if data['data'] == []:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
@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):
# 查询用户所有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)})
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'})
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)
if data['data'] == []:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
@bp.route('/users/<int:user_id>/r30', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_me'])
def users_user_r30_get(user, user_id):
# 查询用户r30
if user_id == 'me':
user_id = user.user_id
if user_id <= 0:
return jsonify({'status': 200, 'code': -4, 'data': {}, 'msg': code_get_msg(-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'})
data = api.users.get_user_r30(user_id)
if data['data'] == []:
return jsonify({'status': 200, 'code': -3, 'data': {}, 'msg': code_get_msg(-3)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
@bp.route('/songs/<string:song_id>', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
def songs_song_get(user, song_id):
# 查询歌曲信息
data = api.songs.get_song_info(song_id)
if not data:
return jsonify({'status': 200, 'code': -2, 'data': {}, 'msg': code_get_msg(-2)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})
@bp.route('/songs', methods=['GET'])
@api.api_auth.role_required(request, ['select', 'select_song_info'])
def songs_get(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)
if not data:
return jsonify({'status': 200, 'code': -2, 'data': {}, 'msg': code_get_msg(-2)})
return jsonify({'status': 200, 'code': 0, 'data': data, 'msg': ''})

View File

@@ -0,0 +1,58 @@
from server.sql import Connect
from server.sql import Sql
import time
def get_song_info(song_id):
# 查询指定歌曲信息,返回字典
r = {}
with Connect('./database/arcsong.db') as c:
c.execute('''select * from songs where sid=:a''', {'a': song_id})
x = c.fetchone()
if x:
r = {'song_id': x[0],
'name': {'name_en': x[1],
'name_jp': x[2]},
'pakset': x[5],
'artist': x[6],
'date': x[9],
'rating_pst': x[12]/10,
'rating_prs': x[13]/10,
'rating_ftr': x[14]/10,
'rating_byn': x[15]/10,
'difficultly_pst': x[16]/2,
'difficultly_prs': x[17]/2,
'difficultly_ftr': x[18]/2,
'difficultly_byn': x[19]/2
}
return r
def get_songs(limit=-1, offset=0, query={}, sort=[]):
# 查询全部歌曲信息,返回字典列表
r = []
with Connect('./database/arcsong.db') as c:
x = Sql.select(c, 'songs', [], limit, offset, query, sort)
if x:
for i in x:
r.append({'sid': i[0],
'name': {'name_en': i[1],
'name_jp': i[2]},
'pakset': i[5],
'artist': i[6],
'date': i[9],
'rating_pst': i[12]/10,
'rating_prs': i[13]/10,
'rating_ftr': i[14]/10,
'rating_byn': i[15]/10,
'difficultly_pst': i[16]/2,
'difficultly_prs': i[17]/2,
'difficultly_ftr': i[18]/2,
'difficultly_byn': i[19]/2
})
return r

View File

@@ -0,0 +1,99 @@
from server.sql import Connect
from server.sql import Sql
import time
import web.webscore
import server.info
def get_users(limit=-1, offset=0, query={}, sort=[]):
# 获取全用户信息,返回字典列表
r = []
with Connect() as c:
x = Sql.select(c, 'user', [], limit, offset, query, sort)
if x:
for i in x:
if i[23] != -1:
character_id = i[23]
else:
character_id = i[6]
r.append({
'user_id': i[0],
'name': i[1],
'join_date': i[3],
'user_code': i[4],
'rating_ptt': i[5]/100,
'character_id': character_id,
'is_char_uncapped': i[8] == 1,
'is_char_uncapped_override': i[9] == 1,
'is_hide_rating': i[10],
'ticket': i[26]
})
return r
def get_user_info(user_id):
# 获取用户信息返回字典其实就是调用user/me信息
r = {}
with Connect() as c:
r = server.info.get_user_me(c, user_id)
return r
def get_user_b30(user_id):
# 获取用户b30信息返回字典
r = []
with Connect() as c:
r = web.webscore.get_user_score(c, user_id, 30)
bestptt = 0
for i in r:
if i['rating']:
bestptt += i['rating']
if 'time_played' in i:
i['time_played'] = int(time.mktime(time.strptime(
i['time_played'], '%Y-%m-%d %H:%M:%S')))
return {'user_id': user_id, 'b30_ptt': bestptt / 30, 'data': r}
def get_user_best(user_id, limit=-1, offset=0, query={}, sort=[]):
# 获取用户b30信息返回字典
r = []
with Connect() as c:
x = Sql.select(c, 'best_score', [], limit, offset, query, sort)
if x:
for i in x:
r.append({
"song_id": i[1],
"difficulty": i[2],
"score": i[3],
"shiny_perfect_count": i[4],
"perfect_count": i[5],
"near_count": i[6],
"miss_count": i[7],
"health": i[8],
"modifier": i[9],
"time_played": i[10],
"best_clear_type": i[11],
"clear_type": i[12],
"rating": i[13]
})
return {'user_id': user_id, 'data': r}
def get_user_r30(user_id):
# 获取用户r30信息返回字典
r = []
with Connect() as c:
r, r10_ptt = web.webscore.get_user_recent30(c, user_id)
return {'user_id': user_id, 'r10_ptt': r10_ptt, 'data': r}

Binary file not shown.

View File

@@ -5,8 +5,17 @@ import json
# 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用 # 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用
conn = sqlite3.connect('arcaea_database.db') ARCAEA_SERVER_VERSION = 'v2.4.0'
def main(path='./'):
conn = sqlite3.connect(path+'arcaea_database.db')
c = conn.cursor() c = conn.cursor()
c.execute('''create table if not exists config(id text primary key,
value text
);''')
c.execute('''insert into config values("version", :a);''',
{'a': ARCAEA_SERVER_VERSION})
c.execute('''create table if not exists user(user_id int primary key, c.execute('''create table if not exists user(user_id int primary key,
name text unique, name text unique,
password text not null, password text not null,
@@ -34,7 +43,8 @@ favorite_character int,
max_stamina_notification_enabled int, max_stamina_notification_enabled int,
current_map text, current_map text,
ticket int, ticket int,
prog_boost int prog_boost int,
email text
);''') );''')
c.execute('''create table if not exists login(access_token text, c.execute('''create table if not exists login(access_token text,
user_id int, user_id int,
@@ -234,27 +244,69 @@ code text,
primary key(user_id, code) primary key(user_id, code)
);''') );''')
c.execute('''create table if not exists role(role_id int primary key,
role_name text,
caption text
);''')
c.execute('''create table if not exists user_role(user_id int,
role_id int,
primary key(user_id, role_id)
);''')
c.execute('''create table if not exists power(power_id int primary key,
power_name text,
caption text
);''')
c.execute('''create table if not exists role_power(role_id int,
power_id int,
primary key(role_id, power_id)
);''')
c.execute('''create table if not exists api_login(user_id int,
token text,
login_time int,
login_ip text,
primary key(user_id, token)
);''')
# 搭档初始化
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', 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'] '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']
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', 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'] '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']
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', '', '', 'shirabe_entry_fee', skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', '', '', 'shirabe_entry_fee',
'', '', '', '', '', '', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', ''] '', '', '', '', '', '', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
for i in range(0, 45): frag = [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]
prog = [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]
overdrive = [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, 90, 80]
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]
# print(len(char))
# print(len(skill_id))
# print(len(skill_id_uncap))
# print(len(frag))
# print(len(prog))
# print(len(overdrive))
# print(len(char_type))
for i in range(0, 46):
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]:
sql = 'insert into character values('+str( sql = '''insert into character values(:a,:b,30,25000,25000,:c,:d,:e,:f,0,0,:g,:h,'',1,1)'''
i)+',"'+char[i]+'''",30,25000,25000,90,90,90,"'''+skill_id[i]+'''",0,0,"'''+skill_id_uncap[i]+'''",0,'',1,1)''' c.execute(sql, {'a': i, 'b': char[i], 'c': frag[i], 'd': prog[i],
c.execute(sql) 'e': overdrive[i], 'f': skill_id[i], 'g': skill_id_uncap[i], 'h': char_type[i]})
else: else:
if i != 5: if i != 5:
sql = 'insert into character values('+str( sql = '''insert into character values(:a,:b,20,10000,10000,:c,:d,:e,:f,0,0,:g,:h,'',0,0)'''
i)+',"'+char[i]+'''",30,25000,25000,90,90,90,"'''+skill_id[i]+'''",0,0,"'''+skill_id_uncap[i]+'''",0,'',0,0)''' c.execute(sql, {'a': i, 'b': char[i], 'c': frag[i], 'd': prog[i],
c.execute(sql) 'e': overdrive[i], 'f': skill_id[i], 'g': skill_id_uncap[i], 'h': char_type[i]})
def b2int(x): def b2int(x):
# int与布尔值转换 # int与布尔值转换
@@ -263,7 +315,6 @@ def b2int(x):
else: else:
return 0 return 0
def insert_items(c, items): def insert_items(c, items):
# 物品数据导入 # 物品数据导入
for i in items: for i in items:
@@ -284,21 +335,45 @@ def insert_items(c, items):
c.execute('''insert into item(item_id, type, is_available, price, orig_price, discount_from, discount_to, _id) values(:a,:b,:c,:d,:e,:f,:g,:h)''', { c.execute('''insert into item(item_id, type, is_available, price, orig_price, discount_from, discount_to, _id) values(:a,:b,:c,:d,:e,:f,:g,:h)''', {
'a': j['id'], 'b': j['type'], 'c': b2int(j['is_available']), 'd': i['price'], 'e': i['orig_price'], 'f': discount_from, 'g': discount_to, 'h': _id}) 'a': j['id'], 'b': j['type'], 'c': b2int(j['is_available']), 'd': i['price'], 'e': i['orig_price'], 'f': discount_from, 'g': discount_to, 'h': _id})
# item初始化
f = open('singles.json', 'r') f = open(path+'singles.json', 'r')
singles = json.load(f) singles = json.load(f)
f.close() f.close()
insert_items(c, singles) insert_items(c, singles)
f = open('packs.json', 'r') f = open(path+'packs.json', 'r')
packs = json.load(f) packs = json.load(f)
f.close() f.close()
insert_items(c, packs) insert_items(c, packs)
# api权限与权限组初始化
role = ['admin', 'user', 'selecter']
role_caption = ['管理员', '用户', '查询接口']
power = ['select', 'select_me', 'change', 'change_me', 'grant',
'grant_inf', 'select_song_rank', 'select_song_info']
power_caption = ['总体查询权限', '自我查询权限', '总体修改权限',
'自我修改权限', '授权权限', '下级授权权限', '歌曲排行榜查询权限', '歌曲信息查询权限']
role_power = {'0': [0, 1, 3, 5, 6, 7],
'1': [1, 3, 6, 7],
'2': [0]
}
for i in range(0, len(role)):
c.execute('''insert into role values(:a,:b,:c)''', {
'a': i, 'b': role[i], 'c': role_caption[i]})
for i in range(0, len(power)):
c.execute('''insert into power values(:a,:b,:c)''', {
'a': i, 'b': power[i], 'c': power_caption[i]})
for i in role_power:
for j in role_power[i]:
c.execute('''insert into role_power values(:a,:b)''',
{'a': int(i), 'b': j})
conn.commit() conn.commit()
conn.close() conn.close()
def arc_register(name: str, password: str): def arc_register(name: str, password: str):
def build_user_code(c): def build_user_code(c):
return '123456789' return '123456789'
@@ -326,7 +401,7 @@ def arc_register(name: str, password: str):
c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', { c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', {
'a': user_id, 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]}) 'a': user_id, 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]})
conn = sqlite3.connect('arcaea_database.db') conn = sqlite3.connect(path+'arcaea_database.db')
c = conn.cursor() c = conn.cursor()
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest() hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute( c.execute(
@@ -352,5 +427,8 @@ def arc_register(name: str, password: str):
conn.close() conn.close()
return None return None
arc_register('admin', 'admin123') arc_register('admin', 'admin123')
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,78 @@
{
"map_id": "byd_lostcivilization",
"is_legacy": false,
"chapter": 1001,
"available_from": -1,
"available_to": 9999999999999,
"is_repeatable": false,
"require_id": "lostcivilization2",
"require_type": "chart_unlock",
"coordinate": "-650,-900",
"is_beyond": true,
"stamina_cost": 3,
"beyond_health": 250,
"character_affinity": [0, 1],
"affinity_multiplier": [1.9, 1.9],
"step_count": 7,
"custom_bg": "",
"curr_position": 0,
"curr_capture": 0,
"is_locked": false,
"steps": [{
"map_id": "byd_lostcivilization",
"position": 0,
"capture": 85
}, {
"map_id": "byd_lostcivilization",
"position": 1,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 2,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 3,
"capture": 5,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 4,
"capture": 100,
"items": [{
"type": "core",
"id": "core_generic",
"amount": 1
}]
}, {
"map_id": "byd_lostcivilization",
"position": 5,
"capture": 50,
"items": [{
"type": "fragment",
"amount": 525
}]
}, {
"map_id": "byd_lostcivilization",
"position": 6,
"capture": 0,
"items": [{
"id": "lostcivilization3",
"type": "world_song"
}]
}]
}

View File

@@ -3,10 +3,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "core", "id": "core",
"_id": "6046bab736651a07ebc537db",
"is_available": true "is_available": true
}], }],
"price": 400, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -15,15 +14,13 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "shiawase", "id": "shiawase",
"_id": "6046bab736651a07ebc53823",
"is_available": true "is_available": true
}, { }, {
"type": "character", "type": "character",
"id": "kou", "id": "kou",
"_id": "6046bab736651a07ebc53822",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -32,15 +29,13 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "dynamix", "id": "dynamix",
"_id": "6046bab736651a07ebc53827",
"is_available": true "is_available": true
}, { }, {
"type": "character", "type": "character",
"id": "sapphire", "id": "sapphire",
"_id": "6046bab736651a07ebc53826",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -49,15 +44,13 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "mirai", "id": "mirai",
"_id": "6046bab736651a07ebc5381d",
"is_available": true "is_available": true
}, { }, {
"type": "character", "type": "character",
"id": "lethe", "id": "lethe",
"_id": "6046bab736651a07ebc5381c",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -66,10 +59,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "yugamu", "id": "yugamu",
"_id": "6046bab736651a07ebc537dc",
"is_available": true "is_available": true
}], }],
"price": 400, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -78,10 +70,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "lanota", "id": "lanota",
"_id": "6046bab736651a07ebc537e8",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -90,10 +81,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "nijuusei", "id": "nijuusei",
"_id": "6046bab736651a07ebc537dd",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -102,10 +92,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "rei", "id": "rei",
"_id": "6046bab736651a07ebc537f6",
"is_available": true "is_available": true
}], }],
"price": 400, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -114,10 +103,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "tonesphere", "id": "tonesphere",
"_id": "6046bab736651a07ebc537ea",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -126,10 +114,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "groovecoaster", "id": "groovecoaster",
"_id": "6046bab736651a07ebc53812",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -138,10 +125,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "zettai", "id": "zettai",
"_id": "6046bab736651a07ebc537f8",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -150,7 +136,6 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "chunithm", "id": "chunithm",
"_id": "6046bab736651a07ebc53806",
"is_available": true "is_available": true
}], }],
"price": 300, "price": 300,
@@ -160,10 +145,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "prelude", "id": "prelude",
"_id": "6046bab736651a07ebc53813",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 400,
"orig_price": 400, "orig_price": 400,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -172,10 +156,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "omatsuri", "id": "omatsuri",
"_id": "6046bab736651a07ebc537f9",
"is_available": true "is_available": true
}], }],
"price": 250, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -184,10 +167,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "vs", "id": "vs",
"_id": "6046bab736651a07ebc53809",
"is_available": true "is_available": true
}], }],
"price": 400, "price": 500,
"orig_price": 500, "orig_price": 500,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -196,10 +178,9 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "extend", "id": "extend",
"_id": "6046bab736651a07ebc53816",
"is_available": true "is_available": true
}], }],
"price": 500, "price": 700,
"orig_price": 700, "orig_price": 700,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -208,7 +189,6 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "alice", "id": "alice",
"_id": "6046bab736651a07ebc537fe",
"is_available": true "is_available": true
}], }],
"orig_price": 500, "orig_price": 500,
@@ -218,7 +198,6 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "alice_append_1", "id": "alice_append_1",
"_id": "6046bab736651a07ebc537e6",
"is_available": true "is_available": true
}], }],
"orig_price": 300, "orig_price": 300,
@@ -228,7 +207,6 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "ongeki", "id": "ongeki",
"_id": "6046bab736651a07ebc5380c",
"is_available": true "is_available": true
}], }],
"orig_price": 400, "orig_price": 400,
@@ -238,7 +216,6 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "maimai", "id": "maimai",
"_id": "6046bab736651a07ebc53819",
"is_available": true "is_available": true
}], }],
"orig_price": 400, "orig_price": 400,
@@ -248,9 +225,26 @@
"items": [{ "items": [{
"type": "pack", "type": "pack",
"id": "chunithm_append_1", "id": "chunithm_append_1",
"_id": "6046bab736651a07ebc537f2",
"is_available": true "is_available": true
}], }],
"orig_price": 300, "orig_price": 300,
"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
}] }]

View File

@@ -12,10 +12,9 @@
"items": [{ "items": [{
"id": "dataerror", "id": "dataerror",
"type": "single", "type": "single",
"_id": "6046bab736651a07ebc537f3",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -24,10 +23,9 @@
"items": [{ "items": [{
"id": "yourvoiceso", "id": "yourvoiceso",
"type": "single", "type": "single",
"_id": "6046bab736651a07ebc53800",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -36,10 +34,9 @@
"items": [{ "items": [{
"id": "crosssoul", "id": "crosssoul",
"type": "single", "type": "single",
"_id": "6046bab736651a07ebc5380d",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -48,10 +45,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "impurebird", "id": "impurebird",
"_id": "6046bab736651a07ebc53801",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -60,7 +56,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "auxesia", "id": "auxesia",
"_id": "6046bab736651a07ebc5380e",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -72,10 +67,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "modelista", "id": "modelista",
"_id": "6046bab736651a07ebc537f4",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -84,10 +78,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "yozakurafubuki", "id": "yozakurafubuki",
"_id": "6046bab736651a07ebc53802",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -96,10 +89,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "surrender", "id": "surrender",
"_id": "6046bab736651a07ebc5380f",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -108,7 +100,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "metallicpunisher", "id": "metallicpunisher",
"_id": "6046bab736651a07ebc537f5",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -118,7 +109,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "carminescythe", "id": "carminescythe",
"_id": "6046bab736651a07ebc53803",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -128,10 +118,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "bethere", "id": "bethere",
"_id": "6046bab736651a07ebc53810",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -140,10 +129,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "callmyname", "id": "callmyname",
"_id": "6046bab736651a07ebc537e9",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -152,10 +140,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "fallensquare", "id": "fallensquare",
"_id": "6046bab736651a07ebc537de",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -164,10 +151,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "dropdead", "id": "dropdead",
"_id": "6046bab736651a07ebc53804",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -176,10 +162,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "alexandrite", "id": "alexandrite",
"_id": "6046bab736651a07ebc53811",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -188,10 +173,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "astraltale", "id": "astraltale",
"_id": "6046bab736651a07ebc537f7",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -200,7 +184,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "phantasia", "id": "phantasia",
"_id": "6046bab736651a07ebc537df",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -210,7 +193,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "empireofwinter", "id": "empireofwinter",
"_id": "6046bab736651a07ebc53805",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -220,10 +202,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "libertas", "id": "libertas",
"_id": "6046bab736651a07ebc537eb",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -232,10 +213,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "dottodot", "id": "dottodot",
"_id": "6046bab736651a07ebc537e0",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -244,10 +224,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "dreadnought", "id": "dreadnought",
"_id": "6046bab736651a07ebc537ec",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -256,10 +235,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "mirzam", "id": "mirzam",
"_id": "6046bab736651a07ebc537e1",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -268,10 +246,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "heavenlycaress", "id": "heavenlycaress",
"_id": "6046bab736651a07ebc53807",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -280,10 +257,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "filament", "id": "filament",
"_id": "6046bab736651a07ebc53814",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -292,7 +268,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "avantraze", "id": "avantraze",
"_id": "6046bab736651a07ebc537ed",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -302,10 +277,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "battlenoone", "id": "battlenoone",
"_id": "6046bab736651a07ebc537fa",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -314,7 +288,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "saikyostronger", "id": "saikyostronger",
"_id": "6046bab736651a07ebc537e2",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -324,10 +297,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "izana", "id": "izana",
"_id": "6046bab736651a07ebc53808",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -336,10 +308,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "einherjar", "id": "einherjar",
"_id": "6046bab736651a07ebc53815",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -348,10 +319,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "laqryma", "id": "laqryma",
"_id": "6046bab736651a07ebc537ee",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -360,10 +330,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "amygdata", "id": "amygdata",
"_id": "6046bab736651a07ebc537fb",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -372,10 +341,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "altale", "id": "altale",
"_id": "6046bab736651a07ebc537e3",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -384,10 +352,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "feelssoright", "id": "feelssoright",
"_id": "6046bab736651a07ebc537ef",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -396,7 +363,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "scarletcage", "id": "scarletcage",
"_id": "6046bab736651a07ebc537fc",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -406,7 +372,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "teriqma", "id": "teriqma",
"_id": "6046bab736651a07ebc537e4",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -416,10 +381,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "mahoroba", "id": "mahoroba",
"_id": "6046bab736651a07ebc5380a",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -428,10 +392,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "badtek", "id": "badtek",
"_id": "6046bab736651a07ebc537f0",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -440,7 +403,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "maliciousmischance", "id": "maliciousmischance",
"_id": "6046bab736651a07ebc53817",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -450,7 +412,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "buchigireberserker", "id": "buchigireberserker",
"_id": "6046bab736651a07ebc537e5",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -460,10 +421,9 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "galaxyfriends", "id": "galaxyfriends",
"_id": "6046bab736651a07ebc5380b",
"is_available": true "is_available": true
}], }],
"price": 50, "price": 100,
"orig_price": 100, "orig_price": 100,
"discount_from": 1615248000000, "discount_from": 1615248000000,
"discount_to": 1615852799000 "discount_to": 1615852799000
@@ -472,7 +432,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "xeraphinite", "id": "xeraphinite",
"_id": "6046bab736651a07ebc53818",
"is_available": true "is_available": true
}], }],
"orig_price": 100, "orig_price": 100,
@@ -482,7 +441,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "xanatos", "id": "xanatos",
"_id": "6046bab736651a07ebc537f1",
"is_available": true "is_available": true
}], }],
"price": 100, "price": 100,
@@ -492,7 +450,6 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "attraqtia", "id": "attraqtia",
"_id": "6046bab736651a07ebc537ff",
"is_available": true "is_available": true
}], }],
"orig_price": 100, "orig_price": 100,
@@ -502,7 +459,15 @@
"items": [{ "items": [{
"type": "single", "type": "single",
"id": "gimmedablood", "id": "gimmedablood",
"_id": "6046bab736651a07ebc537e7", "is_available": true
}],
"orig_price": 100,
"price": 100
}, {
"name": "bassline",
"items": [{
"type": "single",
"id": "bassline",
"is_available": true "is_available": true
}], }],
"orig_price": 100, "orig_price": 100,

View File

@@ -1 +0,0 @@

View File

@@ -10,15 +10,107 @@ import server.setme
import server.arcscore import server.arcscore
import web.login import web.login
import web.index import web.index
import api.api_main
import server.arcworld import server.arcworld
import server.arcdownload import server.arcdownload
import server.arcpurchase import server.arcpurchase
import server.init
import os import os
import sys
app = Flask(__name__) app = Flask(__name__)
wsgi_app = app.wsgi_app wsgi_app = app.wsgi_app
os.chdir(sys.path[0]) # 更改工作路径,以便于愉快使用相对路径
app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY)
app.config['SESSION_TYPE'] = 'filesystem'
app.register_blueprint(web.login.bp)
app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.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!')
def add_url_prefix(url, strange_flag=False):
# 给url加前缀返回字符串
if not url or not Config.GAME_API_PREFIX:
return Config.GAME_API_PREFIX + url
prefix = Config.GAME_API_PREFIX
if prefix[0] != '/':
prefix = '/' + prefix
if prefix[-1] == '/':
prefix = prefix[:-1]
if url[0] != '/':
r = '/' + url
else:
r = url
if strange_flag and prefix.count('/') >= 1: # 为了方便处理双斜杠
t = prefix[::-1]
t = t[t.find('/')+1:]
prefix = t[::-1]
return prefix + r
def error_return(error_code): # 错误返回 def error_return(error_code): # 错误返回
# -7 处理交易时发生了错误 # -7 处理交易时发生了错误
@@ -72,6 +164,7 @@ def error_return(error_code): # 错误返回
@app.route('/') @app.route('/')
def hello(): def hello():
return "Hello World!" return "Hello World!"
# 自定义路径
@app.route('/favicon.ico', methods=['GET']) # 图标 @app.route('/favicon.ico', methods=['GET']) # 图标
@@ -83,14 +176,24 @@ def favicon():
return app.send_static_file('favicon.ico') return app.send_static_file('favicon.ico')
@app.route('/latte/13/auth/login', methods=['POST']) # 登录接口 @app.route(add_url_prefix('/auth/login'), methods=['POST']) # 登录接口
def login(): 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 headers = request.headers
id_pwd = headers['Authorization'] id_pwd = headers['Authorization']
id_pwd = base64.b64decode(id_pwd[6:]).decode() id_pwd = base64.b64decode(id_pwd[6:]).decode()
name, password = id_pwd.split(':', 1) name, password = id_pwd.split(':', 1)
if 'DeviceId' in headers:
device_id = headers['DeviceId'] device_id = headers['DeviceId']
token, error_code = server.auth.arc_login(name, password, device_id) else:
device_id = 'low_version'
token, error_code = server.auth.arc_login(
name, password, device_id, request.remote_addr)
if not error_code: if not error_code:
r = {"success": True, "token_type": "Bearer"} r = {"success": True, "token_type": "Bearer"}
r['access_token'] = token r['access_token'] = token
@@ -99,23 +202,33 @@ def login():
return error_return(error_code) return error_return(error_code)
@app.route('/latte/13/user/', methods=['POST']) # 注册接口 @app.route(add_url_prefix('/user/'), methods=['POST']) # 注册接口
def register(): 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'] name = request.form['name']
password = request.form['password'] password = request.form['password']
email = request.form['email']
if 'device_id' in request.form:
device_id = request.form['device_id'] device_id = request.form['device_id']
else:
device_id = 'low_version'
user_id, token, error_code = server.auth.arc_register( user_id, token, error_code = server.auth.arc_register(
name, password, device_id) name, password, device_id, email, request.remote_addr)
if user_id is not None: if user_id is not None:
r = {"success": True, "value": { r = {"success": True, "value": {
'user_id': user_id, 'access_token': token}} 'user_id': user_id, 'access_token': token}}
return jsonify(r) return jsonify(r)
else: else:
return error_return(error_code) # 应该是101用户名被占用毕竟电子邮箱没记录 return error_return(error_code)
# 集成式请求,没想到什么好办法处理,就先这样写着 # 集成式请求,没想到什么好办法处理,就先这样写着
@app.route('/latte/13/compose/aggregate', methods=['GET']) @app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def aggregate(user_id): def aggregate(user_id):
calls = request.args.get('calls') calls = request.args.get('calls')
@@ -126,7 +239,7 @@ def aggregate(user_id):
return jsonify(r) return jsonify(r)
@app.route('/latte/13/user/me/character', methods=['POST']) # 角色切换 @app.route(add_url_prefix('/user/me/character'), methods=['POST']) # 角色切换
@server.auth.auth_required(request) @server.auth.auth_required(request)
def character_change(user_id): def character_change(user_id):
character_id = request.form['character'] character_id = request.form['character']
@@ -145,7 +258,8 @@ def character_change(user_id):
return error_return(108) return error_return(108)
@app.route('/latte/<path:path>/toggle_uncap', methods=['POST']) # 角色觉醒切换 # 角色觉醒切换
@app.route(add_url_prefix('/<path:path>/toggle_uncap', True), methods=['POST'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def character_uncap(user_id, path): def character_uncap(user_id, path):
while '//' in path: while '//' in path:
@@ -164,7 +278,7 @@ def character_uncap(user_id, path):
return error_return(108) return error_return(108)
@app.route('/latte/13/friend/me/add', methods=['POST']) # 加好友 @app.route(add_url_prefix('/friend/me/add'), methods=['POST']) # 加好友
@server.auth.auth_required(request) @server.auth.auth_required(request)
def add_friend(user_id): def add_friend(user_id):
friend_code = request.form['friend_code'] friend_code = request.form['friend_code']
@@ -190,7 +304,7 @@ def add_friend(user_id):
return error_return(401) return error_return(401)
@app.route('/latte/13/friend/me/delete', methods=['POST']) # 删好友 @app.route(add_url_prefix('/friend/me/delete'), methods=['POST']) # 删好友
@server.auth.auth_required(request) @server.auth.auth_required(request)
def delete_friend(user_id): def delete_friend(user_id):
friend_id = int(request.form['friend_id']) friend_id = int(request.form['friend_id'])
@@ -212,7 +326,8 @@ def delete_friend(user_id):
return error_return(401) return error_return(401)
@app.route('/latte/13/score/song/friend', methods=['GET']) # 好友排名默认最多50 # 好友排名默认最多50
@app.route(add_url_prefix('/score/song/friend'), methods=['GET'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def song_score_friend(user_id): def song_score_friend(user_id):
song_id = request.args.get('song_id') song_id = request.args.get('song_id')
@@ -227,7 +342,7 @@ def song_score_friend(user_id):
return error_return(108) return error_return(108)
@app.route('/latte/13/score/song/me', methods=['GET']) # 我的排名默认最多20 @app.route(add_url_prefix('/score/song/me'), methods=['GET']) # 我的排名默认最多20
@server.auth.auth_required(request) @server.auth.auth_required(request)
def song_score_me(user_id): def song_score_me(user_id):
song_id = request.args.get('song_id') song_id = request.args.get('song_id')
@@ -242,7 +357,7 @@ def song_score_me(user_id):
return error_return(108) return error_return(108)
@app.route('/latte/13/score/song', methods=['GET']) # TOP20 @app.route(add_url_prefix('/score/song'), methods=['GET']) # TOP20
@server.auth.auth_required(request) @server.auth.auth_required(request)
def song_score_top(user_id): def song_score_top(user_id):
song_id = request.args.get('song_id') song_id = request.args.get('song_id')
@@ -257,7 +372,7 @@ def song_score_top(user_id):
return error_return(108) return error_return(108)
@app.route('/latte/13/score/song', methods=['POST']) # 成绩上传 @app.route(add_url_prefix('/score/song'), methods=['POST']) # 成绩上传
@server.auth.auth_required(request) @server.auth.auth_required(request)
def song_score_post(user_id): def song_score_post(user_id):
song_token = request.form['song_token'] song_token = request.form['song_token']
@@ -296,7 +411,8 @@ def song_score_post(user_id):
return error_return(108) return error_return(108)
@app.route('/latte/13/score/token', methods=['GET']) # 成绩上传所需的token显然我不想验证 # 成绩上传所需的token显然我不想验证
@app.route(add_url_prefix('/score/token'), methods=['GET'])
def score_token(): def score_token():
return jsonify({ return jsonify({
"success": True, "success": True,
@@ -307,7 +423,7 @@ def score_token():
# 世界模式成绩上传所需的token无验证 # 世界模式成绩上传所需的token无验证
@app.route('/latte/13/score/token/world', methods=['GET']) @app.route(add_url_prefix('/score/token/world'), methods=['GET'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def score_token_world(user_id): def score_token_world(user_id):
args = request.args args = request.args
@@ -322,7 +438,7 @@ def score_token_world(user_id):
}) })
@app.route('/latte/13/user/me/save', methods=['GET']) # 从云端同步 @app.route(add_url_prefix('/user/me/save'), methods=['GET']) # 从云端同步
@server.auth.auth_required(request) @server.auth.auth_required(request)
def cloud_get(user_id): def cloud_get(user_id):
r = server.arcscore.arc_all_get(user_id) r = server.arcscore.arc_all_get(user_id)
@@ -335,7 +451,7 @@ def cloud_get(user_id):
return error_return(108) return error_return(108)
@app.route('/latte/13/user/me/save', methods=['POST']) # 向云端同步 @app.route(add_url_prefix('/user/me/save'), methods=['POST']) # 向云端同步
@server.auth.auth_required(request) @server.auth.auth_required(request)
def cloud_post(user_id): def cloud_post(user_id):
scores_data = request.form['scores_data'] scores_data = request.form['scores_data']
@@ -356,7 +472,7 @@ def cloud_post(user_id):
}) })
@app.route('/latte/13/purchase/me/redeem', methods=['POST']) # 兑换码 @app.route(add_url_prefix('/purchase/me/redeem'), methods=['POST']) # 兑换码
@server.auth.auth_required(request) @server.auth.auth_required(request)
def redeem(user_id): def redeem(user_id):
code = request.form['code'] code = request.form['code']
@@ -378,7 +494,7 @@ def redeem(user_id):
# 礼物确认 # 礼物确认
@app.route('/latte/13/present/me/claim/<present_id>', methods=['POST']) @app.route(add_url_prefix('/present/me/claim/<present_id>'), methods=['POST'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def claim_present(user_id, present_id): def claim_present(user_id, present_id):
flag = server.arcpurchase.claim_user_present(user_id, present_id) flag = server.arcpurchase.claim_user_present(user_id, present_id)
@@ -390,7 +506,8 @@ def claim_present(user_id, present_id):
return error_return(108) return error_return(108)
@app.route('/latte/13/purchase/me/item', methods=['POST']) # 购买world模式boost # 购买world模式boost
@app.route(add_url_prefix('/purchase/me/item'), methods=['POST'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def prog_boost(user_id): def prog_boost(user_id):
re = {"success": False} re = {"success": False}
@@ -407,20 +524,19 @@ def prog_boost(user_id):
return jsonify(re) return jsonify(re)
@app.route('/latte/13/purchase/me/pack', methods=['POST']) # 曲包和单曲购买 @app.route(add_url_prefix('/purchase/me/pack'), methods=['POST']) # 曲包和单曲购买
@server.auth.auth_required(request) @server.auth.auth_required(request)
def pack(user_id): def pack(user_id):
if 'pack_id' in request.form: if 'pack_id' in request.form:
return jsonify(server.arcpurchase.buy_pack(user_id, request.form['pack_id'])) return jsonify(server.arcpurchase.buy_thing(user_id, request.form['pack_id'], 'pack'))
if 'single_id' in request.form: if 'single_id' in request.form:
return jsonify(server.arcpurchase.buy_single(user_id, request.form['single_id'])) return jsonify(server.arcpurchase.buy_thing(user_id, request.form['single_id'], 'single'))
return jsonify({ return jsonify({"success": True})
"success": True
})
@app.route('/latte/13/purchase/bundle/single', methods=['GET']) # 单曲购买信息获取 # 单曲购买信息获取
@app.route(add_url_prefix('/purchase/bundle/single'), methods=['GET'])
def single(): def single():
return jsonify({ return jsonify({
"success": True, "success": True,
@@ -428,7 +544,7 @@ def single():
}) })
@app.route('/latte/13/world/map/me', methods=['GET']) # 获得世界模式信息,所有地图 @app.route(add_url_prefix('/world/map/me'), methods=['GET']) # 获得世界模式信息,所有地图
@server.auth.auth_required(request) @server.auth.auth_required(request)
def world_all(user_id): def world_all(user_id):
return jsonify({ return jsonify({
@@ -441,7 +557,7 @@ def world_all(user_id):
}) })
@app.route('/latte/13/world/map/me/', methods=['POST']) # 进入地图 @app.route(add_url_prefix('/world/map/me/'), methods=['POST']) # 进入地图
@server.auth.auth_required(request) @server.auth.auth_required(request)
def world_in(user_id): def world_in(user_id):
map_id = request.form['map_id'] map_id = request.form['map_id']
@@ -451,7 +567,8 @@ def world_in(user_id):
}) })
@app.route('/latte/13/world/map/me/<map_id>', methods=['GET']) # 获得单个地图完整信息 # 获得单个地图完整信息
@app.route(add_url_prefix('/world/map/me/<map_id>'), methods=['GET'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def world_one(user_id, map_id): def world_one(user_id, map_id):
server.arcworld.change_user_current_map(user_id, map_id) server.arcworld.change_user_current_map(user_id, map_id)
@@ -465,7 +582,7 @@ def world_one(user_id, map_id):
}) })
@app.route('/latte/13/serve/download/me/song', methods=['GET']) # 歌曲下载 @app.route(add_url_prefix('/serve/download/me/song'), methods=['GET']) # 歌曲下载
@server.auth.auth_required(request) @server.auth.auth_required(request)
def download_song(user_id): def download_song(user_id):
song_ids = request.args.getlist('sid') song_ids = request.args.getlist('sid')
@@ -501,7 +618,8 @@ def download(file_path):
return error_return(108) return error_return(108)
@app.route('/latte/<path:path>', methods=['POST']) # 三个设置,写在最后降低优先级 # 三个设置,写在最后降低优先级
@app.route(add_url_prefix('/<path:path>', True), methods=['POST'])
@server.auth.auth_required(request) @server.auth.auth_required(request)
def sys_set(user_id, path): def sys_set(user_id, path):
set_arg = path[10:] set_arg = path[10:]
@@ -513,63 +631,10 @@ def sys_set(user_id, path):
def main(): def main():
app.config.from_mapping(SECRET_KEY=Config.SECRET_KEY) if Config.SSL_CERT and Config.SSL_KEY:
app.config['SESSION_TYPE'] = 'filesystem' app.run(Config.HOST, Config.PORT, ssl_context=(
app.register_blueprint(web.login.bp) Config.SSL_CERT, Config.SSL_KEY))
app.register_blueprint(web.index.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)
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: else:
app.logger.info('Complete!')
app.run(Config.HOST, Config.PORT) app.run(Config.HOST, Config.PORT)

View File

@@ -25,7 +25,24 @@ def get_file_md5(file_path):
return myhash.hexdigest() return myhash.hexdigest()
def get_one_song(c, user_id, song_id, file_dir='./database/songs'): def get_url(file_path, **kwargs):
# 获取下载地址
t = ''
if 't' in kwargs:
t = kwargs['t']
if Config.DOWNLOAD_LINK_PREFIX:
prefix = Config.DOWNLOAD_LINK_PREFIX
if prefix[-1] != '/':
prefix += '/'
return prefix + file_path + '?t=' + t
else:
return url_for('download', file_path=file_path, t=t, _external=True)
def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True):
# 获取一首歌的下载链接,返回字典 # 获取一首歌的下载链接,返回字典
dir_list = os.listdir(os.path.join(file_dir, song_id)) dir_list = os.listdir(os.path.join(file_dir, song_id))
re = {} re = {}
@@ -37,7 +54,6 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs'):
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']:
token = hashlib.md5( token = hashlib.md5(
(str(user_id) + song_id + i + str(now)).encode(encoding='UTF-8')).hexdigest() (str(user_id) + song_id + i + str(now)).encode(encoding='UTF-8')).hexdigest()
token = token[:8]
if i == 'base.ogg': if i == 'base.ogg':
c.execute( c.execute(
@@ -48,8 +64,12 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs'):
else: else:
checksum = get_file_md5(os.path.join( checksum = get_file_md5(os.path.join(
file_dir, song_id, 'base.ogg')) file_dir, song_id, 'base.ogg'))
re['audio'] = {"checksum": checksum,
"url": url_for('download', file_path=song_id+'/base.ogg', t=token, _external=True)} if url_flag:
re['audio'] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/base.ogg', t=token)}
else:
re['audio'] = {"checksum": checksum}
else: else:
if 'chart' not in re: if 'chart' not in re:
re['chart'] = {} re['chart'] = {}
@@ -60,23 +80,28 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs'):
checksum = x[0] checksum = x[0]
else: else:
checksum = get_file_md5(os.path.join(file_dir, song_id, i)) checksum = get_file_md5(os.path.join(file_dir, song_id, i))
re['chart'][i[0]] = {"checksum": checksum,
"url": url_for('download', file_path=song_id+'/'+i, t=token, _external=True)}
if url_flag:
re['chart'][i[0]] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/'+i, t=token)}
else:
re['chart'][i[0]] = {"checksum": checksum}
if url_flag:
c.execute('''insert into download_token values(:a,:b,:c,:d,:e)''', { c.execute('''insert into download_token values(:a,:b,:c,:d,:e)''', {
'a': user_id, 'b': song_id, 'c': i, 'd': token, 'e': now}) 'a': user_id, 'b': song_id, 'c': i, 'd': token, 'e': now})
return {song_id: re} return {song_id: re}
def get_all_songs(user_id, file_dir='./database/songs'): def get_all_songs(user_id, file_dir='./database/songs', url_flag=True):
# 获取所有歌的下载链接,返回字典 # 获取所有歌的下载链接,返回字典
dir_list = os.listdir(file_dir) dir_list = os.listdir(file_dir)
re = {} re = {}
with Connect() as c: with Connect() as c:
for i in dir_list: for i in dir_list:
if os.path.isdir(os.path.join(file_dir, i)): if os.path.isdir(os.path.join(file_dir, i)):
re.update(get_one_song(c, user_id, i, file_dir)) re.update(get_one_song(c, user_id, i, file_dir, url_flag))
return re return re

View File

@@ -1,4 +1,5 @@
from server.sql import Connect from server.sql import Connect
import server.info
import time import time
import json import json
@@ -67,49 +68,55 @@ def buy_item(c, user_id, price):
return True, ticket - price return True, ticket - price
def buy_pack(user_id, pack_id): def buy_thing(user_id, item_id, item_type):
# 曲包购买,返回字典 # 购买物品接口,返回字典
re = {"success": False} success_flag = False
ticket = 0
packs = []
singles = []
characters = []
with Connect() as c: with Connect() as c:
c.execute('''select price from item where item_id = :a''', c.execute('''select is_available, price, orig_price, discount_from, discount_to from item where item_id=:a and type=:b''',
{'a': pack_id}) {'a': item_id, 'b': item_type})
price = c.fetchone() x = c.fetchone()
if price:
price = price[0]
else:
price = 0 price = 0
flag = False
if x:
is_available = x[0]
price = x[1]
orig_price = x[2]
discount_from = x[3]
discount_to = x[4]
if not is_available:
return False
now = int(time.time() * 1000)
if not(discount_from <= now <= discount_to):
price = orig_price
flag, ticket = buy_item(c, user_id, price) flag, ticket = buy_item(c, user_id, price)
if flag: if flag:
c.execute('''insert into user_item values(:a,:b,'pack')''', c.execute('''insert into user_item values(:a,:b,:c)''',
{'a': user_id, 'b': pack_id}) {'a': user_id, 'b': item_id, 'c': item_type})
re = {"success": True} success_flag = True
return re packs = server.info.get_user_packs(c, user_id)
singles = server.info.get_user_singles(c, user_id)
characters = server.info.get_user_characters(c, user_id)
return {
def buy_single(user_id, single_id): "success": success_flag,
# 单曲购买,返回字典 "value": {'user_id': user_id,
re = {"success": False} 'ticket': ticket,
with Connect() as c: 'packs': packs,
c.execute('''select price from item where item_id = :a''', 'singles': singles,
{'a': single_id}) 'characters': characters
price = c.fetchone() }
if price: }
price = price[0]
else:
price = 0
flag, ticket = buy_item(c, user_id, price)
if flag:
c.execute('''insert into user_item values(:a,:b,'single')''',
{'a': user_id, 'b': single_id})
re = {"success": True}
return re
def get_prog_boost(user_id): def get_prog_boost(user_id):

View File

@@ -3,6 +3,7 @@ import time
import json import json
import server.arcworld import server.arcworld
import hashlib import hashlib
from setting import Config
def b2int(x): def b2int(x):
@@ -129,9 +130,13 @@ def arc_score_me(user_id, song_id, difficulty, limit=20):
'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty}) 'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty})
x = c.fetchone() x = c.fetchone()
myrank = int(x[0]) + 1 myrank = int(x[0]) + 1
c.execute('''select count(*) from best_score where song_id=:a and difficulty=:b''',
{'a': song_id, 'b': difficulty})
amount = int(c.fetchone()[0])
if myrank <= 4: # 排名在前4 if myrank <= 4: # 排名在前4
return arc_score_top(song_id, difficulty, limit) return arc_score_top(song_id, difficulty, limit)
elif myrank >= 5 and myrank <= 9999 - limit + 4: # 万名内前面有4个人 elif myrank >= 5 and myrank <= 9999 - limit + 4 and amount >= 10000: # 万名内前面有4个人
c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', { c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': myrank - 5}) 'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': myrank - 5})
x = c.fetchall() x = c.fetchall()
@@ -157,6 +162,17 @@ def arc_score_me(user_id, song_id, difficulty, limit=20):
y = get_score(c, user_id, song_id, difficulty) y = get_score(c, user_id, song_id, difficulty)
y['rank'] = -1 y['rank'] = -1
r.append(y) r.append(y)
elif amount - myrank < limit - 5: # 后方人数不足
c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': amount - limit})
x = c.fetchall()
if x != []:
rank = amount - limit
for i in x:
rank += 1
y = get_score(c, i[0], song_id, difficulty)
y['rank'] = rank
r.append(y)
else: else:
c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', { c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', {
'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': 9998-limit}) 'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': 9998-limit})
@@ -172,8 +188,22 @@ def arc_score_me(user_id, song_id, difficulty, limit=20):
return r return r
def calculate_rating(defnum, score):
# 计算rating
if score >= 10000000:
ptt = defnum + 2
elif score < 9800000:
ptt = defnum + (score-9500000) / 300000
if ptt < 0 and defnum != -10:
ptt = 0
else:
ptt = defnum + 1 + (score-9800000) / 200000
return ptt
def get_one_ptt(song_id, difficulty, score: int) -> float: def get_one_ptt(song_id, difficulty, score: int) -> float:
# 单曲ptt计算ptt为负说明没面定数数据 # 单曲ptt计算ptt为负说明没面定数数据
ptt = -10 ptt = -10
with Connect('./database/arcsong.db') as c: with Connect('./database/arcsong.db') as c:
if difficulty == 0: if difficulty == 0:
@@ -196,14 +226,7 @@ def get_one_ptt(song_id, difficulty, score: int) -> float:
if defnum <= 0: if defnum <= 0:
defnum = -10 # 缺少难度的当做定数-10 defnum = -10 # 缺少难度的当做定数-10
if score >= 10000000: ptt = calculate_rating(defnum, score)
ptt = defnum + 2
elif score < 9800000:
ptt = defnum + (score-9500000) / 300000
if ptt < 0 and defnum != -10:
ptt = 0
else:
ptt = defnum + 1 + (score-9800000) / 200000
return ptt return ptt
@@ -283,7 +306,7 @@ def get_user_ptt(c, user_id) -> int:
def update_recent30(c, user_id, song_id, rating, is_protected): def update_recent30(c, user_id, song_id, rating, is_protected):
# 刷新r30这里的判断方法存疑 # 刷新r30这里的判断方法存疑这里的song_id结尾包含难度数字
def insert_r30table(c, user_id, a, b): def insert_r30table(c, user_id, a, b):
# 更新r30表 # 更新r30表
c.execute('''delete from recent30 where user_id = :a''', c.execute('''delete from recent30 where user_id = :a''',
@@ -553,6 +576,14 @@ def arc_score_check(user_id, song_id, difficulty, score, shiny_perfect_count, pe
if abs(ascore - score) >= 5: if abs(ascore - score) >= 5:
return False return False
with Connect() as c: # 歌曲谱面MD5检查服务器没有谱面就不管了
c.execute('''select md5 from songfile where song_id=:a and file_type=:b''', {
'a': song_id, 'b': int(difficulty)})
x = c.fetchone()
if x:
if x[0] != song_hash:
return False
x = song_token + song_hash + song_id + str(difficulty) + str(score) + str(shiny_perfect_count) + str( x = song_token + song_hash + song_id + str(difficulty) + str(score) + str(shiny_perfect_count) + str(
perfect_count) + str(near_count) + str(miss_count) + str(health) + str(modifier) + str(clear_type) perfect_count) + str(near_count) + str(miss_count) + str(health) + str(modifier) + str(clear_type)
y = str(user_id) + song_hash y = str(user_id) + song_hash
@@ -563,6 +594,49 @@ def arc_score_check(user_id, song_id, difficulty, score, shiny_perfect_count, pe
return True return True
def refresh_all_score_rating():
# 刷新所有best成绩的rating
error = 'Unknown error.'
song_data = []
with Connect('./database/arcsong.db') as c:
with Connect() as c2:
c.execute(
'''select sid, rating_pst, rating_prs, rating_ftr, rating_byn from songs''')
x = c.fetchall()
if x:
song_list = [i[0] for i in song_data]
with Connect() as c:
c.execute('''update best_score set rating=0 where song_id not in ({0})'''.format(
','.join(['?']*len(song_list))), tuple(song_list))
for i in x:
for j in range(0, 4):
defnum = -10 # 没在库里的全部当做定数-10
if i is not None:
defnum = float(i[j+1]) / 10
if defnum <= 0:
defnum = -10 # 缺少难度的当做定数-10
c.execute('''select user_id, score from best_score where song_id=:a and difficulty=:b''', {
'a': i[0], 'b': j})
y = c.fetchall()
if y:
for k in y:
ptt = calculate_rating(defnum, k[1])
if ptt < 0:
ptt = 0
c.execute('''update best_score set rating=:a where user_id=:b and song_id=:c and difficulty=:d''', {
'a': ptt, 'b': k[0], 'c': i[0], 'd': j})
error = None
else:
error = 'No song data.'
return error
def arc_all_post(user_id, scores_data, clearlamps_data, clearedsongs_data, unlocklist_data, installid_data, devicemodelname_data, story_data): def arc_all_post(user_id, scores_data, clearlamps_data, clearedsongs_data, unlocklist_data, installid_data, devicemodelname_data, story_data):
# 向云端同步,无返回 # 向云端同步,无返回
with Connect() as c: with Connect() as c:
@@ -579,10 +653,10 @@ def arc_all_get(user_id):
scores_data = [] scores_data = []
clearlamps_data = [] clearlamps_data = []
clearedsongs_data = [] clearedsongs_data = []
# unlocklist_data = [] unlocklist_data = []
installid_data = '' installid_data = ''
devicemodelname_data = '' devicemodelname_data = ''
# story_data = [] story_data = []
createdAt = 0 createdAt = 0
with Connect() as c: with Connect() as c:
@@ -594,17 +668,16 @@ def arc_all_get(user_id):
scores_data = json.loads(x[1])[""] scores_data = json.loads(x[1])[""]
clearlamps_data = json.loads(x[2])[""] clearlamps_data = json.loads(x[2])[""]
clearedsongs_data = json.loads(x[3])[""] clearedsongs_data = json.loads(x[3])[""]
# unlocklist_data = json.loads(x[4])[""] unlocklist_data = json.loads(x[4])[""]
installid_data = json.loads(x[5])["val"] installid_data = json.loads(x[5])["val"]
devicemodelname_data = json.loads(x[6])["val"] devicemodelname_data = json.loads(x[6])["val"]
# story_data = json.loads(x[7])[""] story_data = json.loads(x[7])[""]
if x[8]: if x[8]:
createdAt = int(x[8]) createdAt = int(x[8])
return { if Config.SAVE_FULL_UNLOCK:
"user_id": user_id, installid_data = "0fcec8ed-7b62-48e2-9d61-55041a22b123"
"story": { story_data = [{
"": [{
"ma": 1, "ma": 1,
"mi": 1, "mi": 1,
"c": True, "c": True,
@@ -859,6 +932,36 @@ def arc_all_get(user_id):
"mi": 6, "mi": 6,
"c": True, "c": True,
"r": True "r": True
}, {
"ma": 9,
"mi": 1,
"c": True,
"r": True
}, {
"ma": 9,
"mi": 2,
"c": True,
"r": True
}, {
"ma": 9,
"mi": 3,
"c": True,
"r": True
}, {
"ma": 9,
"mi": 4,
"c": True,
"r": True
}, {
"ma": 9,
"mi": 5,
"c": True,
"r": True
}, {
"ma": 9,
"mi": 6,
"c": True,
"r": True
}, { }, {
"ma": 6, "ma": 6,
"mi": 1, "mi": 1,
@@ -920,19 +1023,7 @@ def arc_all_get(user_id):
"c": True, "c": True,
"r": True "r": True
}] }]
}, unlocklist_data = [{
"devicemodelname": {
"val": devicemodelname_data
},
"installid": {
# installid_data 这里如果不固定可能会导致arcaea以为数据一样而不更新
"val": "0fcec8ed-7b62-48e2-9d61-55041a22b123"
},
"unlocklist": {
"": [{
"unlock_key": "worldvanquisher|2|0",
"complete": 1
}, {
"unlock_key": "worldvanquisher|1|0", "unlock_key": "worldvanquisher|1|0",
"complete": 1 "complete": 1
}, { }, {
@@ -941,42 +1032,48 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "viyellastears|2|0", "unlock_key": "viyellastears|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "viyellastears|1|0",
"complete": 1
}, { }, {
"unlock_key": "viciousheroism|2|0", "unlock_key": "viciousheroism|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "loschen|2|0",
"complete": 1
}, { }, {
"unlock_key": "vector|2|0", "unlock_key": "vector|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "corpssansorganes|2|0",
"complete": 1
}, { }, {
"unlock_key": "valhallazero|2|0", "unlock_key": "valhallazero|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "tiferet|1|0", "unlock_key": "tiferet|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "ouroboros|2|0",
"complete": 1
}, { }, {
"unlock_key": "tiemedowngently|1|0", "unlock_key": "tiemedowngently|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "aegleseeker|2|3|crystalgravity|2",
"complete": 10
}, { }, {
"unlock_key": "tempestissimo|0|101", "unlock_key": "tempestissimo|0|101",
"complete": 100 "complete": 100
}, { }, {
"unlock_key": "syro|2|0", "unlock_key": "syro|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "ringedgenesis|2|0",
"complete": 1
}, { }, {
"unlock_key": "suomi|1|0", "unlock_key": "suomi|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "solitarydream|2|0",
"complete": 1
}, { }, {
"unlock_key": "snowwhite|2|0", "unlock_key": "snowwhite|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "sheriruth|2|0",
"complete": 1
}, { }, {
"unlock_key": "senkyou|2|0", "unlock_key": "senkyou|2|0",
"complete": 1 "complete": 1
@@ -986,9 +1083,15 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "scarletlance|2|0", "unlock_key": "scarletlance|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "toaliceliddell|1|0",
"complete": 1
}, { }, {
"unlock_key": "scarletlance|1|0", "unlock_key": "scarletlance|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "aterlbus|1|0",
"complete": 1
}, { }, {
"unlock_key": "rugie|2|0", "unlock_key": "rugie|2|0",
"complete": 1 "complete": 1
@@ -1004,24 +1107,33 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "reinvent|1|0", "unlock_key": "reinvent|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "essenceoftwilight|1|0",
"complete": 1
}, { }, {
"unlock_key": "redandblue|2|0", "unlock_key": "redandblue|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "espebranch|1|0",
"complete": 1
}, { }, {
"unlock_key": "redandblue|1|0", "unlock_key": "redandblue|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "lostcivilization|2|0",
"complete": 1
}, {
"unlock_key": "supernova|1|0",
"complete": 1
}, { }, {
"unlock_key": "rabbitintheblackroom|2|0", "unlock_key": "rabbitintheblackroom|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "pragmatism|1|0",
"complete": 1
}, { }, {
"unlock_key": "rabbitintheblackroom|1|0", "unlock_key": "rabbitintheblackroom|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "worldexecuteme|1|0",
"complete": 1
}, {
"unlock_key": "ringedgenesis|2|0",
"complete": 1
}, { }, {
"unlock_key": "quon|1|0", "unlock_key": "quon|1|0",
"complete": 1 "complete": 1
@@ -1034,8 +1146,17 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "supernova|2|0", "unlock_key": "supernova|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "halcyon|2|0",
"complete": 1
}, { }, {
"unlock_key": "saikyostronger|2|3|einherjar|2", "unlock_key": "saikyostronger|2|3|einherjar|2",
"complete": 3
}, {
"unlock_key": "cyanine|2|0",
"complete": 1
}, {
"unlock_key": "saikyostronger|2|3|laqryma|2",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "purgatorium|1|0", "unlock_key": "purgatorium|1|0",
@@ -1044,17 +1165,23 @@ def arc_all_get(user_id):
"unlock_key": "pragmatism|2|0", "unlock_key": "pragmatism|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "ouroboros|2|0", "unlock_key": "ringedgenesis|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "ouroboros|1|0", "unlock_key": "ouroboros|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "partyvinyl|2|0",
"complete": 1
}, { }, {
"unlock_key": "oracle|1|0", "unlock_key": "oracle|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "onelastdrive|2|0", "unlock_key": "onelastdrive|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "nirvluce|1|0",
"complete": 1
}, { }, {
"unlock_key": "onelastdrive|1|0", "unlock_key": "onelastdrive|1|0",
"complete": 1 "complete": 1
@@ -1062,10 +1189,13 @@ def arc_all_get(user_id):
"unlock_key": "omegafour|1|0", "unlock_key": "omegafour|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "oblivia|2|0", "unlock_key": "harutopia|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "pragmatism|1|0", "unlock_key": "anokumene|1|0",
"complete": 1
}, {
"unlock_key": "oblivia|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "nhelv|2|0", "unlock_key": "nhelv|2|0",
@@ -1076,9 +1206,6 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "melodyoflove|2|0", "unlock_key": "melodyoflove|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "saikyostronger|2|3|laqryma|2",
"complete": 1
}, { }, {
"unlock_key": "omegafour|2|0", "unlock_key": "omegafour|2|0",
"complete": 1 "complete": 1
@@ -1088,6 +1215,12 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "lucifer|2|0", "unlock_key": "lucifer|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "gloryroad|1|0",
"complete": 1
}, {
"unlock_key": "babaroque|2|0",
"complete": 1
}, { }, {
"unlock_key": "lucifer|1|0", "unlock_key": "lucifer|1|0",
"complete": 1 "complete": 1
@@ -1097,21 +1230,36 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "tiemedowngently|2|0", "unlock_key": "tiemedowngently|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "solitarydream|1|0",
"complete": 1
}, { }, {
"unlock_key": "lostdesire|1|0", "unlock_key": "lostdesire|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "viciousheroism|1|0", "unlock_key": "lostcivilization|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "flyburg|1|0", "unlock_key": "loschen|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "lostcivilization|2|0", "unlock_key": "shadesoflight|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "kanagawa|2|0",
"complete": 1
}, {
"unlock_key": "grievouslady|1|101",
"complete": 100
}, { }, {
"unlock_key": "infinityheaven|1|0", "unlock_key": "infinityheaven|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "worldvanquisher|2|0",
"complete": 1
}, {
"unlock_key": "bookmaker|2|0",
"complete": 1
}, { }, {
"unlock_key": "ignotus|2|0", "unlock_key": "ignotus|2|0",
"complete": 1 "complete": 1
@@ -1121,39 +1269,39 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "partyvinyl|1|0", "unlock_key": "partyvinyl|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "viciousheroism|1|0",
"complete": 1
}, {
"unlock_key": "gloryroad|0|0",
"complete": 1
}, { }, {
"unlock_key": "axiumcrisis|1|0", "unlock_key": "axiumcrisis|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "kanagawa|1|0",
"complete": 1
}, { }, {
"unlock_key": "ifi|2|0", "unlock_key": "ifi|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "jump|2|0", "unlock_key": "jump|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "nhelv|1|0",
"complete": 1
}, { }, {
"unlock_key": "harutopia|2|0", "unlock_key": "harutopia|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "revixy|1|0",
"complete": 1
}, {
"unlock_key": "aterlbus|1|0",
"complete": 1
}, {
"unlock_key": "linearaccelerator|2|0",
"complete": 1
}, { }, {
"unlock_key": "guardina|2|0", "unlock_key": "guardina|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "corpssansorganes|2|0",
"complete": 1
}, {
"unlock_key": "linearaccelerator|1|0",
"complete": 1
}, { }, {
"unlock_key": "guardina|1|0", "unlock_key": "guardina|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "lethaeus|1|0",
"complete": 1
}, { }, {
"unlock_key": "saikyostronger|2|0", "unlock_key": "saikyostronger|2|0",
"complete": 1 "complete": 1
@@ -1166,21 +1314,18 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "oshamascramble|2|0", "unlock_key": "oshamascramble|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "aiueoon|2|0",
"complete": 1
}, { }, {
"unlock_key": "blaster|2|0", "unlock_key": "blaster|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "grievouslady|2|101", "unlock_key": "grievouslady|2|101",
"complete": 100 "complete": 100
}, {
"unlock_key": "partyvinyl|2|0",
"complete": 1
}, { }, {
"unlock_key": "darakunosono|1|0", "unlock_key": "darakunosono|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "grievouslady|1|101",
"complete": 100
}, { }, {
"unlock_key": "valhallazero|1|0", "unlock_key": "valhallazero|1|0",
"complete": 1 "complete": 1
@@ -1193,9 +1338,15 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "gothiveofra|1|0", "unlock_key": "gothiveofra|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "cyanine|1|0",
"complete": 1
}, { }, {
"unlock_key": "tempestissimo|3|101", "unlock_key": "tempestissimo|3|101",
"complete": 100 "complete": 100
}, {
"unlock_key": "overwhelm|2|0",
"complete": 1
}, { }, {
"unlock_key": "chronostasis|2|0", "unlock_key": "chronostasis|2|0",
"complete": 1 "complete": 1
@@ -1203,19 +1354,13 @@ def arc_all_get(user_id):
"unlock_key": "gloryroad|2|0", "unlock_key": "gloryroad|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "supernova|1|0", "unlock_key": "etherstrike|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "singularity|2|0", "unlock_key": "singularity|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "gloryroad|0|0", "unlock_key": "buchigireberserker|2|3|gothiveofra|2",
"complete": 1
}, {
"unlock_key": "shadesoflight|1|0",
"complete": 1
}, {
"unlock_key": "kanagawa|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "genesis|1|0", "unlock_key": "genesis|1|0",
@@ -1226,9 +1371,15 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "freefall|2|0", "unlock_key": "freefall|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "revixy|1|0",
"complete": 1
}, { }, {
"unlock_key": "fractureray|2|101", "unlock_key": "fractureray|2|101",
"complete": 100 "complete": 100
}, {
"unlock_key": "goodtek|1|0",
"complete": 1
}, { }, {
"unlock_key": "monochromeprincess|2|0", "unlock_key": "monochromeprincess|2|0",
"complete": 1 "complete": 1
@@ -1244,29 +1395,29 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "clotho|2|0", "unlock_key": "clotho|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "quon|2|0",
"complete": 1
}, { }, {
"unlock_key": "gou|2|0", "unlock_key": "gou|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "felis|2|0", "unlock_key": "felis|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "cyaegha|2|0",
"complete": 1
}, { }, {
"unlock_key": "qualia|1|0", "unlock_key": "qualia|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "buchigireberserker|2|0",
"complete": 1
}, { }, {
"unlock_key": "etherstrike|2|0", "unlock_key": "etherstrike|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "etherstrike|1|0", "unlock_key": "flyburg|1|0",
"complete": 1
}, {
"unlock_key": "syro|1|0",
"complete": 1
}, {
"unlock_key": "anokumene|2|0",
"complete": 1
}, {
"unlock_key": "essenceoftwilight|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "shadesoflight|2|0", "unlock_key": "shadesoflight|2|0",
@@ -1275,26 +1426,23 @@ def arc_all_get(user_id):
"unlock_key": "espebranch|2|0", "unlock_key": "espebranch|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "tempestissimo|1|101", "unlock_key": "aterlbus|2|0",
"complete": 100
}, {
"unlock_key": "nhelv|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "conflict|1|0", "unlock_key": "dreaminattraction|2|0",
"complete": 1
}, {
"unlock_key": "espebranch|1|0",
"complete": 1
}, {
"unlock_key": "lostcivilization|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "goodtek|2|0", "unlock_key": "goodtek|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "infinityheaven|2|0",
"complete": 1
}, { }, {
"unlock_key": "dandelion|2|0", "unlock_key": "dandelion|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "blrink|2|0",
"complete": 1
}, { }, {
"unlock_key": "suomi|2|0", "unlock_key": "suomi|2|0",
"complete": 1 "complete": 1
@@ -1307,104 +1455,104 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "cyberneciacatharsis|1|0", "unlock_key": "cyberneciacatharsis|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "quon|2|0",
"complete": 1
}, {
"unlock_key": "chronostasis|1|0",
"complete": 1
}, {
"unlock_key": "bookmaker|2|0",
"complete": 1
}, {
"unlock_key": "heavensdoor|1|0",
"complete": 1
}, {
"unlock_key": "tempestissimo|2|101",
"complete": 100
}, {
"unlock_key": "cyaegha|2|0",
"complete": 1
}, {
"unlock_key": "axiumcrisis|2|0",
"complete": 1
}, {
"unlock_key": "blrink|2|0",
"complete": 1
}, {
"unlock_key": "rise|1|0",
"complete": 1
}, {
"unlock_key": "cyanine|1|0",
"complete": 1
}, { }, {
"unlock_key": "corpssansorganes|0|0", "unlock_key": "corpssansorganes|0|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "vector|1|0", "unlock_key": "vector|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "infinityheaven|2|0",
"complete": 1
}, {
"unlock_key": "essenceoftwilight|1|0",
"complete": 1
}, { }, {
"unlock_key": "conflict|2|0", "unlock_key": "conflict|2|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "singularity|1|0",
"complete": 1
}, {
"unlock_key": "harutopia|1|0",
"complete": 1
}, {
"unlock_key": "cyberneciacatharsis|2|0",
"complete": 1
}, { }, {
"unlock_key": "ignotus|1|0", "unlock_key": "ignotus|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "nirvluce|1|0",
"complete": 1
}, { }, {
"unlock_key": "monochromeprincess|1|0", "unlock_key": "monochromeprincess|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "lethaeus|1|0",
"complete": 1
}, { }, {
"unlock_key": "clotho|1|0", "unlock_key": "clotho|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "aterlbus|2|0", "unlock_key": "heavensdoor|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "dreaminattraction|2|0", "unlock_key": "chronostasis|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "solitarydream|1|0", "unlock_key": "lethaeus|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "ringedgenesis|1|0", "unlock_key": "amazingmightyyyy|1|0",
"complete": 1
}, {
"unlock_key": "aegleseeker|0|103",
"complete": 1
}, {
"unlock_key": "buchigireberserker|2|3|ouroboros|2",
"complete": 1
}, {
"unlock_key": "oshamascramble|1|0",
"complete": 1
}, {
"unlock_key": "gothiveofra|2|0",
"complete": 1
}, {
"unlock_key": "linearaccelerator|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "corpssansorganes|1|0", "unlock_key": "corpssansorganes|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "buchigireberserker|2|0", "unlock_key": "worldexecuteme|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "bookmaker|1|0", "unlock_key": "bookmaker|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "heavensdoor|2|0", "unlock_key": "fractureray|0|101",
"complete": 100
}, {
"unlock_key": "singularity|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "genesis|2|0", "unlock_key": "blaster|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "halcyon|2|0", "unlock_key": "rise|1|0",
"complete": 1
}, {
"unlock_key": "axiumcrisis|2|0",
"complete": 1
}, {
"unlock_key": "tempestissimo|2|101",
"complete": 100
}, {
"unlock_key": "solitarydream|2|0",
"complete": 1
}, {
"unlock_key": "aegleseeker|1|103",
"complete": 103
}, {
"unlock_key": "amazingmightyyyy|2|0",
"complete": 1
}, {
"unlock_key": "cyaegha|1|0",
"complete": 1
}, {
"unlock_key": "linearaccelerator|1|0",
"complete": 1
}, {
"unlock_key": "tempestissimo|1|101",
"complete": 100
}, {
"unlock_key": "conflict|1|0",
"complete": 1
}, {
"unlock_key": "grievouslady|0|101",
"complete": 100
}, {
"unlock_key": "dreaminattraction|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "saikyostronger|2|3|izana|2", "unlock_key": "saikyostronger|2|3|izana|2",
@@ -1418,6 +1566,9 @@ def arc_all_get(user_id):
}, { }, {
"unlock_key": "felis|1|0", "unlock_key": "felis|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "overwhelm|1|0",
"complete": 1
}, { }, {
"unlock_key": "toaliceliddell|2|0", "unlock_key": "toaliceliddell|2|0",
"complete": 1 "complete": 1
@@ -1425,50 +1576,17 @@ def arc_all_get(user_id):
"unlock_key": "blrink|1|0", "unlock_key": "blrink|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "grievouslady|0|101", "unlock_key": "heavensdoor|2|0",
"complete": 100
}, {
"unlock_key": "buchigireberserker|2|3|gothiveofra|2",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "kanagawa|1|0", "unlock_key": "genesis|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "darakunosono|2|0", "unlock_key": "viyellastears|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "freefall|1|0", "unlock_key": "aegleseeker|2|103",
"complete": 1 "complete": 103
}, {
"unlock_key": "nirvluce|2|0",
"complete": 1
}, {
"unlock_key": "cyanine|2|0",
"complete": 1
}, {
"unlock_key": "goodtek|1|0",
"complete": 1
}, {
"unlock_key": "buchigireberserker|2|3|ouroboros|2",
"complete": 1
}, {
"unlock_key": "fractureray|0|101",
"complete": 100
}, {
"unlock_key": "blaster|1|0",
"complete": 1
}, {
"unlock_key": "dreaminattraction|1|0",
"complete": 1
}, {
"unlock_key": "toaliceliddell|1|0",
"complete": 1
}, {
"unlock_key": "oshamascramble|1|0",
"complete": 1
}, {
"unlock_key": "gothiveofra|2|0",
"complete": 1
}, { }, {
"unlock_key": "tiferet|2|0", "unlock_key": "tiferet|2|0",
"complete": 1 "complete": 1
@@ -1476,40 +1594,64 @@ def arc_all_get(user_id):
"unlock_key": "grimheart|2|0", "unlock_key": "grimheart|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "amazingmightyyyy|1|0", "unlock_key": "aegleseeker|2|3|farawaylight|2",
"complete": 1 "complete": 10
}, {
"unlock_key": "lethaeus|2|0",
"complete": 1
}, { }, {
"unlock_key": "rugie|1|0", "unlock_key": "rugie|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "gou|1|0", "unlock_key": "gou|1|0",
"complete": 1 "complete": 1
}, {
"unlock_key": "sheriruth|2|0",
"complete": 1
}, {
"unlock_key": "aegleseeker|2|3|paperwitch|2",
"complete": 10
}, { }, {
"unlock_key": "sheriruth|1|0", "unlock_key": "sheriruth|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "babaroque|2|0", "unlock_key": "aegleseeker|2|3|loschen|2",
"complete": 10
}, {
"unlock_key": "cyberneciacatharsis|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "aiueoon|2|0", "unlock_key": "essenceoftwilight|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "gloryroad|1|0", "unlock_key": "syro|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "cyaegha|1|0", "unlock_key": "anokumene|2|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "amazingmightyyyy|2|0", "unlock_key": "freefall|1|0",
"complete": 1 "complete": 1
}, { }, {
"unlock_key": "anokumene|1|0", "unlock_key": "darakunosono|2|0",
"complete": 1
}, {
"unlock_key": "nirvluce|2|0",
"complete": 1 "complete": 1
}] }]
}, "clearedsongs": {
return {
"user_id": user_id,
"story": {
"": story_data
},
"devicemodelname": {
"val": devicemodelname_data
},
"installid": {
"val": installid_data
},
"unlocklist": {
"": unlocklist_data
},
"clearedsongs": {
"": clearedsongs_data "": clearedsongs_data
}, },
"clearlamps": { "clearlamps": {

View File

@@ -1,12 +1,12 @@
import hashlib import hashlib
import time import time
import server.arcworld
from server.sql import Connect from server.sql import Connect
import functools import functools
from setting import Config from setting import Config
from flask import jsonify
def arc_login(name: str, password: str, device_id: str): # 登录判断 def arc_login(name: str, password: str, device_id: str, ip: str): # 登录判断
# 查询数据库中的user表验证账号密码返回并记录token多返回个error code # 查询数据库中的user表验证账号密码返回并记录token多返回个error code
# token采用user_id和时间戳连接后hash生成真的是瞎想的没用bear # token采用user_id和时间戳连接后hash生成真的是瞎想的没用bear
# 密码和token的加密方式为 SHA-256 # 密码和token的加密方式为 SHA-256
@@ -43,8 +43,8 @@ def arc_login(name: str, password: str, device_id: str): # 登录判断
if not Config.ALLOW_LOGIN_SAME_DEVICE: if not Config.ALLOW_LOGIN_SAME_DEVICE:
if device_id in device_list: # 对相同设备进行删除 if device_id in device_list: # 对相同设备进行删除
c.execute('''delete from login where login_device=:a''', { c.execute('''delete from login where login_device=:a and user_id=:b''', {
'a': device_id}) 'a': device_id, 'b': user_id})
should_delete_num = len( should_delete_num = len(
device_list) + 1 - device_list.count(device_id) - Config.LOGIN_DEVICE_NUMBER_LIMIT device_list) + 1 - device_list.count(device_id) - Config.LOGIN_DEVICE_NUMBER_LIMIT
@@ -52,8 +52,8 @@ def arc_login(name: str, password: str, device_id: str): # 登录判断
c.execute('''delete from login where rowid in (select rowid from login where user_id=:user_id limit :a);''', 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)}) {'user_id': user_id, 'a': int(should_delete_num)})
c.execute('''insert into login(access_token, user_id, login_device) values(:access_token, :user_id, :device_id)''', { 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}) 'user_id': user_id, 'access_token': token, 'device_id': device_id, 'time': now, 'ip': ip})
error_code = None error_code = None
else: else:
# 密码错误 # 密码错误
@@ -65,8 +65,8 @@ def arc_login(name: str, password: str, device_id: str): # 登录判断
return token, error_code return token, error_code
def arc_register(name: str, password: str, device_id: str): # 注册 def arc_register(name: str, password: str, device_id: str, email: str, ip: str): # 注册
# 账号注册,记录hash密码用户名生成user_id和user_code自动登录返回token # 账号注册记录hash密码用户名和邮箱生成user_id和user_code自动登录返回token
# token和密码的处理同登录部分 # token和密码的处理同登录部分
def build_user_code(c): def build_user_code(c):
@@ -106,24 +106,29 @@ def arc_register(name: str, password: str, device_id: str): # 注册
hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest() hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest()
c.execute( c.execute(
'''select exists(select * from user where name = :name)''', {'name': name}) '''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,): if c.fetchone() == (0,):
user_code = build_user_code(c) user_code = build_user_code(c)
user_id = build_user_id(c) user_id = build_user_id(c)
now = int(time.time() * 1000) now = int(time.time() * 1000)
c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt, 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) 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, '', 0) 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}) ''', {'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)''', { c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': user_id}) 'user_id': user_id})
token = hashlib.sha256( token = hashlib.sha256(
(str(user_id) + str(now)).encode("utf8")).hexdigest() (str(user_id) + str(now)).encode("utf8")).hexdigest()
c.execute('''insert into login(access_token, user_id, login_device) values(:access_token, :user_id, :device_id)''', { 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}) 'user_id': user_id, 'access_token': token, 'device_id': device_id, 'time': now, 'ip': ip})
insert_user_char(c, user_id) insert_user_char(c, user_id)
error_code = 0 error_code = 0
else:
error_code = 102
else: else:
error_code = 101 error_code = 101
@@ -166,8 +171,14 @@ def auth_required(request):
def wrapped_view(*args, **kwargs): def wrapped_view(*args, **kwargs):
user_id = None user_id = None
with Connect() as c:
headers = request.headers 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})
if 'Authorization' in headers:
token = headers['Authorization'] token = headers['Authorization']
token = token[7:] token = token[7:]
user_id = token_get_id(token) user_id = token_get_id(token)
@@ -175,7 +186,7 @@ def auth_required(request):
if user_id is not None: if user_id is not None:
return view(user_id, *args, **kwargs) return view(user_id, *args, **kwargs)
else: else:
return '''{"success":false,"error_code":108}''' return jsonify({"success": False, "error_code": 108})
return wrapped_view return wrapped_view
return decorator return decorator

View File

@@ -1,7 +1,9 @@
from server.sql import Connect from server.sql import Connect
import server.arcworld import server.arcworld
import server.arcpurchase import server.arcpurchase
import server.arcdownload
import time import time
from setting import Config
def int2b(x): def int2b(x):
@@ -116,12 +118,16 @@ def get_user_friend(c, user_id):
is_char_uncapped = int2b(z[0]) is_char_uncapped = int2b(z[0])
is_char_uncapped_override = int2b(z[1]) is_char_uncapped_override = int2b(z[1])
rating = y[5]
if int2b(y[10]):
rating = -1
s.append({ s.append({
"is_mutual": is_mutual, "is_mutual": is_mutual,
"is_char_uncapped_override": is_char_uncapped_override, "is_char_uncapped_override": is_char_uncapped_override,
"is_char_uncapped": is_char_uncapped, "is_char_uncapped": is_char_uncapped,
"is_skill_sealed": int2b(y[7]), "is_skill_sealed": int2b(y[7]),
"rating": y[5], "rating": rating,
"join_date": int(y[3]), "join_date": int(y[3]),
"character": character, "character": character,
"recent_score": get_recent_score(c, i[0]), "recent_score": get_recent_score(c, i[0]),
@@ -160,21 +166,39 @@ def get_user_packs(c, user_id):
return re return re
def get_value_0(c, user_id): def get_user_characters(c, user_id):
# 构造value id=0的数据,返回字典 # 获取用户所拥有角色,返回列表
c.execute('''select character_id from user_char where user_id = :user_id''',
{'user_id': user_id})
x = c.fetchall()
characters = []
if x:
for i in x:
characters.append(i[0])
return characters
def get_user_me(c, user_id):
# 构造user/me的数据返回字典
c.execute('''select * from user where user_id = :x''', {'x': user_id}) c.execute('''select * from user where user_id = :x''', {'x': user_id})
x = c.fetchone() x = c.fetchone()
r = {} r = {}
if x is not None: if x is not None:
user_character = get_user_character(c, user_id) user_character = get_user_character(c, user_id)
# 下面没有使用get_user_characters函数是为了节省一次查询
characters = [] characters = []
for i in user_character: for i in user_character:
characters.append(i['character_id']) characters.append(i['character_id'])
prog_boost = 0 prog_boost = 0
if x[27] and x[27] != 0: if x[27] and x[27] != 0:
prog_boost = 300 prog_boost = 300
r = {"is_aprilfools": False, r = {"is_aprilfools": Config.IS_APRILFOOLS,
"curr_available_maps": [], "curr_available_maps": [],
"character_stats": user_character, "character_stats": user_character,
"friends": get_user_friend(c, user_id), "friends": get_user_friend(c, user_id),
@@ -197,7 +221,7 @@ def get_value_0(c, user_id):
"max_stamina_ts": 1586274871917, "max_stamina_ts": 1586274871917,
"stamina": 12, "stamina": 12,
"world_unlocks": ["scenery_chap1", "scenery_chap2", "scenery_chap3", "scenery_chap4", "scenery_chap5"], "world_unlocks": ["scenery_chap1", "scenery_chap2", "scenery_chap3", "scenery_chap4", "scenery_chap5"],
"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"], "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"],
"singles": get_user_singles(c, user_id), "singles": get_user_singles(c, user_id),
"packs": get_user_packs(c, user_id), "packs": get_user_packs(c, user_id),
"characters": characters, "characters": characters,
@@ -205,7 +229,8 @@ def get_value_0(c, user_id):
"recent_score": get_recent_score(c, user_id), "recent_score": get_recent_score(c, user_id),
"max_friend": 50, "max_friend": 50,
"rating": x[5], "rating": x[5],
"join_date": int(x[3]) "join_date": int(x[3]),
"global_rank": 114514
} }
return r return r
@@ -218,26 +243,26 @@ def arc_aggregate_small(user_id):
r = {"success": True, r = {"success": True,
"value": [{ "value": [{
"id": 0, "id": 0,
"value": get_value_0(c, user_id) "value": get_user_me(c, user_id)
}]} }]}
return r return r
def arc_aggregate_big(user_id): def arc_aggregate_big(user_id):
# 返回用户数据和地图歌曲信息 # 返回比较全的用户数据
r = {"success": False} r = {"success": False}
with Connect() as c: with Connect() as c:
r = {"success": True, r = {"success": True,
"value": [{ "value": [{
"id": 0, "id": 0,
"value": get_value_0(c, user_id) "value": get_user_me(c, user_id)
}, { }, {
"id": 1, "id": 1,
"value": server.arcpurchase.get_item(c, 'pack') "value": server.arcpurchase.get_item(c, 'pack')
}, { }, {
"id": 2, "id": 2,
"value": {} "value": server.arcdownload.get_all_songs(user_id, url_flag=False)
}, { }, {
"id": 3, "id": 3,
"value": { "value": {

View File

@@ -0,0 +1,98 @@
import os
from shutil import copy, copy2
from server.sql import Connect
from database.database_initialize import main, ARCAEA_SERVER_VERSION
from web.system import update_database
def try_rename(path, new_path):
# 尝试重命名文件,并尝试避免命名冲突,返回最终路径
final_path = new_path
if os.path.exists(new_path):
i = 1
while os.path.exists(new_path + str(i)):
i += 1
os.rename(path, new_path + str(i))
final_path = new_path + str(i)
else:
os.rename(path, new_path)
return final_path
def check_before_run(app):
# 运行前检查关键文件,返回布尔值,其实是因为有人经常忘了
f = True
if not os.path.exists('setting.py'):
app.logger.warning('File `setting.py` is missing.')
f = False
if not os.path.exists('database'):
app.logger.warning('Folder `database` is missing.')
f = False
if not os.path.exists('database/songs'):
app.logger.warning('Folder `database/songs` is missing.')
f = False
if not os.path.exists('database/arcaea_database.db'):
app.logger.warning('File `database/arcaea_database.db` is missing.')
f = False
try:
app.logger.info(
'Try to new the file `database/arcaea_database.db`.')
main('./database/')
app.logger.info(
'Success to new the file `database/arcaea_database.db`.')
f = True
except:
app.logger.warning(
'Fail to new the file `database/arcaea_database.db`.')
else:
with Connect() as c:
try:
c.execute('''select value from config where id="version"''')
x = c.fetchone()
except:
x = None
# 数据库自动更新,不强求
if not x or x[0] != ARCAEA_SERVER_VERSION:
app.logger.warning(
'Maybe the file `database/arcaea_database.db` is an old version.')
try:
app.logger.info(
'Try to update the file `database/arcaea_database.db`.')
path = try_rename('database/arcaea_database.db',
'database/arcaea_database.db.bak')
try:
copy2(path, 'database/arcaea_database.db')
except:
copy(path, 'database/arcaea_database.db')
if os.path.isfile("database/old_arcaea_database.db"):
os.remove('database/old_arcaea_database.db')
try_rename('database/arcaea_database.db',
'database/old_arcaea_database.db')
main('./database/')
update_database()
app.logger.info(
'Success to update the file `database/arcaea_database.db`.')
except:
app.logger.warning(
'Fail to update the file `database/arcaea_database.db`.')
if not os.path.exists('database/arcsong.db'):
app.logger.warning('File `database/arcsong.db` is missing.')
f = False
return f

View File

@@ -29,3 +29,52 @@ class Connect():
traceback.format_exception(exc_type, exc_val, exc_tb)) traceback.format_exception(exc_type, exc_val, exc_tb))
return True return True
class Sql():
@staticmethod
def select(c, table_name, target_column=[], limit=-1, offset=0, query={}, sort=[]):
# 执行查询单句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 = []
for i in query:
where_field.append(i)
where_value.append(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 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 limit >= 0:
sql += ' limit :limit offset :offset'
sql_dict['limit'] = limit
sql_dict['offset'] = offset
c.execute(sql, sql_dict)
return c.fetchall()

View File

@@ -8,12 +8,57 @@ class Config():
主机的地址和端口号 主机的地址和端口号
Host and port of your server Host and port of your server
''' '''
HOST = '192.168.1.105' HOST = '192.168.1.101'
PORT = '80' PORT = '80'
''' '''
-------------------- --------------------
''' '''
'''
--------------------
游戏API地址前缀
Game API's URL prefix
'''
GAME_API_PREFIX = '/blockchain/14'
'''
--------------------
'''
'''
--------------------
允许使用的游戏版本,若为空,则默认全部允许
Allowed game versions
If it is blank, all are allowed.
'''
ALLOW_APPVERSION = ['3.5.3', '3.5.3c',
'3.6.0', '3.6.0c', '3.6.1', '3.6.1c']
'''
--------------------
'''
'''
--------------------
SSL证书路径
留空则使用HTTP
SSL certificate path
If left blank, use HTTP.
'''
SSL_CERT = '' # *.pem
SSL_KEY = '' # *.key
'''
--------------------
'''
'''
--------------------
愚人节模式开关
Switch of April Fool's Day
'''
IS_APRILFOOLS = True
'''
--------------------
'''
''' '''
-------------------- --------------------
Web后台管理页面的用户名和密码 Web后台管理页面的用户名和密码
@@ -36,6 +81,28 @@ class Config():
-------------------- --------------------
''' '''
'''
--------------------
API接口完全控制权限Token留空则不使用
API interface full control permission Token
If you don't want to use it, leave it blank.
'''
API_TOKEN = ''
'''
--------------------
'''
'''
--------------------
歌曲下载地址前缀,留空则自动获取
Song download address prefix
If left blank, it will be obtained automatically.
'''
DOWNLOAD_LINK_PREFIX = '' # http://***.com/download/
'''
--------------------
'''
''' '''
-------------------- --------------------
玩家歌曲下载的24小时次数限制每个文件算一次 玩家歌曲下载的24小时次数限制每个文件算一次
@@ -75,3 +142,23 @@ class Config():
''' '''
-------------------- --------------------
''' '''
'''
--------------------
用户注册时的默认记忆源点数量
The default amount of memories at the time of user registration
'''
DEFAULT_MEMORIES = 0
'''
--------------------
'''
'''
--------------------
是否强制使用全解锁云端存档
If forcing full unlocked cloud save is enabled
'''
SAVE_FULL_UNLOCK = False
'''
--------------------
'''

View File

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

View File

@@ -19,7 +19,7 @@
<input name="rating_ftr" id="rating_ftr" required> <input name="rating_ftr" id="rating_ftr" required>
<label for="rating_byd">Beyond chart const</label> <label for="rating_byd">Beyond chart const</label>
<input name="rating_byd" id="rating_byd" required> <input name="rating_byd" id="rating_byd" required>
<div class="content">如果没有某个面,应该填入-1。</div> <div class="content">如果没有某个面,应该填入-1。</div>
<div class="content">If there is no some chart, fill in -1 please.</div> <div class="content">If there is no some chart, fill in -1 please.</div>
<input type="submit" value="Add"> <input type="submit" value="Add">
</form> </form>

View File

@@ -13,7 +13,7 @@
<a href="{{ url_for('index.single_player_score') }}">单个玩家成绩查询 Single player score</a></br></br> <a href="{{ url_for('index.single_player_score') }}">单个玩家成绩查询 Single player score</a></br></br>
<a href="{{ url_for('index.single_player_ptt') }}">单个玩家PTT详情查询 Single player ptt</a></br></br> <a href="{{ url_for('index.single_player_ptt') }}">单个玩家PTT详情查询 Single player ptt</a></br></br>
<a href="{{ url_for('index.all_player') }}">所有玩家信息查询 All players</a></br></br> <a href="{{ url_for('index.all_player') }}">所有玩家信息查询 All players</a></br></br>
<a href="{{ url_for('index.all_song') }}">面信息查询 All songs</a></br></br> <a href="{{ url_for('index.all_song') }}">面信息查询 All songs</a></br></br>
<a href="{{ url_for('index.all_character') }}">角色信息查询 All characters</a></br></br> <a href="{{ url_for('index.all_character') }}">角色信息查询 All characters</a></br></br>
<a href="{{ url_for('index.all_item') }}">购买信息查询 All items</a></br></br> <a href="{{ url_for('index.all_item') }}">购买信息查询 All items</a></br></br>
<a href="{{ url_for('index.all_present') }}">奖励信息查询 All presents</a></br></br> <a href="{{ url_for('index.all_present') }}">奖励信息查询 All presents</a></br></br>

View File

@@ -18,4 +18,26 @@
Uploadable files: arcaea_database.db & arcsong.db<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. Data that does not exist in the new database will be added and the existing duplicate data will also be changed.
</div> </div>
<br />
<hr />
<form action="/web/updatedatabase/refreshsonghash" method="post">
<div class="title">Refresh songs' hash</div>
<br />
<input type="submit" value="Refresh">
<div class="content">这里可以刷新储存在数据库中的songs文件夹下所有文件的Hash值。目的是应对歌曲谱面文件的更新。</div>
<div class="content">Here you can refresh the hash values of all the files stored in the songs folder in the
database. The purpose is to deal with the updating of songs' files.</div>
</form>
<br />
<hr />
<form action="/web/updatedatabase/refreshsongrating" method="post">
<div class="title">Refresh songs' rating</div>
<br />
<input type="submit" value="Refresh">
<div class="content">这里可以刷新数据库中所有成绩的评分。目的是应对歌曲谱面定数的更新。</div>
<div class="content">Here you can refresh the ratings of all scores in the database. The purpose is to deal
with the updating of songs' chart consts.</div>
</form>
{% endblock %} {% endblock %}

View File

@@ -10,6 +10,7 @@ import time
import server.arcscore import server.arcscore
import os import os
import json import json
from server.arcdownload import initialize_songfile
UPLOAD_FOLDER = 'database' UPLOAD_FOLDER = 'database'
ALLOWED_EXTENSIONS = {'db'} ALLOWED_EXTENSIONS = {'db'}
@@ -277,6 +278,30 @@ def update_database():
return render_template('web/updatedatabase.html') return render_template('web/updatedatabase.html')
@bp.route('/updatedatabase/refreshsonghash', methods=['POST'])
@login_required
def update_song_hash():
# 更新数据库内谱面文件hash值
error = initialize_songfile()
if error:
flash(error)
else:
flash('数据刷新成功 Success refresh data.')
return render_template('web/updatedatabase.html')
@bp.route('/updatedatabase/refreshsongrating', methods=['POST'])
@login_required
def update_song_rating():
# 更新所有分数的rating
error = server.arcscore.refresh_all_score_rating()
if error:
flash(error)
else:
flash('数据刷新成功 Success refresh data.')
return render_template('web/updatedatabase.html')
@bp.route('/changesong', methods=['GET']) @bp.route('/changesong', methods=['GET'])
@login_required @login_required
def change_song(): def change_song():
@@ -386,7 +411,7 @@ def all_character():
def change_character(): def change_character():
# 修改角色数据 # 修改角色数据
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere', skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion'] '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']
return render_template('web/changechar.html', skill_ids=skill_ids) return render_template('web/changechar.html', skill_ids=skill_ids)

View File

@@ -195,6 +195,11 @@ def update_database():
update_one_table(c1, c2, 'user_present') update_one_table(c1, c2, 'user_present')
update_one_table(c1, c2, 'redeem') update_one_table(c1, c2, 'redeem')
update_one_table(c1, c2, 'user_redeem') update_one_table(c1, c2, 'user_redeem')
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_user_char(c2) update_user_char(c2)

View File

@@ -1,11 +1,11 @@
import time import time
def get_user_score(c, user_id, limit=-1): def get_user_score(c, user_id, limit=-1, offset=0):
# 返回用户的所有歌曲数据,带排名,返回字典列表 # 返回用户的所有歌曲数据,带排名,返回字典列表
if limit >= 0: if limit >= 0:
c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b''', c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b offset :c''',
{'a': user_id, 'b': limit}) {'a': user_id, 'b': limit, 'c': offset})
else: else:
c.execute( c.execute(
'''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id}) '''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id})
@@ -77,7 +77,7 @@ def get_user(c, user_id):
def get_user_recent30(c, user_id): def get_user_recent30(c, user_id):
# 获取玩家recent30信息并计算这一部分的ptt返回字典列表和一个值 # 获取玩家recent30信息并计算recent10的ptt返回字典列表和一个值
c.execute('''select * from recent30 where user_id=:a''', {'a': user_id}) c.execute('''select * from recent30 where user_id=:a''', {'a': user_id})
sumr = 0 sumr = 0
x = c.fetchone() x = c.fetchone()