23 Commits
v2.8 ... v2.8.5

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

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

- Fix a safety problem when downloading.
2022-02-14 16:35:36 +08:00
Lost-MSth
ceebba4664 Fix a bug
Fix a bug that the high version of ios client cannot log in.
2022-01-27 19:26:13 +08:00
Lost-MSth
5bc9b9a3dc Fix a bug
Fix a bug about link play not getting the right address.
If you use reverse proxy, this may be helpful.
2022-01-24 22:38:43 +08:00
Lost-MSth
d9a543bc5a Change something about constant table and aggregate 2022-01-24 21:44:37 +08:00
Lost-MSth
4666c9c153 Merge pull request #35 from Young-Lord/master
加入aggregate
2022-01-24 21:28:44 +08:00
Lost-MSth
32bcbb0ccd Change something about aggregate 2022-01-24 21:22:38 +08:00
Young-Lord
34497d0638 Remove requests from requirements 2022-01-22 10:42:42 +08:00
Young-Lord
23bf3c020f Change the implement of experimental agreegate API 2022-01-22 10:40:06 +08:00
Young-Lord
e1fc1bbcd1 Minor fix 2022-01-22 07:35:57 +08:00
Young-Lord
355ec3557f Add experimental aggregate API 2022-01-21 22:55:23 +08:00
Young-Lord
cc4ac192e7 Update .gitignore 2022-01-21 22:53:22 +08:00
41 changed files with 2680 additions and 1657 deletions

4
.gitignore vendored
View File

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

View File

@@ -66,26 +66,31 @@ It is just so interesting. What it can do is under exploration.
> Tips: When updating, please keep the original database in case of data loss.
### Version 2.8
- 适用于Arcaea 3.11.2版本 For Arcaea 3.11.2
### Version 2.8.5
- 适用于Arcaea 3.12.8版本 For Arcaea 3.12.8
- 更新了歌曲数据库 Update the song database.
- 新增对Link Play的支持 Add support for Link Play.
- 搭档**彩梦**已觉醒 Uncap the character **ayu**.
- 修复一个导致无法升级角色的拼写错误 Fix a typing error, which makes giving characters Exp wrong.
- 尝试强制指定多进程启动方式为spawn这可能对UNIX系统中UDP服务器的启动有所帮助 Try to forcibly specify the multiprocess startup mode as spawn, which may be helpful for the startup of UDP server in UNIX system.
- 添加了注册API对外接口 Add an external API interface of user register.
- 重构一些代码,顺手修复了登陆时因多设备登录封号的用户没有正确显示错误提示的问题 Refactoring some codes and in passing fix the problem that users which has been banned because of multiple devices do not show error messages correctly when logging in.
> 注意:
> - 现在Flask最低版本要求提高到2.0
> - 服务端可能不再完全支持低版本客户端
> - 对3.12.6c版本,愚人节开关打开时点击`网络`按钮会闪退,原因不明
>
> Note:
> - Now the version of Flask which is required should be up to 2.0.
> - The server may no longer fully support lower version clients.
> - For version 3.12.6c, when the switch of April Fool's Day is on, clicking the `Network` button will make the client break down. The reason is not clear now.
- 以下是累积更新 The following are cumulative updates:
- 修复注册接口端点多了个杠的问题 Fix an unexpected slash at the registered interface endpoint.
- 新搭档**白姬**已解锁 Unlock the character **shirahime**.
- 修正两个角色的数值问题 Fix the values of two characters.
- 新搭档**玛莉嘉**已解锁 Unlock the character **marija**.
> 提示Link Play可能存在大量bug
> Tips: There may be many bugs in Link Play system.
## 运行环境与依赖 Running environment and requirements
- Windows/Linux/Mac OS/Android...
- Python 3
- Flask module, Cryptography module
- Flask module >= 2.0, Cryptography module
- Charles, IDA, proxy app... (optional)
<!--

View File

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

View File

@@ -3,12 +3,13 @@ from flask import (
)
import functools
import api.api_auth
import api.users
from . import users
import api.songs
from api.api_code import code_get_msg
from .api_code import code_get_msg, return_encode
bp = Blueprint('api', __name__, url_prefix='/api/v1')
bp.register_blueprint(users.bp)
class Query():
@@ -62,16 +63,6 @@ def get_query_parameter(request, query_able=[], sort_able=[]):
return decorator
def return_encode(code: int = 0, data: dict = {}, status: int = 200, msg: str = ''):
# 构造返回返回jsonify处理过后的response_class
if msg == '':
msg = code_get_msg(code)
if code < 0:
return jsonify({'status': status, 'code': code, 'data': {}, 'msg': msg})
else:
return jsonify({'status': status, 'code': code, 'data': data, 'msg': msg})
@bp.route('/')
def ping():
return return_encode()
@@ -111,7 +102,7 @@ def token_delete(user):
def users_get(query, user):
# 查询全用户信息
data = api.users.get_users(query)
data = users.get_users(query)
if not data:
return return_encode(-2)
@@ -133,7 +124,7 @@ def users_user_get(user, user_id):
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_info(user_id)
data = users.get_user_info(user_id)
if not data:
return return_encode(-3)
@@ -152,7 +143,7 @@ def users_user_b30_get(user, user_id):
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_b30(user_id)
data = users.get_user_b30(user_id)
if data['data'] == []:
return return_encode(-3)
@@ -173,7 +164,7 @@ def users_user_best_get(query, user, user_id):
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_best(user_id, query)
data = users.get_user_best(user_id, query)
if data['data'] == []:
return return_encode(-3)
@@ -192,7 +183,7 @@ def users_user_r30_get(user, user_id):
if user_id != user.user_id and not 'select' in user.power and user.user_id != 0: # 查别人需要select权限
return return_encode(-1, {}, 403, 'No permission')
data = api.users.get_user_r30(user_id)
data = users.get_user_r30(user_id)
if data['data'] == []:
return return_encode(-3)

View File

@@ -1,9 +1,48 @@
from flask import (
Blueprint, request
)
from .api_code import code_get_msg, return_encode
from .api_auth import role_required
from core.user import UserRegister
from core.error import ArcError, PostError
from server.sql import Connect
from server.sql import Sql
import time
import web.webscore
import server.info
bp = Blueprint('users', __name__, url_prefix='/users')
@bp.route('', methods=['POST'])
@role_required(request, ['change'])
def users_post(user):
# 注册用户
with Connect() as c:
new_user = UserRegister(c)
try:
if 'name' in request.json:
new_user.set_name(request.json['name'])
else:
raise PostError('No name provided.')
if 'password' in request.json:
new_user.set_password(request.json['password'])
else:
raise PostError('No password provided.')
if 'email' in request.json:
new_user.set_email(request.json['email'])
else:
raise PostError('No email provided.')
new_user.register()
except ArcError as e:
return return_encode(e.api_error_code)
return return_encode(0, {'user_id': new_user.user_id, 'user_code': new_user.user_code})
def get_users(query=None):
# 获取全用户信息,返回字典列表

View File

View File

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

View File

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

View File

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

View File

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

View File

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

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

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

Binary file not shown.

View File

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

View File

@@ -552,5 +552,23 @@
],
"price": 400,
"orig_price": 400
},
{
"name": "lanota_append_1",
"items": [
{
"type": "pack",
"id": "lanota_append_1",
"is_available": true
},
{
"type": "core",
"amount": 3,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 300,
"price": 300
}
]

View File

@@ -1054,5 +1054,131 @@
],
"orig_price": 100,
"price": 100
},
{
"name": "internetoverdose",
"items": [
{
"type": "single",
"id": "internetoverdose",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "macromod",
"items": [
{
"type": "single",
"id": "macromod",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "neowings",
"items": [
{
"type": "single",
"id": "neowings",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "kissinglucifer",
"items": [
{
"type": "single",
"id": "kissinglucifer",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "headbonkache",
"items": [
{
"type": "single",
"id": "headbonkache",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "aurgelmir",
"items": [
{
"type": "single",
"id": "aurgelmir",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
},
{
"name": "picopicotranslation",
"items": [
{
"type": "single",
"id": "picopicotranslation",
"is_available": true
},
{
"type": "core",
"amount": 1,
"id": "core_generic",
"is_available": true
}
],
"orig_price": 100,
"price": 100
}
]

View File

@@ -1,9 +1,9 @@
# encoding: utf-8
from flask import Flask, json, request, jsonify, make_response, send_from_directory
from flask import Flask, json, request, jsonify, send_from_directory
from logging.config import dictConfig
from setting import Config
import base64
import server
import server.auth
import server.info
import server.setme
@@ -17,10 +17,13 @@ import server.arcpurchase
import server.init
import server.character
import server.arclinkplay
from udpserver.udp_main import link_play
import os
import sys
from multiprocessing import Process, Pipe
from multiprocessing import Process, Pipe, set_start_method
from urllib.parse import parse_qs, urlparse
from werkzeug.datastructures import ImmutableMultiDict
app = Flask(__name__)
@@ -33,6 +36,7 @@ app.config['SESSION_TYPE'] = 'filesystem'
app.register_blueprint(web.login.bp)
app.register_blueprint(web.index.bp)
app.register_blueprint(api.api_main.bp)
app.register_blueprint(server.bp)
conn1, conn2 = Pipe()
@@ -122,10 +126,16 @@ def error_return(error_code, extra={}): # 错误返回
})
def success_return(value):
return jsonify({
"success": True,
"value": value
})
@app.route('/')
def hello():
return "Hello World!"
# 自定义路径
@app.route('/favicon.ico', methods=['GET']) # 图标
@@ -137,114 +147,81 @@ def favicon():
return app.send_static_file('favicon.ico')
@app.route(add_url_prefix('/auth/login'), methods=['POST']) # 登录接口
def login():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return error_return(1203)
headers = request.headers
id_pwd = headers['Authorization']
id_pwd = base64.b64decode(id_pwd[6:]).decode()
name, password = id_pwd.split(':', 1)
if 'DeviceId' in headers:
device_id = headers['DeviceId']
else:
device_id = 'low_version'
token, error_code, extra = server.auth.arc_login(
name, password, device_id, request.remote_addr)
if not error_code:
r = {"success": True, "token_type": "Bearer"}
r['access_token'] = token
return jsonify(r)
else:
if extra:
return error_return(error_code, extra)
else:
return error_return(error_code)
@app.route(add_url_prefix('/user'), methods=['POST']) # 注册接口
def register():
if 'AppVersion' in request.headers: # 版本检查
if Config.ALLOW_APPVERSION:
if request.headers['AppVersion'] not in Config.ALLOW_APPVERSION:
return error_return(5)
name = request.form['name']
password = request.form['password']
email = request.form['email']
if 'device_id' in request.form:
device_id = request.form['device_id']
else:
device_id = 'low_version'
user_id, token, error_code = server.auth.arc_register(
name, password, device_id, email, request.remote_addr)
if user_id is not None:
r = {"success": True, "value": {
'user_id': user_id, 'access_token': token}}
return jsonify(r)
else:
return error_return(error_code)
# 集成式请求,没想到什么好办法处理,就先这样写着
@app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
@app.route(add_url_prefix('/purchase/bundle/pack'), methods=['GET']) # 曲包信息
@server.auth.auth_required(request)
def aggregate(user_id):
calls = request.args.get('calls')
if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断我猜get的参数就两种
r = server.info.arc_aggregate_small(user_id)
else:
r = server.info.arc_aggregate_big(user_id)
return jsonify(r)
def bundle_pack(user_id):
return success_return(server.info.get_purchase_pack(user_id))
@app.route(add_url_prefix('/user/me'), methods=['GET']) # 用户信息给baa查分器用的
@app.route(add_url_prefix('/game/info'), methods=['GET']) # 系统信息
def game_info():
return success_return(server.info.get_game_info())
@app.route(add_url_prefix('/present/me'), methods=['GET']) # 用户奖励信息
@server.auth.auth_required(request)
def user_me(user_id):
r = server.info.arc_aggregate_small(user_id)
if r['success']:
r['value'] = r['value'][0]['value']
return jsonify(r)
def present_info(user_id):
return success_return(server.info.get_user_present(user_id))
@app.route(add_url_prefix('/user/me/character'), methods=['POST']) # 角色切换
@server.auth.auth_required(request)
def character_change(user_id):
character_id = request.form['character']
skill_sealed = request.form['skill_sealed']
@app.route(add_url_prefix('/compose/aggregate'), methods=['GET']) # 集成式请求
def aggregate():
try:
#global request
finally_response = {'success': True, 'value': []}
#request_ = request
get_list = json.loads(request.args.get('calls'))
if len(get_list) > 10:
# 请求太多驳回
return error_return(108)
flag = server.setme.change_char(user_id, character_id, skill_sealed)
if flag:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"character": character_id
}
})
else:
for i in get_list:
endpoint = i['endpoint']
url = add_url_prefix(endpoint)
request.args = ImmutableMultiDict(
{key: value[0] for key, value in parse_qs(urlparse(url).query).items()})
resp_t = map_dict[urlparse(endpoint).path]()
if hasattr(resp_t, "response"):
resp_t = resp_t.response[0].decode().rstrip('\n')
resp = json.loads(resp_t)
if hasattr(resp, 'get') and resp.get('success') is False:
finally_response = {'success': False, 'error_code': 7, 'extra': {
"id": i['id'], 'error_code': resp.get('error_code')}}
if "extra" in resp:
finally_response['extra']['extra'] = resp['extra']
#request = request_
return jsonify(finally_response)
finally_response['value'].append(
{'id': i.get('id'), 'value': resp['value'] if hasattr(resp, 'get') else resp})
#request = request_
return jsonify(finally_response)
except KeyError:
return error_return(108)
# 角色觉醒切换
@app.route(add_url_prefix('/<path:path>/toggle_uncap', True), methods=['POST'])
# # 集成式请求,没想到什么好办法处理,就先这样写着
# @app.route(add_url_prefix('/compose/aggregate'), methods=['GET'])
# @server.auth.auth_required(request)
# def aggregate(user_id):
# calls = request.args.get('calls')
# if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断我猜get的参数就两种
# r = server.info.arc_aggregate_small(user_id)
# else:
# r = server.info.arc_aggregate_big(user_id)
# return jsonify(r)
@app.route(add_url_prefix('/user/me'), methods=['GET']) # 用户信息
@server.auth.auth_required(request)
def character_uncap(user_id, path):
character_id = int(path[path.find('character')+10:])
r = server.setme.change_char_uncap(user_id, character_id)
if r is not None:
return jsonify({
"success": True,
"value": {
"user_id": user_id,
"character": [r]
}
})
def user_me(user_id):
r = server.info.get_user_me_c(user_id)
if r:
return success_return(r)
else:
return error_return(108)
@@ -564,10 +541,11 @@ def pack(user_id):
# 单曲购买信息获取
@app.route(add_url_prefix('/purchase/bundle/single'), methods=['GET'])
def single():
@server.auth.auth_required(request)
def single(user_id):
return jsonify({
"success": True,
"value": server.arcpurchase.get_single_purchase()
"value": server.arcpurchase.get_single_purchase(user_id)
})
@@ -614,10 +592,11 @@ def world_one(user_id, map_id):
@server.auth.auth_required(request)
def download_song(user_id):
song_ids = request.args.getlist('sid')
if server.arcdownload.is_able_download(user_id):
url_flag = json.loads(request.args.get('url', 'true'))
if server.arcdownload.is_able_download(user_id) or not url_flag:
re = {}
if not song_ids:
re = server.arcdownload.get_all_songs(user_id)
re = server.arcdownload.get_all_songs(user_id, url_flag=url_flag)
else:
re = server.arcdownload.get_some_songs(user_id, song_ids)
@@ -633,7 +612,7 @@ def download_song(user_id):
def download(file_path):
try:
t = request.args.get('t')
message = server.arcdownload.is_token_able_download(t)
message = server.arcdownload.is_token_able_download(t, file_path)
if message == 0:
path = os.path.join('./database/songs', file_path)
if os.path.isfile(path) and not('../' in path or '..\\' in path):
@@ -658,7 +637,11 @@ def room_create(user_id):
conn1, user_id, client_song_map)
if error_code == 0:
value['endPoint'] = request.host.split(':')[0]
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
@@ -680,7 +663,11 @@ def room_join(user_id, room_code):
conn1, user_id, client_song_map, room_code)
if error_code == 0:
value['endPoint'] = request.host.split(':')[0]
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
@@ -700,7 +687,11 @@ def multiplayer_update(user_id):
error_code, value = server.arclinkplay.update_room(conn1, user_id, token)
if error_code == 0:
value['endPoint'] = request.host.split(':')[0]
if Config.LINK_PLAY_HOST == '':
value['endPoint'] = request.host.split(':')[0]
else:
value['endPoint'] = Config.LINK_PLAY_HOST
value['port'] = int(Config.UDP_PORT)
return jsonify({
"success": True,
@@ -723,9 +714,20 @@ def sys_set(user_id, path):
set_arg = path[5:]
value = request.form['value']
server.setme.arc_sys_set(user_id, value, set_arg)
r = server.info.arc_aggregate_small(user_id)
r['value'] = r['value'][0]['value']
return jsonify(r)
r = server.info.get_user_me_c(user_id)
if r:
return success_return(r)
else:
return error_return(108)
map_dict = {'/user/me': user_me,
'/purchase/bundle/pack': bundle_pack,
'/serve/download/me/song': download_song,
'/game/info': game_info,
'/present/me': present_info,
'/world/map/me': world_all,
'/score/song/friend': song_score_friend}
def main():
@@ -787,6 +789,7 @@ def main():
app.logger.info('Complete!')
if Config.UDP_PORT and Config.UDP_PORT != '':
from udpserver.udp_main import link_play
process = [Process(target=link_play, args=(
conn2, Config.HOST, int(Config.UDP_PORT)))]
[p.start() for p in process]
@@ -806,6 +809,7 @@ def main():
if __name__ == '__main__':
set_start_method("spawn")
main()

View File

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

View File

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

View File

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

View File

@@ -51,11 +51,14 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True
'a': user_id, 'b': song_id})
for i in dir_list:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg']:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg']:
token = hashlib.md5(
(str(user_id) + song_id + i + str(now)).encode(encoding='UTF-8')).hexdigest()
if i == 'base.ogg':
if 'audio' not in re:
re['audio'] = {}
c.execute(
'''select md5 from songfile where song_id=:a and file_type=-1''', {'a': song_id})
x = c.fetchone()
@@ -65,11 +68,29 @@ def get_one_song(c, user_id, song_id, file_dir='./database/songs', url_flag=True
checksum = get_file_md5(os.path.join(
file_dir, song_id, 'base.ogg'))
re['audio']["checksum"] = checksum
if url_flag:
re['audio'] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/base.ogg', t=token)}
re['audio']["url"] = get_url(
file_path=song_id+'/base.ogg', t=token)
elif i == '3.ogg':
if 'audio' not in re:
re['audio'] = {}
c.execute(
'''select md5 from songfile where song_id=:a and file_type=-2''', {'a': song_id})
x = c.fetchone()
if x:
checksum = x[0]
else:
re['audio'] = {"checksum": checksum}
checksum = get_file_md5(os.path.join(
file_dir, song_id, '3.ogg'))
if url_flag:
re['audio']['3'] = {"checksum": checksum, "url": get_url(
file_path=song_id+'/3.ogg', t=token)}
else:
re['audio']['3'] = {"checksum": checksum}
else:
if 'chart' not in re:
re['chart'] = {}
@@ -116,7 +137,7 @@ def get_some_songs(user_id, song_ids):
return re
def is_token_able_download(t):
def is_token_able_download(t, path):
# token是否可以下载返回错误码0即可以
errorcode = 108
with Connect() as c:
@@ -124,7 +145,7 @@ def is_token_able_download(t):
{'t': t})
x = c.fetchone()
now = int(time.time())
if x and now - x[4] <= time_gap_limit:
if x and now - x[4] <= time_gap_limit and x[1]+'/'+x[2] == path:
c.execute(
'''select count(*) from user_download where user_id = :a''', {'a': x[0]})
y = c.fetchone()
@@ -162,10 +183,13 @@ def initialize_one_songfile(c, song_id, file_dir='./database/songs'):
# 计算并添加歌曲md5到表中无返回
dir_list = os.listdir(os.path.join(file_dir, song_id))
for i in dir_list:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg']:
if os.path.isfile(os.path.join(file_dir, song_id, i)) and i in ['0.aff', '1.aff', '2.aff', '3.aff', 'base.ogg', '3.ogg']:
if i == 'base.ogg':
c.execute('''insert into songfile values(:a,-1,:c)''', {
'a': song_id, 'c': get_file_md5(os.path.join(file_dir, song_id, 'base.ogg'))})
elif i == '3.ogg':
c.execute('''insert into songfile values(:a,-2,:c)''', {
'a': song_id, 'c': get_file_md5(os.path.join(file_dir, song_id, '3.ogg'))})
else:
c.execute('''insert into songfile values(:a,:b,:c)''', {
'a': song_id, 'b': int(i[0]), 'c': get_file_md5(os.path.join(file_dir, song_id, i))})

View File

@@ -46,7 +46,11 @@ def create_room(conn, user_id, client_song_map):
song_unlock = get_song_unlock(client_song_map)
conn.send((1, name, song_unlock))
data = conn.recv()
if conn.poll(10):
data = conn.recv()
else:
data = (-1,)
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
@@ -74,7 +78,11 @@ def join_room(conn, user_id, client_song_map, room_code):
song_unlock = get_song_unlock(client_song_map)
conn.send((2, name, song_unlock, room_code))
data = conn.recv()
if conn.poll(10):
data = conn.recv()
else:
data = (-1,)
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],
@@ -96,7 +104,11 @@ def update_room(conn, user_id, token):
error_code = 108
conn.send((3, int(token)))
data = conn.recv()
if conn.poll(10):
data = conn.recv()
else:
data = (-1,)
if data[0] == 0:
error_code = 0
return error_code, {'roomCode': data[1],

View File

@@ -2,7 +2,6 @@ from server.sql import Connect
import server.item
import server.character
import time
import json
def int2b(x):
@@ -13,7 +12,7 @@ def int2b(x):
return True
def get_purchase(c, type='pack'):
def get_purchase(c, user_id, type='pack'):
# 读取packs内容返回字典列表
c.execute(
'''select * from purchase where purchase_name in (select purchase_name from purchase_item where type = :a)''', {'a': type})
@@ -50,7 +49,6 @@ def get_purchase(c, type='pack'):
"amount": amount
})
if t is not None:
# 放到列表头
items = [t, items]
@@ -62,19 +60,27 @@ def get_purchase(c, type='pack'):
if i[3] > 0:
r['discount_from'] = i[3]
if i[4] > 0:
r['discount_to'] = i[4]
if i[4] > 0:
r['discount_to'] = i[4]
if i[5] == 'anni5tix' and i[3] <= int(time.time() * 1000) <= i[4]:
c.execute(
'''select amount from user_item where user_id=? and item_id="anni5tix"''', (user_id,))
z = c.fetchone()
if z and z[0] >= 1:
r['discount_reason'] = 'anni5tix'
r['price'] = 0
re.append(r)
return re
def get_single_purchase():
def get_single_purchase(user_id):
# main里面没开数据库这里写一下代替
re = []
with Connect() as c:
re = get_purchase(c, type='single')
re = get_purchase(c, user_id, 'single')
return re
@@ -98,6 +104,25 @@ def buy_item(c, user_id, price):
return True, ticket - price
def buy_item_with_anni5tix(c, user_id):
# 兑换券购买接口,返回成功与否标志
c.execute('''select amount from user_item where user_id = :a and item_id = "anni5tix"''',
{'a': user_id})
amount = c.fetchone()
if amount:
amount = amount[0]
else:
return False
if amount <= 0:
return False
c.execute('''update user_item set amount = :b where user_id = :a and item_id = "anni5tix"''',
{'a': user_id, 'b': amount-1})
return True
def buy_thing(user_id, purchase_id):
# 购买物品接口,返回字典
success_flag = False
@@ -107,7 +132,7 @@ def buy_thing(user_id, purchase_id):
characters = []
with Connect() as c:
c.execute('''select price, orig_price, discount_from, discount_to from purchase where purchase_name=:a''',
c.execute('''select price, orig_price, discount_from, discount_to, discount_reason from purchase where purchase_name=:a''',
{'a': purchase_id})
x = c.fetchone()
price = 0
@@ -117,6 +142,7 @@ def buy_thing(user_id, purchase_id):
orig_price = x[1]
discount_from = x[2]
discount_to = x[3]
discount_reason = x[4]
else:
return {
"success": False,
@@ -130,6 +156,8 @@ def buy_thing(user_id, purchase_id):
now = int(time.time() * 1000)
if not(discount_from <= now <= discount_to):
price = orig_price
elif discount_reason == 'anni5tix' and buy_item_with_anni5tix(c, user_id):
price = 0
flag, ticket = buy_item(c, user_id, price)

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
import json
from server.sql import Connect
from .config import Constant
from setting import Config
import server.item
import server.character
@@ -10,11 +11,6 @@ import time
import random
ETO_UNCAP_BONUS_PROGRESS = 7
LUNA_UNCAP_BONUS_PROGRESS = 7
AYU_UNCAP_BONUS_PROGRESS = 5
def int2b(x):
# int与布尔值转换
if x is None or x == 0:
@@ -26,14 +22,14 @@ def int2b(x):
def calc_stamina(max_stamina_ts, curr_stamina):
# 计算体力,返回剩余体力数值
stamina = int(server.info.MAX_STAMINA - (max_stamina_ts -
int(time.time()*1000)) / server.info.STAMINA_RECOVER_TICK)
stamina = int(Constant.MAX_STAMINA - (max_stamina_ts -
int(time.time()*1000)) / Constant.STAMINA_RECOVER_TICK)
if stamina >= server.info.MAX_STAMINA:
if curr_stamina >= server.info.MAX_STAMINA:
if stamina >= Constant.MAX_STAMINA:
if curr_stamina >= Constant.MAX_STAMINA:
stamina = curr_stamina
else:
stamina = server.info.MAX_STAMINA
stamina = Constant.MAX_STAMINA
if stamina < 0:
stamina = 0
@@ -245,8 +241,8 @@ def play_world_song(user_id, args):
return {}
stamina = calc_stamina(max_stamina_ts, stamina) - \
info['stamina_cost'] * stamina_multiply
max_stamina_ts = now + server.info.STAMINA_RECOVER_TICK * \
(server.info.MAX_STAMINA - stamina)
max_stamina_ts = now + Constant.STAMINA_RECOVER_TICK * \
(Constant.MAX_STAMINA - stamina)
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))
r = {
@@ -356,7 +352,7 @@ def climb_step(user_id, map_id, step, prev_capture, prev_position):
return rewards, steps, curr_position, curr_capture, info
def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gauge, stamina_multiply=1, fragment_multiply=100, prog_boost_multiply=0):
def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gauge, health, stamina_multiply=1, fragment_multiply=100, prog_boost_multiply=0):
# 成绩上传后世界模式更新,返回字典
step_times = stamina_multiply * fragment_multiply / \
@@ -377,6 +373,11 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
is_skill_sealed = x[3] if x and x[3] is not None else 1
skill = False
skill_uncap = False
level = 1
exp = 0
frag = 50
prog = 50
overdrive = 50
if not is_skill_sealed:
if x:
skill = True
@@ -385,55 +386,76 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
if x[5] is not None and x[5] == 1:
skill_uncap = False
c.execute('''select frag1,prog1,overdrive1,frag20,prog20,overdrive20,frag30,prog30,overdrive30,skill_id,skill_id_uncap from character where character_id=?''', (character_id,))
x = c.fetchone()
c.execute('''select frag1,prog1,overdrive1,frag20,prog20,overdrive20,frag30,prog30,overdrive30,skill_id,skill_id_uncap from character where character_id=?''', (character_id,))
x = c.fetchone()
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select level, exp from user_char_full where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
else:
c.execute('''select level, exp from user_char where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
y = c.fetchone()
if y:
level = y[0]
exp = y[1]
else:
level = 1
exp = 0
if x:
flag = server.character.calc_char_value(level, x[0], x[3], x[6])
prog = server.character.calc_char_value(level, x[1], x[4], x[7])
overdrive = server.character.calc_char_value(level, x[2], x[5], x[8])
if x[9] is not None and x[9] != '' and skill:
skill = x[9]
if Config.CHARACTER_FULL_UNLOCK:
c.execute('''select level, exp from user_char_full where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
else:
c.execute('''select level, exp from user_char where user_id = :a and character_id = :b''', {
'a': user_id, 'b': character_id})
y = c.fetchone()
if y:
level = y[0]
exp = y[1]
else:
level = 1
exp = 0
if x:
frag = server.character.calc_char_value(level, x[0], x[3], x[6])
prog = server.character.calc_char_value(level, x[1], x[4], x[7])
overdrive = server.character.calc_char_value(
level, x[2], x[5], x[8])
if x[9] is not None and x[9] != '' and skill:
skill = x[9]
else:
skill = None
if x[10] is not None and x[9] != '' and skill_uncap:
skill_uncap = x[10]
else:
skill_uncap = None
else:
frag = 0
prog = 0
overdrive = 0
skill = None
if x[10] is not None and x[9] != '' and skill_uncap:
skill_uncap = x[10]
else:
skill_uncap = None
else:
flag = 0
prog = 0
overdrive = 0
skill = None
skill_uncap = None
skill_special = ''
if skill_uncap is not None and skill_uncap and skill_uncap in ['eto_uncap', 'luna_uncap', 'ayu_uncap', 'skill_vita']:
skill_special = skill_uncap
elif skill is not None and skill and skill in ['eto_uncap', 'luna_uncap', 'ayu_uncap', 'skill_vita']:
skill_special = skill
c.execute('''select current_map from user where user_id = :a''', {
'a': user_id})
map_id = c.fetchone()[0]
if beyond_gauge == 0: # 是否是beyond挑战
prog_tempest = 0
if not is_skill_sealed and character_id == 35:
# 风暴对立
if Config.CHARACTER_FULL_UNLOCK:
prog_tempest = 60
else:
c.execute(
'''select sum(level) from user_char where user_id=?''', (user_id,))
prog_tempest = int(x[0]) / 10 if x else 0
if prog_tempest > 60:
prog_tempest = 60
elif prog_tempest < 0:
prog_tempest = 0
base_step = 2.5 + 2.45*rating**0.5
step = base_step * (prog/50) * step_times
step = base_step * (prog + prog_tempest) / 50 * step_times
else:
info = get_world_info(map_id)
if clear_type == 0:
base_step = 8/9 + (rating/1.3)**0.5
base_step = 25/28 + (rating)**0.5 * 0.43
else:
base_step = 8/3 + (rating/1.3)**0.5
base_step = 75/28 + (rating)**0.5 * 0.43
if character_id in info['character_affinity']:
affinity_multiplier = info['affinity_multiplier'][info['character_affinity'].index(
@@ -441,7 +463,17 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
else:
affinity_multiplier = 1
step = base_step * (prog/50) * step_times * affinity_multiplier
if skill_special == 'skill_vita':
# vita技能overdrive随回忆率提升提升量最多为10
# 此处采用线性函数
overdrive_extra = 0
if 0 < health <= 100:
overdrive_extra = health / 10
overdrive += overdrive_extra
step = base_step * overdrive / 50 * \
step_times * affinity_multiplier
c.execute('''select * from user_world where user_id = :a and map_id =:b''',
{'a': user_id, 'b': map_id})
@@ -451,13 +483,9 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2])
# Eto和Luna的技能
# Eto、Luna、Ayu的技能
character_bonus_progress = None
skill_special = ''
if skill_uncap is not None and skill_uncap and skill_uncap in ['eto_uncap', 'luna_uncap', 'ayu_uncap']:
skill_special = skill_uncap
elif skill is not None and skill and skill in ['eto_uncap', 'luna_uncap', 'ayu_uncap']:
skill_special = skill
if skill_special == 'eto_uncap':
# eto觉醒技能获得残片奖励时世界模式进度加7
fragment_flag = False
@@ -469,7 +497,7 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
if fragment_flag:
break
if fragment_flag:
character_bonus_progress = ETO_UNCAP_BONUS_PROGRESS
character_bonus_progress = Constant.ETO_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
@@ -477,7 +505,7 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
elif skill_special == 'luna_uncap':
# luna觉醒技能限制格开始时世界模式进度加7
if 'restrict_id' in steps[0] and 'restrict_type' in steps[0] and steps[0]['restrict_type'] != '' and steps[0]['restrict_id'] != '':
character_bonus_progress = LUNA_UNCAP_BONUS_PROGRESS
character_bonus_progress = Constant.LUNA_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
rewards, steps, curr_position, curr_capture, info = climb_step(
user_id, map_id, step, y[3], y[2]) # 二次爬梯,重新计算
@@ -485,9 +513,9 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
elif skill_special == 'ayu_uncap':
# ayu觉醒技能世界模式进度+5或-5但不会小于0
if random.random() >= 0.5:
character_bonus_progress = AYU_UNCAP_BONUS_PROGRESS
character_bonus_progress = Constant.AYU_UNCAP_BONUS_PROGRESS
else:
character_bonus_progress = -AYU_UNCAP_BONUS_PROGRESS
character_bonus_progress = -Constant.AYU_UNCAP_BONUS_PROGRESS
step += character_bonus_progress * step_times
if step < 0:
@@ -515,62 +543,42 @@ def world_update(c, user_id, song_id, difficulty, rating, clear_type, beyond_gau
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
else:
exp = server.character.LEVEL_STEPS[level]
exp = Constant.LEVEL_STEPS[level]
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health']
},
"char_stats": {
"character_id": character_id,
"frag": frag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": calc_stamina(max_stamina_ts, stamina),
"max_stamina_ts": max_stamina_ts
}
if beyond_gauge == 0:
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health'],
"steps": steps
},
"char_stats": {
"character_id": character_id,
"frag": flag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": calc_stamina(max_stamina_ts, stamina),
"max_stamina_ts": max_stamina_ts
}
re["user_map"]["steps"] = steps
else:
re = {
"rewards": rewards,
"exp": exp,
"level": level,
"base_progress": base_step,
"progress": step,
"user_map": {
"user_id": user_id,
"curr_position": curr_position,
"curr_capture": curr_capture,
"is_locked": int2b(y[4]),
"map_id": map_id,
"prev_capture": y[3],
"prev_position": y[2],
"beyond_health": info['beyond_health'],
"step_count": len(steps)
},
"char_stats": {
"character_id": character_id,
"frag": flag,
"prog": prog,
"overdrive": overdrive
},
"current_stamina": calc_stamina(max_stamina_ts, stamina),
"max_stamina_ts": max_stamina_ts
}
re["user_map"]["steps"] = len(steps)
if character_id == 35 and not is_skill_sealed:
re['char_stats']['prog_tempest'] = prog_tempest
re['char_stats']['prog'] += prog_tempest
if character_bonus_progress is not None:
re['character_bonus_progress'] = character_bonus_progress
@@ -600,11 +608,11 @@ def add_stamina(c, user_id, add_stamina):
if x and x[0] is not None and x[1] is not None:
stamina = calc_stamina(x[0], x[1]) + add_stamina
max_stamina_ts = now - \
(stamina-server.info.MAX_STAMINA) * \
server.info.STAMINA_RECOVER_TICK
(stamina-Constant.MAX_STAMINA) * \
Constant.STAMINA_RECOVER_TICK
else:
max_stamina_ts = now
stamina = server.info.MAX_STAMINA
stamina = Constant.MAX_STAMINA
c.execute('''update user set max_stamina_ts=?, stamina=? where user_id=?''',
(max_stamina_ts, stamina, user_id))

View File

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

View File

@@ -1,12 +1,10 @@
from setting import Config
from server.sql import Connect
from .config import Constant
import server.info
import server.item
import server.setme
LEVEL_STEPS = {1: 0, 2: 50, 3: 100, 4: 150, 5: 200, 6: 300, 7: 450, 8: 650, 9: 900, 10: 1200, 11: 1600, 12: 2100, 13: 2700, 14: 3400, 15: 4200, 16: 5100,
17: 6100, 18: 7200, 19: 8500, 20: 10000, 21: 11500, 22: 13000, 23: 14500, 24: 16000, 25: 17500, 26: 19000, 27: 20500, 28: 22000, 29: 23500, 30: 25000}
def int2b(x):
# int与布尔值转换
@@ -18,7 +16,7 @@ def int2b(x):
def get_level_steps():
# 返回level_steps字典数组
return [{'level': i, 'level_exp': LEVEL_STEPS[i]} for i in LEVEL_STEPS]
return [{'level': i, 'level_exp': Constant.LEVEL_STEPS[i]} for i in Constant.LEVEL_STEPS]
def calc_char_value(level, value1, value20, value30):
@@ -114,7 +112,7 @@ def get_user_character(c, user_id):
"overdrive": calc_char_value(i[2], i[11], i[14], i[17]),
"prog": calc_char_value(i[2], i[10], i[13], i[16]),
"frag": calc_char_value(i[2], i[9], i[12], i[15]),
"level_exp": LEVEL_STEPS[i[2]],
"level_exp": Constant.LEVEL_STEPS[i[2]],
"exp": i[3],
"level": i[2],
"name": i[7],
@@ -151,13 +149,13 @@ def get_one_character(c, user_id, character_id):
"overdrive": calc_char_value(x[2], x[11], x[14], x[17]),
"prog": calc_char_value(x[2], x[10], x[13], x[16]),
"frag": calc_char_value(x[2], x[9], x[12], x[15]),
"level_exp": LEVEL_STEPS[x[2]],
"level_exp": Constant.LEVEL_STEPS[x[2]],
"exp": x[3],
"level": x[2],
"name": x[7],
"character_id": x[1]
}
if x[1] == 21:
if x[1] == 21 or x[1] == 46:
r["voice"] = [0, 1, 2, 3, 100, 1000, 1001]
return r
@@ -168,18 +166,18 @@ def calc_level_up(c, user_id, character_id, exp, exp_addition):
exp += exp_addition
if exp >= LEVEL_STEPS[20]: # 未觉醒溢出
if exp >= Constant.LEVEL_STEPS[20]: # 未觉醒溢出
c.execute('''select is_uncapped from user_char where user_id=? and character_id=?''',
(user_id, character_id))
x = c.fetchone()
if x and x[0] == 0:
return LEVEL_STEPS[20], 20
return Constant.LEVEL_STEPS[20], 20
a = []
b = []
for i in LEVEL_STEPS:
for i in Constant.LEVEL_STEPS:
a.append(i)
b.append(LEVEL_STEPS[i])
b.append(Constant.LEVEL_STEPS[i])
if exp >= b[-1]: # 溢出
return b[-1], a[-1]
@@ -213,11 +211,11 @@ def char_use_core(user_id, character_id, amount):
x = c.fetchone()
if x:
exp, level = calc_level_up(
c, user_id, character_id, x[0], amount*server.info.CORE_EXP)
c, user_id, character_id, x[0], amount*Constant.CORE_EXP)
c.execute('''update user_char set level=?, exp=? where user_id=? and character_id=?''',
(level, exp, user_id, character_id))
(level, exp, user_id, character_id))
server.item.claim_user_item(
c, user_id, 'core_generic', 'core', -amount)
c, user_id, 'core_generic', 'core', -amount)
r = {'character': [get_one_character(c, user_id, character_id)]}
r['cores'] = server.item.get_user_cores(c, user_id)

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -6,10 +6,7 @@ import server.character
import server.item
import time
from setting import Config
MAX_STAMINA = 12
STAMINA_RECOVER_TICK = 1800000
CORE_EXP = 250
from .config import Constant
def int2b(x):
@@ -202,6 +199,38 @@ def get_user_me(c, user_id):
return r
def get_user_me_c(user_id):
# user/me调用上边没开数据库这里开一下
with Connect() as c:
return get_user_me(c, user_id)
def get_purchase_pack(user_id):
# 返回曲包数据
with Connect() as c:
return server.arcpurchase.get_purchase(c, user_id)
def get_game_info():
# 返回游戏基本信息
r = {
"max_stamina": Constant.MAX_STAMINA,
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
"core_exp": Constant.CORE_EXP,
"curr_ts": int(time.time()*1000),
"level_steps": server.character.get_level_steps(),
"world_ranking_enabled": True,
"is_byd_chapter_unlocked": True
}
return r
def get_user_present(user_id):
# 返回奖励信息
with Connect() as c:
return server.arcpurchase.get_user_present(c, user_id)
def arc_aggregate_small(user_id):
# 返回用户数据
r = {"success": False}
@@ -232,16 +261,16 @@ def arc_aggregate_big(user_id):
"value": get_user_me(c, user_id)
}, {
"id": 1,
"value": server.arcpurchase.get_purchase(c, 'pack')
"value": server.arcpurchase.get_purchase(c, user_id)
}, {
"id": 2,
"value": id_2
}, {
"id": 3,
"value": {
"max_stamina": MAX_STAMINA,
"stamina_recover_tick": STAMINA_RECOVER_TICK,
"core_exp": CORE_EXP,
"max_stamina": Constant.MAX_STAMINA,
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
"core_exp": Constant.CORE_EXP,
"curr_ts": int(time.time()*1000),
"level_steps": server.character.get_level_steps(),
"world_ranking_enabled": True,

View File

@@ -55,20 +55,20 @@ def claim_user_item(c, user_id, item_id, item_type, amount=1):
else:
return False
if item_type == 'core':
if item_type in ['core', 'anni5tix']:
c.execute(
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (user_id, item_id))
'''select amount from user_item where user_id=? and item_id=? and type=?''', (user_id, item_id, item_type))
x = c.fetchone()
if x:
if x[0] + amount < 0: # 数量不足
return False
c.execute('''update user_item set amount=? where user_id=? and item_id=? and type="core"''',
(x[0]+amount, user_id, item_id))
c.execute('''update user_item set amount=? where user_id=? and item_id=? and type=?''',
(x[0]+amount, user_id, item_id, item_type))
else:
if amount < 0: # 添加数量错误
return False
c.execute('''insert into user_item values(?,?,"core",?)''',
(user_id, item_id, amount))
c.execute('''insert into user_item values(?,?,?,?)''',
(user_id, item_id, item_type, amount))
elif item_type == 'memory':
c.execute('''select ticket from user where user_id=?''', (user_id,))

View File

@@ -1,5 +1,6 @@
from server.sql import Connect
from setting import Config
from .config import Constant
import server.info
import server.character
@@ -90,7 +91,7 @@ def change_char_uncap(user_id, character_id):
"overdrive": server.character.calc_char_value(y[2], y[11], y[14], y[17]),
"prog": server.character.calc_char_value(y[2], y[10], y[13], y[16]),
"frag": server.character.calc_char_value(y[2], y[9], y[12], y[15]),
"level_exp": server.character.LEVEL_STEPS[y[2]],
"level_exp": Constant.LEVEL_STEPS[y[2]],
"exp": y[3],
"level": y[2],
"name": y[7],

View File

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

View File

@@ -19,7 +19,7 @@ class Config():
游戏API地址前缀
Game API's URL prefix
'''
GAME_API_PREFIX = '/merikuri/17'
GAME_API_PREFIX = '/years/19'
'''
--------------------
'''
@@ -30,7 +30,7 @@ class Config():
Allowed game versions
If it is blank, all are allowed.
'''
ALLOW_APPVERSION = ['3.5.3', '3.5.3c', '3.11.2', '3.11.2c']
ALLOW_APPVERSION = ['3.12.6', '3.12.6c', '3.12.8', '3.12.8c']
'''
--------------------
'''
@@ -46,6 +46,17 @@ class Config():
--------------------
'''
'''
--------------------
联机功能地址,留空则自动获取
Link Play address
If left blank, it will be obtained automatically.
'''
LINK_PLAY_HOST = '' # ***.com
'''
--------------------
'''
'''
--------------------
SSL证书路径
@@ -64,7 +75,7 @@ class Config():
愚人节模式开关
Switch of April Fool's Day
'''
IS_APRILFOOLS = True
IS_APRILFOOLS = False
'''
--------------------
'''
@@ -225,7 +236,7 @@ class Config():
'''
--------------------
是否全解锁世界场景
If unlocking all world scenerys is enabled
If unlocking all world sceneries is enabled
'''
WORLD_SCENERY_FULL_UNLOCK = True
'''

View File

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

View File

@@ -27,6 +27,10 @@
<span>Discount to: </span>
<span class="char-num">{{item['discount_to']}}</span>
<br />
<span>Whether it is allowed to purchase with exchange certificate: </span>
<span class="char-num">{{item['discount_reason']}}</span>
<br />
{% if item['items'] %}<br />
{% for x in item['items'] %}
<span>Item id: </span>

View File

@@ -27,6 +27,7 @@
<option value='core'>Character core</option>
<option value='fragment'>Fragment</option>
<option value='memory'>Memory</option>
<option value='anni5tix'>Anniversary 5 ticket</option>
</select>
</div>
<label for="amount">Amount</label>

View File

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

View File

@@ -38,6 +38,7 @@
<option value='core'>Character core</option>
<option value='fragment'>Fragment</option>
<option value='memory'>Memory</option>
<option value='anni5tix'>Anniversary 5 ticket</option>
</select>
</div>
<label for="amount">Amount</label>

View File

@@ -418,7 +418,7 @@ def all_character():
def change_character():
# 修改角色数据
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap']
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', 'skill_lagrange', 'gauge_safe_10', 'frags_nami', 'skill_elizabeth', 'skill_lily', 'skill_kanae_midsummer', 'eto_uncap', 'luna_uncap', 'frags_preferred_song', 'visual_ghost_skynotes', 'ayu_uncap', 'skill_vita']
return render_template('web/changechar.html', skill_ids=skill_ids)
@@ -764,6 +764,7 @@ def change_purchase():
orig_price = request.form['orig_price']
discount_from = request.form['discount_from']
discount_to = request.form['discount_to']
discount_reason = request.form['discount_reason']
if price:
price = int(price)
@@ -783,6 +784,10 @@ def change_purchase():
discount_to, "%Y-%m-%dT%H:%M"))) * 1000
else:
discount_to = -1
if not discount_reason:
discount_reason = ''
except:
error = '数据错误 Wrong data.'
flash(error)
@@ -792,8 +797,8 @@ def change_purchase():
c.execute(
'''select exists(select * from purchase where purchase_name=:a)''', {'a': purchase_name})
if c.fetchone() == (0,):
c.execute('''insert into purchase values(?,?,?,?,?)''',
(purchase_name, price, orig_price, discount_from, discount_to))
c.execute('''insert into purchase values(?,?,?,?,?,?)''',
(purchase_name, price, orig_price, discount_from, discount_to, discount_reason))
flash('购买项目添加成功 Successfully add the purchase.')
else:

View File

@@ -288,6 +288,7 @@ def get_all_purchase():
discount_from = None
discount_to = None
discount_reason = 'Yes' if i[5] == 'anni5tix' else 'No'
if i[3] and i[3] >= 0:
discount_from = time.strftime(
@@ -302,13 +303,15 @@ def get_all_purchase():
items = []
if y:
for j in y:
items.append({'item_id': j[1], 'type': j[2], 'amount':j[3]})
items.append(
{'item_id': j[1], 'type': j[2], 'amount': j[3]})
re.append({'purchase_name': i[0],
'price': i[1],
'orig_price': i[2],
'discount_from': discount_from,
'discount_to': discount_to,
'discount_reason': discount_reason,
'items': items
})