[Refactor] Database initialization and migration

- Code refactoring for database initialization and migration
This commit is contained in:
Lost-MSth
2022-10-21 18:19:37 +08:00
parent ca03360f0c
commit 40630fff4d
19 changed files with 867 additions and 803 deletions

View File

@@ -30,7 +30,7 @@ def songs_get(data, user):
B = ['song_id', 'name', 'rating_pst',
'rating_prs', 'rating_ftr', 'rating_byn']
with Connect() as c:
query = Query(A, A, B).from_data(data)
query = Query(A, A, B).from_dict(data)
x = Sql(c).select('chart', query=query)
r = []
for i in x:

View File

@@ -37,7 +37,7 @@ def users_get(data, user):
B = ['user_id', 'name', 'user_code', 'join_date',
'rating_ptt', 'time_played', 'ticket', 'world_rank_score']
with Connect() as c:
query = Query(A, A, B).from_data(data)
query = Query(A, A, B).from_dict(data)
x = Sql(c).select('user', query=query)
r = []
for i in x:
@@ -111,7 +111,7 @@ def users_user_best_get(data, user, user_id):
with Connect() as c:
x = UserScoreList(c, UserInfo(c, user_id))
x.query.from_data(data)
x.query.from_dict(data)
x.select_from_user()
r = x.to_dict_list()
return success_return({'user_id': user_id, 'data': r})

View File

@@ -75,6 +75,8 @@ class Config:
SONG_FILE_FOLDER_PATH = './database/songs/'
SONGLIST_FILE_PATH = './database/songs/songlist'
SQLITE_DATABASE_PATH = './database/arcaea_database.db'
SQLITE_DATABASE_BACKUP_FOLDER_PATH = './database/backup/'
DATABASE_INIT_PATH = './database/init/'
GAME_LOGIN_RATE_LIMIT = '30/5 minutes'
API_LOGIN_RATE_LIMIT = '10/5 minutes'

View File

@@ -1,5 +1,7 @@
from .config_manager import Config
ARCAEA_SERVER_VERSION = 'v2.10.0.2'
class Constant:
@@ -92,3 +94,8 @@ class Constant:
(0X5D6FC5, 0xab97ef), (0X237206D, 0xdfef2), (0XA3DEE,
0x6CB300), (0XA35687B, 0xE456CDEA)
]
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']
UPDATE_WITH_NEW_CHARACTER_DATA = Config.UPDATE_WITH_NEW_CHARACTER_DATA

244
latest version/core/init.py Normal file
View File

@@ -0,0 +1,244 @@
import os
import sys
from importlib import import_module
from json import load
from shutil import copy, copy2
from time import time
from core.config_manager import Config
from core.constant import ARCAEA_SERVER_VERSION
from core.course import Course
from core.purchase import Purchase
from core.sql import Connect, DatabaseMigrator
from core.user import UserRegister
from core.util import try_rename
class DatabaseInit:
def __init__(self, db_path: str = Config.SQLITE_DATABASE_PATH, init_folder_path: str = Config.DATABASE_INIT_PATH) -> None:
self.db_path = db_path
self.init_folder_path = init_folder_path
self.c = None
self.init_data = None
@property
def sql_path(self) -> str:
return os.path.join(self.init_folder_path, 'tables.sql')
@property
def course_path(self) -> str:
return os.path.join(self.init_folder_path, 'courses.json')
@property
def pack_path(self) -> str:
return os.path.join(self.init_folder_path, 'packs.json')
@property
def single_path(self) -> str:
return os.path.join(self.init_folder_path, 'singles.json')
def table_init(self) -> None:
'''初始化数据库结构'''
with open(self.sql_path, 'r') as f:
self.c.executescript(f.read())
self.c.execute('''insert into config values("version", :a);''', {
'a': ARCAEA_SERVER_VERSION})
def character_init(self) -> None:
'''初始化搭档信息'''
for i in range(0, len(self.init_data.char)):
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]:
max_level = 30
uncapable = 1
else:
max_level = 20
uncapable = 0
sql = '''insert into character values(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)'''
self.c.execute(sql, (i, self.init_data.char[i], max_level, self.init_data.frag1[i], self.init_data.prog1[i], self.init_data.overdrive1[i], self.init_data.frag20[i], self.init_data.prog20[i], self.init_data.overdrive20[i],
self.init_data.frag30[i], self.init_data.prog30[i], self.init_data.overdrive30[i], self.init_data.skill_id[i], self.init_data.skill_unlock_level[i], skill_requires_uncap, self.init_data.skill_id_uncap[i], self.init_data.char_type[i], uncapable))
self.c.execute('''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)''', (
99, 'shirahime', 38, 33, 28, 66, 58, 50, 66, 58, 50, 'frags_preferred_song', 0, 0, '', 0))
for i in self.init_data.char_core:
self.c.executemany('''insert into char_item values(?,?,'core',?)''', [
(i, j['core_id'], j['amount']) for j in self.init_data.char_core[i]])
def insert_purchase_item(self, purchases: list):
'''处理singles和packs'''
for i in purchases:
x = Purchase(self.c).from_dict(i)
x.insert_all()
def item_init(self) -> None:
'''初始化物品信息'''
self.c.executemany('''insert into item values(?,"core",1)''',
[(i,) for i in self.init_data.cores])
self.c.executemany('''insert into item values(?,"world_song",1)''', [
(i,) for i in self.init_data.world_songs])
self.c.executemany('''insert into item values(?,"world_unlock",1)''', [
(i,) for i in self.init_data.world_unlocks])
self.c.executemany('''insert into item values(?,"course_banner",1)''', [
(i,) for i in self.init_data.course_banners])
self.c.execute('''insert into item values(?,?,?)''',
('fragment', 'fragment', 1))
self.c.execute('''insert into item values(?,?,?)''',
('memory', 'memory', 1))
self.c.execute('''insert into item values(?,?,?)''',
('anni5tix', 'anni5tix', 1))
with open(self.pack_path, 'r') as f:
self.insert_purchase_item(load(f))
with open(self.single_path, 'r') as f:
self.insert_purchase_item(load(f))
self.c.execute(
'''select exists(select * from item where item_id='epilogue')''')
if self.c.fetchone() == (0,):
self.c.execute(
'''insert into item values('epilogue','pack',1)''')
def course_init(self) -> None:
'''初始化课题信息'''
courses = []
with open(self.course_path, 'r', encoding='utf-8') as f:
courses = load(f)
for i in courses:
x = Course(self.c).from_dict(i)
x.insert_all()
def role_power_init(self) -> None:
'''初始化power和role'''
self.c.executemany('''insert into role values(?,?)''', [(
self.init_data.role[i], self.init_data.role_caption[i]) for i in range(len(self.init_data.role))])
self.c.executemany('''insert into power values(?,?)''', [(
self.init_data.power[i], self.init_data.power_caption[i]) for i in range(len(self.init_data.power))])
for i in self.init_data.role_power:
self.c.executemany('''insert into role_power values(?,?)''', [
(i, j) for j in self.init_data.role_power[i]])
def admin_init(self) -> None:
'''初始化测试账号'''
x = UserRegister(self.c)
x.user_code = '123456789'
x.user_id = 2000000
x.name = 'admin'
x.email = 'admin@admin.com'
now = int(time() * 1000)
x._insert_user_char()
self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket, prog_boost, email)
values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0, '', :memories, 0, :email)
''', {'user_code': x.user_code, 'user_id': x.user_id, 'join_date': now, 'name': x.name, 'password': '41e5653fc7aeb894026d6bb7b2db7f65902b454945fa8fd65a6327047b5277fb', 'memories': 114514, 'email': x.email})
self.c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': x.user_id})
self.c.execute(
'''insert into user_role values(?, "admin")''', (x.user_id,))
def init(self) -> None:
sys.path.append(os.path.join(sys.path[0], self.init_folder_path))
self.init_data = import_module('data').InitData
with Connect(self.db_path) as c:
self.c = c
self.table_init()
self.character_init()
self.item_init()
self.course_init()
self.role_power_init()
self.admin_init()
class FileChecker:
def __init__(self, app=None):
self.app = app
def check_file(self, file_path: str) -> bool:
f = os.path.isfile(file_path)
if not f:
self.app.logger.warning('File `%s` is missing.' % file_path)
return f
def check_folder(self, folder_path: str) -> bool:
f = os.path.isdir(folder_path)
if not f:
self.app.logger.warning('Folder `%s` is missing.' % folder_path)
return f
def chech_update_database(self) -> bool:
if not self.check_file(Config.SQLITE_DATABASE_PATH):
# 新建数据库
try:
self.app.logger.info(
'Try to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
DatabaseInit().init()
self.app.logger.info(
'Success to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
except:
self.app.logger.warning(
'Fail to new the file `%s`.' % Config.SQLITE_DATABASE_PATH)
return False
else:
# 检查更新
with Connect() as c:
try:
c.execute('''select value from config where id="version"''')
x = c.fetchone()
except:
x = None
# 数据库自动更新,不强求
if not x or x[0] != ARCAEA_SERVER_VERSION:
self.app.logger.warning(
'Maybe the file `%s` is an old version.' % Config.SQLITE_DATABASE_PATH)
try:
self.app.logger.info(
'Try to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
if not os.path.isdir(Config.SQLITE_DATABASE_BACKUP_FOLDER_PATH):
os.makedirs(Config.SQLITE_DATABASE_BACKUP_FOLDER_PATH)
backup_path = try_rename(Config.SQLITE_DATABASE_PATH, os.path.join(
Config.SQLITE_DATABASE_BACKUP_FOLDER_PATH, os.path.split(Config.SQLITE_DATABASE_PATH)[-1] + '.bak'))
try:
copy2(backup_path, Config.SQLITE_DATABASE_PATH)
except:
copy(backup_path, Config.SQLITE_DATABASE_PATH)
temp_path = os.path.join(
*os.path.split(Config.SQLITE_DATABASE_PATH)[:-1], 'old_arcaea_database.db')
if os.path.isfile(temp_path):
os.remove(temp_path)
try_rename(Config.SQLITE_DATABASE_PATH, temp_path)
DatabaseInit().init()
self.update_database(temp_path)
self.app.logger.info(
'Success to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
except ValueError:
self.app.logger.warning(
'Fail to update the file `%s`.' % Config.SQLITE_DATABASE_PATH)
return True
@staticmethod
def update_database(old_path: str, new_path: str = Config.SQLITE_DATABASE_PATH) -> None:
'''更新数据库,并删除旧文件'''
if os.path.isfile(old_path) and os.path.isfile(new_path):
DatabaseMigrator(old_path, new_path).update_database()
os.remove(old_path)
def check_before_run(self) -> bool:
'''运行前检查,返回布尔值'''
return self.check_folder(Config.SONG_FILE_FOLDER_PATH) & self.chech_update_database()

View File

@@ -53,6 +53,36 @@ class Purchase:
r['discount_reason'] = self.discount_reason
return r
def from_dict(self, d: dict) -> 'Purchase':
self.purchase_name = d['name']
self.price = d['price']
self.orig_price = d['orig_price']
self.discount_from = d.get('discount_from', -1)
self.discount_to = d.get('discount_to', -1)
self.discount_reason = d.get('discount_reason', '')
for i in d.get('items', []):
self.items.append(ItemFactory.from_dict(i))
return self
def insert_all(self) -> None:
'''向数据库插入包括item表和purchase_item表'''
self.c.execute('''insert into purchase values(?,?,?,?,?,?)''',
(self.purchase_name, self.price, self.orig_price, self.discount_from, self.discount_to, self.discount_reason))
self.insert_items()
def insert_items(self) -> None:
'''向数据库插入物品,注意已存在的物品不会变更'''
for i in self.items:
self.c.execute(
'''select exists(select * from item where item_id=?)''', (i.item_id,))
if self.c.fetchone() == (0,):
self.c.execute('''insert into item values(?,?,?)''',
(i.item_id, i.item_type, i.is_available))
self.c.execute('''insert into purchase_item values(?,?,?,?)''',
(self.purchase_name, i.item_id, i.item_type, i.amount))
def select(self, purchase_name: str = None) -> 'Purchase':
'''
用purchase_name查询信息

View File

@@ -12,8 +12,8 @@ class Connect:
def __init__(self, file_path=Constant.SQLITE_DATABASE_PATH):
"""
数据库连接默认连接arcaea_database.db\
接受:文件路径\
数据库连接默认连接arcaea_database.db\
接受:文件路径\
返回sqlite3连接操作对象
"""
self.file_path = file_path
@@ -143,7 +143,7 @@ class Query:
self.fuzzy_query = fuzzy_query
self.sort = sort
def from_data(self, d: dict) -> 'Query':
def from_dict(self, d: dict) -> 'Query':
self.set_value(d.get('limit', -1), d.get('offset', 0),
d.get('query', {}), d.get('fuzzy_query', {}), d.get('sort', []))
return self
@@ -157,9 +157,9 @@ class Sql:
def __init__(self, c=None) -> None:
self.c = c
def select(self, table_name: str, target_column: 'list' = [], query: 'Query' = None) -> list:
'''单表内行查询单句sql语句返回fetchall数据'''
@staticmethod
def get_select_sql(table_name: str, target_column: list = [], query: 'Query' = None):
'''拼接单表内行查询单句sql语句返回语句和参数列表'''
sql = 'select '
sql_list = []
if len(target_column) >= 2:
@@ -173,8 +173,7 @@ class Sql:
sql += '* from ' + table_name
if query is None:
self.c.execute(sql)
return self.c.fetchall()
return sql, sql_list
where_key = []
where_like_key = []
@@ -215,5 +214,140 @@ class Sql:
sql_list.append(query.limit)
sql_list.append(query.offset)
return sql, sql_list
@staticmethod
def get_insert_sql(table_name: str, key: list = [], value_len: int = None, insert_type: str = None) -> str:
'''拼接insert语句请注意只返回sql语句insert_type为replace或ignore'''
insert_type = 'replace' if insert_type in [
'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)) + ')'
@staticmethod
def get_delete_sql(table_name: str, query: 'Query' = None):
'''拼接删除语句query中只有query(where =)会被处理'''
sql = 'delete from ' + table_name
sql_list = []
if query is not None and query.query:
sql += ' where '
where_key = []
for i in query.query:
where_key.append(i)
sql_list.append(query.query[i])
sql += where_key[0] + '=?'
if len(where_key) >= 1:
for i in range(1, len(where_key)):
sql += ' and ' + where_key[i] + '=?'
return sql, sql_list
def select(self, table_name: str, target_column: list = [], query: 'Query' = None) -> list:
'''单表内行select单句sql语句返回fetchall数据'''
sql, sql_list = self.get_select_sql(table_name, target_column, query)
self.c.execute(sql, sql_list)
return self.c.fetchall()
def select_exists(self, table_name: str, target_column: list = [], query: 'Query' = None) -> bool:
'''单表内行select exists单句sql语句返回bool值'''
sql, sql_list = self.get_select_sql(table_name, target_column, query)
self.c.execute('select exists(' + sql + ')', sql_list)
return self.c.fetchone() == (1,)
def insert(self, table_name: str, key: list, value: tuple, insert_type: str = None) -> None:
'''单行插入或覆盖插入key传[]则为全部列insert_type为replace或ignore'''
self.c.execute(self.get_insert_sql(
table_name, key, len(value), insert_type), value)
def insert_many(self, table_name: str, key: list, value_list: list, insert_type: str = None) -> None:
'''多行插入或覆盖插入key传[]则为全部列insert_type为replace或ignore'''
if not value_list:
return
self.c.executemany(self.get_insert_sql(
table_name, key, len(value_list[0]), insert_type), value_list)
def delete(self, table_name: str, query: 'Query' = None) -> None:
'''删除query中只有query(where =)会被处理'''
sql, sql_list = self.get_delete_sql(table_name, query)
self.c.execute(sql, sql_list)
def get_table_info(self, table_name: str):
'''得到表结构,返回主键列表和字段名列表'''
pk = []
name = []
self.c.execute('''pragma table_info ("%s")''' % table_name) # 这里无法参数化
x = self.c.fetchall()
if x:
for i in x:
name.append(i[1])
if i[5] != 0:
pk.append(i[1])
return pk, name
class DatabaseMigrator:
def __init__(self, c1_path: str, c2_path: str) -> None:
self.c1_path = c1_path
self.c2_path = c2_path
@staticmethod
def update_one_table(c1, c2, table_name: str) -> bool:
'''从c1向c2更新数据表c1中存在的信息不变即c2中的冲突信息会被覆盖'''
c1.execute(
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
c2.execute(
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
if not c1.fetchone() or not c2.fetchone():
return False
sql1 = Sql(c1)
sql2 = Sql(c2)
db1_pk, db1_name = sql1.get_table_info(table_name)
db2_pk, db2_name = sql2.get_table_info(table_name)
if db1_pk != db2_pk:
return False
sql2.insert_many(table_name, [], sql1.select(
table_name, list(filter(lambda x: x in db2_name, db1_name))), insert_type='replace')
return True
@staticmethod
def update_user_char_full(c) -> None:
'''用character表数据更新user_char_full'''
c.execute('''select character_id, max_level, is_uncapped from character''')
x = c.fetchall()
c.execute('''select user_id from user''')
y = c.fetchall()
c.execute('''delete from user_char_full''')
for i in x:
exp = 25000 if i[1] == 30 else 10000
c.executemany('''insert into user_char_full values(?,?,?,?,?,?)''', [
(j[0], i[0], i[1], exp, i[2], 0) for j in y])
@staticmethod
def update_user_epilogue(c) -> None:
'''给用户添加epilogue包'''
c.execute('''select user_id from user''')
Sql(c).insert_many('user_item', [], [(i[0], 'epilogue', 'pack', 1)
for i in c.fetchall()], insert_type='ignore')
def update_database(self) -> None:
'''
将c1数据库不存在数据加入或覆盖到c2数据库上
对于c2更新一些表并用character数据更新user_char_full
'''
with Connect(self.c2_path) as c2:
with Connect(self.c1_path) as c1:
[self.update_one_table(c1, c2, i)
for i in Constant.DATABASE_MIGRATE_TABLES]
if not Constant.UPDATE_WITH_NEW_CHARACTER_DATA:
self.update_one_table(c1, c2, 'character')
self.update_user_char_full(c2) # 更新user_char_full
self.update_user_epilogue(c2) # 更新user的epilogue

View File

@@ -130,8 +130,10 @@ class UserRegister(User):
def register(self):
now = int(time.time() * 1000)
self._build_user_code()
self._build_user_id()
if self.user_code is None:
self._build_user_code()
if self.user_id is None:
self._build_user_id()
self._insert_user_char()
self.c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,

View File

@@ -2,7 +2,7 @@ import hashlib
import os
def md5(code):
def md5(code: str) -> str:
# md5加密算法
code = code.encode()
md5s = hashlib.md5()
@@ -12,8 +12,8 @@ def md5(code):
return codes
def get_file_md5(file_path):
# 计算文件MD5
def get_file_md5(file_path: str) -> str:
'''计算文件MD5'''
if not os.path.isfile(file_path):
return None
myhash = hashlib.md5()
@@ -25,3 +25,17 @@ def get_file_md5(file_path):
myhash.update(b)
return myhash.hexdigest()
def try_rename(path: str, new_path: str) -> str:
'''尝试重命名文件,并尝试避免命名冲突(在后面加自增数字),返回最终路径'''
final_path = new_path
if os.path.exists(new_path):
i = 1
while os.path.exists(new_path + str(i)):
i += 1
final_path = new_path + str(i)
os.rename(path, final_path)
return final_path

View File

@@ -1,596 +1,15 @@
import sqlite3
import time
import json
import os
import sys
from importlib import import_module
# 数据库初始化文件删掉arcaea_database.db文件后运行即可谨慎使用
ARCAEA_SERVER_VERSION = 'v2.10.0.1'
def main(path='./'):
conn = sqlite3.connect(path+'arcaea_database.db')
c = conn.cursor()
c.execute('''create table if not exists config(id text primary key,
value text
);''')
c.execute('''insert into config values("version", :a);''',
{'a': ARCAEA_SERVER_VERSION})
c.execute('''create table if not exists user(user_id int primary key,
name text unique,
password text not null,
join_date char(20),
user_code char(10),
rating_ptt int,
character_id int,
is_skill_sealed int,
is_char_uncapped int,
is_char_uncapped_override int,
is_hide_rating int,
song_id text,
difficulty int,
score int,
shiny_perfect_count int,
perfect_count int,
near_count int,
miss_count int,
health int,
modifier int,
time_played int,
clear_type int,
rating real,
favorite_character int,
max_stamina_notification_enabled int,
current_map text,
ticket int,
prog_boost int,
email text,
world_rank_score int,
ban_flag text,
next_fragstam_ts int,
max_stamina_ts int,
stamina int,
world_mode_locked_end_ts int
);''')
c.execute('''create table if not exists login(access_token text,
user_id int,
login_time int,
login_ip text,
login_device text,
primary key(access_token, user_id)
);''')
c.execute('''create table if not exists friend(user_id_me int,
user_id_other int,
primary key (user_id_me, user_id_other)
);''')
c.execute('''create table if not exists best_score(user_id int,
song_id text,
difficulty int,
score int,
shiny_perfect_count int,
perfect_count int,
near_count int,
miss_count int,
health int,
modifier int,
time_played int,
best_clear_type int,
clear_type int,
rating real,
primary key(user_id, song_id, difficulty)
);''')
c.execute('''create table if not exists user_char(user_id int,
character_id int,
level int,
exp real,
is_uncapped int,
is_uncapped_override int,
primary key(user_id, character_id)
);''')
c.execute('''create table if not exists user_char_full(user_id int,
character_id int,
level int,
exp real,
is_uncapped int,
is_uncapped_override int,
primary key(user_id, character_id)
);''')
c.execute('''create table if not exists character(character_id int primary key,
name text,
max_level int,
frag1 real,
prog1 real,
overdrive1 real,
frag20 real,
prog20 real,
overdrive20 real,
frag30 real,
prog30 real,
overdrive30 real,
skill_id text,
skill_unlock_level int,
skill_requires_uncap int,
skill_id_uncap text,
char_type int,
is_uncapped int
);''')
c.execute('''create table if not exists char_item(character_id int,
item_id text,
type text,
amount int,
primary key(character_id, item_id, type)
);''')
c.execute('''create table if not exists recent30(user_id int primary key,
r0 real,
song_id0 text,
r1 real,
song_id1 text,
r2 real,
song_id2 text,
r3 real,
song_id3 text,
r4 real,
song_id4 text,
r5 real,
song_id5 text,
r6 real,
song_id6 text,
r7 real,
song_id7 text,
r8 real,
song_id8 text,
r9 real,
song_id9 text,
r10 real,
song_id10 text,
r11 real,
song_id11 text,
r12 real,
song_id12 text,
r13 real,
song_id13 text,
r14 real,
song_id14 text,
r15 real,
song_id15 text,
r16 real,
song_id16 text,
r17 real,
song_id17 text,
r18 real,
song_id18 text,
r19 real,
song_id19 text,
r20 real,
song_id20 text,
r21 real,
song_id21 text,
r22 real,
song_id22 text,
r23 real,
song_id23 text,
r24 real,
song_id24 text,
r25 real,
song_id25 text,
r26 real,
song_id26 text,
r27 real,
song_id27 text,
r28 real,
song_id28 text,
r29 real,
song_id29 text
);''')
c.execute('''create table if not exists user_world(user_id int,
map_id text,
curr_position int,
curr_capture real,
is_locked int,
primary key(user_id, map_id)
);''')
c.execute('''create table if not exists songplay_token(token text primary key,
user_id int,
song_id text,
difficulty int,
course_id text,
course_state int,
course_score int,
course_clear_type int,
stamina_multiply int,
fragment_multiply int,
prog_boost_multiply int
);''')
c.execute('''create table if not exists download_token(user_id int,
song_id text,
file_name text,
token text,
time int,
primary key(user_id, song_id, file_name)
);''')
c.execute('''create table if not exists item(item_id text,
type text,
is_available int,
_id text,
primary key(item_id, type)
);''')
c.execute('''create table if not exists user_item(user_id int,
item_id text,
type text,
amount int,
primary key(user_id, item_id, type)
);''')
c.execute('''create table if not exists purchase(purchase_name text primary key,
price int,
orig_price int,
discount_from int,
discount_to int,
discount_reason text
);''')
c.execute('''create table if not exists purchase_item(purchase_name text,
item_id text,
type text,
amount int,
primary key(purchase_name, item_id, type)
);''')
c.execute('''create table if not exists user_save(user_id int primary key,
scores_data text,
clearlamps_data text,
clearedsongs_data text,
unlocklist_data text,
installid_data text,
devicemodelname_data text,
story_data text,
createdAt int,
finalestate_data text
);''')
c.execute('''create table if not exists present(present_id text primary key,
expire_ts int,
description text
);''')
c.execute('''create table if not exists user_present(user_id int,
present_id text,
primary key(user_id, present_id)
);''')
c.execute('''create table if not exists present_item(present_id text,
item_id text,
type text,
amount int,
primary key(present_id, item_id, type)
);''')
c.execute('''create table if not exists chart(song_id text primary key,
name text,
rating_pst int,
rating_prs int,
rating_ftr int,
rating_byn int
);''')
c.execute('''create table if not exists redeem(code text primary key,
type int
);''')
c.execute('''create table if not exists user_redeem(user_id int,
code text,
primary key(user_id, code)
);''')
c.execute('''create table if not exists redeem_item(code text,
item_id text,
type text,
amount int,
primary key(code, item_id, type)
);''')
c.execute('''create table if not exists role(role_id text primary key,
caption text
);''')
c.execute('''create table if not exists user_role(user_id int,
role_id text,
primary key(user_id, role_id)
);''')
c.execute('''create table if not exists power(power_id text primary key,
caption text
);''')
c.execute('''create table if not exists role_power(role_id text,
power_id text,
primary key(role_id, power_id)
);''')
c.execute('''create table if not exists api_login(user_id int,
token text,
login_time int,
login_ip text,
primary key(user_id, token)
);''')
c.execute('''create table if not exists course(course_id text primary key,
course_name text,
dan_name text,
style int,
gauge_requirement text,
flag_as_hidden_when_requirements_not_met int,
can_start int
);''')
c.execute('''create table if not exists user_course(user_id int,
course_id text,
high_score int,
best_clear_type int,
primary key(user_id, course_id)
);''')
c.execute('''create table if not exists course_chart(course_id text,
song_id text,
difficulty int,
flag_as_hidden int,
song_index int,
primary key(course_id, song_index)
);''')
c.execute('''create table if not exists course_requirement(course_id text,
required_id text,
primary key(course_id, required_id)
);''')
c.execute('''create table if not exists course_item(course_id text,
item_id text,
type text,
amount int,
primary key(course_id, item_id, type)
);''')
c.execute(
'''create index best_score_1 on best_score (song_id, difficulty);''') # 排名查询优化
c.execute(
'''create index download_token_1 on download_token (song_id, file_name);''') # 下载token判断优化
# 搭档初始化
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']
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_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0,
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0]
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]
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]
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]
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]
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]
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]
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]
prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 105, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86]
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]
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]
char_core = {
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
1: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_desolate', 'amount': 25}],
2: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_crimson', 'amount': 25}],
4: [{'core_id': 'core_ambivalent', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
13: [{'core_id': 'core_scarlet', 'amount': 30}],
21: [{'core_id': 'core_scarlet', 'amount': 30}],
26: [{'core_id': 'core_chunithm', 'amount': 15}],
27: [{'core_id': 'core_chunithm', 'amount': 15}],
28: [{'core_id': 'core_chunithm', 'amount': 15}],
29: [{'core_id': 'core_chunithm', 'amount': 15}],
36: [{'core_id': 'core_chunithm', 'amount': 15}],
42: [{'core_id': 'core_chunithm', 'amount': 15}],
43: [{'core_id': 'core_chunithm', 'amount': 15}],
11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}],
12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
19: [{'core_id': 'core_colorful', 'amount': 30}]
}
for i in range(0, 58):
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]:
sql = '''insert into character values(?,?,30,?,?,?,?,?,?,?,?,?,?,?,?,?,?,1)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
else:
sql = '''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)'''
c.execute(sql, (i, char[i], frag1[i], prog1[i], overdrive1[i], frag20[i], prog20[i], overdrive20[i],
frag30[i], prog30[i], overdrive30[i], skill_id[i], skill_unlock_level[i], skill_requires_uncap, skill_id_uncap[i], char_type[i]))
c.execute('''insert into character values(?,?,20,?,?,?,?,?,?,?,?,?,?,?,?,?,?,0)''', (99,
'shirahime', 38, 33, 28, 66, 58, 50, 66, 58, 50, 'frags_preferred_song', 0, 0, '', 0))
for i in char_core:
c.executemany('''insert into char_item values(?,?,'core',?)''', [
(i, j['core_id'], j['amount']) for j in char_core[i]])
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']
c.executemany('''insert into item values(?,"core",1,'')''',
[(i,) for i in cores])
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']
c.executemany('''insert into item values(?,"world_song",1,'')''', [
(i,) for i in world_songs])
world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]
c.executemany('''insert into item values(?,"world_unlock",1,'')''', [
(i,) for i in world_unlocks])
course_banners = ['course_banner_' + str(i) for i in range(1, 12)]
c.executemany('''insert into item values(?,"course_banner",1,'')''', [
(i,) for i in course_banners])
c.execute('''insert into item values(?,?,?,?)''',
('fragment', 'fragment', 1, ''))
c.execute('''insert into item values(?,?,?,?)''',
('memory', 'memory', 1, ''))
c.execute('''insert into item values(?,?,?,?)''',
('anni5tix', 'anni5tix', 1, ''))
def insert_items(c, items):
# 物品数据导入
for i in items:
if 'discount_from' not in i:
discount_from = -1
else:
discount_from = i['discount_from']
if 'discount_to' not in i:
discount_to = -1
else:
discount_to = i['discount_to']
if 'discount_reason' not in i:
discount_reason = ''
else:
discount_reason = i['discount_reason']
c.execute('''insert into purchase values(?,?,?,?,?,?)''',
(i['name'], i['price'], i['orig_price'], discount_from, discount_to, discount_reason))
for j in i['items']:
if "_id" not in j:
_id = ''
else:
_id = j['_id']
c.execute(
'''select exists(select * from item where item_id=?)''', (j['id'],))
if c.fetchone() == (0,):
c.execute('''insert into item values(?,?,?,?)''',
(j['id'], j['type'], j['is_available'], _id))
if 'amount' in j:
amount = j['amount']
else:
amount = 1
c.execute('''insert into purchase_item values(?,?,?,?)''',
(i['name'], j['id'], j['type'], amount))
# item初始化
try:
f = open(path+'singles.json', 'r')
singles = json.load(f)
f.close()
insert_items(c, singles)
except:
pass
try:
f = open(path+'packs.json', 'r')
packs = json.load(f)
f.close()
insert_items(c, packs)
c.execute('''select exists(select * from item where item_id='epilogue')''')
if c.fetchone() == (0,):
c.execute('''insert into item values('epilogue','pack',1,'')''')
except:
pass
# course初始化
try:
f = open(path+'courses.json', 'r', encoding="utf-8")
courses = json.load(f)
f.close()
except:
courses = []
if courses:
if path == './':
# 有什么好办法吗
import sys
sys.path.append('..')
from core.course import Course
for i in courses:
x = Course(c).from_dict(i)
x.insert_all()
# api权限与权限组初始化
role = ['system', 'admin', 'user', 'selecter']
role_caption = ['系统', '管理员', '用户', '查询接口']
power = ['system', 'select', 'select_me', 'change', 'change_me', 'grant',
'grant_inf', 'select_song_rank', 'select_song_info']
power_caption = ['系统权限', '总体查询权限', '自我查询权限', '总体修改权限',
'自我修改权限', '授权权限', '下级授权权限', '歌曲排行榜查询权限', '歌曲信息查询权限']
role_power = {'system': power,
'admin': ['select', 'select_me', 'change_me', 'grant_inf', 'select_song_rank', 'select_song_info'],
'user': ['select_me', 'change_me', 'select_song_rank', 'select_song_info'],
'selector': ['select']
}
c.executemany('''insert into role values(?,?)''', [
(role[i], role_caption[i]) for i in range(len(role))])
c.executemany('''insert into power values(?,?)''', [
(power[i], power_caption[i]) for i in range(len(power))])
for i in role_power:
c.executemany('''insert into role_power values(?,?)''',
[(i, j) for j in role_power[i]])
conn.commit()
conn.close()
def arc_register(name: str):
def build_user_code(c):
return '123456789'
def build_user_id(c):
return 2000000
def insert_user_char(c, user_id):
# 为用户添加可用角色
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 0, 30, 25000, 1, 0))
c.execute('''insert into user_char values(?,?,?,?,?,?)''',
(user_id, 1, 30, 25000, 1, 0))
c.execute(
'''select character_id, max_level, is_uncapped from character''')
x = c.fetchall()
if x:
for i in x:
exp = 25000 if i[1] == 30 else 10000
c.execute('''insert into user_char_full values(?,?,?,?,?,?)''',
(user_id, i[0], i[1], exp, i[2], 0))
conn = sqlite3.connect(path+'arcaea_database.db')
c = conn.cursor()
hash_pwd = '41e5653fc7aeb894026d6bb7b2db7f65902b454945fa8fd65a6327047b5277fb'
c.execute(
'''select exists(select * from user where name = :name)''', {'name': name})
if c.fetchone() == (0,):
user_code = build_user_code(c)
user_id = build_user_id(c)
now = int(time.time() * 1000)
c.execute('''insert into user(user_id, name, password, join_date, user_code, rating_ptt,
character_id, is_skill_sealed, is_char_uncapped, is_char_uncapped_override, is_hide_rating, favorite_character, max_stamina_notification_enabled, current_map, ticket)
values(:user_id, :name, :password, :join_date, :user_code, 1250, 1, 0, 1, 0, 0, -1, 0, '', 114514)
''', {'user_code': user_code, 'user_id': user_id, 'join_date': now, 'name': name, 'password': hash_pwd})
c.execute('''insert into recent30(user_id) values(:user_id)''', {
'user_id': user_id})
c.execute(
'''insert into best_score values(2000000,'vexaria',3,10000000,100,0,0,0,100,0,1599667200,3,3,10.8)''')
insert_user_char(c, user_id)
conn.commit()
conn.close()
return None
else:
conn.commit()
conn.close()
return None
arc_register('admin')
def main(core_path: str = '../', db_path: str = './arcaea_database.db', init_folder_path: str = './init'):
sys.path.append(os.path.join(sys.path[0], core_path))
sys.path.append(os.path.join(sys.path[0], core_path, './core/'))
init = import_module('init').DatabaseInit(db_path, init_folder_path)
init.init()
if __name__ == '__main__':

View File

@@ -0,0 +1,86 @@
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',
'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']
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_id_uncap = ['', '', 'frags_kou', '', 'visual_ink', '', '', '', '', '', '', 'eto_uncap', 'luna_uncap', 'shirabe_entry_fee',
'', '', '', '', '', 'ayu_uncap', '', 'frags_yume', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', '']
skill_unlock_level = [0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0,
0, 0, 0, 8, 0, 14, 0, 0, 8, 8, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8, 0, 0, 8, 0]
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]
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]
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]
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]
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]
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]
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]
prog30 = [71, 90, 80, 75, 100, 80, 90, 102, 84, 78, 105, 77, 73, 78, 0, 99, 80, 66, 46, 93, 40, 83,
80, 90, 93, 50, 96, 88, 99, 108, 75, 80, 50, 64, 55, 100, 100, 110, 80, 50, 74, 90, 80, 80, 56, 80, 79, 55, 65, 59, 90, 50, 90, 90, 75, 210, 35, 86]
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]
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]
char_core = {
0: [{'core_id': 'core_hollow', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
1: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_desolate', 'amount': 25}],
2: [{'core_id': 'core_hollow', 'amount': 5}, {'core_id': 'core_crimson', 'amount': 25}],
4: [{'core_id': 'core_ambivalent', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
13: [{'core_id': 'core_scarlet', 'amount': 30}],
21: [{'core_id': 'core_scarlet', 'amount': 30}],
26: [{'core_id': 'core_chunithm', 'amount': 15}],
27: [{'core_id': 'core_chunithm', 'amount': 15}],
28: [{'core_id': 'core_chunithm', 'amount': 15}],
29: [{'core_id': 'core_chunithm', 'amount': 15}],
36: [{'core_id': 'core_chunithm', 'amount': 15}],
42: [{'core_id': 'core_chunithm', 'amount': 15}],
43: [{'core_id': 'core_chunithm', 'amount': 15}],
11: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_hollow', 'amount': 5}],
12: [{'core_id': 'core_binary', 'amount': 25}, {'core_id': 'core_desolate', 'amount': 5}],
19: [{'core_id': 'core_colorful', 'amount': 30}]
}
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']
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']
world_unlocks = ["scenery_chap1", "scenery_chap2",
"scenery_chap3", "scenery_chap4", "scenery_chap5", "scenery_chap6", "scenery_chap7"]
course_banners = ['course_banner_' + str(i) for i in range(1, 12)]
role = ['system', 'admin', 'user', 'selecter']
role_caption = ['系统', '管理员', '用户', '查询接口']
power = ['system', 'select', 'select_me', 'change', 'change_me', 'grant',
'grant_inf', 'select_song_rank', 'select_song_info']
power_caption = ['系统权限', '总体查询权限', '自我查询权限', '总体修改权限',
'自我修改权限', '授权权限', '下级授权权限', '歌曲排行榜查询权限', '歌曲信息查询权限']
role_power = {'system': power,
'admin': ['select', 'select_me', 'change_me', 'grant_inf', 'select_song_rank', 'select_song_info'],
'user': ['select_me', 'change_me', 'select_song_rank', 'select_song_info'],
'selector': ['select']
}

View File

@@ -0,0 +1,317 @@
create table if not exists config(id text primary key, value text);
create table if not exists user(user_id int primary key,
name text unique,
password text,
join_date char(20),
user_code char(10),
rating_ptt int,
character_id int,
is_skill_sealed int,
is_char_uncapped int,
is_char_uncapped_override int,
is_hide_rating int,
song_id text,
difficulty int,
score int,
shiny_perfect_count int,
perfect_count int,
near_count int,
miss_count int,
health int,
modifier int,
time_played int,
clear_type int,
rating real,
favorite_character int,
max_stamina_notification_enabled int,
current_map text,
ticket int,
prog_boost int,
email text,
world_rank_score int,
ban_flag text,
next_fragstam_ts int,
max_stamina_ts int,
stamina int,
world_mode_locked_end_ts int
);
create table if not exists login(access_token text,
user_id int,
login_time int,
login_ip text,
login_device text,
primary key(access_token, user_id)
);
create table if not exists friend(user_id_me int,
user_id_other int,
primary key (user_id_me, user_id_other)
);
create table if not exists best_score(user_id int,
song_id text,
difficulty int,
score int,
shiny_perfect_count int,
perfect_count int,
near_count int,
miss_count int,
health int,
modifier int,
time_played int,
best_clear_type int,
clear_type int,
rating real,
primary key(user_id, song_id, difficulty)
);
create table if not exists user_char(user_id int,
character_id int,
level int,
exp real,
is_uncapped int,
is_uncapped_override int,
primary key(user_id, character_id)
);
create table if not exists user_char_full(user_id int,
character_id int,
level int,
exp real,
is_uncapped int,
is_uncapped_override int,
primary key(user_id, character_id)
);
create table if not exists character(character_id int primary key,
name text,
max_level int,
frag1 real,
prog1 real,
overdrive1 real,
frag20 real,
prog20 real,
overdrive20 real,
frag30 real,
prog30 real,
overdrive30 real,
skill_id text,
skill_unlock_level int,
skill_requires_uncap int,
skill_id_uncap text,
char_type int,
is_uncapped int
);
create table if not exists char_item(character_id int,
item_id text,
type text,
amount int,
primary key(character_id, item_id, type)
);
create table if not exists recent30(user_id int primary key,
r0 real,
song_id0 text,
r1 real,
song_id1 text,
r2 real,
song_id2 text,
r3 real,
song_id3 text,
r4 real,
song_id4 text,
r5 real,
song_id5 text,
r6 real,
song_id6 text,
r7 real,
song_id7 text,
r8 real,
song_id8 text,
r9 real,
song_id9 text,
r10 real,
song_id10 text,
r11 real,
song_id11 text,
r12 real,
song_id12 text,
r13 real,
song_id13 text,
r14 real,
song_id14 text,
r15 real,
song_id15 text,
r16 real,
song_id16 text,
r17 real,
song_id17 text,
r18 real,
song_id18 text,
r19 real,
song_id19 text,
r20 real,
song_id20 text,
r21 real,
song_id21 text,
r22 real,
song_id22 text,
r23 real,
song_id23 text,
r24 real,
song_id24 text,
r25 real,
song_id25 text,
r26 real,
song_id26 text,
r27 real,
song_id27 text,
r28 real,
song_id28 text,
r29 real,
song_id29 text
);
create table if not exists user_world(user_id int,
map_id text,
curr_position int,
curr_capture real,
is_locked int,
primary key(user_id, map_id)
);
create table if not exists songplay_token(token text primary key,
user_id int,
song_id text,
difficulty int,
course_id text,
course_state int,
course_score int,
course_clear_type int,
stamina_multiply int,
fragment_multiply int,
prog_boost_multiply int
);
create table if not exists download_token(user_id int,
song_id text,
file_name text,
token text,
time int,
primary key(user_id, song_id, file_name)
);
create table if not exists item(item_id text,
type text,
is_available int,
primary key(item_id, type)
);
create table if not exists user_item(user_id int,
item_id text,
type text,
amount int,
primary key(user_id, item_id, type)
);
create table if not exists purchase(purchase_name text primary key,
price int,
orig_price int,
discount_from int,
discount_to int,
discount_reason text
);
create table if not exists purchase_item(purchase_name text,
item_id text,
type text,
amount int,
primary key(purchase_name, item_id, type)
);
create table if not exists user_save(user_id int primary key,
scores_data text,
clearlamps_data text,
clearedsongs_data text,
unlocklist_data text,
installid_data text,
devicemodelname_data text,
story_data text,
createdAt int,
finalestate_data text
);
create table if not exists present(present_id text primary key,
expire_ts int,
description text
);
create table if not exists user_present(user_id int,
present_id text,
primary key(user_id, present_id)
);
create table if not exists present_item(present_id text,
item_id text,
type text,
amount int,
primary key(present_id, item_id, type)
);
create table if not exists chart(song_id text primary key,
name text,
rating_pst int,
rating_prs int,
rating_ftr int,
rating_byn int
);
create table if not exists redeem(code text primary key,
type int
);
create table if not exists user_redeem(user_id int,
code text,
primary key(user_id, code)
);
create table if not exists redeem_item(code text,
item_id text,
type text,
amount int,
primary key(code, item_id, type)
);
create table if not exists role(role_id text primary key,
caption text
);
create table if not exists user_role(user_id int,
role_id text,
primary key(user_id, role_id)
);
create table if not exists power(power_id text primary key,
caption text
);
create table if not exists role_power(role_id text,
power_id text,
primary key(role_id, power_id)
);
create table if not exists api_login(user_id int,
token text,
login_time int,
login_ip text,
primary key(user_id, token)
);
create table if not exists course(course_id text primary key,
course_name text,
dan_name text,
style int,
gauge_requirement text,
flag_as_hidden_when_requirements_not_met int,
can_start int
);
create table if not exists user_course(user_id int,
course_id text,
high_score int,
best_clear_type int,
primary key(user_id, course_id)
);
create table if not exists course_chart(course_id text,
song_id text,
difficulty int,
flag_as_hidden int,
song_index int,
primary key(course_id, song_index)
);
create table if not exists course_requirement(course_id text,
required_id text,
primary key(course_id, required_id)
);
create table if not exists course_item(course_id text,
item_id text,
type text,
amount int,
primary key(course_id, item_id, type)
);
create index if not exists best_score_1 on best_score (song_id, difficulty);
create index if not exists download_token_1 on download_token (song_id, file_name);

View File

@@ -18,13 +18,13 @@ if os.path.exists('config.py') or os.path.exists('config'):
import api
import server
import server.init
import web.index
import web.login
from core.constant import Constant
from core.download import (UserDownload, get_only_3_song_ids,
initialize_songfile)
from core.error import ArcError, NoAccess, RateLimit
from core.init import FileChecker
from core.sql import Connect
from server.func import error_return
@@ -161,7 +161,7 @@ def main():
dictConfig(log_dict)
if not server.init.check_before_run(app):
if not FileChecker(app).check_before_run():
app.logger.error('Something wrong. The server will not run.')
input('Press ENTER key to exit.')
sys.exit()

View File

@@ -2,7 +2,8 @@ import os
import time
import server.arcscore
from core.download import initialize_songfile, get_only_3_song_ids
from core.download import get_only_3_song_ids, initialize_songfile
from core.init import FileChecker
from core.rank import RankList
from core.sql import Connect
from flask import Blueprint, flash, redirect, render_template, request, url_for
@@ -270,7 +271,8 @@ def update_database():
file.save(os.path.join(UPLOAD_FOLDER, filename))
flash('上传成功 Success upload.')
try:
web.system.update_database()
FileChecker.update_database(
os.path.join(UPLOAD_FOLDER, filename))
flash('数据更新成功 Success update data.')
except:
flash('数据更新失败 Cannot update data.')
@@ -699,7 +701,7 @@ def change_item():
c.execute(
'''select exists(select * from item where item_id=:a and type=:b)''', {'a': item_id, 'b': item_type})
if c.fetchone() == (0,):
c.execute('''insert into item values(?,?,?,'')''',
c.execute('''insert into item values(?,?,?)''',
(item_id, item_type, is_available))
flash('物品添加成功 Successfully add the item.')
else:

View File

@@ -1,9 +1,7 @@
import hashlib
import os
import time
from random import Random
from core.config_manager import Config
from core.sql import Connect
@@ -26,139 +24,6 @@ def random_str(randomlength=10):
return s
def get_table_info(c, table_name):
# 得到表结构,返回主键列表和字段名列表
pk = []
name = []
c.execute('''pragma table_info ("'''+table_name+'''")''')
x = c.fetchall()
if x:
for i in x:
name.append(i[1])
if i[5] != 0:
pk.append(i[1])
return pk, name
def get_sql_select_table(table_name, get_field, where_field=[], where_value=[]):
# sql语句拼接select ... from ... where ...
sql = 'select '
sql_dict = {}
if len(get_field) >= 2:
sql += get_field[0]
for i in range(1, len(get_field)):
sql += ',' + get_field[i]
sql += ' from ' + table_name
elif len(get_field) == 1:
sql += get_field[0] + ' from ' + table_name
else:
sql += '* from ' + table_name
if where_field and where_value:
sql += ' where '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 1:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
sql += ' order by rowid'
return sql, sql_dict
def get_sql_insert_table(table_name, field, value):
# sql语句拼接insert into ...(...) values(...)
sql = 'insert into ' + table_name + '('
sql_dict = {}
sql2 = ''
if len(field) >= 2:
sql += field[0]
sql2 += ':' + field[0]
sql_dict[field[0]] = value[0]
for i in range(1, len(field)):
sql += ',' + field[i]
sql2 += ', :' + field[i]
sql_dict[field[i]] = value[i]
sql += ') values('
elif len(field) == 1:
sql += field[0] + ') values('
sql2 += ':' + field[0]
sql_dict[field[0]] = value[0]
else:
return 'error', {}
sql += sql2 + ')'
return sql, sql_dict
def get_sql_delete_table(table_name, where_field=[], where_value=[]):
# sql语句拼接delete from ... where ...
sql = 'delete from ' + table_name
sql_dict = {}
if where_field and where_value:
sql += ' where '
sql += where_field[0] + '=:' + where_field[0]
sql_dict[where_field[0]] = where_value[0]
if len(where_field) >= 1:
for i in range(1, len(where_field)):
sql_dict[where_field[i]] = where_value[i]
sql += ' and ' + where_field[i] + '=:' + where_field[i]
return sql, sql_dict
def update_one_table(c1, c2, table_name):
# 从c1向c2更新数据表c1中存在的信息不变
c1.execute(
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
c2.execute(
'''select * from sqlite_master where type = 'table' and name = :a''', {'a': table_name})
if not c1.fetchone() or not c2.fetchone():
return 'error'
db1_pk, db1_name = get_table_info(c1, table_name)
db2_pk, db2_name = get_table_info(c2, table_name)
if db1_pk != db2_pk:
return 'error'
field = []
for i in db1_name:
if i in db2_name:
field.append(i)
sql, sql_dict = get_sql_select_table(table_name, db1_pk)
c1.execute(sql)
x = c1.fetchall()
sql, sql_dict = get_sql_select_table(table_name, field)
c1.execute(sql)
y = c1.fetchall()
if x:
for i in range(0, len(x)):
sql, sql_dict = get_sql_select_table(
table_name, [], db1_pk, list(x[i]))
sql = 'select exists(' + sql + ')'
c2.execute(sql, sql_dict)
if c2.fetchone() == (1,): # 如果c2里存在先删除
sql, sql_dict = get_sql_delete_table(
table_name, db1_pk, list(x[i]))
c2.execute(sql, sql_dict)
sql, sql_dict = get_sql_insert_table(
table_name, field, list(y[i]))
c2.execute(sql, sql_dict)
return None
def update_user_char(c):
# 用character数据更新user_char
c.execute('''select character_id, max_level, is_uncapped from character''')
@@ -175,64 +40,6 @@ def update_user_char(c):
(j[0], i[0], i[1], exp, i[2], 0))
def update_user_epilogue(c):
c.execute('''select user_id from user''')
x = c.fetchall()
for i in x:
c.execute(
'''select exists(select * from user_item where user_id=? and item_id=? and type='pack')''', (i[0], 'epilogue'))
if c.fetchone() == (0,):
c.execute('''insert into user_item values(?,?,'pack',1)''',
(i[0], 'epilogue'))
def update_database():
# 将old数据库不存在数据加入到新数据库上并删除old数据库
# 对于arcaea_datebase.db更新一些表并用character数据更新user_char_full
if os.path.isfile("database/old_arcaea_database.db") and os.path.isfile("database/arcaea_database.db"):
with Connect('./database/old_arcaea_database.db') as c1:
with Connect() as c2:
update_one_table(c1, c2, 'user')
update_one_table(c1, c2, 'friend')
update_one_table(c1, c2, 'best_score')
update_one_table(c1, c2, 'recent30')
update_one_table(c1, c2, 'user_world')
update_one_table(c1, c2, 'item')
update_one_table(c1, c2, 'user_item')
update_one_table(c1, c2, 'purchase')
update_one_table(c1, c2, 'purchase_item')
update_one_table(c1, c2, 'user_save')
update_one_table(c1, c2, 'login')
update_one_table(c1, c2, 'present')
update_one_table(c1, c2, 'user_present')
update_one_table(c1, c2, 'present_item')
update_one_table(c1, c2, 'redeem')
update_one_table(c1, c2, 'user_redeem')
update_one_table(c1, c2, 'redeem_item')
# update_one_table(c1, c2, 'role')
# update_one_table(c1, c2, 'user_role')
# update_one_table(c1, c2, 'power')
# update_one_table(c1, c2, 'role_power')
update_one_table(c1, c2, 'api_login')
update_one_table(c1, c2, 'chart')
update_one_table(c1, c2, 'user_course')
# update_one_table(c1, c2, 'course')
# update_one_table(c1, c2, 'course_item')
# update_one_table(c1, c2, 'course_chart')
# update_one_table(c1, c2, 'course_requirement')
update_one_table(c1, c2, 'user_char')
if not Config.UPDATE_WITH_NEW_CHARACTER_DATA:
update_one_table(c1, c2, 'character')
update_user_char(c2) # 更新user_char_full
update_user_epilogue(c2) # 更新user的epilogue
os.remove('database/old_arcaea_database.db')
def unlock_all_user_item(c):
# 解锁所有用户购买