mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-09 09:17:26 +08:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a4ff11578 | ||
|
|
18e79a7799 | ||
|
|
e548d5639f | ||
|
|
bd74d96250 | ||
|
|
880b66a995 | ||
|
|
8efd11cd02 | ||
|
|
1c58aeb526 | ||
|
|
6f82e9b269 | ||
|
|
f92d8a9642 | ||
|
|
1672d337ff |
87
README.md
87
README.md
@@ -17,42 +17,53 @@ This procedure is mainly used for study and research, and shall not be used for
|
|||||||
|
|
||||||
## 特性 Features
|
## 特性 Features
|
||||||
|
|
||||||
有以下 We have:
|
:x: : 不支持 Not supported
|
||||||
|
:warning: : 可能存在问题 / 可能与官方不一样 Possible issues / may differ from official
|
||||||
|
:wastebasket: : 不再更新,可能会移除或重构 No longer updated, may be removed or refactored
|
||||||
|
:construction: : 建设中 In construction
|
||||||
|
|
||||||
- 登录、注册 Login and registration
|
- 登录、注册 Login and registration
|
||||||
- 多设备登录 Multi device login
|
- 多设备自动封号 Auto-ban of multiple devices
|
||||||
|
- :warning: 多设备登录 Multi device login
|
||||||
|
- 登录频次限制 Login rate limit
|
||||||
|
- :x: 销号 Destroy account
|
||||||
- 成绩上传 Score upload
|
- 成绩上传 Score upload
|
||||||
- PTT
|
- 成绩校验 Score check
|
||||||
- 世界排名 Global rank
|
- 成绩排名 Score rank
|
||||||
- 排名 Rank
|
- 潜力值机制 Potential
|
||||||
|
- Best 30
|
||||||
|
- :warning: Recent Top 10
|
||||||
|
- :warning: 世界排名 Global rank
|
||||||
- 段位系统 Course system
|
- 段位系统 Course system
|
||||||
- Link Play
|
- :warning: Link Play
|
||||||
- 好友系统 Friends
|
- 好友系统 Friends
|
||||||
|
- :x: 好友位提升 Max friend number increase
|
||||||
- 云端存档 Cloud save
|
- 云端存档 Cloud save
|
||||||
- 爬梯 Climbing steps
|
- 尝试全剧情、曲目解锁 Try to unlock all the stories and songs
|
||||||
- 自定义世界模式 Customizable World Mode
|
- 世界模式 World mode
|
||||||
- 自定义歌曲下载 Customizable songs download
|
- 体力系统 Stamina system
|
||||||
- 单曲和曲包购买(没啥用) Single songs and song packs purchase(useless)
|
- :warning: 普通梯子强化和绳子强化 Normal steps boost & beyond boost
|
||||||
|
- :warning: 角色技能 Character skills
|
||||||
|
- 歌曲下载 Songs downloading
|
||||||
|
- :x: 加密下载 Encrypted downloading
|
||||||
|
- 下载校验 Download check
|
||||||
|
- 下载频次限制 Download rate limit
|
||||||
|
- 购买系统 Purchase system
|
||||||
|
- 单曲和曲包 Single & Pack
|
||||||
|
- :x: 捆绑包 Bundle
|
||||||
|
- 折扣 Discount
|
||||||
|
- 五周年兑换券 5-th anniversary ticket
|
||||||
|
- :x: Extend 包自动降价 Extend pack automatic price reduction
|
||||||
- 奖励系统 Present system
|
- 奖励系统 Present system
|
||||||
- 兑换码系统 Redeem code system
|
- 兑换码系统 Redeem code system
|
||||||
- 角色系统 Character system
|
- 角色系统 Character system
|
||||||
- 全剧情解锁 Unlock all the stories
|
- 数据记录 Data recording
|
||||||
- 后台查询 Background search
|
- 用户成绩 Users' scores
|
||||||
- 后台自定义信息 Customize some things in the background
|
- 用户每日潜力值 Users' daily potential
|
||||||
- 成绩校验 Score check
|
- :wastebasket: 简单的网页管理后台 Simple web admin backend
|
||||||
- 下载校验 Download check
|
- :construction: API
|
||||||
- 服务器日志 Server log
|
- 服务器日志 Server log
|
||||||
|
|
||||||
没有以下 We don't have:
|
|
||||||
|
|
||||||
- 服务器安全性保证 Server security assurance
|
|
||||||
|
|
||||||
可能有问题 There may be problems:
|
|
||||||
|
|
||||||
- Recent 30
|
|
||||||
- 一些歌曲的解锁 Some songs' unlocking
|
|
||||||
- 同设备多共存登录 Multiple app logins on the same device
|
|
||||||
|
|
||||||
## 说明 Statement
|
## 说明 Statement
|
||||||
|
|
||||||
只是很有趣,用处探索中。
|
只是很有趣,用处探索中。
|
||||||
@@ -70,27 +81,27 @@ It is just so interesting. What it can do is under exploration.
|
|||||||
只保留最新版本 Only keep the latest version.
|
只保留最新版本 Only keep the latest version.
|
||||||
|
|
||||||
> 提醒:更新时请注意保留原先的数据库,以防数据丢失。
|
> 提醒:更新时请注意保留原先的数据库,以防数据丢失。
|
||||||
>
|
|
||||||
> Tips: When updating, please keep the original database in case of data loss.
|
> Tips: When updating, please keep the original database in case of data loss.
|
||||||
|
>
|
||||||
|
> 其它小改动请参考各个 commit 信息
|
||||||
|
> Please refer to the commit messages for other minor changes.
|
||||||
|
|
||||||
### Version 2.11.0
|
### Version 2.11.2
|
||||||
|
|
||||||
- 适用于Arcaea 4.3.0版本 For Arcaea 4.3.0
|
- 适用于 Arcaea 4.4.6 版本 For Arcaea 4.4.6
|
||||||
- 新搭档 **霞玛(大~宇~宙)**、**米露可(大~宇~宙)**、**紫黑**、**百合咲美香** 已解锁 Unlock the character **Shama(UNiVERSE)**, **Milk(UNiVERSE)**, **Shikoku**, **Mika Yurisaki**.
|
- 新搭档 **奈美(暮光)** 已解锁 Unlock the character **Nami (Twilight)**.
|
||||||
- 搭档 **依莉丝** 已觉醒 Uncap the character **Ilith**.
|
- 新增用户潜力值每日记录功能 Add support for recording users' potential each day.
|
||||||
- 为觉醒 **依莉丝** 以及 **百合咲美香** 的技能提供支持 Add support for the skills of uncapped **Ilith** and **Mika Yurisaki**.
|
- 修复搭档 **光 & 对立(Reunion)** 无法觉醒的问题 Fix a bug that the character **Hikari & Tairitsu (Reunion)** cannot be uncapped. (#100)
|
||||||
- 为 Beyond 图倍增提供支持 Add support for beyond gauge boost.
|
- 添加 `finale/finale_end` 接口尝试修复最终挑战无法解锁结局的问题 Add the `finale/finale_end` endpoint to try to fix the problem that the endings cannot be unlocked correctly in the finale challenge. (#110)
|
||||||
- 为 Beyond 连锁图提供支持 Add support for beyond chain maps.
|
- 新增获取用户潜力值记录的 API 接口 Add an API endpoint for getting the user's rating records.
|
||||||
- 修复联机时无人房间仍可进入的问题 Fix a logic bug that the room without anyone can be entered in multiplayer.
|
|
||||||
- 对一些数值的算法进行了更改 Some changes in some values' algorithms.
|
|
||||||
- 小重构 Link Play 子程序 Refactor simply for Link Play subprogram.
|
|
||||||
- 新增增删改兑换码、购买项目、登陆奖励、物品的API接口 Add some API endpoints, including creating, changing, deleting about redeem, purchase, login present and item.
|
|
||||||
|
|
||||||
## 运行环境与依赖 Running environment and requirements
|
## 运行环境与依赖 Running environment and requirements
|
||||||
|
|
||||||
- Windows / Linux / Mac OS / Android...
|
- Windows / Linux / Mac OS / Android...
|
||||||
- Python >= 3.6
|
- Python >= 3.6
|
||||||
- Flask module >= 2.0, Cryptography module >= 3.0.0, limits >= 2.7.0
|
- Flask >= 2.0
|
||||||
|
- Cryptography >= 3.0.0
|
||||||
|
- limits >= 2.7.0
|
||||||
- Charles, IDA, proxy app... (optional)
|
- Charles, IDA, proxy app... (optional)
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
from . import (users, songs, token, system, items,
|
from . import (users, songs, token, system, items,
|
||||||
purchases, presents, redeems)
|
purchases, presents, redeems, characters)
|
||||||
|
|
||||||
bp = Blueprint('api', __name__, url_prefix='/api/v1')
|
bp = Blueprint('api', __name__, url_prefix='/api/v1')
|
||||||
bp.register_blueprint(users.bp)
|
bp.register_blueprint(users.bp)
|
||||||
@@ -12,3 +12,4 @@ bp.register_blueprint(items.bp)
|
|||||||
bp.register_blueprint(purchases.bp)
|
bp.register_blueprint(purchases.bp)
|
||||||
bp.register_blueprint(presents.bp)
|
bp.register_blueprint(presents.bp)
|
||||||
bp.register_blueprint(redeems.bp)
|
bp.register_blueprint(redeems.bp)
|
||||||
|
bp.register_blueprint(characters.bp)
|
||||||
|
|||||||
@@ -52,11 +52,12 @@ def role_required(request, powers=[]):
|
|||||||
|
|
||||||
def request_json_handle(request, required_keys: list = [], optional_keys: list = [], must_change: bool = False, is_batch: bool = False):
|
def request_json_handle(request, required_keys: list = [], optional_keys: list = [], must_change: bool = False, is_batch: bool = False):
|
||||||
'''
|
'''
|
||||||
提取post参数,返回dict,写成了修饰器\
|
提取post参数,返回dict,写成了修饰器
|
||||||
parameters: \
|
|
||||||
`request`: `Request` - 当前请求\
|
parameters:
|
||||||
`required_keys`: `list` - 必须的参数\
|
`request`: `Request` - 当前请求
|
||||||
`optional_keys`: `list` - 可选的参数\
|
`required_keys`: `list` - 必须的参数
|
||||||
|
`optional_keys`: `list` - 可选的参数
|
||||||
`must_change`: `bool` - 当全都是可选参数时,是否必须有至少一项修改
|
`must_change`: `bool` - 当全都是可选参数时,是否必须有至少一项修改
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -113,7 +114,6 @@ def api_try(view):
|
|||||||
data = view(*args, **kwargs)
|
data = view(*args, **kwargs)
|
||||||
if data is None:
|
if data is None:
|
||||||
return error_return()
|
return error_return()
|
||||||
else:
|
|
||||||
return data
|
return data
|
||||||
except ArcError as e:
|
except ArcError as e:
|
||||||
if Config.ALLOW_WARNING_LOG:
|
if Config.ALLOW_WARNING_LOG:
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ CODE_MSG = {
|
|||||||
-122: 'Item already exists',
|
-122: 'Item already exists',
|
||||||
-123: 'The collection already has this item',
|
-123: 'The collection already has this item',
|
||||||
-124: 'The collection does not have this item',
|
-124: 'The collection does not have this item',
|
||||||
|
-130: 'No such character',
|
||||||
|
-131: 'Invalid skill ID',
|
||||||
-200: 'No permission', # 2xx用户相关错误
|
-200: 'No permission', # 2xx用户相关错误
|
||||||
-201: 'Wrong username or password',
|
-201: 'Wrong username or password',
|
||||||
-202: 'User is banned',
|
-202: 'User is banned',
|
||||||
|
|||||||
134
latest version/api/characters.py
Normal file
134
latest version/api/characters.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from core.error import InputError, NoData
|
||||||
|
from core.item import ItemFactory
|
||||||
|
from core.character import Character
|
||||||
|
from core.sql import Connect, Query, Sql
|
||||||
|
|
||||||
|
from .api_auth import api_try, request_json_handle, role_required
|
||||||
|
from .api_code import success_return
|
||||||
|
from .constant import Constant
|
||||||
|
|
||||||
|
|
||||||
|
bp = Blueprint('characters', __name__, url_prefix='/characters')
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('', methods=['GET'])
|
||||||
|
@role_required(request, ['select'])
|
||||||
|
@request_json_handle(request, optional_keys=Constant.QUERY_KEYS)
|
||||||
|
@api_try
|
||||||
|
def characters_get(data, user):
|
||||||
|
'''查询全角色信息'''
|
||||||
|
A = ['character_id', 'name', 'skill_id',
|
||||||
|
'skill_id_uncap', 'char_type', 'is_uncapped']
|
||||||
|
B = ['name', 'skill_id', 'skill_id_uncap']
|
||||||
|
C = ['name', 'frag1', 'prog1', 'overdrive1', 'frag20',
|
||||||
|
'prog20', 'overdrive20', 'frag30', 'prog30', 'overdrive30']
|
||||||
|
with Connect() as c:
|
||||||
|
query = Query(A, B, C).from_dict(data)
|
||||||
|
x = Sql(c).select('character', query=query)
|
||||||
|
r = [Character().from_list(i) for i in x]
|
||||||
|
|
||||||
|
if not r:
|
||||||
|
raise NoData(api_error_code=-2)
|
||||||
|
|
||||||
|
return success_return([x.to_dict() for x in r])
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:character_id>', methods=['GET'])
|
||||||
|
@role_required(request, ['select'])
|
||||||
|
@api_try
|
||||||
|
def characters_character_get(user, character_id: int):
|
||||||
|
# 包含core
|
||||||
|
with Connect() as c:
|
||||||
|
c = Character(c).select(character_id)
|
||||||
|
c.select_character_core()
|
||||||
|
return success_return(c.to_dict(has_cores=True))
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:character_id>', methods=['PUT'])
|
||||||
|
@role_required(request, ['change'])
|
||||||
|
@request_json_handle(request, optional_keys=['max_level', 'skill_id', 'skill_id_uncap', 'skill_unlock_level', 'skill_requires_uncap', 'char_type', 'is_uncapped', 'frag1', 'prog1', 'overdrive1', 'frag20', 'prog20', 'overdrive20', 'frag30', 'prog30', 'overdrive30'], must_change=True)
|
||||||
|
@api_try
|
||||||
|
def characters_character_put(data, user, character_id: int):
|
||||||
|
'''修改角色信息'''
|
||||||
|
if ('skill_id' in data and data['skill_id'] != '' and data['skill_id'] not in Constant.SKILL_IDS) or ('skill_id_uncap' in data and data['skill_id_uncap'] != '' and data['skill_id_uncap'] not in Constant.SKILL_IDS):
|
||||||
|
raise InputError('Invalid skill_id', api_error_code=-131)
|
||||||
|
with Connect() as c:
|
||||||
|
c = Character(c).select(character_id)
|
||||||
|
try:
|
||||||
|
if 'max_level' in data:
|
||||||
|
c.max_level = int(data['max_level'])
|
||||||
|
if 'skill_id' in data:
|
||||||
|
c.skill_id = data['skill_id']
|
||||||
|
if 'skill_id_uncap' in data:
|
||||||
|
c.skill_id_uncap = data['skill_id_uncap']
|
||||||
|
if 'skill_unlock_level' in data:
|
||||||
|
c.skill_unlock_level = int(data['skill_unlock_level'])
|
||||||
|
if 'skill_requires_uncap' in data:
|
||||||
|
c.skill_requires_uncap = data['skill_requires_uncap'] == 1
|
||||||
|
if 'char_type' in data:
|
||||||
|
c.char_type = int(data['char_type'])
|
||||||
|
if 'is_uncapped' in data:
|
||||||
|
c.is_uncapped = data['is_uncapped'] == 1
|
||||||
|
t = ['frag1', 'prog1', 'overdrive1', 'frag20', 'prog20',
|
||||||
|
'overdrive20', 'frag30', 'prog30', 'overdrive30']
|
||||||
|
for i in t:
|
||||||
|
if i not in data:
|
||||||
|
continue
|
||||||
|
if i.endswith('1'):
|
||||||
|
x = getattr(c, i[:-1])
|
||||||
|
x.start = float(data[i])
|
||||||
|
elif i.endswith('20'):
|
||||||
|
x = getattr(c, i[:-2])
|
||||||
|
x.mid = float(data[i])
|
||||||
|
else:
|
||||||
|
x = getattr(c, i[:-2])
|
||||||
|
x.end = float(data[i])
|
||||||
|
except ValueError as e:
|
||||||
|
raise InputError('Invalid input', api_error_code=-101) from e
|
||||||
|
c.update()
|
||||||
|
return success_return(c.to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:character_id>/cores', methods=['GET'])
|
||||||
|
@role_required(request, ['select'])
|
||||||
|
@api_try
|
||||||
|
def characters_character_cores_get(user, character_id: int):
|
||||||
|
with Connect() as c:
|
||||||
|
c = Character(c)
|
||||||
|
c.character_id = character_id
|
||||||
|
c.select_character_core()
|
||||||
|
return success_return(c.uncap_cores_to_dict())
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:character_id>/cores', methods=['PATCH'])
|
||||||
|
@role_required(request, ['change'])
|
||||||
|
@request_json_handle(request, is_batch=True)
|
||||||
|
@api_try
|
||||||
|
def characters_character_cores_patch(data, user, character_id: int):
|
||||||
|
'''修改角色觉醒cores'''
|
||||||
|
def force_type_core(x: dict) -> dict:
|
||||||
|
x['item_type'] = 'core'
|
||||||
|
x['type'] = 'core'
|
||||||
|
return x
|
||||||
|
|
||||||
|
with Connect() as c:
|
||||||
|
ch = Character(c)
|
||||||
|
ch.character_id = character_id
|
||||||
|
ch.select_character_core()
|
||||||
|
ch.remove_items([ItemFactory.from_dict(x, c=c)
|
||||||
|
for x in map(force_type_core, data.get('remove', []))])
|
||||||
|
ch.add_items([ItemFactory.from_dict(x, c=c)
|
||||||
|
for x in map(force_type_core, data.get('create', []))])
|
||||||
|
updates = list(map(force_type_core, data.get('update', [])))
|
||||||
|
for x in updates:
|
||||||
|
if 'amount' not in x:
|
||||||
|
raise InputError('`amount` is required in `update`')
|
||||||
|
if not isinstance(x['amount'], int) or x['amount'] <= 0:
|
||||||
|
raise InputError(
|
||||||
|
'`amount` must be a positive integer', api_error_code=-101)
|
||||||
|
|
||||||
|
ch.update_items(
|
||||||
|
[ItemFactory.from_dict(x, c=c) for x in updates])
|
||||||
|
return success_return(ch.uncap_cores_to_dict())
|
||||||
@@ -2,3 +2,6 @@ class Constant:
|
|||||||
QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort']
|
QUERY_KEYS = ['limit', 'offset', 'query', 'fuzzy_query', 'sort']
|
||||||
|
|
||||||
PATCH_KEYS = ['create', 'update', 'remove']
|
PATCH_KEYS = ['create', 'update', 'remove']
|
||||||
|
|
||||||
|
SKILL_IDS = ['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', 'skill_vita', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa']
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from base64 import b64decode
|
from base64 import b64decode
|
||||||
|
|
||||||
from flask import Blueprint, request
|
from flask import Blueprint, current_app, request
|
||||||
|
|
||||||
from core.api_user import APIUser
|
from core.api_user import APIUser
|
||||||
from core.error import PostError
|
from core.error import PostError
|
||||||
@@ -17,21 +17,23 @@ bp = Blueprint('token', __name__, url_prefix='/token')
|
|||||||
@api_try
|
@api_try
|
||||||
def token_post(data):
|
def token_post(data):
|
||||||
'''
|
'''
|
||||||
登录,获取token\
|
登录,获取token
|
||||||
|
|
||||||
{'auth': base64('<user_id>:<password>')}
|
{'auth': base64('<user_id>:<password>')}
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
auth_decode = bytes.decode(b64decode(data['auth']))
|
auth_decode = bytes.decode(b64decode(data['auth']))
|
||||||
except:
|
except Exception as e:
|
||||||
raise PostError(api_error_code=-100)
|
raise PostError(api_error_code=-100) from e
|
||||||
if not ':' in auth_decode:
|
if ':' not in auth_decode:
|
||||||
raise PostError(api_error_code=-100)
|
raise PostError(api_error_code=-100)
|
||||||
name, password = auth_decode.split(':', 1)
|
name, password = auth_decode.split(':', 1)
|
||||||
|
|
||||||
with Connect() as c:
|
with Connect() as c:
|
||||||
user = APIUser(c)
|
user = APIUser(c)
|
||||||
user.login(name, password, request.remote_addr)
|
user.login(name, password, request.remote_addr)
|
||||||
return success_return({'token': user.token, 'user_id': user.user_id})
|
current_app.logger.info(f'API user `{user.user_id}` log in')
|
||||||
|
return success_return({'token': user.api_token, 'user_id': user.user_id})
|
||||||
|
|
||||||
|
|
||||||
@bp.route('', methods=['GET'])
|
@bp.route('', methods=['GET'])
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
from flask import Blueprint, request
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.api_user import APIUser
|
from core.api_user import APIUser
|
||||||
|
from core.config_manager import Config
|
||||||
from core.error import InputError, NoAccess, NoData
|
from core.error import InputError, NoAccess, NoData
|
||||||
from core.score import Potential, UserScoreList
|
from core.score import Potential, UserScoreList
|
||||||
from core.sql import Connect, Query, Sql
|
from core.sql import Connect, Query, Sql
|
||||||
from core.user import UserChanger, UserInfo, UserRegister
|
from core.user import UserChanger, UserInfo, UserRegister
|
||||||
|
from core.util import get_today_timestamp
|
||||||
|
|
||||||
from .api_auth import api_try, request_json_handle, role_required
|
from .api_auth import api_try, request_json_handle, role_required
|
||||||
from .api_code import error_return, success_return
|
from .api_code import error_return, success_return
|
||||||
@@ -128,7 +130,7 @@ def users_user_b30_get(user, user_id):
|
|||||||
f'No best30 data of user `{user_id}`', api_error_code=-3)
|
f'No best30 data of user `{user_id}`', api_error_code=-3)
|
||||||
x.select_song_name()
|
x.select_song_name()
|
||||||
r = x.to_dict_list()
|
r = x.to_dict_list()
|
||||||
rating_sum = sum([i.rating for i in x.scores])
|
rating_sum = sum(i.rating for i in x.scores)
|
||||||
return success_return({'user_id': user_id, 'b30_ptt': rating_sum / 30, 'data': r})
|
return success_return({'user_id': user_id, 'b30_ptt': rating_sum / 30, 'data': r})
|
||||||
|
|
||||||
|
|
||||||
@@ -191,3 +193,32 @@ def users_user_role_get(user, user_id):
|
|||||||
x = APIUser(c, user_id)
|
x = APIUser(c, user_id)
|
||||||
x.select_role_and_powers()
|
x.select_role_and_powers()
|
||||||
return success_return({'user_id': x.user_id, 'role': x.role.role_id, 'powers': [i.power_id for i in x.role.powers]})
|
return success_return({'user_id': x.user_id, 'role': x.role.role_id, 'powers': [i.power_id for i in x.role.powers]})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/<int:user_id>/rating', methods=['GET'])
|
||||||
|
@role_required(request, ['select', 'select_me'])
|
||||||
|
@request_json_handle(request, optional_keys=['start_timestamp', 'end_timestamp', 'duration'])
|
||||||
|
@api_try
|
||||||
|
def users_user_rating_get(data, user, user_id):
|
||||||
|
'''查询用户历史rating,`duration`是相对于今天的天数'''
|
||||||
|
# 查别人需要select权限
|
||||||
|
if user_id != user.user_id and not user.role.has_power('select'):
|
||||||
|
return error_return(NoAccess('No permission', api_error_code=-1), 403)
|
||||||
|
|
||||||
|
start_timestamp = data.get('start_timestamp', None)
|
||||||
|
end_timestamp = data.get('end_timestamp', None)
|
||||||
|
duration = data.get('duration', None)
|
||||||
|
sql = '''select time, rating_ptt from user_rating where user_id = ?'''
|
||||||
|
sql_data = [user_id]
|
||||||
|
if start_timestamp is not None and end_timestamp is not None:
|
||||||
|
sql += ''' and time between ? and ?'''
|
||||||
|
sql_data += [start_timestamp, end_timestamp]
|
||||||
|
elif duration is not None:
|
||||||
|
sql += ''' and time between ? and ?'''
|
||||||
|
t = get_today_timestamp()
|
||||||
|
sql_data += [t - duration * 24 * 3600, t]
|
||||||
|
|
||||||
|
with Connect(Config.SQLITE_LOG_DATABASE_PATH) as c:
|
||||||
|
c.execute(sql, sql_data)
|
||||||
|
r = c.fetchall()
|
||||||
|
return success_return({'user_id': user_id, 'data': [{'time': i[0], 'rating_ptt': i[1]} for i in r]})
|
||||||
|
|||||||
@@ -55,8 +55,8 @@ class Role:
|
|||||||
{'a': self.role_id})
|
{'a': self.role_id})
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if x is None:
|
if x is None:
|
||||||
raise NoData('The role `%s` does not exist.' %
|
raise NoData(
|
||||||
self.role_id, api_error_code=-200)
|
f'The role `{self.role_id}` does not exist.', api_error_code=-200)
|
||||||
self.caption = x[0]
|
self.caption = x[0]
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -127,25 +127,26 @@ class APIUser(UserOnline):
|
|||||||
if ip is not None:
|
if ip is not None:
|
||||||
self.ip = ip
|
self.ip = ip
|
||||||
if not self.limiter.hit(name):
|
if not self.limiter.hit(name):
|
||||||
raise RateLimit('Too many login attempts', api_error_code=-205)
|
raise RateLimit(
|
||||||
|
f'Too many login attempts of username {name}', api_error_code=-205)
|
||||||
|
|
||||||
self.c.execute('''select user_id, password from user where name = :a''', {
|
self.c.execute('''select user_id, password from user where name = :a''', {
|
||||||
'a': self.name})
|
'a': self.name})
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if x is None:
|
if x is None:
|
||||||
raise NoData('The user `%s` does not exist.' %
|
raise NoData(
|
||||||
self.name, api_error_code=-201, status=401)
|
f'The user `{self.name}` does not exist.', api_error_code=-201, status=401)
|
||||||
if x[1] == '':
|
if x[1] == '':
|
||||||
raise UserBan('The user `%s` is banned.' % self.name)
|
raise UserBan(f'The user `{x[0]}` is banned.')
|
||||||
if self.hash_pwd != x[1]:
|
if self.hash_pwd != x[1]:
|
||||||
raise NoAccess('The password is incorrect.',
|
raise NoAccess(f'The password of user `{x[0]}` is incorrect.',
|
||||||
api_error_code=-201, status=401)
|
api_error_code=-201, status=401)
|
||||||
|
|
||||||
self.user_id = x[0]
|
self.user_id = x[0]
|
||||||
now = int(time() * 1000)
|
now = int(time() * 1000)
|
||||||
self.token = sha256(
|
self.api_token = sha256(
|
||||||
(str(self.user_id) + str(now)).encode("utf8") + urandom(8)).hexdigest()
|
(str(self.user_id) + str(now)).encode("utf8") + urandom(8)).hexdigest()
|
||||||
|
|
||||||
self.logout()
|
self.logout()
|
||||||
self.c.execute('''insert into api_login values(?,?,?,?)''',
|
self.c.execute('''insert into api_login values(?,?,?,?)''',
|
||||||
(self.user_id, self.token, now, self.ip))
|
(self.user_id, self.api_token, now, self.ip))
|
||||||
|
|||||||
50
latest version/core/bgtask.py
Normal file
50
latest version/core/bgtask.py
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
from atexit import register
|
||||||
|
from concurrent.futures import ThreadPoolExecutor
|
||||||
|
|
||||||
|
from .constant import Constant
|
||||||
|
from .sql import Connect
|
||||||
|
|
||||||
|
|
||||||
|
class BGTask:
|
||||||
|
executor = ThreadPoolExecutor(max_workers=1)
|
||||||
|
|
||||||
|
def __init__(self, func, *args, **kwargs):
|
||||||
|
self.future = self.executor.submit(func, *args, **kwargs)
|
||||||
|
|
||||||
|
def result(self):
|
||||||
|
return self.future.result()
|
||||||
|
|
||||||
|
def cancel(self) -> bool:
|
||||||
|
return self.future.cancel()
|
||||||
|
|
||||||
|
def done(self) -> bool:
|
||||||
|
return self.future.done()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def shutdown(wait: bool = True):
|
||||||
|
BGTask.executor.shutdown(wait)
|
||||||
|
|
||||||
|
|
||||||
|
@register
|
||||||
|
def atexit():
|
||||||
|
BGTask.shutdown()
|
||||||
|
|
||||||
|
|
||||||
|
def logdb_execute_func(sql, *args, **kwargs):
|
||||||
|
with Connect(Constant.SQLITE_LOG_DATABASE_PATH) as c:
|
||||||
|
c.execute(sql, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def logdb_execute_many_func(sql, *args, **kwargs):
|
||||||
|
with Connect(Constant.SQLITE_LOG_DATABASE_PATH) as c:
|
||||||
|
c.executemany(sql, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def logdb_execute(sql: str, *args, **kwargs):
|
||||||
|
'''异步执行SQL,日志库写入,注意不会直接返回结果'''
|
||||||
|
return BGTask(logdb_execute_func, sql, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def logdb_execute_many(sql: str, *args, **kwargs):
|
||||||
|
'''异步批量执行SQL,日志库写入,注意不会直接返回结果'''
|
||||||
|
return BGTask(logdb_execute_many_func, sql, *args, **kwargs)
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
from .config_manager import Config
|
from .config_manager import Config
|
||||||
from .constant import Constant
|
from .constant import Constant
|
||||||
from .error import ArcError, InputError, ItemNotEnough, NoData
|
from .error import ArcError, InputError, ItemNotEnough, NoData
|
||||||
from .item import Item, ItemCore
|
from .item import CollectionItemMixin, ItemCore
|
||||||
|
|
||||||
|
|
||||||
class Level:
|
class Level:
|
||||||
@@ -9,9 +9,9 @@ class Level:
|
|||||||
min_level = 1
|
min_level = 1
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.max_level = None
|
self.max_level: int = None
|
||||||
self.level = None
|
self.level: int = None
|
||||||
self.exp = None
|
self.exp: float = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def level_exp(self):
|
def level_exp(self):
|
||||||
@@ -29,9 +29,9 @@ class Level:
|
|||||||
|
|
||||||
a = []
|
a = []
|
||||||
b = []
|
b = []
|
||||||
for i in Constant.LEVEL_STEPS:
|
for k, v in Constant.LEVEL_STEPS.items():
|
||||||
a.append(i)
|
a.append(k)
|
||||||
b.append(Constant.LEVEL_STEPS[i])
|
b.append(v)
|
||||||
|
|
||||||
if exp < b[0]: # 向下溢出,是异常状态,不该被try捕获,不然数据库无法回滚
|
if exp < b[0]: # 向下溢出,是异常状态,不该被try捕获,不然数据库无法回滚
|
||||||
raise ValueError('EXP value error.')
|
raise ValueError('EXP value error.')
|
||||||
@@ -46,23 +46,10 @@ class Level:
|
|||||||
|
|
||||||
class Skill:
|
class Skill:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.skill_id = None
|
self.skill_id: str = None
|
||||||
self.skill_id_uncap = None
|
self.skill_id_uncap: str = None
|
||||||
self.skill_unlock_level = None
|
self.skill_unlock_level: int = None
|
||||||
self.skill_requires_uncap = None
|
self.skill_requires_uncap: bool = None
|
||||||
|
|
||||||
|
|
||||||
class Core(Item):
|
|
||||||
item_type = 'core'
|
|
||||||
|
|
||||||
def __init__(self, core_type: str = '', amount: int = 0) -> None:
|
|
||||||
super().__init__()
|
|
||||||
self.item_id = core_type
|
|
||||||
self.amount = amount
|
|
||||||
self.is_available = True
|
|
||||||
|
|
||||||
def to_dict(self):
|
|
||||||
return {'core_type': self.item_id, 'amount': self.amount}
|
|
||||||
|
|
||||||
|
|
||||||
class CharacterValue:
|
class CharacterValue:
|
||||||
@@ -75,7 +62,6 @@ class CharacterValue:
|
|||||||
# 4/6859 = 0.00058317539
|
# 4/6859 = 0.00058317539
|
||||||
if level <= 10:
|
if level <= 10:
|
||||||
return 0.00058317539 * (level - 1) ** 3 * (value_20 - value_1) + value_1
|
return 0.00058317539 * (level - 1) ** 3 * (value_20 - value_1) + value_1
|
||||||
else:
|
|
||||||
return - 0.00058317539 * (20 - level) ** 3 * (value_20 - value_1) + value_20
|
return - 0.00058317539 * (20 - level) ** 3 * (value_20 - value_1) + value_20
|
||||||
|
|
||||||
# @staticmethod
|
# @staticmethod
|
||||||
@@ -95,44 +81,51 @@ class CharacterValue:
|
|||||||
return (level - lva) * (statb - stata) / (lvb - lva) + stata
|
return (level - lva) * (statb - stata) / (lvb - lva) + stata
|
||||||
|
|
||||||
def set_parameter(self, start: float = 0, mid: float = 0, end: float = 0):
|
def set_parameter(self, start: float = 0, mid: float = 0, end: float = 0):
|
||||||
self.start = start
|
self.start: float = start
|
||||||
self.mid = mid
|
self.mid: float = mid
|
||||||
self.end = end
|
self.end: float = end
|
||||||
|
|
||||||
def get_value(self, level: Level):
|
def get_value(self, level: Level):
|
||||||
if level.min_level <= level.level <= level.mid_level:
|
if level.min_level <= level.level <= level.mid_level:
|
||||||
return self._calc_char_value_20_math(level.level, self.start, self.mid)
|
return self._calc_char_value_20_math(level.level, self.start, self.mid)
|
||||||
elif level.mid_level < level.level <= level.max_level:
|
if level.mid_level < level.level <= level.max_level:
|
||||||
return self._calc_char_value_30(level.level, self.mid, self.end)
|
return self._calc_char_value_30(level.level, self.mid, self.end)
|
||||||
else:
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
class Character:
|
class Character(CollectionItemMixin):
|
||||||
database_table_name = None
|
database_table_name = None
|
||||||
|
|
||||||
|
collection_item_const = {
|
||||||
|
'name': 'character',
|
||||||
|
'table_name': 'char_item',
|
||||||
|
'table_primary_key': 'character_id',
|
||||||
|
'id_name': 'character_id',
|
||||||
|
'items_name': 'uncap_cores'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, c=None) -> None:
|
def __init__(self, c=None) -> None:
|
||||||
self.c = c
|
self.c = c
|
||||||
|
|
||||||
self.character_id = None
|
self.character_id: int = None
|
||||||
self.name = None
|
self.name: str = None
|
||||||
self.char_type = None
|
self.char_type: int = None
|
||||||
self.is_uncapped = None
|
self.is_uncapped: bool = None
|
||||||
self.is_uncapped_override = None
|
self.is_uncapped_override: bool = None
|
||||||
self.skill = Skill()
|
self.skill = Skill()
|
||||||
self.level = Level()
|
self.level = Level()
|
||||||
self.frag = CharacterValue()
|
self.frag = CharacterValue()
|
||||||
self.prog = CharacterValue()
|
self.prog = CharacterValue()
|
||||||
self.overdrive = CharacterValue()
|
self.overdrive = CharacterValue()
|
||||||
self.uncap_cores = []
|
self.uncap_cores: list = []
|
||||||
self.voice = None
|
self.voice: list = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def skill_id_displayed(self) -> str:
|
def skill_id_displayed(self) -> str:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def uncap_cores_to_dict(self):
|
def uncap_cores_to_dict(self):
|
||||||
return [x.to_dict() for x in self.uncap_cores]
|
return [x.to_dict(character_format=True) for x in self.uncap_cores]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_uncapped_displayed(self) -> bool:
|
def is_uncapped_displayed(self) -> bool:
|
||||||
@@ -144,11 +137,71 @@ class Character:
|
|||||||
# 应该是只有对立这样
|
# 应该是只有对立这样
|
||||||
return self.character_id == 1
|
return self.character_id == 1
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self, has_cores=False) -> dict:
|
||||||
pass
|
# 用于API里,游戏内的数据不太一样,故不能super
|
||||||
|
r = {
|
||||||
|
'character_id': self.character_id,
|
||||||
|
'name': self.name,
|
||||||
|
'char_type': self.char_type,
|
||||||
|
'is_uncapped': self.is_uncapped,
|
||||||
|
'max_level': self.level.max_level,
|
||||||
|
'skill_id': self.skill.skill_id,
|
||||||
|
'skill_unlock_level': self.skill.skill_unlock_level,
|
||||||
|
'skill_requires_uncap': self.skill.skill_requires_uncap,
|
||||||
|
'skill_id_uncap': self.skill.skill_id_uncap,
|
||||||
|
'frag1': self.frag.start,
|
||||||
|
'frag20': self.frag.mid,
|
||||||
|
'frag30': self.frag.end,
|
||||||
|
'prog1': self.prog.start,
|
||||||
|
'prog20': self.prog.mid,
|
||||||
|
'prog30': self.prog.end,
|
||||||
|
'overdrive1': self.overdrive.start,
|
||||||
|
'overdrive20': self.overdrive.mid,
|
||||||
|
'overdrive30': self.overdrive.end,
|
||||||
|
}
|
||||||
|
if has_cores:
|
||||||
|
r['uncap_cores'] = self.uncap_cores_to_dict()
|
||||||
|
return r
|
||||||
|
|
||||||
def from_list(self, l: list) -> 'Character':
|
def from_list(self, l: tuple) -> 'Character':
|
||||||
pass
|
self.character_id = l[0]
|
||||||
|
self.name = l[1]
|
||||||
|
self.level.max_level = l[2]
|
||||||
|
self.frag.set_parameter(l[3], l[6], l[9])
|
||||||
|
self.prog.set_parameter(l[4], l[7], l[10])
|
||||||
|
self.overdrive.set_parameter(l[5], l[8], l[11])
|
||||||
|
self.skill.skill_id = l[12]
|
||||||
|
self.skill.skill_unlock_level = l[13]
|
||||||
|
self.skill.skill_requires_uncap = l[14] == 1
|
||||||
|
self.skill.skill_id_uncap = l[15]
|
||||||
|
self.char_type = l[16]
|
||||||
|
self.is_uncapped = l[17] == 1
|
||||||
|
return self
|
||||||
|
|
||||||
|
def select(self, character_id: int = None) -> 'Character':
|
||||||
|
if character_id is not None:
|
||||||
|
self.character_id = character_id
|
||||||
|
self.c.execute(
|
||||||
|
'select * from character where character_id = ?', (self.character_id,))
|
||||||
|
x = self.c.fetchone()
|
||||||
|
if not x:
|
||||||
|
raise NoData(
|
||||||
|
f'No such character: {self.character_id}', api_error_code=-130)
|
||||||
|
return self.from_list(x)
|
||||||
|
|
||||||
|
def update(self) -> None:
|
||||||
|
self.c.execute('''update character set name = ?, max_level = ?, frag1 = ?, frag20 = ?, frag30 = ?, prog1 = ?, prog20 = ?, prog30 = ?, overdrive1 = ?, overdrive20 = ?, overdrive30 = ?, skill_id = ?, skill_unlock_level = ?, skill_requires_uncap = ?, skill_id_uncap = ?, char_type = ?, is_uncapped = ? where character_id = ?''', (
|
||||||
|
self.name, self.level.max_level, self.frag.start, self.frag.mid, self.frag.end, self.prog.start, self.prog.mid, self.prog.end, self.overdrive.start, self.overdrive.mid, self.overdrive.end, self.skill.skill_id, self.skill.skill_unlock_level, self.skill.skill_requires_uncap, self.skill.skill_id_uncap, self.char_type, self.is_uncapped, self.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(ItemCore(self.c, i[0], i[1]))
|
||||||
|
|
||||||
|
|
||||||
class UserCharacter(Character):
|
class UserCharacter(Character):
|
||||||
@@ -169,27 +222,16 @@ class UserCharacter(Character):
|
|||||||
'''对外显示的技能id'''
|
'''对外显示的技能id'''
|
||||||
if self.is_uncapped_displayed and self.skill.skill_id_uncap:
|
if self.is_uncapped_displayed and self.skill.skill_id_uncap:
|
||||||
return self.skill.skill_id_uncap
|
return self.skill.skill_id_uncap
|
||||||
elif self.skill.skill_id and self.level.level >= self.skill.skill_unlock_level:
|
if self.skill.skill_id and self.level.level >= self.skill.skill_unlock_level:
|
||||||
return self.skill.skill_id
|
return self.skill.skill_id
|
||||||
else:
|
|
||||||
return None
|
return None
|
||||||
|
|
||||||
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=None):
|
def select_character_uncap_condition(self, user=None):
|
||||||
# parameter: user - User类或子类的实例
|
# parameter: user - User类或子类的实例
|
||||||
# 获取此角色的觉醒信息
|
# 获取此角色的觉醒信息
|
||||||
if user:
|
if user:
|
||||||
self.user = user
|
self.user = user
|
||||||
self.c.execute('''select is_uncapped, is_uncapped_override from %s where user_id = :a and character_id = :b''' % self.database_table_name,
|
self.c.execute(f'''select is_uncapped, is_uncapped_override from {self.database_table_name} where user_id = :a and character_id = :b''',
|
||||||
{'a': self.user.user_id, 'b': self.character_id})
|
{'a': self.user.user_id, 'b': self.character_id})
|
||||||
|
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
@@ -206,7 +248,7 @@ class UserCharacter(Character):
|
|||||||
# 获取所给用户此角色信息
|
# 获取所给用户此角色信息
|
||||||
if user:
|
if user:
|
||||||
self.user = user
|
self.user = user
|
||||||
self.c.execute('''select * from %s a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''' % self.database_table_name,
|
self.c.execute(f'''select * from {self.database_table_name} a,character b where a.user_id=? and a.character_id=b.character_id and a.character_id=?''',
|
||||||
(self.user.user_id, self.character_id))
|
(self.user.user_id, self.character_id))
|
||||||
|
|
||||||
y = self.c.fetchone()
|
y = self.c.fetchone()
|
||||||
@@ -228,7 +270,7 @@ class UserCharacter(Character):
|
|||||||
self.skill.skill_unlock_level = y[19]
|
self.skill.skill_unlock_level = y[19]
|
||||||
self.skill.skill_requires_uncap = y[20] == 1
|
self.skill.skill_requires_uncap = y[20] == 1
|
||||||
|
|
||||||
if self.character_id == 21 or self.character_id == 46:
|
if self.character_id in (21, 46):
|
||||||
self.voice = [0, 1, 2, 3, 100, 1000, 1001]
|
self.voice = [0, 1, 2, 3, 100, 1000, 1001]
|
||||||
|
|
||||||
self.select_character_core()
|
self.select_character_core()
|
||||||
@@ -267,7 +309,7 @@ class UserCharacter(Character):
|
|||||||
# 切换觉醒状态
|
# 切换觉醒状态
|
||||||
if user:
|
if user:
|
||||||
self.user = user
|
self.user = user
|
||||||
self.c.execute('''select is_uncapped, is_uncapped_override from %s where user_id = :a and character_id = :b''' % self.database_table_name,
|
self.c.execute(f'''select is_uncapped, is_uncapped_override from {self.database_table_name} where user_id = :a and character_id = :b''',
|
||||||
{'a': self.user.user_id, 'b': self.character_id})
|
{'a': self.user.user_id, 'b': self.character_id})
|
||||||
|
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
@@ -277,7 +319,7 @@ class UserCharacter(Character):
|
|||||||
self.c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
|
self.c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', {
|
||||||
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id})
|
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id})
|
||||||
|
|
||||||
self.c.execute('''update %s set is_uncapped_override = :a where user_id = :b and character_id = :c''' % self.database_table_name, {
|
self.c.execute(f'''update {self.database_table_name} set is_uncapped_override = :a where user_id = :b and character_id = :c''', {
|
||||||
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id, 'c': self.character_id})
|
'a': 1 if x[1] == 0 else 0, 'b': self.user.user_id, 'c': self.character_id})
|
||||||
|
|
||||||
self.is_uncapped_override = x[1] == 0
|
self.is_uncapped_override = x[1] == 0
|
||||||
@@ -307,11 +349,11 @@ class UserCharacter(Character):
|
|||||||
self.c.execute(
|
self.c.execute(
|
||||||
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (self.user.user_id, i.item_id))
|
'''select amount from user_item where user_id=? and item_id=? and type="core"''', (self.user.user_id, i.item_id))
|
||||||
y = self.c.fetchone()
|
y = self.c.fetchone()
|
||||||
if not y or i.amount > y[0]:
|
if i.amount > 0 and (not y or i.amount > y[0]):
|
||||||
raise ItemNotEnough('The cores are not enough.')
|
raise ItemNotEnough('The cores are not enough.')
|
||||||
|
|
||||||
for i in self.uncap_cores:
|
for i in self.uncap_cores:
|
||||||
ItemCore(self.c, i, reverse=True).user_claim_item(self.user)
|
i.user_claim_item(self.user, reverse=True)
|
||||||
|
|
||||||
self.c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''',
|
self.c.execute('''update user_char set is_uncapped=1, is_uncapped_override=0 where user_id=? and character_id=?''',
|
||||||
(self.user.user_id, self.character_id))
|
(self.user.user_id, self.character_id))
|
||||||
@@ -382,7 +424,7 @@ class UserCharacterList:
|
|||||||
|
|
||||||
def select_user_characters(self):
|
def select_user_characters(self):
|
||||||
self.c.execute(
|
self.c.execute(
|
||||||
'''select character_id from %s where user_id=?''' % self.database_table_name, (self.user.user_id,))
|
f'''select character_id from {self.database_table_name} where user_id=?''', (self.user.user_id,))
|
||||||
x = self.c.fetchall()
|
x = self.c.fetchall()
|
||||||
self.characters: list = []
|
self.characters: list = []
|
||||||
if x:
|
if x:
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class Config:
|
|||||||
|
|
||||||
SONG_FILE_HASH_PRE_CALCULATE = True
|
SONG_FILE_HASH_PRE_CALCULATE = True
|
||||||
|
|
||||||
GAME_API_PREFIX = '/join/21'
|
GAME_API_PREFIX = '/pollen/22'
|
||||||
|
|
||||||
ALLOW_APPVERSION = [] # list[str]
|
ALLOW_APPVERSION = [] # list[str]
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from .config_manager import Config
|
from .config_manager import Config
|
||||||
|
|
||||||
ARCAEA_SERVER_VERSION = 'v2.11.0'
|
ARCAEA_SERVER_VERSION = 'v2.11.2'
|
||||||
|
ARCAEA_LOG_DATBASE_VERSION = 'v1.1'
|
||||||
|
|
||||||
|
|
||||||
class Constant:
|
class Constant:
|
||||||
@@ -101,4 +102,6 @@ class Constant:
|
|||||||
DATABASE_MIGRATE_TABLES = ['user', 'friend', 'best_score', 'recent30', 'user_world', 'item', 'user_item', 'purchase', 'purchase_item', 'user_save',
|
DATABASE_MIGRATE_TABLES = ['user', 'friend', 'best_score', 'recent30', 'user_world', 'item', 'user_item', 'purchase', 'purchase_item', 'user_save',
|
||||||
'login', 'present', 'user_present', 'present_item', 'redeem', 'user_redeem', 'redeem_item', 'api_login', 'chart', 'user_course', 'user_char', 'user_role']
|
'login', 'present', 'user_present', 'present_item', 'redeem', 'user_redeem', 'redeem_item', 'api_login', 'chart', 'user_course', 'user_char', 'user_role']
|
||||||
|
|
||||||
|
LOG_DATABASE_MIGRATE_TABLES = ['cache', 'user_score', 'user_rating']
|
||||||
|
|
||||||
UPDATE_WITH_NEW_CHARACTER_DATA = Config.UPDATE_WITH_NEW_CHARACTER_DATA
|
UPDATE_WITH_NEW_CHARACTER_DATA = Config.UPDATE_WITH_NEW_CHARACTER_DATA
|
||||||
|
|||||||
@@ -96,7 +96,7 @@ class Course:
|
|||||||
'''select * from course where course_id = ?''', (self.course_id,))
|
'''select * from course where course_id = ?''', (self.course_id,))
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if x is None:
|
if x is None:
|
||||||
raise NoData('The course `%s` is not found.' % self.course_id)
|
raise NoData(f'The course `{self.course_id}` is not found.')
|
||||||
return self.from_list(x)
|
return self.from_list(x)
|
||||||
|
|
||||||
def select_course_chart(self) -> None:
|
def select_course_chart(self) -> None:
|
||||||
@@ -151,7 +151,8 @@ class Course:
|
|||||||
|
|
||||||
class UserCourse(Course):
|
class UserCourse(Course):
|
||||||
'''
|
'''
|
||||||
用户课题类\
|
用户课题类
|
||||||
|
|
||||||
parameter: `user` - `User`类或子类的实例
|
parameter: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -200,7 +201,8 @@ class UserCourse(Course):
|
|||||||
|
|
||||||
class UserCourseList:
|
class UserCourseList:
|
||||||
'''
|
'''
|
||||||
用户课题列表类\
|
用户课题列表类
|
||||||
|
|
||||||
parameter: `user` - `User`类或子类的实例
|
parameter: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -237,8 +239,9 @@ class UserCourseList:
|
|||||||
|
|
||||||
class CoursePlay(UserCourse):
|
class CoursePlay(UserCourse):
|
||||||
'''
|
'''
|
||||||
课题模式打歌类,联动UserPlay\
|
课题模式打歌类,联动UserPlay
|
||||||
parameter: `user` - `UserOnline`类或子类的实例\
|
|
||||||
|
parameter: `user` - `UserOnline`类或子类的实例
|
||||||
'user_play` - `UserPlay`类的实例
|
'user_play` - `UserPlay`类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,8 @@ class SonglistParser:
|
|||||||
|
|
||||||
class UserDownload:
|
class UserDownload:
|
||||||
'''
|
'''
|
||||||
用户下载类\
|
用户下载类
|
||||||
|
|
||||||
properties: `user` - `User`类或子类的实例
|
properties: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -158,7 +159,6 @@ class UserDownload:
|
|||||||
if prefix[-1] != '/':
|
if prefix[-1] != '/':
|
||||||
prefix += '/'
|
prefix += '/'
|
||||||
return f'{prefix}{self.song_id}/{self.file_name}?t={self.token}'
|
return f'{prefix}{self.song_id}/{self.file_name}?t={self.token}'
|
||||||
else:
|
|
||||||
return url_for('download', file_path=f'{self.song_id}/{self.file_name}', t=self.token, _external=True)
|
return url_for('download', file_path=f'{self.song_id}/{self.file_name}', t=self.token, _external=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -243,7 +243,7 @@ class DownloadList(UserDownload):
|
|||||||
re['audio']['3'] = {"checksum": x.hash, "url": x.url}
|
re['audio']['3'] = {"checksum": x.hash, "url": x.url}
|
||||||
else:
|
else:
|
||||||
re['audio']['3'] = {"checksum": x.hash}
|
re['audio']['3'] = {"checksum": x.hash}
|
||||||
elif i == 'video.mp4' or i == 'video_audio.ogg':
|
elif i in ('video.mp4', 'video_audio.ogg'):
|
||||||
if 'additional_files' not in re:
|
if 'additional_files' not in re:
|
||||||
re['additional_files'] = []
|
re['additional_files'] = []
|
||||||
|
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ from time import time
|
|||||||
from traceback import format_exc
|
from traceback import format_exc
|
||||||
|
|
||||||
from core.config_manager import Config
|
from core.config_manager import Config
|
||||||
from core.constant import ARCAEA_SERVER_VERSION
|
from core.constant import ARCAEA_LOG_DATBASE_VERSION, ARCAEA_SERVER_VERSION
|
||||||
from core.course import Course
|
from core.course import Course
|
||||||
from core.download import DownloadList
|
from core.download import DownloadList
|
||||||
from core.purchase import Purchase
|
from core.purchase import Purchase
|
||||||
from core.sql import Connect, DatabaseMigrator, MemoryDatabase
|
from core.sql import (Connect, DatabaseMigrator, LogDatabaseMigrator,
|
||||||
|
MemoryDatabase)
|
||||||
from core.user import UserRegister
|
from core.user import UserRegister
|
||||||
from core.util import try_rename
|
from core.util import try_rename
|
||||||
|
|
||||||
@@ -41,16 +42,17 @@ class DatabaseInit:
|
|||||||
|
|
||||||
def table_init(self) -> None:
|
def table_init(self) -> None:
|
||||||
'''初始化数据库结构'''
|
'''初始化数据库结构'''
|
||||||
with open(self.sql_path, 'r') as f:
|
with open(self.sql_path, 'r', encoding='utf-8') as f:
|
||||||
self.c.executescript(f.read())
|
self.c.executescript(f.read())
|
||||||
self.c.execute('''insert into config values("version", :a);''', {
|
self.c.execute('''insert into config values("version", :a);''', {
|
||||||
'a': ARCAEA_SERVER_VERSION})
|
'a': ARCAEA_SERVER_VERSION})
|
||||||
|
|
||||||
def character_init(self) -> None:
|
def character_init(self) -> None:
|
||||||
'''初始化搭档信息'''
|
'''初始化搭档信息'''
|
||||||
|
uncapped_characters = self.init_data.char_core.keys()
|
||||||
for i in range(0, len(self.init_data.char)):
|
for i in range(0, len(self.init_data.char)):
|
||||||
skill_requires_uncap = 1 if i == 2 else 0
|
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, 5, 10]:
|
if i in uncapped_characters:
|
||||||
max_level = 30
|
max_level = 30
|
||||||
uncapable = 1
|
uncapable = 1
|
||||||
else:
|
else:
|
||||||
@@ -207,6 +209,29 @@ class FileChecker:
|
|||||||
self.logger.error(
|
self.logger.error(
|
||||||
f'Failed to new the file {Config.SQLITE_LOG_DATABASE_PATH}')
|
f'Failed to new the file {Config.SQLITE_LOG_DATABASE_PATH}')
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
|
# 检查更新
|
||||||
|
with Connect(Config.SQLITE_LOG_DATABASE_PATH) as c:
|
||||||
|
try:
|
||||||
|
x = c.execute(
|
||||||
|
'''select value from cache where key="version"''').fetchone()
|
||||||
|
except:
|
||||||
|
x = None
|
||||||
|
if not x or x[0] != ARCAEA_LOG_DATBASE_VERSION:
|
||||||
|
self.logger.warning(
|
||||||
|
f'Maybe the file `{Config.SQLITE_LOG_DATABASE_PATH}` is an old version.')
|
||||||
|
try:
|
||||||
|
self.logger.info(
|
||||||
|
f'Try to update the file `{Config.SQLITE_LOG_DATABASE_PATH}`')
|
||||||
|
self.update_log_database()
|
||||||
|
self.logger.info(
|
||||||
|
f'Success to update the file `{Config.SQLITE_LOG_DATABASE_PATH}`')
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(format_exc())
|
||||||
|
self.logger.error(
|
||||||
|
f'Failed to update the file `{Config.SQLITE_LOG_DATABASE_PATH}`')
|
||||||
|
return False
|
||||||
|
|
||||||
if not self.check_file(Config.SQLITE_DATABASE_PATH):
|
if not self.check_file(Config.SQLITE_DATABASE_PATH):
|
||||||
# 新建数据库
|
# 新建数据库
|
||||||
try:
|
try:
|
||||||
@@ -274,6 +299,12 @@ class FileChecker:
|
|||||||
DatabaseMigrator(old_path, new_path).update_database()
|
DatabaseMigrator(old_path, new_path).update_database()
|
||||||
os.remove(old_path)
|
os.remove(old_path)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def update_log_database(old_path: str = Config.SQLITE_LOG_DATABASE_PATH) -> None:
|
||||||
|
'''直接更新日志数据库'''
|
||||||
|
if os.path.isfile(old_path):
|
||||||
|
LogDatabaseMigrator(old_path).update_database()
|
||||||
|
|
||||||
def check_song_file(self) -> bool:
|
def check_song_file(self) -> bool:
|
||||||
'''检查song有关文件并初始化缓存'''
|
'''检查song有关文件并初始化缓存'''
|
||||||
f = self.check_folder(Config.SONG_FILE_FOLDER_PATH)
|
f = self.check_folder(Config.SONG_FILE_FOLDER_PATH)
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from .config_manager import Config
|
from .config_manager import Config
|
||||||
from .error import InputError, ItemNotEnough, ItemUnavailable, NoData
|
from .error import DataExist, InputError, ItemNotEnough, ItemUnavailable, NoData
|
||||||
|
|
||||||
|
|
||||||
class Item:
|
class Item:
|
||||||
@@ -73,7 +73,8 @@ class UserItem(Item):
|
|||||||
|
|
||||||
def select_user_item(self, user=None):
|
def select_user_item(self, user=None):
|
||||||
'''
|
'''
|
||||||
查询用户item\
|
查询用户item
|
||||||
|
|
||||||
parameter: `user` - `User`类或子类的实例
|
parameter: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
if user is not None:
|
if user is not None:
|
||||||
@@ -102,7 +103,6 @@ class NormalItem(UserItem):
|
|||||||
if x[0] == 0:
|
if x[0] == 0:
|
||||||
self.is_available = False
|
self.is_available = False
|
||||||
raise ItemUnavailable('The item is unavailable.')
|
raise ItemUnavailable('The item is unavailable.')
|
||||||
else:
|
|
||||||
self.is_available = True
|
self.is_available = True
|
||||||
else:
|
else:
|
||||||
raise NoData('No item data.')
|
raise NoData('No item data.')
|
||||||
@@ -142,16 +142,29 @@ class PositiveItem(UserItem):
|
|||||||
class ItemCore(PositiveItem):
|
class ItemCore(PositiveItem):
|
||||||
item_type = 'core'
|
item_type = 'core'
|
||||||
|
|
||||||
def __init__(self, c, core=None, reverse=False) -> None:
|
def __init__(self, c, core_type: str = '', amount: int = 0) -> None:
|
||||||
super().__init__(c)
|
super().__init__(c)
|
||||||
self.is_available = True
|
self.is_available = True
|
||||||
if core:
|
self.item_id = core_type
|
||||||
self.item_id = core.item_id
|
self.amount = amount
|
||||||
self.amount = - core.amount if reverse else core.amount
|
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.item_id + '_' + str(self.amount)
|
return self.item_id + '_' + str(self.amount)
|
||||||
|
|
||||||
|
def to_dict(self, has_is_available: bool = False, has_amount: bool = True, character_format: bool = False) -> dict:
|
||||||
|
if character_format:
|
||||||
|
# 搭档的core是特殊格式的
|
||||||
|
return {'core_type': self.item_id, 'amount': self.amount}
|
||||||
|
return super().to_dict(has_is_available=has_is_available, has_amount=has_amount)
|
||||||
|
|
||||||
|
def user_claim_item(self, user, reverse: bool = False) -> None:
|
||||||
|
# 骚操作,将amount变为负数后使用再变回来
|
||||||
|
if reverse:
|
||||||
|
self.amount = -self.amount
|
||||||
|
super().user_claim_item(user)
|
||||||
|
if reverse:
|
||||||
|
self.amount = -self.amount
|
||||||
|
|
||||||
|
|
||||||
class ItemCharacter(UserItem):
|
class ItemCharacter(UserItem):
|
||||||
item_type = 'character'
|
item_type = 'character'
|
||||||
@@ -257,16 +270,10 @@ class CourseBanner(NormalItem):
|
|||||||
class Single(NormalItem):
|
class Single(NormalItem):
|
||||||
item_type = 'single'
|
item_type = 'single'
|
||||||
|
|
||||||
def __init__(self, c) -> None:
|
|
||||||
super().__init__(c)
|
|
||||||
|
|
||||||
|
|
||||||
class Pack(NormalItem):
|
class Pack(NormalItem):
|
||||||
item_type = 'pack'
|
item_type = 'pack'
|
||||||
|
|
||||||
def __init__(self, c) -> None:
|
|
||||||
super().__init__(c)
|
|
||||||
|
|
||||||
|
|
||||||
class ProgBoost(UserItem):
|
class ProgBoost(UserItem):
|
||||||
item_type = 'prog_boost_300'
|
item_type = 'prog_boost_300'
|
||||||
@@ -276,7 +283,8 @@ class ProgBoost(UserItem):
|
|||||||
|
|
||||||
def user_claim_item(self, user):
|
def user_claim_item(self, user):
|
||||||
'''
|
'''
|
||||||
世界模式prog_boost\
|
世界模式prog_boost
|
||||||
|
|
||||||
parameters: `user` - `UserOnline`类或子类的实例
|
parameters: `user` - `UserOnline`类或子类的实例
|
||||||
'''
|
'''
|
||||||
user.update_user_one_column('prog_boost', 300)
|
user.update_user_one_column('prog_boost', 300)
|
||||||
@@ -290,7 +298,7 @@ class Stamina6(UserItem):
|
|||||||
|
|
||||||
def user_claim_item(self, user):
|
def user_claim_item(self, user):
|
||||||
'''
|
'''
|
||||||
世界模式记忆源点或残片买体力+6\
|
世界模式记忆源点或残片买体力+6
|
||||||
顺手清一下世界模式过载状态
|
顺手清一下世界模式过载状态
|
||||||
'''
|
'''
|
||||||
user.select_user_about_stamina()
|
user.select_user_about_stamina()
|
||||||
@@ -386,8 +394,8 @@ class ItemFactory:
|
|||||||
|
|
||||||
class UserItemList:
|
class UserItemList:
|
||||||
'''
|
'''
|
||||||
用户的item列表\
|
用户的item列表
|
||||||
注意只能查在user_item里面的,character不行\
|
注意只能查在user_item里面的,character不行
|
||||||
properties: `user` - `User`类或子类的实例
|
properties: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -420,3 +428,63 @@ class UserItemList:
|
|||||||
self.items.append(ItemFactory.from_dict(
|
self.items.append(ItemFactory.from_dict(
|
||||||
{'item_id': i[0], 'amount': amount, 'item_type': item_type}, self.c))
|
{'item_id': i[0], 'amount': amount, 'item_type': item_type}, self.c))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionItemMixin:
|
||||||
|
'''
|
||||||
|
批量修改一些集合中的items
|
||||||
|
'''
|
||||||
|
collection_item_const = {
|
||||||
|
'name': 'collection',
|
||||||
|
'table_name': 'collection_item',
|
||||||
|
'table_primary_key': 'collection_id',
|
||||||
|
'id_name': 'collection_id',
|
||||||
|
'items_name': 'items'
|
||||||
|
}
|
||||||
|
|
||||||
|
def add_items(self, items: 'list[Item]') -> None:
|
||||||
|
collection_id: 'str' = getattr(
|
||||||
|
self, self.collection_item_const['id_name'])
|
||||||
|
collection_items: 'list[Item]' = getattr(
|
||||||
|
self, self.collection_item_const['items_name'])
|
||||||
|
|
||||||
|
for i in items:
|
||||||
|
if not i.select_exists():
|
||||||
|
raise NoData(
|
||||||
|
f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121)
|
||||||
|
if i in collection_items:
|
||||||
|
raise DataExist(
|
||||||
|
f'Item `{i.item_type}`: `{i.item_id}` already exists in {self.collection_item_const["name"]} `{collection_id}`', api_error_code=-123)
|
||||||
|
self.c.executemany(f'''insert into {self.collection_item_const["table_name"]} values (?, ?, ?, ?)''', [
|
||||||
|
(collection_id, i.item_id, i.item_type, i.amount) for i in items])
|
||||||
|
collection_items.extend(items)
|
||||||
|
|
||||||
|
def remove_items(self, items: 'list[Item]') -> None:
|
||||||
|
collection_id: 'str' = getattr(
|
||||||
|
self, self.collection_item_const['id_name'])
|
||||||
|
collection_items: 'list[Item]' = getattr(
|
||||||
|
self, self.collection_item_const['items_name'])
|
||||||
|
|
||||||
|
for i in items:
|
||||||
|
if i not in collection_items:
|
||||||
|
raise NoData(
|
||||||
|
f'No such item `{i.item_type}`: `{i.item_id}` in {self.collection_item_const["name"]} `{collection_id}`', api_error_code=-124)
|
||||||
|
self.c.executemany(f'''delete from {self.collection_item_const["table_name"]} where {self.collection_item_const["table_primary_key"]}=? and item_id=? and type=?''', [
|
||||||
|
(collection_id, i.item_id, i.item_type) for i in items])
|
||||||
|
for i in items:
|
||||||
|
collection_items.remove(i)
|
||||||
|
|
||||||
|
def update_items(self, items: 'list[Item]') -> None:
|
||||||
|
collection_id: 'str' = getattr(
|
||||||
|
self, self.collection_item_const['id_name'])
|
||||||
|
collection_items: 'list[Item]' = getattr(
|
||||||
|
self, self.collection_item_const['items_name'])
|
||||||
|
|
||||||
|
for i in items:
|
||||||
|
if i not in collection_items:
|
||||||
|
raise NoData(
|
||||||
|
f'No such item `{i.item_type}`: `{i.item_id}` in {self.collection_item_const["name"]} `{collection_id}`', api_error_code=-124)
|
||||||
|
self.c.executemany(f'''update {self.collection_item_const["table_name"]} set amount=? where {self.collection_item_const["table_primary_key"]}=? and item_id=? and type=?''', [
|
||||||
|
(i.amount, collection_id, i.item_id, i.item_type) for i in items])
|
||||||
|
for i in items:
|
||||||
|
collection_items[collection_items.index(i)].amount = i.amount
|
||||||
|
|||||||
@@ -103,9 +103,9 @@ class RemoteMultiPlayer:
|
|||||||
sock.sendall(bytes(data + "\n", "utf-8"))
|
sock.sendall(bytes(data + "\n", "utf-8"))
|
||||||
try:
|
try:
|
||||||
received = str(sock.recv(1024), "utf-8").strip()
|
received = str(sock.recv(1024), "utf-8").strip()
|
||||||
except socket.timeout:
|
except socket.timeout as e:
|
||||||
raise Timeout(
|
raise Timeout(
|
||||||
'Timeout when waiting for data from link play server.', status=400)
|
'Timeout when waiting for data from link play server.', status=400) from e
|
||||||
# print(received)
|
# print(received)
|
||||||
return received
|
return received
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,7 @@ class RefreshAllScoreRating(BaseOperation):
|
|||||||
where_values = []
|
where_values = []
|
||||||
for k in y:
|
for k in y:
|
||||||
ptt = Score.calculate_rating(defnum, k[1])
|
ptt = Score.calculate_rating(defnum, k[1])
|
||||||
if ptt < 0:
|
ptt = max(ptt, 0)
|
||||||
ptt = 0
|
|
||||||
values.append((ptt,))
|
values.append((ptt,))
|
||||||
where_values.append((k[0], i[0], j))
|
where_values.append((k[0], i[0], j))
|
||||||
if values:
|
if values:
|
||||||
@@ -76,7 +75,7 @@ class RefreshSongFileCache(BaseOperation):
|
|||||||
|
|
||||||
class SaveUpdateScore(BaseOperation):
|
class SaveUpdateScore(BaseOperation):
|
||||||
'''
|
'''
|
||||||
云存档更新成绩,是覆盖式更新\
|
云存档更新成绩,是覆盖式更新
|
||||||
提供user参数时,只更新该用户的成绩,否则更新所有用户的成绩
|
提供user参数时,只更新该用户的成绩,否则更新所有用户的成绩
|
||||||
'''
|
'''
|
||||||
_name = 'save_update_score'
|
_name = 'save_update_score'
|
||||||
@@ -125,8 +124,7 @@ class SaveUpdateScore(BaseOperation):
|
|||||||
if i['song_id'] in song_chart_const:
|
if i['song_id'] in song_chart_const:
|
||||||
rating = Score.calculate_rating(
|
rating = Score.calculate_rating(
|
||||||
song_chart_const[i['song_id']][i['difficulty']] / 10, i['score'])
|
song_chart_const[i['song_id']][i['difficulty']] / 10, i['score'])
|
||||||
if rating < 0:
|
rating = max(rating, 0)
|
||||||
rating = 0
|
|
||||||
|
|
||||||
y = f'{i["song_id"]}{i["difficulty"]}'
|
y = f'{i["song_id"]}{i["difficulty"]}'
|
||||||
if y in clear_state:
|
if y in clear_state:
|
||||||
@@ -143,7 +141,7 @@ class SaveUpdateScore(BaseOperation):
|
|||||||
def _all_update(self):
|
def _all_update(self):
|
||||||
with Connect() as c:
|
with Connect() as c:
|
||||||
c.execute(
|
c.execute(
|
||||||
f'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
|
'''select song_id, rating_pst, rating_prs, rating_ftr, rating_byn from chart''')
|
||||||
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4]]
|
song_chart_const = {i[0]: [i[1], i[2], i[3], i[4]]
|
||||||
for i in c.fetchall()} # chart const * 10
|
for i in c.fetchall()} # chart const * 10
|
||||||
c.execute('''select user_id from user_save''')
|
c.execute('''select user_id from user_save''')
|
||||||
@@ -162,8 +160,7 @@ class SaveUpdateScore(BaseOperation):
|
|||||||
if i['song_id'] in song_chart_const:
|
if i['song_id'] in song_chart_const:
|
||||||
rating = Score.calculate_rating(
|
rating = Score.calculate_rating(
|
||||||
song_chart_const[i['song_id']][i['difficulty']] / 10, i['score'])
|
song_chart_const[i['song_id']][i['difficulty']] / 10, i['score'])
|
||||||
if rating < 0:
|
rating = max(rating, 0)
|
||||||
rating = 0
|
|
||||||
|
|
||||||
y = f'{i["song_id"]}{i["difficulty"]}'
|
y = f'{i["song_id"]}{i["difficulty"]}'
|
||||||
if y in clear_state:
|
if y in clear_state:
|
||||||
@@ -180,7 +177,7 @@ class SaveUpdateScore(BaseOperation):
|
|||||||
|
|
||||||
class UnlockUserItem(BaseOperation):
|
class UnlockUserItem(BaseOperation):
|
||||||
'''
|
'''
|
||||||
全解锁/锁定用户物品\
|
全解锁/锁定用户物品
|
||||||
提供user参数时,只更新该用户的,否则更新所有用户的
|
提供user参数时,只更新该用户的,否则更新所有用户的
|
||||||
'''
|
'''
|
||||||
_name = 'unlock_user_item'
|
_name = 'unlock_user_item'
|
||||||
@@ -198,7 +195,7 @@ class UnlockUserItem(BaseOperation):
|
|||||||
self.user.user_id = int(user_id)
|
self.user.user_id = int(user_id)
|
||||||
if method in ['unlock', 'lock']:
|
if method in ['unlock', 'lock']:
|
||||||
self.method = method
|
self.method = method
|
||||||
if isinstance(item_types, list) and all([i in self.ALLOW_TYPES for i in item_types]):
|
if isinstance(item_types, list) and all(i in self.ALLOW_TYPES for i in item_types):
|
||||||
self.item_types = item_types
|
self.item_types = item_types
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|||||||
@@ -1,10 +1,18 @@
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from .error import ArcError, DataExist, NoData
|
from .error import ArcError, NoData
|
||||||
from .item import ItemFactory
|
from .item import CollectionItemMixin, ItemFactory
|
||||||
|
|
||||||
|
|
||||||
class Present:
|
class Present(CollectionItemMixin):
|
||||||
|
collection_item_const = {
|
||||||
|
'name': 'present',
|
||||||
|
'table_name': 'present_item',
|
||||||
|
'table_primary_key': 'present_id',
|
||||||
|
'id_name': 'present_id',
|
||||||
|
'items_name': 'items'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, c=None) -> None:
|
def __init__(self, c=None) -> None:
|
||||||
self.c = c
|
self.c = c
|
||||||
self.present_id: str = None
|
self.present_id: str = None
|
||||||
@@ -109,47 +117,11 @@ class Present:
|
|||||||
self.c.execute('''update present set expire_ts=?, description=? where present_id=?''',
|
self.c.execute('''update present set expire_ts=?, description=? where present_id=?''',
|
||||||
(self.expire_ts, self.description, self.present_id))
|
(self.expire_ts, self.description, self.present_id))
|
||||||
|
|
||||||
def remove_items(self, items: list) -> None:
|
|
||||||
'''删除present_item表中的物品'''
|
|
||||||
for i in items:
|
|
||||||
if i not in self.items:
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}` in present `{self.present_id}`', api_error_code=-124)
|
|
||||||
self.c.executemany('''delete from present_item where present_id=? and item_id=? and type=?''', [
|
|
||||||
(self.present_id, i.item_id, i.item_type) for i in items])
|
|
||||||
for i in items:
|
|
||||||
self.items.remove(i)
|
|
||||||
|
|
||||||
def add_items(self, items: list) -> None:
|
|
||||||
'''添加物品到present_item表'''
|
|
||||||
for i in items:
|
|
||||||
if not i.select_exists():
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121)
|
|
||||||
if i in self.items:
|
|
||||||
raise DataExist(
|
|
||||||
f'Item `{i.item_type}`: `{i.item_id}` already exists in present `{self.present_id}`', api_error_code=-123)
|
|
||||||
self.c.executemany('''insert into present_item values(?,?,?,?)''', [
|
|
||||||
(self.present_id, i.item_id, i.item_type, i.amount) for i in items])
|
|
||||||
self.items.extend(items)
|
|
||||||
|
|
||||||
def update_items(self, items: list) -> None:
|
|
||||||
'''更新present_item表中的物品'''
|
|
||||||
for i in items:
|
|
||||||
if i not in self.items:
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}` in present `{self.present_id}`', api_error_code=-124)
|
|
||||||
self.c.executemany('''update present_item set amount=? where present_id=? and item_id=? and type=?''', [
|
|
||||||
(i.amount, self.present_id, i.item_id, i.item_type) for i in items])
|
|
||||||
|
|
||||||
for i in items:
|
|
||||||
self.items[self.items.index(i)].amount = i.amount
|
|
||||||
|
|
||||||
|
|
||||||
class UserPresent(Present):
|
class UserPresent(Present):
|
||||||
'''
|
'''
|
||||||
用户登录奖励类\
|
用户登录奖励类
|
||||||
忽视了description的多语言\
|
忽视了description的多语言
|
||||||
properties: `user` - `User`类或子类的实例
|
properties: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,21 @@
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from .error import DataExist, InputError, NoData, TicketNotEnough
|
from .error import InputError, NoData, TicketNotEnough
|
||||||
from .item import ItemFactory
|
from .item import CollectionItemMixin, ItemFactory
|
||||||
|
|
||||||
|
|
||||||
class Purchase:
|
class Purchase(CollectionItemMixin):
|
||||||
'''
|
'''
|
||||||
购买类\
|
购买类
|
||||||
properties: `user` - `User`类或子类的实例
|
properties: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
collection_item_const = {
|
||||||
|
'name': 'purchase',
|
||||||
|
'table_name': 'purchase_item',
|
||||||
|
'table_primary_key': 'purchase_name',
|
||||||
|
'id_name': 'purchase_name',
|
||||||
|
'items_name': 'items'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, c=None, user=None):
|
def __init__(self, c=None, user=None):
|
||||||
self.c = c
|
self.c = c
|
||||||
@@ -212,45 +219,11 @@ class Purchase:
|
|||||||
self.c.execute('''update purchase set price=:a, orig_price=:b, discount_from=:c, discount_to=:d, discount_reason=:e where purchase_name=:f''', {
|
self.c.execute('''update purchase set price=:a, orig_price=:b, discount_from=:c, discount_to=:d, discount_reason=:e where purchase_name=:f''', {
|
||||||
'a': self.price, 'b': self.orig_price, 'c': self.discount_from, 'd': self.discount_to, 'e': self.discount_reason, 'f': self.purchase_name})
|
'a': self.price, 'b': self.orig_price, 'c': self.discount_from, 'd': self.discount_to, 'e': self.discount_reason, 'f': self.purchase_name})
|
||||||
|
|
||||||
def add_items(self, items: list) -> None:
|
|
||||||
'''添加purchase_item表'''
|
|
||||||
for i in items:
|
|
||||||
if not i.select_exists():
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121)
|
|
||||||
if i in self.items:
|
|
||||||
raise DataExist(
|
|
||||||
f'Item `{i.item_type}`: `{i.item_id}` already exists in purchase `{self.purchase_name}`', api_error_code=-123)
|
|
||||||
self.c.executemany('''insert into purchase_item values (?, ?, ?, ?)''', [
|
|
||||||
(self.purchase_name, i.item_id, i.item_type, i.amount) for i in items])
|
|
||||||
self.items.extend(items)
|
|
||||||
|
|
||||||
def remove_items(self, items: list) -> None:
|
|
||||||
'''删除purchase_item表'''
|
|
||||||
for i in items:
|
|
||||||
if i not in self.items:
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}` in purchase `{self.purchase_name}`', api_error_code=-124)
|
|
||||||
self.c.executemany('''delete from purchase_item where purchase_name=? and item_id=? and type=?''', [
|
|
||||||
(self.purchase_name, i.item_id, i.item_type) for i in items])
|
|
||||||
for i in items:
|
|
||||||
self.items.remove(i)
|
|
||||||
|
|
||||||
def update_items(self, items: list) -> None:
|
|
||||||
'''更新purchase_item表,只能更新amount'''
|
|
||||||
for i in items:
|
|
||||||
if i not in self.items:
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}` in purchase `{self.purchase_name}`', api_error_code=-124)
|
|
||||||
self.c.executemany('''update purchase_item set amount=? where purchase_name=? and item_id=? and type=?''', [
|
|
||||||
(i.amount, self.purchase_name, i.item_id, i.item_type) for i in items])
|
|
||||||
for i in items:
|
|
||||||
self.items[self.items.index(i)].amount = i.amount
|
|
||||||
|
|
||||||
|
|
||||||
class PurchaseList:
|
class PurchaseList:
|
||||||
'''
|
'''
|
||||||
购买列表类\
|
购买列表类
|
||||||
|
|
||||||
property: `user` - `User`类或子类的实例
|
property: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,9 @@ from .user import UserInfo
|
|||||||
|
|
||||||
class RankList:
|
class RankList:
|
||||||
'''
|
'''
|
||||||
排行榜类\
|
排行榜类
|
||||||
默认limit=20,limit<0认为是all\
|
默认limit=20,limit<0认为是all
|
||||||
|
|
||||||
property: `user` - `User`类或者子类的实例
|
property: `user` - `User`类或者子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -85,7 +86,8 @@ class RankList:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def get_my_rank_parameter(my_rank: int, amount: int, all_limit: int = 20, max_local_position: int = Constant.MY_RANK_MAX_LOCAL_POSITION, max_global_position: int = Constant.MY_RANK_MAX_GLOBAL_POSITION):
|
def get_my_rank_parameter(my_rank: int, amount: int, all_limit: int = 20, max_local_position: int = Constant.MY_RANK_MAX_LOCAL_POSITION, max_global_position: int = Constant.MY_RANK_MAX_GLOBAL_POSITION):
|
||||||
'''
|
'''
|
||||||
计算我的排名中的查询参数\
|
计算我的排名中的查询参数
|
||||||
|
|
||||||
returns:
|
returns:
|
||||||
`sql_limit`: int - 查询limit参数
|
`sql_limit`: int - 查询limit参数
|
||||||
`sql_offset`: int - 查询offset参数
|
`sql_offset`: int - 查询offset参数
|
||||||
@@ -103,7 +105,7 @@ class RankList:
|
|||||||
need_myself = True
|
need_myself = True
|
||||||
elif amount - my_rank < all_limit - max_local_position: # 后方人数不足,显示排名
|
elif amount - my_rank < all_limit - max_local_position: # 后方人数不足,显示排名
|
||||||
sql_offset = amount - all_limit
|
sql_offset = amount - all_limit
|
||||||
elif my_rank >= max_local_position and my_rank <= max_global_position - all_limit + max_local_position - 1: # 前方人数足够,显示排名
|
elif max_local_position <= my_rank <= max_global_position - all_limit + max_local_position - 1: # 前方人数足够,显示排名
|
||||||
sql_offset = my_rank - max_local_position
|
sql_offset = my_rank - max_local_position
|
||||||
else: # 我已经忘了这是什么了
|
else: # 我已经忘了这是什么了
|
||||||
sql_offset = max_global_position - all_limit
|
sql_offset = max_global_position - all_limit
|
||||||
|
|||||||
@@ -1,8 +1,16 @@
|
|||||||
from .error import DataExist, NoData, RedeemUnavailable
|
from .error import NoData, RedeemUnavailable
|
||||||
from .item import ItemFactory
|
from .item import CollectionItemMixin, ItemFactory
|
||||||
|
|
||||||
|
|
||||||
class Redeem:
|
class Redeem(CollectionItemMixin):
|
||||||
|
collection_item_const = {
|
||||||
|
'name': 'redeem',
|
||||||
|
'table_name': 'redeem_item',
|
||||||
|
'table_primary_key': 'code',
|
||||||
|
'id_name': 'code',
|
||||||
|
'items_name': 'items'
|
||||||
|
}
|
||||||
|
|
||||||
def __init__(self, c=None) -> None:
|
def __init__(self, c=None) -> None:
|
||||||
self.c = c
|
self.c = c
|
||||||
self.code: str = None
|
self.code: str = None
|
||||||
@@ -87,46 +95,11 @@ class Redeem:
|
|||||||
self.c.execute('''update redeem set type=? where code=?''',
|
self.c.execute('''update redeem set type=? where code=?''',
|
||||||
(self.redeem_type, self.code))
|
(self.redeem_type, self.code))
|
||||||
|
|
||||||
def remove_items(self, items: list) -> None:
|
|
||||||
'''删除redeem_item表中的物品'''
|
|
||||||
for i in items:
|
|
||||||
if i not in self.items:
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}` in redeem `{self.code}`', api_error_code=-124)
|
|
||||||
self.c.executemany('''delete from redeem_item where code=? and item_id=? and type=?''', [
|
|
||||||
(self.code, i.item_id, i.item_type) for i in items])
|
|
||||||
for i in items:
|
|
||||||
self.items.remove(i)
|
|
||||||
|
|
||||||
def add_items(self, items: list) -> None:
|
|
||||||
'''添加物品到redeem_item表'''
|
|
||||||
for i in items:
|
|
||||||
if not i.select_exists():
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}`', api_error_code=-121)
|
|
||||||
if i in self.items:
|
|
||||||
raise DataExist(
|
|
||||||
f'Item `{i.item_type}`: `{i.item_id}` already exists in redeem `{self.code}`', api_error_code=-123)
|
|
||||||
self.c.executemany('''insert into redeem_item values(?,?,?,?)''', [
|
|
||||||
(self.code, i.item_id, i.item_type, i.amount) for i in items])
|
|
||||||
self.items.extend(items)
|
|
||||||
|
|
||||||
def update_items(self, items: list) -> None:
|
|
||||||
'''更新redeem_item表中的物品'''
|
|
||||||
for i in items:
|
|
||||||
if i not in self.items:
|
|
||||||
raise NoData(
|
|
||||||
f'No such item `{i.item_type}`: `{i.item_id}` in redeem `{self.code}`', api_error_code=-124)
|
|
||||||
self.c.executemany('''update redeem_item set amount=? where code=? and item_id=? and type=?''', [
|
|
||||||
(i.amount, self.code, i.item_id, i.item_type) for i in items])
|
|
||||||
|
|
||||||
for i in items:
|
|
||||||
self.items[self.items.index(i)].amount = i.amount
|
|
||||||
|
|
||||||
|
|
||||||
class UserRedeem(Redeem):
|
class UserRedeem(Redeem):
|
||||||
'''
|
'''
|
||||||
用户兑换码类\
|
用户兑换码类
|
||||||
|
|
||||||
properties: `user` - `User`类或子类的实例
|
properties: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -165,7 +138,7 @@ class UserRedeem(Redeem):
|
|||||||
if self.redeem_type == 0:
|
if self.redeem_type == 0:
|
||||||
raise RedeemUnavailable(
|
raise RedeemUnavailable(
|
||||||
'The redeem `%s` is unavailable.' % self.code)
|
'The redeem `%s` is unavailable.' % self.code)
|
||||||
elif self.redeem_type == 1:
|
if self.redeem_type == 1:
|
||||||
raise RedeemUnavailable(
|
raise RedeemUnavailable(
|
||||||
'The redeem `%s` is unavailable.' % self.code, 506)
|
'The redeem `%s` is unavailable.' % self.code, 506)
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ class SaveData:
|
|||||||
|
|
||||||
def __init__(self, c=None) -> None:
|
def __init__(self, c=None) -> None:
|
||||||
self.c = c
|
self.c = c
|
||||||
|
self.user = None
|
||||||
|
|
||||||
self.scores_data = []
|
self.scores_data = []
|
||||||
self.clearlamps_data = []
|
self.clearlamps_data = []
|
||||||
self.clearedsongs_data = []
|
self.clearedsongs_data = []
|
||||||
@@ -127,7 +129,7 @@ class SaveData:
|
|||||||
'Property `%s` is not found in the instance of `SaveData` class.' % key)
|
'Property `%s` is not found in the instance of `SaveData` class.' % key)
|
||||||
|
|
||||||
if md5(value) == checksum:
|
if md5(value) == checksum:
|
||||||
if key == 'installid_data' or key == 'devicemodelname_data' or key == 'finalestate_data':
|
if key in ('installid_data', 'devicemodelname_data', 'finalestate_data'):
|
||||||
self.__dict__[key] = json.loads(value)['val']
|
self.__dict__[key] = json.loads(value)['val']
|
||||||
else:
|
else:
|
||||||
self.__dict__[key] = json.loads(value)['']
|
self.__dict__[key] = json.loads(value)['']
|
||||||
|
|||||||
@@ -2,18 +2,22 @@ from base64 import b64encode
|
|||||||
from os import urandom
|
from os import urandom
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
from .bgtask import BGTask, logdb_execute
|
||||||
|
from .config_manager import Config
|
||||||
from .constant import Constant
|
from .constant import Constant
|
||||||
from .course import CoursePlay
|
from .course import CoursePlay
|
||||||
from .error import NoData, StaminaNotEnough
|
from .error import NoData, StaminaNotEnough
|
||||||
from .item import ItemCore
|
from .item import ItemCore
|
||||||
from .song import Chart
|
from .song import Chart
|
||||||
from .sql import Connect, Query, Sql
|
from .sql import Connect, Query, Sql
|
||||||
from .util import md5
|
from .util import get_today_timestamp, md5
|
||||||
from .world import WorldPlay
|
from .world import WorldPlay
|
||||||
|
|
||||||
|
|
||||||
class Score:
|
class Score:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
|
self.c = None
|
||||||
|
|
||||||
self.song: 'Chart' = Chart()
|
self.song: 'Chart' = Chart()
|
||||||
self.score: int = None
|
self.score: int = None
|
||||||
self.shiny_perfect_count: int = None
|
self.shiny_perfect_count: int = None
|
||||||
@@ -45,17 +49,16 @@ class Score:
|
|||||||
'''分数转换为评级'''
|
'''分数转换为评级'''
|
||||||
if score >= 9900000: # EX+
|
if score >= 9900000: # EX+
|
||||||
return 6
|
return 6
|
||||||
elif 9800000 <= score < 9900000: # EX
|
if score >= 9800000: # EX
|
||||||
return 5
|
return 5
|
||||||
elif 9500000 <= score < 9800000: # AA
|
if score >= 9500000: # AA
|
||||||
return 4
|
return 4
|
||||||
elif 9200000 <= score < 9500000: # A
|
if score >= 9200000: # A
|
||||||
return 3
|
return 3
|
||||||
elif 8900000 <= score < 9200000: # B
|
if score >= 8900000: # B
|
||||||
return 2
|
return 2
|
||||||
elif 8600000 <= score < 8900000: # C
|
if score >= 8600000: # C
|
||||||
return 1
|
return 1
|
||||||
else:
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -67,21 +70,24 @@ class Score:
|
|||||||
'''clear_type转换为成绩状态,用数字大小标识便于比较'''
|
'''clear_type转换为成绩状态,用数字大小标识便于比较'''
|
||||||
if clear_type == 3: # PM
|
if clear_type == 3: # PM
|
||||||
return 5
|
return 5
|
||||||
elif clear_type == 2: # FC
|
if clear_type == 2: # FC
|
||||||
return 4
|
return 4
|
||||||
elif clear_type == 5: # Hard Clear
|
if clear_type == 5: # Hard Clear
|
||||||
return 3
|
return 3
|
||||||
elif clear_type == 1: # Clear
|
if clear_type == 1: # Clear
|
||||||
return 2
|
return 2
|
||||||
elif clear_type == 4: # Easy Clear
|
if clear_type == 4: # Easy Clear
|
||||||
return 1
|
return 1
|
||||||
else: # Track Lost
|
return 0 # Track Lost
|
||||||
return 0
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def song_state(self) -> int:
|
def song_state(self) -> int:
|
||||||
return self.get_song_state(self.clear_type)
|
return self.get_song_state(self.clear_type)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def all_note_count(self) -> int:
|
||||||
|
return self.perfect_count + self.near_count + self.miss_count
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_valid(self) -> bool:
|
def is_valid(self) -> bool:
|
||||||
'''分数有效性检查'''
|
'''分数有效性检查'''
|
||||||
@@ -90,7 +96,7 @@ class Score:
|
|||||||
if self.song.difficulty not in (0, 1, 2, 3):
|
if self.song.difficulty not in (0, 1, 2, 3):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
all_note = self.perfect_count + self.near_count + self.miss_count
|
all_note = self.all_note_count
|
||||||
if all_note == 0:
|
if all_note == 0:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -112,8 +118,7 @@ class Score:
|
|||||||
ptt = defnum + 2
|
ptt = defnum + 2
|
||||||
elif score < 9800000:
|
elif score < 9800000:
|
||||||
ptt = defnum + (score-9500000) / 300000
|
ptt = defnum + (score-9500000) / 300000
|
||||||
if ptt < 0:
|
ptt = max(ptt, 0)
|
||||||
ptt = 0
|
|
||||||
else:
|
else:
|
||||||
ptt = defnum + 1 + (score-9800000) / 200000
|
ptt = defnum + 1 + (score-9800000) / 200000
|
||||||
|
|
||||||
@@ -217,7 +222,10 @@ class UserPlay(UserScore):
|
|||||||
self.course_play_state: int = None
|
self.course_play_state: int = None
|
||||||
self.course_play: 'CoursePlay' = None
|
self.course_play: 'CoursePlay' = None
|
||||||
|
|
||||||
|
self.combo_interval_bonus: int = None # 不能给 None 以外的默认值
|
||||||
|
|
||||||
def to_dict(self) -> dict:
|
def to_dict(self) -> dict:
|
||||||
|
# 不能super
|
||||||
if self.is_world_mode is None or self.course_play_state is None:
|
if self.is_world_mode is None or self.course_play_state is None:
|
||||||
return {}
|
return {}
|
||||||
if self.course_play_state == 4:
|
if self.course_play_state == 4:
|
||||||
@@ -249,10 +257,15 @@ class UserPlay(UserScore):
|
|||||||
if songfile_hash and songfile_hash != self.song_hash:
|
if songfile_hash and songfile_hash != self.song_hash:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
x = self.song_token + self.song_hash + self.song.song_id + str(self.song.difficulty) + str(self.score) + str(self.shiny_perfect_count) + str(
|
x = f'''{self.song_token}{self.song_hash}{self.song.song_id}{self.song.difficulty}{self.score}{self.shiny_perfect_count}{self.perfect_count}{self.near_count}{self.miss_count}{self.health}{self.modifier}{self.clear_type}'''
|
||||||
self.perfect_count) + str(self.near_count) + str(self.miss_count) + str(self.health) + str(self.modifier) + str(self.clear_type)
|
if self.combo_interval_bonus is not None:
|
||||||
y = str(self.user.user_id) + self.song_hash
|
if self.combo_interval_bonus < 0 or self.combo_interval_bonus > self.all_note_count / 150:
|
||||||
|
return False
|
||||||
|
x = x + str(self.combo_interval_bonus)
|
||||||
|
|
||||||
|
y = f'{self.user.user_id}{self.song_hash}'
|
||||||
checksum = md5(x+md5(y))
|
checksum = md5(x+md5(y))
|
||||||
|
|
||||||
if checksum != self.submission_hash:
|
if checksum != self.submission_hash:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -302,7 +315,7 @@ class UserPlay(UserScore):
|
|||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if x:
|
if x:
|
||||||
self.prog_boost_multiply = 300 if x[0] == 300 else 0
|
self.prog_boost_multiply = 300 if x[0] == 300 else 0
|
||||||
if x[1] < self.beyond_boost_gauge_usage or (self.beyond_boost_gauge_usage != 100 and self.beyond_boost_gauge_usage != 200):
|
if x[1] < self.beyond_boost_gauge_usage or self.beyond_boost_gauge_usage not in (100, 200):
|
||||||
# 注意:偷懒了,没判断是否是beyond图
|
# 注意:偷懒了,没判断是否是beyond图
|
||||||
self.beyond_boost_gauge_usage = 0
|
self.beyond_boost_gauge_usage = 0
|
||||||
|
|
||||||
@@ -374,8 +387,8 @@ class UserPlay(UserScore):
|
|||||||
'''更新此分数对应用户的recent30'''
|
'''更新此分数对应用户的recent30'''
|
||||||
old_recent_10 = self.ptt.recent_10
|
old_recent_10 = self.ptt.recent_10
|
||||||
if self.is_protected:
|
if self.is_protected:
|
||||||
old_r30 = [x for x in self.ptt.r30]
|
old_r30 = self.ptt.r30.copy()
|
||||||
old_s30 = [x for x in self.ptt.s30]
|
old_s30 = self.ptt.s30.copy()
|
||||||
|
|
||||||
# 寻找pop位置
|
# 寻找pop位置
|
||||||
songs = list(set(self.ptt.s30))
|
songs = list(set(self.ptt.s30))
|
||||||
@@ -416,10 +429,21 @@ class UserPlay(UserScore):
|
|||||||
|
|
||||||
def record_score(self) -> None:
|
def record_score(self) -> None:
|
||||||
'''向log数据库记录分数,请注意列名不同'''
|
'''向log数据库记录分数,请注意列名不同'''
|
||||||
with Connect(Constant.SQLITE_LOG_DATABASE_PATH) as c2:
|
logdb_execute('''insert into user_score values(?,?,?,?,?,?,?,?,?,?,?,?,?)''', (self.user.user_id, self.song.song_id, self.song.difficulty, self.time_played,
|
||||||
c2.execute('''insert into user_score values(?,?,?,?,?,?,?,?,?,?,?,?,?)''', (self.user.user_id, self.song.song_id, self.song.difficulty, self.time_played,
|
|
||||||
self.score, self.shiny_perfect_count, self.perfect_count, self.near_count, self.miss_count, self.health, self.modifier, self.clear_type, self.rating))
|
self.score, self.shiny_perfect_count, self.perfect_count, self.near_count, self.miss_count, self.health, self.modifier, self.clear_type, self.rating))
|
||||||
|
|
||||||
|
def record_rating_ptt(self, user_rating_ptt: float) -> None:
|
||||||
|
'''向log数据库记录用户ptt变化'''
|
||||||
|
today_timestamp = get_today_timestamp()
|
||||||
|
with Connect(Config.SQLITE_LOG_DATABASE_PATH) as c2:
|
||||||
|
old_ptt = c2.execute('''select rating_ptt from user_rating where user_id=? and time=?''', (
|
||||||
|
self.user.user_id, today_timestamp)).fetchone()
|
||||||
|
|
||||||
|
old_ptt = 0 if old_ptt is None else old_ptt[0]
|
||||||
|
if old_ptt != user_rating_ptt:
|
||||||
|
c2.execute('''insert or replace into user_rating values(?,?,?)''',
|
||||||
|
(self.user.user_id, today_timestamp, user_rating_ptt))
|
||||||
|
|
||||||
def upload_score(self) -> None:
|
def upload_score(self) -> None:
|
||||||
'''上传分数,包括user的recent更新,best更新,recent30更新,世界模式计算'''
|
'''上传分数,包括user的recent更新,best更新,recent30更新,世界模式计算'''
|
||||||
self.get_play_state()
|
self.get_play_state()
|
||||||
@@ -463,7 +487,9 @@ class UserPlay(UserScore):
|
|||||||
self.update_recent30()
|
self.update_recent30()
|
||||||
|
|
||||||
# 总PTT更新
|
# 总PTT更新
|
||||||
self.user.rating_ptt = int(self.ptt.value * 100)
|
user_rating_ptt = self.ptt.value
|
||||||
|
self.user.rating_ptt = int(user_rating_ptt * 100)
|
||||||
|
BGTask(self.record_rating_ptt, user_rating_ptt) # 记录总PTT变换
|
||||||
self.c.execute('''update user set rating_ptt = :a where user_id = :b''', {
|
self.c.execute('''update user set rating_ptt = :a where user_id = :b''', {
|
||||||
'a': self.user.rating_ptt, 'b': self.user.user_id})
|
'a': self.user.rating_ptt, 'b': self.user.user_id})
|
||||||
|
|
||||||
@@ -479,7 +505,8 @@ class UserPlay(UserScore):
|
|||||||
|
|
||||||
class Potential:
|
class Potential:
|
||||||
'''
|
'''
|
||||||
用户潜力值计算处理类\
|
用户潜力值计算处理类
|
||||||
|
|
||||||
property: `user` - `User`类或子类的实例
|
property: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -487,8 +514,8 @@ class Potential:
|
|||||||
self.c = c
|
self.c = c
|
||||||
self.user = user
|
self.user = user
|
||||||
|
|
||||||
self.r30: list = None
|
self.r30: 'list[float]' = None
|
||||||
self.s30: list = None
|
self.s30: 'list[str]' = None
|
||||||
self.songs_selected: list = None
|
self.songs_selected: list = None
|
||||||
|
|
||||||
self.b30: list = None
|
self.b30: list = None
|
||||||
@@ -503,7 +530,7 @@ class Potential:
|
|||||||
'''获取用户best30的总潜力值'''
|
'''获取用户best30的总潜力值'''
|
||||||
self.c.execute('''select rating from best_score where user_id = :a order by rating DESC limit 30''', {
|
self.c.execute('''select rating from best_score where user_id = :a order by rating DESC limit 30''', {
|
||||||
'a': self.user.user_id})
|
'a': self.user.user_id})
|
||||||
return sum([x[0] for x in self.c.fetchall()])
|
return sum(x[0] for x in self.c.fetchall())
|
||||||
|
|
||||||
def select_recent_30(self) -> None:
|
def select_recent_30(self) -> None:
|
||||||
'''获取用户recent30数据'''
|
'''获取用户recent30数据'''
|
||||||
@@ -578,7 +605,8 @@ class Potential:
|
|||||||
|
|
||||||
class UserScoreList:
|
class UserScoreList:
|
||||||
'''
|
'''
|
||||||
用户分数查询类\
|
用户分数查询类
|
||||||
|
|
||||||
properties: `user` - `User`类或子类的实例
|
properties: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ class Chart:
|
|||||||
|
|
||||||
def __init__(self, c=None, song_id: str = None, difficulty: int = None) -> None:
|
def __init__(self, c=None, song_id: str = None, difficulty: int = None) -> None:
|
||||||
self.c = c
|
self.c = c
|
||||||
|
self.song_id: str = None
|
||||||
|
self.difficulty: int = None
|
||||||
self.set_chart(song_id, difficulty)
|
self.set_chart(song_id, difficulty)
|
||||||
self.defnum: int = None
|
self.defnum: int = None
|
||||||
self.song_name: str = None
|
self.song_name: str = None
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
|
import os
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import traceback
|
import traceback
|
||||||
from atexit import register
|
from atexit import register
|
||||||
|
|
||||||
from .constant import Constant
|
from .config_manager import Config
|
||||||
|
from .constant import ARCAEA_LOG_DATBASE_VERSION, Constant
|
||||||
from .error import ArcError, InputError
|
from .error import ArcError, InputError
|
||||||
|
|
||||||
|
|
||||||
@@ -12,8 +14,8 @@ class Connect:
|
|||||||
|
|
||||||
def __init__(self, file_path: str = Constant.SQLITE_DATABASE_PATH, in_memory: bool = False, logger=None) -> None:
|
def __init__(self, file_path: str = Constant.SQLITE_DATABASE_PATH, in_memory: bool = False, logger=None) -> None:
|
||||||
"""
|
"""
|
||||||
数据库连接,默认连接arcaea_database.db\
|
数据库连接,默认连接arcaea_database.db
|
||||||
接受:文件路径\
|
接受:文件路径
|
||||||
返回:sqlite3连接操作对象
|
返回:sqlite3连接操作对象
|
||||||
"""
|
"""
|
||||||
self.file_path = file_path
|
self.file_path = file_path
|
||||||
@@ -21,6 +23,9 @@ class Connect:
|
|||||||
if logger is not None:
|
if logger is not None:
|
||||||
self.logger = logger
|
self.logger = logger
|
||||||
|
|
||||||
|
self.conn: sqlite3.Connection = None
|
||||||
|
self.c: sqlite3.Cursor = None
|
||||||
|
|
||||||
def __enter__(self) -> sqlite3.Cursor:
|
def __enter__(self) -> sqlite3.Cursor:
|
||||||
if self.in_memory:
|
if self.in_memory:
|
||||||
self.conn = sqlite3.connect(
|
self.conn = sqlite3.connect(
|
||||||
@@ -144,19 +149,19 @@ class Query:
|
|||||||
raise InputError(api_error_code=-104)
|
raise InputError(api_error_code=-104)
|
||||||
self.__sort = sort
|
self.__sort = sort
|
||||||
|
|
||||||
def set_value(self, limit=-1, offset=0, query={}, fuzzy_query={}, sort=[]) -> None:
|
def set_value(self, limit=-1, offset=0, query=None, fuzzy_query=None, sort=None) -> None:
|
||||||
self.limit = limit
|
self.limit = limit
|
||||||
self.offset = offset
|
self.offset = offset
|
||||||
self.query = query
|
self.query = query if query is not None else {}
|
||||||
self.fuzzy_query = fuzzy_query
|
self.fuzzy_query = fuzzy_query if fuzzy_query is not None else {}
|
||||||
self.sort = sort
|
self.sort = sort if sort is not None else []
|
||||||
|
|
||||||
def from_dict(self, d: dict) -> 'Query':
|
def from_dict(self, d: dict) -> 'Query':
|
||||||
self.set_value(d.get('limit', -1), d.get('offset', 0),
|
self.set_value(d.get('limit', -1), d.get('offset', 0),
|
||||||
d.get('query', {}), d.get('fuzzy_query', {}), d.get('sort', []))
|
d.get('query', {}), d.get('fuzzy_query', {}), d.get('sort', []))
|
||||||
return self
|
return self
|
||||||
|
|
||||||
def from_args(self, query: dict, limit: int = -1, offset: int = 0, sort: list = [], fuzzy_query: dict = {}) -> 'Query':
|
def from_args(self, query: dict, limit: int = -1, offset: int = 0, sort: list = None, fuzzy_query: dict = None) -> 'Query':
|
||||||
self.set_value(limit, offset, query, fuzzy_query, sort)
|
self.set_value(limit, offset, query, fuzzy_query, sort)
|
||||||
return self
|
return self
|
||||||
|
|
||||||
@@ -170,7 +175,7 @@ class Sql:
|
|||||||
self.c = c
|
self.c = c
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_select_sql(table_name: str, target_column: list = [], query: 'Query' = None):
|
def get_select_sql(table_name: str, target_column: list = None, query: 'Query' = None):
|
||||||
'''拼接单表内行查询单句sql语句,返回语句和参数列表'''
|
'''拼接单表内行查询单句sql语句,返回语句和参数列表'''
|
||||||
sql_list = []
|
sql_list = []
|
||||||
if not target_column:
|
if not target_column:
|
||||||
@@ -210,8 +215,10 @@ class Sql:
|
|||||||
return sql, sql_list
|
return sql, sql_list
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_insert_sql(table_name: str, key: list = [], value_len: int = None, insert_type: str = None) -> str:
|
def get_insert_sql(table_name: str, key: list = None, value_len: int = None, insert_type: str = None) -> str:
|
||||||
'''拼接insert语句,请注意只返回sql语句,insert_type为replace或ignore'''
|
'''拼接insert语句,请注意只返回sql语句,insert_type为replace或ignore'''
|
||||||
|
if key is None:
|
||||||
|
key = []
|
||||||
insert_type = 'replace' if insert_type in [
|
insert_type = 'replace' if insert_type in [
|
||||||
'replace', 'R', 'r', 'REPLACE'] else 'ignore'
|
'replace', 'R', 'r', 'REPLACE'] else 'ignore'
|
||||||
return ('insert into ' if insert_type is None else 'insert or ' + insert_type + ' into ') + table_name + ('(' + ','.join(key) + ')' if key else '') + ' values(' + ','.join(['?'] * (len(key) if value_len is None else value_len)) + ')'
|
return ('insert into ' if insert_type is None else 'insert or ' + insert_type + ' into ') + table_name + ('(' + ','.join(key) + ')' if key else '') + ' values(' + ','.join(['?'] * (len(key) if value_len is None else value_len)) + ')'
|
||||||
@@ -281,13 +288,13 @@ class Sql:
|
|||||||
|
|
||||||
return sql, sql_list
|
return sql, sql_list
|
||||||
|
|
||||||
def select(self, table_name: str, target_column: list = [], query: 'Query' = None) -> list:
|
def select(self, table_name: str, target_column: list = None, query: 'Query' = None) -> list:
|
||||||
'''单表内行select单句sql语句,返回fetchall数据'''
|
'''单表内行select单句sql语句,返回fetchall数据'''
|
||||||
sql, sql_list = self.get_select_sql(table_name, target_column, query)
|
sql, sql_list = self.get_select_sql(table_name, target_column, query)
|
||||||
self.c.execute(sql, sql_list)
|
self.c.execute(sql, sql_list)
|
||||||
return self.c.fetchall()
|
return self.c.fetchall()
|
||||||
|
|
||||||
def select_exists(self, table_name: str, target_column: list = [], query: 'Query' = None) -> bool:
|
def select_exists(self, table_name: str, target_column: list = None, query: 'Query' = None) -> bool:
|
||||||
'''单表内行select exists单句sql语句,返回bool值'''
|
'''单表内行select exists单句sql语句,返回bool值'''
|
||||||
sql, sql_list = self.get_select_sql(table_name, target_column, query)
|
sql, sql_list = self.get_select_sql(table_name, target_column, query)
|
||||||
self.c.execute('select exists(' + sql + ')', sql_list)
|
self.c.execute('select exists(' + sql + ')', sql_list)
|
||||||
@@ -329,7 +336,7 @@ class Sql:
|
|||||||
pk = []
|
pk = []
|
||||||
name = []
|
name = []
|
||||||
|
|
||||||
self.c.execute('''pragma table_info ("%s")''' % table_name) # 这里无法参数化
|
self.c.execute(f'''pragma table_info ("{table_name}")''') # 这里无法参数化
|
||||||
x = self.c.fetchall()
|
x = self.c.fetchall()
|
||||||
if x:
|
if x:
|
||||||
for i in x:
|
for i in x:
|
||||||
@@ -390,8 +397,8 @@ class DatabaseMigrator:
|
|||||||
'''
|
'''
|
||||||
with Connect(self.c2_path) as c2:
|
with Connect(self.c2_path) as c2:
|
||||||
with Connect(self.c1_path) as c1:
|
with Connect(self.c1_path) as c1:
|
||||||
[self.update_one_table(c1, c2, i)
|
for i in Constant.DATABASE_MIGRATE_TABLES:
|
||||||
for i in Constant.DATABASE_MIGRATE_TABLES]
|
self.update_one_table(c1, c2, i)
|
||||||
|
|
||||||
if not Constant.UPDATE_WITH_NEW_CHARACTER_DATA:
|
if not Constant.UPDATE_WITH_NEW_CHARACTER_DATA:
|
||||||
self.update_one_table(c1, c2, 'character')
|
self.update_one_table(c1, c2, 'character')
|
||||||
@@ -399,6 +406,31 @@ class DatabaseMigrator:
|
|||||||
self.update_user_char_full(c2) # 更新user_char_full
|
self.update_user_char_full(c2) # 更新user_char_full
|
||||||
|
|
||||||
|
|
||||||
|
class LogDatabaseMigrator:
|
||||||
|
|
||||||
|
def __init__(self, c1_path: str = Config.SQLITE_LOG_DATABASE_PATH) -> None:
|
||||||
|
self.c1_path = c1_path
|
||||||
|
# self.c2_path = c2_path
|
||||||
|
self.init_folder_path = Config.DATABASE_INIT_PATH
|
||||||
|
self.c = None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sql_path(self) -> str:
|
||||||
|
return os.path.join(self.init_folder_path, 'log_tables.sql')
|
||||||
|
|
||||||
|
def table_update(self) -> None:
|
||||||
|
'''直接更新数据库结构'''
|
||||||
|
with open(self.sql_path, 'r') as f:
|
||||||
|
self.c.executescript(f.read())
|
||||||
|
self.c.execute(
|
||||||
|
'''insert or replace into cache values("version", :a, -1);''', {'a': ARCAEA_LOG_DATBASE_VERSION})
|
||||||
|
|
||||||
|
def update_database(self) -> None:
|
||||||
|
with Connect(self.c1_path) as c:
|
||||||
|
self.c = c
|
||||||
|
self.table_update()
|
||||||
|
|
||||||
|
|
||||||
class MemoryDatabase:
|
class MemoryDatabase:
|
||||||
conn = sqlite3.connect('file:arc_tmp?mode=memory&cache=shared', uri=True)
|
conn = sqlite3.connect('file:arc_tmp?mode=memory&cache=shared', uri=True)
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class GameInfo:
|
|||||||
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
|
"stamina_recover_tick": Constant.STAMINA_RECOVER_TICK,
|
||||||
"core_exp": Constant.CORE_EXP,
|
"core_exp": Constant.CORE_EXP,
|
||||||
"curr_ts": int(time()*1000),
|
"curr_ts": int(time()*1000),
|
||||||
"level_steps": [{'level': i, 'level_exp': Constant.LEVEL_STEPS[i]} for i in Constant.LEVEL_STEPS],
|
"level_steps": [{'level': k, 'level_exp': v} for k, v in Constant.LEVEL_STEPS.items()],
|
||||||
"world_ranking_enabled": True,
|
"world_ranking_enabled": True,
|
||||||
"is_byd_chapter_unlocked": True
|
"is_byd_chapter_unlocked": True
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import base64
|
|||||||
import hashlib
|
import hashlib
|
||||||
import time
|
import time
|
||||||
from os import urandom
|
from os import urandom
|
||||||
|
from random import randint
|
||||||
|
|
||||||
from .character import UserCharacter, UserCharacterList
|
from .character import UserCharacter, UserCharacterList
|
||||||
from .config_manager import Config
|
from .config_manager import Config
|
||||||
@@ -100,7 +101,6 @@ class UserRegister(User):
|
|||||||
|
|
||||||
def _build_user_code(self):
|
def _build_user_code(self):
|
||||||
# 生成9位的user_code,用的自然是随机
|
# 生成9位的user_code,用的自然是随机
|
||||||
from random import randint
|
|
||||||
random_times = 0
|
random_times = 0
|
||||||
|
|
||||||
while random_times <= 1000:
|
while random_times <= 1000:
|
||||||
@@ -237,7 +237,8 @@ class UserLogin(User):
|
|||||||
self.set_ip(ip)
|
self.set_ip(ip)
|
||||||
|
|
||||||
if not self.limiter.hit(name):
|
if not self.limiter.hit(name):
|
||||||
raise RateLimit('Too many login attempts.', 123, -203)
|
raise RateLimit(
|
||||||
|
f'Too many login attempts of username `{name}`', 123, -203)
|
||||||
|
|
||||||
self.c.execute('''select user_id, password, ban_flag from user where name = :name''', {
|
self.c.execute('''select user_id, password, ban_flag from user where name = :name''', {
|
||||||
'name': self.name})
|
'name': self.name})
|
||||||
@@ -251,7 +252,7 @@ class UserLogin(User):
|
|||||||
# 自动封号检查
|
# 自动封号检查
|
||||||
ban_timestamp = int(x[2].split(':', 1)[1])
|
ban_timestamp = int(x[2].split(':', 1)[1])
|
||||||
if ban_timestamp > self.now:
|
if ban_timestamp > self.now:
|
||||||
raise UserBan('Too many devices logging in during 24 hours.', 105, extra_data={
|
raise UserBan(f'Too many devices user `{self.user_id}` logging in during 24 hours.', 105, extra_data={
|
||||||
'remaining_ts': ban_timestamp-self.now})
|
'remaining_ts': ban_timestamp-self.now})
|
||||||
|
|
||||||
if x[1] == '':
|
if x[1] == '':
|
||||||
@@ -260,7 +261,7 @@ class UserLogin(User):
|
|||||||
f'The account `{self.user_id}` has been banned.', 106)
|
f'The account `{self.user_id}` has been banned.', 106)
|
||||||
|
|
||||||
if x[1] != self.hash_pwd:
|
if x[1] != self.hash_pwd:
|
||||||
raise NoAccess('Wrong password.', 104)
|
raise NoAccess(f'Wrong password of user `{self.user_id}`', 104)
|
||||||
|
|
||||||
self.token = base64.b64encode(hashlib.sha256(
|
self.token = base64.b64encode(hashlib.sha256(
|
||||||
(str(self.user_id) + str(self.now)).encode("utf8") + urandom(8)).digest()).decode()
|
(str(self.user_id) + str(self.now)).encode("utf8") + urandom(8)).digest()).decode()
|
||||||
@@ -309,6 +310,8 @@ class UserInfo(User):
|
|||||||
self.beyond_boost_gauge: float = 0
|
self.beyond_boost_gauge: float = 0
|
||||||
self.next_fragstam_ts: int = None
|
self.next_fragstam_ts: int = None
|
||||||
self.world_mode_locked_end_ts: int = None
|
self.world_mode_locked_end_ts: int = None
|
||||||
|
self.current_map: 'Map' = None
|
||||||
|
self.stamina: 'UserStamina' = None
|
||||||
|
|
||||||
self.__cores: list = None
|
self.__cores: list = None
|
||||||
self.__packs: list = None
|
self.__packs: list = None
|
||||||
@@ -406,7 +409,7 @@ class UserInfo(User):
|
|||||||
self.c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
self.c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''',
|
||||||
{'x': i[0], 'y': self.user_id})
|
{'x': i[0], 'y': self.user_id})
|
||||||
|
|
||||||
is_mutual = True if self.c.fetchone() == (1,) else False
|
is_mutual = self.c.fetchone() == (1,)
|
||||||
|
|
||||||
you = UserOnline(self.c, i[0])
|
you = UserOnline(self.c, i[0])
|
||||||
you.select_user()
|
you.select_user()
|
||||||
@@ -660,16 +663,16 @@ class UserInfo(User):
|
|||||||
|
|
||||||
score_sum = 0
|
score_sum = 0
|
||||||
if len(song_list_ftr) >= 2:
|
if len(song_list_ftr) >= 2:
|
||||||
self.c.execute('''select sum(score) from best_score where user_id=? and difficulty=2 and song_id in ({0})'''.format(
|
self.c.execute(
|
||||||
','.join(['?']*(len(song_list_ftr)-1))), tuple(song_list_ftr))
|
f'''select sum(score) from best_score where user_id=? and difficulty=2 and song_id in ({','.join(['?']*(len(song_list_ftr)-1))})''', tuple(song_list_ftr))
|
||||||
|
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if x[0] is not None:
|
if x[0] is not None:
|
||||||
score_sum += x[0]
|
score_sum += x[0]
|
||||||
|
|
||||||
if len(song_list_byn) >= 2:
|
if len(song_list_byn) >= 2:
|
||||||
self.c.execute('''select sum(score) from best_score where user_id=? and difficulty=3 and song_id in ({0})'''.format(
|
self.c.execute(
|
||||||
','.join(['?']*(len(song_list_byn)-1))), tuple(song_list_byn))
|
f'''select sum(score) from best_score where user_id=? and difficulty=3 and song_id in ({','.join(['?']*(len(song_list_byn)-1))})''', tuple(song_list_byn))
|
||||||
|
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if x[0] is not None:
|
if x[0] is not None:
|
||||||
@@ -682,13 +685,13 @@ class UserInfo(User):
|
|||||||
|
|
||||||
def select_user_one_column(self, column_name: str, default_value=None) -> None:
|
def select_user_one_column(self, column_name: str, default_value=None) -> None:
|
||||||
'''
|
'''
|
||||||
查询user表的某个属性\
|
查询user表的某个属性
|
||||||
请注意必须是一个普通属性,不能是一个类的实例
|
请注意必须是一个普通属性,不能是一个类的实例
|
||||||
'''
|
'''
|
||||||
if column_name not in self.__dict__:
|
if column_name not in self.__dict__:
|
||||||
raise InputError('No such column.')
|
raise InputError('No such column.')
|
||||||
self.c.execute('''select %s from user where user_id = :a''' %
|
self.c.execute(f'''select {column_name} from user where user_id = :a''', {
|
||||||
column_name, {'a': self.user_id})
|
'a': self.user_id})
|
||||||
x = self.c.fetchone()
|
x = self.c.fetchone()
|
||||||
if not x:
|
if not x:
|
||||||
raise NoData('No user.', 108, -3)
|
raise NoData('No user.', 108, -3)
|
||||||
@@ -697,15 +700,15 @@ class UserInfo(User):
|
|||||||
|
|
||||||
def update_user_one_column(self, column_name: str, value=None) -> None:
|
def update_user_one_column(self, column_name: str, value=None) -> None:
|
||||||
'''
|
'''
|
||||||
更新user表的某个属性\
|
更新user表的某个属性
|
||||||
请注意必须是一个普通属性,不能是一个类的实例
|
请注意必须是一个普通属性,不能是一个类的实例
|
||||||
'''
|
'''
|
||||||
if column_name not in self.__dict__:
|
if column_name not in self.__dict__:
|
||||||
raise InputError('No such column.')
|
raise InputError('No such column.')
|
||||||
if value is not None:
|
if value is not None:
|
||||||
self.__dict__[column_name] = value
|
self.__dict__[column_name] = value
|
||||||
self.c.execute('''update user set %s = :a where user_id = :b''' %
|
self.c.execute(f'''update user set {column_name} = :a where user_id = :b''', {
|
||||||
column_name, {'a': self.__dict__[column_name], 'b': self.user_id})
|
'a': self.__dict__[column_name], 'b': self.user_id})
|
||||||
|
|
||||||
|
|
||||||
class UserOnline(UserInfo):
|
class UserOnline(UserInfo):
|
||||||
@@ -761,7 +764,7 @@ class UserChanger(UserInfo, UserRegister):
|
|||||||
if columns is not None:
|
if columns is not None:
|
||||||
d = {}
|
d = {}
|
||||||
for column in columns:
|
for column in columns:
|
||||||
if column == 'password':
|
if column == 'password' and self.password != '':
|
||||||
d[column] = self.hash_pwd
|
d[column] = self.hash_pwd
|
||||||
else:
|
else:
|
||||||
d[column] = self.__dict__[column]
|
d[column] = self.__dict__[column]
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import os
|
import os
|
||||||
|
from datetime import date
|
||||||
|
from time import mktime
|
||||||
|
|
||||||
|
|
||||||
def md5(code: str) -> str:
|
def md5(code: str) -> str:
|
||||||
@@ -37,3 +39,8 @@ def try_rename(path: str, new_path: str) -> str:
|
|||||||
|
|
||||||
os.rename(path, final_path)
|
os.rename(path, final_path)
|
||||||
return final_path
|
return final_path
|
||||||
|
|
||||||
|
|
||||||
|
def get_today_timestamp():
|
||||||
|
'''相对于本机本地时间的今天0点的时间戳'''
|
||||||
|
return int(mktime(date.today().timetuple()))
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import json
|
|
||||||
import os
|
import os
|
||||||
from functools import lru_cache
|
from functools import lru_cache
|
||||||
|
from json import load
|
||||||
from random import random
|
from random import random
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
@@ -25,15 +25,15 @@ def get_world_name(file_dir: str = Constant.WORLD_MAP_FOLDER_PATH) -> list:
|
|||||||
def get_world_info(map_id: str) -> dict:
|
def get_world_info(map_id: str) -> dict:
|
||||||
'''读取json文件内容,返回字典'''
|
'''读取json文件内容,返回字典'''
|
||||||
world_info = {}
|
world_info = {}
|
||||||
with open(os.path.join(Constant.WORLD_MAP_FOLDER_PATH, map_id+'.json'), 'r') as f:
|
with open(os.path.join(Constant.WORLD_MAP_FOLDER_PATH, f'{map_id}.json'), 'rb') as f:
|
||||||
world_info = json.load(f)
|
world_info = load(f)
|
||||||
|
|
||||||
return world_info
|
return world_info
|
||||||
|
|
||||||
|
|
||||||
def get_world_all(c, user) -> list:
|
def get_world_all(c, user) -> list:
|
||||||
'''
|
'''
|
||||||
读取所有地图信息,返回列表\
|
读取所有地图信息,返回列表
|
||||||
parameter: `user` - `User`类或子类的实例
|
parameter: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
worlds = get_world_name()
|
worlds = get_world_name()
|
||||||
@@ -44,7 +44,7 @@ class Step:
|
|||||||
'''台阶类'''
|
'''台阶类'''
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.postion: int = None
|
self.position: int = None
|
||||||
self.capture: int = None
|
self.capture: int = None
|
||||||
self.items: list = []
|
self.items: list = []
|
||||||
self.restrict_id: str = None
|
self.restrict_id: str = None
|
||||||
@@ -198,7 +198,7 @@ class Map:
|
|||||||
|
|
||||||
class UserMap(Map):
|
class UserMap(Map):
|
||||||
'''
|
'''
|
||||||
用户地图类\
|
用户地图类
|
||||||
parameters: `user` - `User`类或者子类的实例
|
parameters: `user` - `User`类或者子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -413,7 +413,8 @@ class Stamina:
|
|||||||
|
|
||||||
class UserStamina(Stamina):
|
class UserStamina(Stamina):
|
||||||
'''
|
'''
|
||||||
用户体力类\
|
用户体力类
|
||||||
|
|
||||||
parameter: `user` - `User`类或子类的实例
|
parameter: `user` - `User`类或子类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -439,8 +440,9 @@ class UserStamina(Stamina):
|
|||||||
|
|
||||||
class WorldPlay:
|
class WorldPlay:
|
||||||
'''
|
'''
|
||||||
世界模式打歌类,处理特殊角色技能,联动UserMap和UserPlay\
|
世界模式打歌类,处理特殊角色技能,联动UserMap和UserPlay
|
||||||
parameter: `user` - `UserOnline`类或子类的实例\
|
|
||||||
|
parameter: `user` - `UserOnline`类或子类的实例
|
||||||
'user_play` - `UserPlay`类的实例
|
'user_play` - `UserPlay`类的实例
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -598,8 +600,8 @@ class WorldPlay:
|
|||||||
if self.user_play.beyond_gauge == 0:
|
if self.user_play.beyond_gauge == 0:
|
||||||
# 更新byd大招蓄力条
|
# 更新byd大招蓄力条
|
||||||
self.user.beyond_boost_gauge += self.beyond_boost_gauge_addition
|
self.user.beyond_boost_gauge += self.beyond_boost_gauge_addition
|
||||||
if self.user.beyond_boost_gauge > 200:
|
self.user.beyond_boost_gauge = min(
|
||||||
self.user.beyond_boost_gauge = 200
|
self.user.beyond_boost_gauge, 200)
|
||||||
self.user.update_user_one_column(
|
self.user.update_user_one_column(
|
||||||
'beyond_boost_gauge', self.user.beyond_boost_gauge)
|
'beyond_boost_gauge', self.user.beyond_boost_gauge)
|
||||||
elif self.user_play.beyond_boost_gauge_usage != 0 and self.user_play.beyond_boost_gauge_usage <= self.user.beyond_boost_gauge:
|
elif self.user_play.beyond_boost_gauge_usage != 0 and self.user_play.beyond_boost_gauge_usage <= self.user.beyond_boost_gauge:
|
||||||
@@ -656,6 +658,8 @@ class WorldPlay:
|
|||||||
self._special_tempest()
|
self._special_tempest()
|
||||||
elif self.character_used.skill_id_displayed == 'ilith_awakened_skill':
|
elif self.character_used.skill_id_displayed == 'ilith_awakened_skill':
|
||||||
self._ilith_awakened_skill()
|
self._ilith_awakened_skill()
|
||||||
|
elif self.character_used.skill_id_displayed == 'skill_mithra':
|
||||||
|
self._skill_mithra()
|
||||||
else:
|
else:
|
||||||
if self.character_used.skill_id_displayed == 'skill_vita':
|
if self.character_used.skill_id_displayed == 'skill_vita':
|
||||||
self._skill_vita()
|
self._skill_vita()
|
||||||
@@ -684,7 +688,7 @@ class WorldPlay:
|
|||||||
|
|
||||||
def _skill_vita(self) -> None:
|
def _skill_vita(self) -> None:
|
||||||
'''
|
'''
|
||||||
vita技能,overdrive随回忆率提升,提升量最多为10\
|
vita技能,overdrive随回忆率提升,提升量最多为10
|
||||||
此处采用线性函数
|
此处采用线性函数
|
||||||
'''
|
'''
|
||||||
self.over_skill_increase = 0
|
self.over_skill_increase = 0
|
||||||
@@ -745,7 +749,7 @@ class WorldPlay:
|
|||||||
'''
|
'''
|
||||||
x: 'Step' = self.user.current_map.steps_for_climbing[0]
|
x: 'Step' = self.user.current_map.steps_for_climbing[0]
|
||||||
if ('randomsong' in x.step_type or 'speedlimit' in x.step_type) and self.user_play.song_grade < 5:
|
if ('randomsong' in x.step_type or 'speedlimit' in x.step_type) and self.user_play.song_grade < 5:
|
||||||
self.character_bonus_progress = -self.step_value / 2 / self.step_times
|
self.character_bonus_progress = -1 * self.step_value / 2 / self.step_times
|
||||||
self.step_value = self.step_value / 2
|
self.step_value = self.step_value / 2
|
||||||
self.user.current_map.reclimb(self.step_value)
|
self.user.current_map.reclimb(self.step_value)
|
||||||
|
|
||||||
@@ -765,3 +769,10 @@ class WorldPlay:
|
|||||||
self.character_used.level)
|
self.character_used.level)
|
||||||
self.prog_skill_increase = self.character_used.prog.get_value(
|
self.prog_skill_increase = self.character_used.prog.get_value(
|
||||||
self.character_used.level)
|
self.character_used.level)
|
||||||
|
|
||||||
|
def _skill_mithra(self) -> None:
|
||||||
|
'''
|
||||||
|
mithra 技能,每 150 combo 增加世界模式进度+1,直接理解成 prog 值增加
|
||||||
|
'''
|
||||||
|
if self.user_play.combo_interval_bonus:
|
||||||
|
self.prog_skill_increase = self.user_play.combo_interval_bonus
|
||||||
|
|||||||
@@ -1,45 +1,45 @@
|
|||||||
class InitData:
|
class InitData:
|
||||||
char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', 'hikari&tairitsu(reunion)', 'Tairitsu(Axium)', 'Tairitsu(Grievous Lady)', 'stella', 'Hikari & Fisica', 'ilith', 'eto', 'luna', 'shirabe', 'Hikari(Zero)', 'Hikari(Fracture)', 'Hikari(Summer)', 'Tairitsu(Summer)', 'Tairitsu & Trin',
|
char = ['hikari', 'tairitsu', 'kou', 'sapphire', 'lethe', 'hikari&tairitsu(reunion)', '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', 'vita', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)', 'shama(UNiVERSE)', 'milk(UNiVERSE)', 'shikoku', 'mika yurisaki']
|
'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', 'hikari(fatalis)', 'saki', 'setsuna', 'amane', 'kou(winter)', 'lagrange(aria)', 'lethe(apophenia)', 'shama(UNiVERSE)', 'milk(UNiVERSE)', 'shikoku', 'mika yurisaki', 'Mithra Tercera', 'Toa Kozukata', 'Nami(Twilight)']
|
||||||
|
|
||||||
skill_id = ['gauge_easy', '', '', '', 'note_mirror', 'skill_reunion', '', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
|
skill_id = ['gauge_easy', '', '', '', 'note_mirror', 'skill_reunion', '', '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', 'skill_vita', 'skill_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika']
|
'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_fatalis', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', '', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'skill_mithra', 'skill_toa', 'skill_nami_twilight']
|
||||||
|
|
||||||
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', 'ilith_awakened_skill', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
|
skill_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', 'ilith_awakened_skill', '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,
|
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, 8, 0, 0, 0, 0, 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, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
|
||||||
|
|
||||||
frag1 = [55, 55, 60, 50, 47, 79, 47, 57, 41, 22, 50, 54, 60, 56, 78, 42, 41, 61, 52, 50, 52, 32,
|
frag1 = [55, 55, 60, 50, 47, 79, 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, 38, 25, 58, 50, 61, 45, 45, 38, 34, 27, 18, 56, 47, 30, 45, 57, 55.5, 47, 33, 26]
|
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, 27, 18, 56, 47, 30, 45, 57, 56, 47, 33, 26, 29, 66, 40]
|
||||||
|
|
||||||
prog1 = [35, 55, 47, 50, 60, 70, 60, 70, 58, 45, 70, 45, 42, 46, 61, 67, 49, 44, 28, 45, 24, 46, 52,
|
prog1 = [35, 55, 47, 50, 60, 70, 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, 50, 130, 18, 57, 55, 50, 45, 70, 37.5, 29, 44, 26]
|
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, 130, 18, 57, 55, 50, 45, 70, 37.5, 29, 44, 26, 26, 35, 40]
|
||||||
|
|
||||||
overdrive1 = [35, 55, 25, 50, 47, 70, 72, 57, 41, 7, 10, 32, 65, 31, 61, 53, 31, 47, 38, 12, 39, 18,
|
overdrive1 = [35, 55, 25, 50, 47, 70, 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, 38, 30, 49, 15, 34, 45, 45, 38, 67, 120, 44, 33, 55, 50, 45, 57, 31, 29, 65, 26]
|
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, 120, 44, 33, 55, 50, 45, 57, 31, 29, 65, 26, 29, 42.5, 40]
|
||||||
|
|
||||||
frag20 = [78, 80, 90, 75, 70, 79, 70, 79, 65, 40, 50, 80, 90, 82, 0, 61, 67, 92, 85, 50, 86, 52,
|
frag20 = [78, 80, 90, 75, 70, 79, 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, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 89.5, 50, 50, 51]
|
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, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 54, 100, 50]
|
||||||
|
|
||||||
prog20 = [61, 80, 70, 75, 90, 70, 90, 102, 84, 78, 105, 67, 63, 68, 0, 99, 80, 66, 46, 83, 40, 73,
|
prog20 = [61, 80, 70, 75, 90, 70, 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, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51]
|
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, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 50, 53, 85]
|
||||||
|
|
||||||
overdrive20 = [61, 80, 47, 75, 70, 70, 95, 79, 65, 31, 50, 59, 90, 58, 0, 78, 50, 70, 62, 49, 64,
|
overdrive20 = [61, 80, 47, 75, 70, 70, 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, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51]
|
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, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 54, 65.5, 60]
|
||||||
|
|
||||||
frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62,
|
frag30 = [88, 90, 100, 75, 80, 89, 70, 79, 65, 40, 50, 90, 100, 92, 0, 61, 67, 92, 85, 50, 86, 62,
|
||||||
65, 85, 67, 88, 74, 0.5, 105, 80, 95, 50, 80, 87, 71, 50, 95, 0, 80, 75, 50, 70, 80, 100, 65, 80, 61, 50, 68, 60, 90, 67, 50, 60, 51, 50, 35, 85, 47, 50, 75, 80, 89.5, 50, 50, 51]
|
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, 50, 35, 85, 47, 50, 75, 80, 90, 80, 50, 51, 64, 100, 50]
|
||||||
|
|
||||||
prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
|
prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 110, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
|
||||||
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51]
|
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, 210, 35, 86, 92, 80, 75, 100, 60, 50, 68, 51, 60, 53, 85]
|
||||||
|
|
||||||
overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64,
|
overdrive30 = [71, 90, 57, 75, 80, 80, 95, 79, 65, 31, 50, 69, 100, 68, 0, 78, 50, 70, 62, 59, 64,
|
||||||
56, 73, 95, 67, 84, 80, 88, 79, 80, 50, 80, 80, 63, 25, 50, 82, 55, 50, 95, 55, 70, 100, 80, 99, 80, 61, 40, 69, 62, 51, 90, 67, 60, 100, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51]
|
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, 200, 85, 50, 92, 50, 75, 80, 49.5, 50, 100, 51, 64, 65.5, 60]
|
||||||
|
|
||||||
char_type = [1, 0, 0, 0, 0, 0, 0, 2, 0, 1, 2, 0, 0, 0, 2, 3, 1, 0, 0, 0, 1,
|
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, 1, 0, 0, 0, 0, 0, 0, 0, 2, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 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, 3, 0, 2, 2, 0, 0, 2, 0, 0, 2, 0, 2, 2, 1]
|
||||||
|
|
||||||
char_core = {
|
char_core = {
|
||||||
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
|
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
|
||||||
@@ -58,14 +58,16 @@ class InitData:
|
|||||||
11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}],
|
11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}],
|
||||||
12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
|
12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
|
||||||
19: [{'core_id': 'core_colorful', 'amount': 30}],
|
19: [{'core_id': 'core_colorful', 'amount': 30}],
|
||||||
10: [{'core_id': 'core_umbral', 'amount': 30}], # TODO: check
|
10: [{'core_id': 'core_umbral', 'amount': 30}],
|
||||||
|
66: [{'core_id': 'core_chunithm', 'amount': 15}],
|
||||||
|
5: [{'core_id': 'core_hollow', 'amount': 0}]
|
||||||
}
|
}
|
||||||
|
|
||||||
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
|
cores = ['core_hollow', 'core_desolate', 'core_chunithm', 'core_crimson',
|
||||||
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase', 'core_umbral']
|
'core_ambivalent', 'core_scarlet', 'core_groove', 'core_generic', 'core_binary', 'core_colorful', 'core_course_skip_purchase', 'core_umbral']
|
||||||
|
|
||||||
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",
|
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", "nulctrl", "avril", "ddd", "merlin3", "omakeno3", "nekonote", "sanskia", 'altair', 'mukishitsu', 'trapcrow', 'redandblue3', 'ignotus3', 'singularity3', 'dropdead3', 'arcahv', 'freefall3', 'partyvinyl3', 'tsukinimurakumo', 'mantis', 'worldfragments', 'astrawalkthrough', 'chronicle', 'trappola3', 'letsrock']
|
"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", "sanskia", 'altair', 'mukishitsu', 'trapcrow', 'redandblue3', 'ignotus3', 'singularity3', 'dropdead3', 'arcahv', 'freefall3', 'partyvinyl3', 'tsukinimurakumo', 'mantis', 'worldfragments', 'astrawalkthrough', 'chronicle', 'trappola3', 'letsrock', 'shadesoflight3', 'teriqma3', 'impact3', 'lostemotion', 'gimmick', 'lawlesspoint', 'hybris']
|
||||||
|
|
||||||
world_unlocks = ["scenery_chap1", "scenery_chap2",
|
world_unlocks = ["scenery_chap1", "scenery_chap2",
|
||||||
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]
|
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]
|
||||||
|
|||||||
@@ -17,6 +17,11 @@ clear_type int,
|
|||||||
rating real,
|
rating real,
|
||||||
primary key(user_id, song_id, difficulty, time_played)
|
primary key(user_id, song_id, difficulty, time_played)
|
||||||
);
|
);
|
||||||
|
create table if not exists user_rating(user_id int,
|
||||||
|
time int,
|
||||||
|
rating_ptt real,
|
||||||
|
primary key(user_id, time)
|
||||||
|
);
|
||||||
|
|
||||||
create index if not exists user_score_1 on user_score (song_id, difficulty);
|
create index if not exists user_score_1 on user_score (song_id, difficulty);
|
||||||
create index if not exists user_score_2 on user_score (time_played);
|
create index if not exists user_score_2 on user_score (time_played);
|
||||||
|
|||||||
@@ -664,5 +664,23 @@
|
|||||||
],
|
],
|
||||||
"orig_price": 400,
|
"orig_price": 400,
|
||||||
"price": 400
|
"price": 400
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "chunithm_append_2",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "pack",
|
||||||
|
"id": "chunithm_append_2",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 5,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 500,
|
||||||
|
"price": 500
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -1360,5 +1360,113 @@
|
|||||||
],
|
],
|
||||||
"orig_price": 100,
|
"orig_price": 100,
|
||||||
"price": 100
|
"price": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "primitivelights",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "single",
|
||||||
|
"id": "primitivelights",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 1,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 100,
|
||||||
|
"price": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "thesurvivor",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "single",
|
||||||
|
"id": "thesurvivor",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 1,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 100,
|
||||||
|
"price": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "newyorkbackraise",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "single",
|
||||||
|
"id": "newyorkbackraise",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 1,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 100,
|
||||||
|
"price": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "lostintheabyss",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "single",
|
||||||
|
"id": "lostintheabyss",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 1,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 100,
|
||||||
|
"price": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "galacticlove",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "single",
|
||||||
|
"id": "galacticlove",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 1,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 100,
|
||||||
|
"price": 100
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tothemilkyway",
|
||||||
|
"items": [
|
||||||
|
{
|
||||||
|
"type": "single",
|
||||||
|
"id": "tothemilkyway",
|
||||||
|
"is_available": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "core",
|
||||||
|
"amount": 1,
|
||||||
|
"id": "core_generic",
|
||||||
|
"is_available": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"orig_price": 100,
|
||||||
|
"price": 100
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -61,16 +61,16 @@ def memory_clean(now):
|
|||||||
with Store.lock:
|
with Store.lock:
|
||||||
clean_room_list = []
|
clean_room_list = []
|
||||||
clean_player_list = []
|
clean_player_list = []
|
||||||
for token in Store.link_play_data:
|
for token, v in Store.link_play_data.items():
|
||||||
room = Store.link_play_data[token]['room']
|
room = v['room']
|
||||||
if now - room.timestamp >= Config.TIME_LIMIT:
|
if now - room.timestamp >= Config.TIME_LIMIT:
|
||||||
clean_room_list.append(room.room_id)
|
clean_room_list.append(room.room_id)
|
||||||
|
|
||||||
if now - room.players[Store.link_play_data[token]['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT:
|
if now - room.players[v['player_index']].last_timestamp // 1000 >= Config.TIME_LIMIT:
|
||||||
clean_player_list.append(token)
|
clean_player_list.append(token)
|
||||||
|
|
||||||
for room_id in Store.room_id_dict:
|
for room_id, v in Store.room_id_dict.items():
|
||||||
if now - Store.room_id_dict[room_id].timestamp >= Config.TIME_LIMIT:
|
if now - v.timestamp >= Config.TIME_LIMIT:
|
||||||
clean_room_list.append(room_id)
|
clean_room_list.append(room_id)
|
||||||
|
|
||||||
for room_id in clean_room_list:
|
for room_id in clean_room_list:
|
||||||
@@ -190,10 +190,10 @@ class TCPRouter:
|
|||||||
if player_num == 4:
|
if player_num == 4:
|
||||||
# 满人
|
# 满人
|
||||||
return '1201'
|
return '1201'
|
||||||
elif player_num == 0:
|
if player_num == 0:
|
||||||
# 房间不存在
|
# 房间不存在
|
||||||
return '1202'
|
return '1202'
|
||||||
elif room.state != 2:
|
if room.state != 2:
|
||||||
# 无法加入
|
# 无法加入
|
||||||
return '1205'
|
return '1205'
|
||||||
|
|
||||||
|
|||||||
@@ -164,7 +164,6 @@ class Room:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
else:
|
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def is_finish(self):
|
def is_finish(self):
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ class CommandParser:
|
|||||||
self.room = room
|
self.room = room
|
||||||
self.player_index = player_index
|
self.player_index = player_index
|
||||||
self.s = CommandSender(self.room)
|
self.s = CommandSender(self.room)
|
||||||
|
self.command: bytes = None
|
||||||
|
|
||||||
def get_commands(self, command):
|
def get_commands(self, command: bytes):
|
||||||
self.command = command
|
self.command = command
|
||||||
r = getattr(self, self.route[self.command[2]])()
|
r = getattr(self, self.route[self.command[2]])()
|
||||||
|
|
||||||
@@ -47,8 +48,6 @@ class CommandParser:
|
|||||||
self.s.random_code = self.command[16:24]
|
self.s.random_code = self.command[16:24]
|
||||||
self.room.command_queue.append(self.s.command_10())
|
self.room.command_queue.append(self.s.command_10())
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def command_02(self):
|
def command_02(self):
|
||||||
self.s.random_code = self.command[16:24]
|
self.s.random_code = self.command[16:24]
|
||||||
song_idx = bi(self.command[24:26])
|
song_idx = bi(self.command[24:26])
|
||||||
@@ -81,8 +80,6 @@ class CommandParser:
|
|||||||
self.room.make_finish()
|
self.room.make_finish()
|
||||||
self.room.command_queue.append(self.s.command_13())
|
self.room.command_queue.append(self.s.command_13())
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def command_04(self):
|
def command_04(self):
|
||||||
# 踢人
|
# 踢人
|
||||||
self.s.random_code = self.command[16:24]
|
self.s.random_code = self.command[16:24]
|
||||||
@@ -109,7 +106,6 @@ class CommandParser:
|
|||||||
self.room.song_idx = 0xffff
|
self.room.song_idx = 0xffff
|
||||||
|
|
||||||
self.room.command_queue.append(self.s.command_13())
|
self.room.command_queue.append(self.s.command_13())
|
||||||
return None
|
|
||||||
|
|
||||||
def command_07(self):
|
def command_07(self):
|
||||||
self.s.random_code = self.command[16:24]
|
self.s.random_code = self.command[16:24]
|
||||||
@@ -117,15 +113,12 @@ class CommandParser:
|
|||||||
self.room.update_song_unlock()
|
self.room.update_song_unlock()
|
||||||
|
|
||||||
self.room.command_queue.append(self.s.command_14())
|
self.room.command_queue.append(self.s.command_14())
|
||||||
return None
|
|
||||||
|
|
||||||
def command_08(self):
|
def command_08(self):
|
||||||
self.room.round_switch = bi(self.command[24:25])
|
self.room.round_switch = bi(self.command[24:25])
|
||||||
self.s.random_code = self.command[16:24]
|
self.s.random_code = self.command[16:24]
|
||||||
self.room.command_queue.append(self.s.command_13())
|
self.room.command_queue.append(self.s.command_13())
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
def command_09(self):
|
def command_09(self):
|
||||||
re = []
|
re = []
|
||||||
self.s.random_code = self.command[16:24]
|
self.s.random_code = self.command[16:24]
|
||||||
@@ -196,7 +189,7 @@ class CommandParser:
|
|||||||
# 将换房主时间提前到此刻
|
# 将换房主时间提前到此刻
|
||||||
self.room.make_round()
|
self.room.make_round()
|
||||||
|
|
||||||
if self.room.state == 4 or self.room.state == 5 or self.room.state == 6:
|
if self.room.state in (4, 5, 6):
|
||||||
timestamp = round(time.time() * 1000)
|
timestamp = round(time.time() * 1000)
|
||||||
self.room.countdown -= timestamp - self.room.timestamp
|
self.room.countdown -= timestamp - self.room.timestamp
|
||||||
self.room.timestamp = timestamp
|
self.room.timestamp = timestamp
|
||||||
@@ -225,10 +218,9 @@ class CommandParser:
|
|||||||
self.room.countdown = 0xffffffff
|
self.room.countdown = 0xffffffff
|
||||||
flag_13 = True
|
flag_13 = True
|
||||||
|
|
||||||
if self.room.countdown <= 0:
|
self.room.countdown = self.room.countdown if self.room.countdown > 0 else 0
|
||||||
self.room.countdown = 0
|
|
||||||
|
|
||||||
if self.room.state == 7 or self.room.state == 8:
|
if self.room.state in (7, 8):
|
||||||
if player.timer < bi(self.command[28:32]) or bi(self.command[28:32]) == 0 and player.timer != 0:
|
if player.timer < bi(self.command[28:32]) or bi(self.command[28:32]) == 0 and player.timer != 0:
|
||||||
player.last_timer = player.timer
|
player.last_timer = player.timer
|
||||||
player.last_score = player.score
|
player.last_score = player.score
|
||||||
@@ -270,13 +262,12 @@ class CommandParser:
|
|||||||
|
|
||||||
self.room.command_queue.append(self.s.command_12(self.player_index))
|
self.room.command_queue.append(self.s.command_12(self.player_index))
|
||||||
|
|
||||||
if self.room.state == 3 or self.room.state == 2:
|
if self.room.state in (2, 3):
|
||||||
self.room.state = 1
|
self.room.state = 1
|
||||||
self.room.song_idx = 0xffff
|
self.room.song_idx = 0xffff
|
||||||
# self.room.command_queue.append(self.s.command_11())
|
# self.room.command_queue.append(self.s.command_11())
|
||||||
self.room.command_queue.append(self.s.command_13())
|
self.room.command_queue.append(self.s.command_13())
|
||||||
self.room.command_queue.append(self.s.command_14())
|
self.room.command_queue.append(self.s.command_14())
|
||||||
return None
|
|
||||||
|
|
||||||
def command_0b(self):
|
def command_0b(self):
|
||||||
# 推荐歌曲
|
# 推荐歌曲
|
||||||
@@ -285,5 +276,3 @@ class CommandParser:
|
|||||||
if self.player_index != i and self.room.players[i].online == 1:
|
if self.player_index != i and self.room.players[i].online == 1:
|
||||||
self.room.players[i].extra_command_queue.append(
|
self.room.players[i].extra_command_queue.append(
|
||||||
self.s.command_0f(self.player_index, song_idx))
|
self.s.command_0f(self.player_index, song_idx))
|
||||||
|
|
||||||
return None
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from core.config_manager import Config
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint
|
||||||
|
|
||||||
|
from core.config_manager import Config
|
||||||
|
|
||||||
from . import (auth, course, friend, multiplayer, others, present, purchase,
|
from . import (auth, course, friend, multiplayer, others, present, purchase,
|
||||||
score, user, world)
|
score, user, world)
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import base64
|
import base64
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
|
|
||||||
|
from flask import Blueprint, current_app, g, jsonify, request
|
||||||
|
|
||||||
from core.error import ArcError, NoAccess
|
from core.error import ArcError, NoAccess
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserAuth, UserLogin
|
from core.user import UserAuth, UserLogin
|
||||||
from flask import Blueprint, g, jsonify, request, current_app
|
|
||||||
|
|
||||||
from .func import arc_try, error_return, header_check
|
from .func import arc_try, error_return, header_check
|
||||||
|
|
||||||
@@ -31,19 +32,25 @@ def login():
|
|||||||
|
|
||||||
user = UserLogin(c)
|
user = UserLogin(c)
|
||||||
user.login(name, password, device_id, request.remote_addr)
|
user.login(name, password, device_id, request.remote_addr)
|
||||||
|
current_app.logger.info(f'User `{user.user_id}` log in')
|
||||||
return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token})
|
return jsonify({"success": True, "token_type": "Bearer", 'user_id': user.user_id, 'access_token': user.token})
|
||||||
|
|
||||||
|
|
||||||
def auth_required(request):
|
@bp.route('/verify', methods=['POST']) # 邮箱验证进度查询
|
||||||
|
@arc_try
|
||||||
|
def email_verify():
|
||||||
|
raise ArcError('Email verification unavailable.', 151, status=404)
|
||||||
|
|
||||||
|
|
||||||
|
def auth_required(req):
|
||||||
# arcaea登录验证,写成了修饰器
|
# arcaea登录验证,写成了修饰器
|
||||||
def decorator(view):
|
def decorator(view):
|
||||||
@wraps(view)
|
@wraps(view)
|
||||||
def wrapped_view(*args, **kwargs):
|
def wrapped_view(*args, **kwargs):
|
||||||
|
|
||||||
headers = request.headers
|
headers = req.headers
|
||||||
|
|
||||||
e = header_check(request)
|
e = header_check(req)
|
||||||
if e is not None:
|
if e is not None:
|
||||||
current_app.logger.warning(
|
current_app.logger.warning(
|
||||||
f' - {e.error_code}|{e.api_error_code}: {e}')
|
f' - {e.error_code}|{e.api_error_code}: {e}')
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.constant import Constant
|
from core.constant import Constant
|
||||||
from core.course import UserCourseList
|
from core.course import UserCourseList
|
||||||
from core.item import ItemCore
|
from core.item import ItemCore
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserOnline
|
from core.user import UserOnline
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserOnline, code_get_id
|
from core.user import UserOnline, code_get_id
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
|
|||||||
@@ -40,6 +40,8 @@ def error_return(e: ArcError = default_error): # 错误返回
|
|||||||
# 124 你今天不能再使用这个IP地址创建新的账号
|
# 124 你今天不能再使用这个IP地址创建新的账号
|
||||||
# 150 非常抱歉您已被限制使用此功能
|
# 150 非常抱歉您已被限制使用此功能
|
||||||
# 151 目前无法使用此功能
|
# 151 目前无法使用此功能
|
||||||
|
# 160 账户未邮箱认证,请检查邮箱
|
||||||
|
# 161 账户认证过期,请重新注册
|
||||||
# 401 用户不存在
|
# 401 用户不存在
|
||||||
# 403 无法连接至服务器
|
# 403 无法连接至服务器
|
||||||
# 501 502 -6 此物品目前无法获取
|
# 501 502 -6 此物品目前无法获取
|
||||||
@@ -51,6 +53,7 @@ def error_return(e: ArcError = default_error): # 错误返回
|
|||||||
# 604 你不能加自己为好友
|
# 604 你不能加自己为好友
|
||||||
# 903 下载量超过了限制,请24小时后重试
|
# 903 下载量超过了限制,请24小时后重试
|
||||||
# 905 请在再次使用此功能前等待24小时
|
# 905 请在再次使用此功能前等待24小时
|
||||||
|
# 910 重新请求验证邮件前需等待x分钟 extra: retry_at
|
||||||
# 1001 设备数量达到上限
|
# 1001 设备数量达到上限
|
||||||
# 1002 此设备已使用过此功能
|
# 1002 此设备已使用过此功能
|
||||||
# 1201 房间已满
|
# 1201 房间已满
|
||||||
@@ -86,7 +89,6 @@ def arc_try(view):
|
|||||||
data = view(*args, **kwargs)
|
data = view(*args, **kwargs)
|
||||||
if data is None:
|
if data is None:
|
||||||
return error_return()
|
return error_return()
|
||||||
else:
|
|
||||||
return data
|
return data
|
||||||
except ArcError as e:
|
except ArcError as e:
|
||||||
if Config.ALLOW_WARNING_LOG:
|
if Config.ALLOW_WARNING_LOG:
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.config_manager import Config
|
from core.config_manager import Config
|
||||||
from core.error import ArcError
|
from core.error import ArcError
|
||||||
from core.linkplay import Player, RemoteMultiPlayer, Room
|
from core.linkplay import Player, RemoteMultiPlayer, Room
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import json
|
import json
|
||||||
from urllib.parse import parse_qs, urlparse
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
|
from flask import Blueprint, jsonify, request
|
||||||
|
from werkzeug.datastructures import ImmutableMultiDict
|
||||||
|
|
||||||
from core.download import DownloadList
|
from core.download import DownloadList
|
||||||
from core.error import RateLimit
|
from core.error import RateLimit
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.system import GameInfo
|
from core.system import GameInfo
|
||||||
from core.user import UserOnline
|
from core.user import UserOnline
|
||||||
from flask import Blueprint, jsonify, request
|
|
||||||
from werkzeug.datastructures import ImmutableMultiDict
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, error_return, success_return
|
from .func import arc_try, error_return, success_return
|
||||||
@@ -53,6 +54,11 @@ def finale_start():
|
|||||||
return success_return({})
|
return success_return({})
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/finale/finale_end', methods=['POST'])
|
||||||
|
def finale_end():
|
||||||
|
return success_return({})
|
||||||
|
|
||||||
|
|
||||||
map_dict = {'/user/me': user_me,
|
map_dict = {'/user/me': user_me,
|
||||||
'/purchase/bundle/pack': bundle_pack,
|
'/purchase/bundle/pack': bundle_pack,
|
||||||
'/serve/download/me/song': download_song,
|
'/serve/download/me/song': download_song,
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.present import UserPresent, UserPresentList
|
from core.present import UserPresent, UserPresentList
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserOnline
|
from core.user import UserOnline
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.error import InputError, ItemUnavailable, PostError
|
from core.error import InputError, ItemUnavailable, PostError
|
||||||
from core.item import ItemFactory, Stamina6
|
from core.item import ItemFactory, Stamina6
|
||||||
from core.purchase import Purchase, PurchaseList
|
from core.purchase import Purchase, PurchaseList
|
||||||
from core.redeem import UserRedeem
|
from core.redeem import UserRedeem
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserOnline
|
from core.user import UserOnline
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
from time import time
|
from time import time
|
||||||
from core.course import CoursePlay
|
|
||||||
|
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from core.course import CoursePlay
|
||||||
from core.error import InputError
|
from core.error import InputError
|
||||||
from core.rank import RankList
|
from core.rank import RankList
|
||||||
from core.score import UserPlay
|
from core.score import UserPlay
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserOnline
|
from core.user import UserOnline
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
@@ -94,6 +95,8 @@ def song_score_post(user_id):
|
|||||||
request.form['miss_count'], request.form['health'], request.form['modifier'], int(time() * 1000), request.form['clear_type'])
|
request.form['miss_count'], request.form['health'], request.form['modifier'], int(time() * 1000), request.form['clear_type'])
|
||||||
x.beyond_gauge = int(request.form['beyond_gauge'])
|
x.beyond_gauge = int(request.form['beyond_gauge'])
|
||||||
x.submission_hash = request.form['submission_hash']
|
x.submission_hash = request.form['submission_hash']
|
||||||
|
if 'combo_interval_bonus' in request.form:
|
||||||
|
x.combo_interval_bonus = int(request.form['combo_interval_bonus'])
|
||||||
if not x.is_valid:
|
if not x.is_valid:
|
||||||
raise InputError('Invalid score.', 107)
|
raise InputError('Invalid score.', 107)
|
||||||
x.upload_score()
|
x.upload_score()
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
|
from flask import Blueprint, current_app, request
|
||||||
|
|
||||||
from core.character import UserCharacter
|
from core.character import UserCharacter
|
||||||
from core.config_manager import Config
|
from core.error import ArcError
|
||||||
from core.error import ArcError, NoAccess
|
|
||||||
from core.item import ItemCore
|
from core.item import ItemCore
|
||||||
from core.save import SaveData
|
from core.save import SaveData
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import User, UserLogin, UserOnline, UserRegister
|
from core.user import User, UserLogin, UserOnline, UserRegister
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, header_check, success_return
|
from .func import arc_try, header_check, success_return
|
||||||
@@ -16,7 +16,6 @@ bp = Blueprint('user', __name__, url_prefix='/user')
|
|||||||
@bp.route('', methods=['POST']) # 注册接口
|
@bp.route('', methods=['POST']) # 注册接口
|
||||||
@arc_try
|
@arc_try
|
||||||
def register():
|
def register():
|
||||||
headers = request.headers
|
|
||||||
error = header_check(request)
|
error = header_check(request)
|
||||||
if error is not None:
|
if error is not None:
|
||||||
raise error
|
raise error
|
||||||
@@ -37,6 +36,7 @@ def register():
|
|||||||
user = UserLogin(c)
|
user = UserLogin(c)
|
||||||
user.login(new_user.name, new_user.password,
|
user.login(new_user.name, new_user.password,
|
||||||
device_id, request.remote_addr)
|
device_id, request.remote_addr)
|
||||||
|
current_app.logger.info(f'New user `{user.user_id}` registered')
|
||||||
return success_return({'user_id': user.user_id, 'access_token': user.token})
|
return success_return({'user_id': user.user_id, 'access_token': user.token})
|
||||||
|
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ def sys_set(user_id, set_arg):
|
|||||||
user.change_favorite_character(int(value))
|
user.change_favorite_character(int(value))
|
||||||
else:
|
else:
|
||||||
value = 'true' == value
|
value = 'true' == value
|
||||||
if 'is_hide_rating' == set_arg or 'max_stamina_notification_enabled' == set_arg:
|
if set_arg in ('is_hide_rating', 'max_stamina_notification_enabled'):
|
||||||
user.update_user_one_column(set_arg, value)
|
user.update_user_one_column(set_arg, value)
|
||||||
return success_return(user.to_dict())
|
return success_return(user.to_dict())
|
||||||
|
|
||||||
@@ -165,3 +165,9 @@ def sys_set(user_id, set_arg):
|
|||||||
@arc_try
|
@arc_try
|
||||||
def user_delete(user_id):
|
def user_delete(user_id):
|
||||||
raise ArcError('Cannot delete the account.', 151, status=404)
|
raise ArcError('Cannot delete the account.', 151, status=404)
|
||||||
|
|
||||||
|
|
||||||
|
@bp.route('/email/resend_verify', methods=['POST']) # 邮箱验证重发
|
||||||
|
@arc_try
|
||||||
|
def email_resend_verify():
|
||||||
|
raise ArcError('Email verification unavailable.', 151, status=404)
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
from core.sql import Connect
|
from core.sql import Connect
|
||||||
from core.user import UserOnline
|
from core.user import UserOnline
|
||||||
from core.world import UserMap, get_world_all
|
from core.world import UserMap, get_world_all
|
||||||
from flask import Blueprint, request
|
|
||||||
|
|
||||||
from .auth import auth_required
|
from .auth import auth_required
|
||||||
from .func import arc_try, success_return
|
from .func import arc_try, success_return
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ def all_character():
|
|||||||
def change_character():
|
def change_character():
|
||||||
# 修改角色数据
|
# 修改角色数据
|
||||||
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
|
skill_ids = ['No_skill', 'gauge_easy', 'note_mirror', 'gauge_hard', 'frag_plus_10_pack_stellights', 'gauge_easy|frag_plus_15_pst&prs', 'gauge_hard|fail_frag_minus_100', 'frag_plus_5_side_light', 'visual_hide_hp', 'frag_plus_5_side_conflict', 'challenge_fullcombo_0gauge', 'gauge_overflow', 'gauge_easy|note_mirror', 'note_mirror', 'visual_tomato_pack_tonesphere',
|
||||||
'frag_rng_ayu', 'gaugestart_30|gaugegain_70', 'combo_100-frag_1', 'audio_gcemptyhit_pack_groovecoaster', 'gauge_saya', 'gauge_chuni', 'kantandeshou', 'gauge_haruna', 'frags_nono', 'gauge_pandora', 'gauge_regulus', 'omatsuri_daynight', 'sometimes(note_mirror|frag_plus_5)', 'scoreclear_aa|visual_scoregauge', 'gauge_tempest', 'gauge_hard', 'gauge_ilith_summer', 'frags_kou', 'visual_ink', 'shirabe_entry_fee', 'frags_yume', 'note_mirror|visual_hide_far', 'frags_ongeki', 'gauge_areus', 'gauge_seele', 'gauge_isabelle', 'gauge_exhaustion', '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', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror']
|
'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', 'skill_fatalis', 'skill_reunion', 'frags_ongeki_slash', 'frags_ongeki_hard', 'skill_amane', 'skill_kou_winter', 'gauge_hard|note_mirror', 'skill_shama', 'skill_milk', 'skill_shikoku', 'skill_mika', 'ilith_awakened_skill', 'skill_mithra', 'skill_toa', 'skill_nami_twilight']
|
||||||
return render_template('web/changechar.html', skill_ids=skill_ids)
|
return render_template('web/changechar.html', skill_ids=skill_ids)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
121
tools/clean_logdb.py
Normal file
121
tools/clean_logdb.py
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
import os
|
||||||
|
import sqlite3
|
||||||
|
from datetime import datetime
|
||||||
|
from time import mktime
|
||||||
|
|
||||||
|
LOG_DATABASE_PATH = './arcaea_log.db'
|
||||||
|
|
||||||
|
|
||||||
|
class Connect():
|
||||||
|
# 数据库连接类,上下文管理
|
||||||
|
|
||||||
|
def __init__(self, file_path=LOG_DATABASE_PATH):
|
||||||
|
"""
|
||||||
|
数据库连接
|
||||||
|
接受:文件路径
|
||||||
|
返回: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) -> bool:
|
||||||
|
if exc_type is not None:
|
||||||
|
print(f'exc_type: {exc_type}')
|
||||||
|
print(f'exc_val: {exc_val}')
|
||||||
|
print(f'exc_tb: {exc_tb}')
|
||||||
|
if self.conn:
|
||||||
|
self.conn.rollback()
|
||||||
|
|
||||||
|
if self.conn:
|
||||||
|
self.conn.commit()
|
||||||
|
self.conn.close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def clean_by_time(table_name: str, time_colume_name: str, colume_count: int):
|
||||||
|
with Connect() as c:
|
||||||
|
today = datetime.now()
|
||||||
|
print(f'The time now is {today}.')
|
||||||
|
day = input(
|
||||||
|
'Please input the number of days the data before which you want to delete: ')
|
||||||
|
try:
|
||||||
|
day = int(day)
|
||||||
|
except ValueError:
|
||||||
|
print('Invalid input!')
|
||||||
|
return
|
||||||
|
time = mktime(today.timetuple()) - day * 24 * 60 * 60
|
||||||
|
delete_count = c.execute(
|
||||||
|
f'select count(*) from {table_name} where {time_colume_name} < ?', (time,)).fetchone()[0]
|
||||||
|
all_count = c.execute(
|
||||||
|
f'select count(*) from {table_name}').fetchone()[0]
|
||||||
|
print(
|
||||||
|
f'Before {day} days, there are {delete_count} records to be deleted, {all_count} records in total.')
|
||||||
|
flag = input('Are you sure to delete these records? (y/n) ')
|
||||||
|
if flag == 'y' or flag == 'Y':
|
||||||
|
if delete_count >= 1000000:
|
||||||
|
print(
|
||||||
|
'It will cost a long time to delete these records, please wait patiently...')
|
||||||
|
print('Deleting...')
|
||||||
|
c.execute('PRAGMA cache_size = 32768')
|
||||||
|
c.execute('PRAGMA synchronous = OFF')
|
||||||
|
c.execute('PRAGMA temp_store = MEMORY')
|
||||||
|
if delete_count / all_count >= 0.8:
|
||||||
|
data = c.execute(
|
||||||
|
f'select * from {table_name} where {time_colume_name} > ?', (time,)).fetchall()
|
||||||
|
c.execute(f'delete from {table_name}')
|
||||||
|
c.executemany(
|
||||||
|
f'insert into {table_name} values ({",".join(["?"]*colume_count)})', data)
|
||||||
|
else:
|
||||||
|
c.execute(
|
||||||
|
f'delete from {table_name} where {time_colume_name} < ?', (time,))
|
||||||
|
c.execute('PRAGMA temp_store = DEFAULT')
|
||||||
|
print('Delete successfully!')
|
||||||
|
else:
|
||||||
|
print('Delete canceled!')
|
||||||
|
|
||||||
|
|
||||||
|
def vacuum():
|
||||||
|
print('This operation will release unused space in the database file.')
|
||||||
|
print('It will cost a long time to release unused space if the database file is so large.')
|
||||||
|
flag = input('Are you sure to release unused space? (y/n) ')
|
||||||
|
if flag == 'y' or flag == 'Y':
|
||||||
|
with Connect() as c:
|
||||||
|
print('Releasing unused space...')
|
||||||
|
c.execute('vacuum')
|
||||||
|
print('Release unused space successfully!')
|
||||||
|
else:
|
||||||
|
print('Release unused space canceled!')
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if not os.path.exists(LOG_DATABASE_PATH):
|
||||||
|
print('The database file `arcaea_log.db` does not exist!')
|
||||||
|
print('-- Arcaea Server Log Database Cleaner --')
|
||||||
|
print('Note: It is more recommended to delete the database file directly.')
|
||||||
|
while True:
|
||||||
|
print('-' * 40)
|
||||||
|
print('1. clean `user_score` table')
|
||||||
|
print('2. clean `user_rating` table')
|
||||||
|
print('3. release unused space (`vacuum` command)')
|
||||||
|
print('0. exit')
|
||||||
|
choice = input('Please input your choice: ')
|
||||||
|
if choice == '1':
|
||||||
|
clean_by_time('user_score', 'time_played', 13)
|
||||||
|
elif choice == '2':
|
||||||
|
clean_by_time('user_rating', 'time', 3)
|
||||||
|
elif choice == '3':
|
||||||
|
vacuum()
|
||||||
|
elif choice == '0':
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
print('Invalid choice!')
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
|
input('Press `Enter` key to exit.')
|
||||||
Reference in New Issue
Block a user