From 1b850bccb3c30fd7522b65ad8a27825270aa6480 Mon Sep 17 00:00:00 2001 From: Lost-MSth <66946123+Lost-MSth@users.noreply.github.com> Date: Fri, 2 Oct 2020 00:16:11 +0800 Subject: [PATCH] Add files via upload Update to v1.2 --- v1.2/database/arcaea_database.db | Bin 0 -> 61440 bytes v1.2/database/arcsong.db | Bin 0 -> 106496 bytes v1.2/database/database_initialize.py | 233 ++ v1.2/main.py | 425 +++ v1.2/run.bat | 1 + v1.2/server/arcscore.py | 1248 +++++++ v1.2/server/auth.py | 149 + v1.2/server/info.py | 4060 +++++++++++++++++++++++ v1.2/server/setme.py | 155 + v1.2/setting.ini | 3 + v1.2/static/style.css | 217 ++ v1.2/templates/base.html | 23 + v1.2/templates/web/allplayer.html | 81 + v1.2/templates/web/allsong.html | 48 + v1.2/templates/web/changesong.html | 35 + v1.2/templates/web/index.html | 24 + v1.2/templates/web/login.html | 14 + v1.2/templates/web/singlecharttop.html | 108 + v1.2/templates/web/singleplayer.html | 85 + v1.2/templates/web/singleplayerptt.html | 191 ++ v1.2/templates/web/updatedatabase.html | 21 + v1.2/web/index.py | 357 ++ v1.2/web/login.py | 50 + v1.2/web/system.py | 102 + v1.2/web/webscore.py | 104 + 25 files changed, 7734 insertions(+) create mode 100644 v1.2/database/arcaea_database.db create mode 100644 v1.2/database/arcsong.db create mode 100644 v1.2/database/database_initialize.py create mode 100644 v1.2/main.py create mode 100644 v1.2/run.bat create mode 100644 v1.2/server/arcscore.py create mode 100644 v1.2/server/auth.py create mode 100644 v1.2/server/info.py create mode 100644 v1.2/server/setme.py create mode 100644 v1.2/setting.ini create mode 100644 v1.2/static/style.css create mode 100644 v1.2/templates/base.html create mode 100644 v1.2/templates/web/allplayer.html create mode 100644 v1.2/templates/web/allsong.html create mode 100644 v1.2/templates/web/changesong.html create mode 100644 v1.2/templates/web/index.html create mode 100644 v1.2/templates/web/login.html create mode 100644 v1.2/templates/web/singlecharttop.html create mode 100644 v1.2/templates/web/singleplayer.html create mode 100644 v1.2/templates/web/singleplayerptt.html create mode 100644 v1.2/templates/web/updatedatabase.html create mode 100644 v1.2/web/index.py create mode 100644 v1.2/web/login.py create mode 100644 v1.2/web/system.py create mode 100644 v1.2/web/webscore.py diff --git a/v1.2/database/arcaea_database.db b/v1.2/database/arcaea_database.db new file mode 100644 index 0000000000000000000000000000000000000000..18b28cf50115a5860c2f74f1db3e506b08d628cb GIT binary patch literal 61440 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 literal 0 HcmV?d00001 diff --git a/v1.2/database/database_initialize.py b/v1.2/database/database_initialize.py new file mode 100644 index 0000000..cb754c3 --- /dev/null +++ b/v1.2/database/database_initialize.py @@ -0,0 +1,233 @@ +import sqlite3 +import hashlib +import time +# 数据库初始化文件,删掉arcaea_database.db文件后运行即可,谨慎使用 + +conn = sqlite3.connect('arcaea_database.db') +c = conn.cursor() +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 +);''') +c.execute('''create table if not exists login(access_token text, +user_id int, +last_login_time int, +last_login_ip text, +last_login_device text +);''') +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, +level_exp real, +frag int, +prog int, +overdrive int, +skill_id text, +skill_unlock_level int, +skill_requires_uncap int, +skill_id_uncap text, +char_type int, +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, +level int, +exp real, +level_exp real, +frag int, +prog int, +overdrive int, +skill_id text, +skill_unlock_level int, +skill_requires_uncap int, +skill_id_uncap text, +char_type int, +uncap_cores text, +is_uncapped int, +is_uncapped_override int +);''') +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 +);''') + +char = ['Hikari','Tairitsu','Kou','Sapphire','Lethe','','Tairitsu(Axium)' +,'Tairitsu(Grievous Lady)','Stella','Hikari & Fisica','Ilith','Eto','Luna' +,'Shirabe','Hikari(Zero)','Hikari(Fracture)','Hikari(Summer)','Tairitsu(Summer)' +,'Tairitsu&Trin','Ayu','Eto&Luna','Yume','Seine & Hikari','Saya','Tairitsu & Chuni Penguin' +,'Chuni Penguin','Haruna','Nono','MTA-XXX','MDA-21','Kanae','Hikari(Fantasia)','Tairitsu(Sonata)','Sia','DORO*C' +,'Tairitsu(Tempest)','Brillante','Ilith(Summer)'] + +for i in range(0, 38): + if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21]: + sql = 'insert into character values('+str(i)+',"'+char[i]+'''",30,25000,25000,90,90,90,'',0,0,'',0,'',1,1)''' + c.execute(sql) + else: + if i != 5: + sql = 'insert into character values('+str(i)+',"'+char[i]+'''",30,25000,25000,90,90,90,'',0,0,'',0,'',0,0)''' + c.execute(sql) + + + +conn.commit() +conn.close() + + + +def arc_register(name: str, password: str): + def build_user_code(c): + return '123456789' + + def build_user_id(c): + return 2000000 + +## def insert_user_char(c, user_id): +## for i in range(0, 38): +## if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21]: +## sql = 'insert into user_char values('+str(user_id)+','+str( +## i)+''',30,25000,25000,90,90,90,'',0,0,'',0,1,1)''' +## c.execute(sql) +## else: +## if i != 5: +## sql = 'insert into user_char values('+str(user_id)+','+str( +## i)+''',30,25000,25000,90,90,90,'',0,0,'',0,0,0)''' +## c.execute(sql) + def insert_user_char(c, user_id): + # 为用户添加所有可用角色 + c.execute('''select * from character''') + x = c.fetchall() + if x != []: + for i in x: + c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', { + 'a': user_id, 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]}) + + conn = sqlite3.connect('arcaea_database.db') + c = conn.cursor() + hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest() + 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) + values(:user_id, :name, :password, :join_date, :user_code, 1250, 1, 0, 1, 0, 0, -1, 0) + ''', {'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', 'admin123') diff --git a/v1.2/main.py b/v1.2/main.py new file mode 100644 index 0000000..a3c1d1e --- /dev/null +++ b/v1.2/main.py @@ -0,0 +1,425 @@ +from flask import Flask, request, jsonify, make_response +import configparser +import base64 +import server.auth +import server.info +import server.setme +import server.arcscore +import web.login +import web.index + +app = Flask(__name__) +wsgi_app = app.wsgi_app + + +def error_return(error_code): # 错误返回 + # 100 无法在此ip地址下登录游戏 + # 101 用户名占用 + # 102 电子邮箱已注册 + # 103 已有一个账号由此设备创建 + # 104 用户名密码错误 + # 105 24小时内登入两台设备 + # 106 账户冻结 + # 107 你没有足够的体力 + # 401 用户不存在 + # 403 无法连接至服务器 + # 501 502 此物品目前无法获取 + # 504 无效的序列码 + # 505 此序列码已被使用 + # 506 你已拥有了此物品 + # 601 好友列表已满 + # 602 此用户已是好友 + # 604 你不能加自己为好友 + # 其它 发生未知错误 + return jsonify({ + "success": False, + "error_code": error_code + }) + + +@app.route('/') +def hello(): + return "Hello World!" + + +@app.route('/coffee/12/auth/login', methods=['POST']) # 登录接口 +def login(): + headers = request.headers + id_pwd = headers['Authorization'] + id_pwd = base64.b64decode(id_pwd[6:]).decode() + name, password = id_pwd.split(':', 1) + + try: + token = server.auth.arc_login(name, password) + if token is not None: + r = {"success": True, "token_type": "Bearer"} + r['access_token'] = token + return jsonify(r) + else: + return error_return(104) # 用户名或密码错误 + except: + return error_return(108) + + +@app.route('/coffee/12/user/', methods=['POST']) # 注册接口 +def register(): + name = request.form['name'] + password = request.form['password'] + try: + user_id, token, error_code = server.auth.arc_register(name, password) + if user_id is not None: + r = {"success": True, "value": { + 'user_id': user_id, 'access_token': token}} + return jsonify(r) + else: + return error_return(error_code) # 应该是101,用户名被占用,毕竟电子邮箱、设备号没记录 + except: + return error_return(108) + + +@app.route('/coffee/12/compose/aggregate', methods=['GET']) # 用户信息获取 +def aggregate(): + calls = request.args.get('calls') + headers = request.headers + token = headers['Authorization'] + token = token[7:] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + if calls == '[{ "endpoint": "/user/me", "id": 0 }]': # 极其沙雕的判断,我猜get的参数就两种 + r = server.info.arc_aggregate_small(user_id) + else: + r = server.info.arc_aggregate_big(user_id) + return jsonify(r) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/user/me/character', methods=['POST']) # 角色切换 +def character_change(): + headers = request.headers + token = headers['Authorization'] + token = token[7:] + character_id = request.form['character'] + skill_sealed = request.form['skill_sealed'] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + flag = server.setme.change_char( + user_id, character_id, skill_sealed) + if flag: + return jsonify({ + "success": True, + "value": { + "user_id": user_id, + "character": character_id + } + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee//toggle_uncap', methods=['POST']) # 角色觉醒切换 +def character_uncap(path): + character_id = int(path[22:]) + headers = request.headers + token = headers['Authorization'] + token = token[7:] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + r = server.setme.change_char_uncap(user_id, character_id) + if r is not None: + return jsonify({ + "success": True, + "value": { + "user_id": user_id, + "character": [r] + } + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/friend/me/add', methods=['POST']) # 加好友 +def add_friend(): + headers = request.headers + token = headers['Authorization'] + token = token[7:] + friend_code = request.form['friend_code'] + try: + user_id = server.auth.token_get_id(token) + friend_id = server.auth.code_get_id(friend_code) + if user_id is not None and friend_id is not None: + r = server.setme.arc_add_friend(user_id, friend_id) + if r is not None and r != 602 and r != 604: + return jsonify({ + "success": True, + "value": { + "user_id": user_id, + "updatedAt": "2020-09-07T07:32:12.740Z", + "createdAt": "2020-09-06T10:05:18.471Z", + "friends": r + } + }) + else: + if r is not None: + return error_return(r) + else: + return error_return(108) + else: + if friend_id is None: + return error_return(401) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/friend/me/delete', methods=['POST']) # 删好友 +def delete_friend(): + headers = request.headers + token = headers['Authorization'] + token = token[7:] + friend_id = int(request.form['friend_id']) + try: + user_id = server.auth.token_get_id(token) + if user_id is not None and friend_id is not None: + r = server.setme.arc_delete_friend(user_id, friend_id) + if r is not None: + return jsonify({ + "success": True, + "value": { + "user_id": user_id, + "updatedAt": "2020-09-07T07:32:12.740Z", + "createdAt": "2020-09-06T10:05:18.471Z", + "friends": r + } + }) + else: + return error_return(108) + else: + if friend_id is None: + return error_return(401) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/score/song/friend', methods=['GET']) # 好友排名,默认最多50 +def song_score_friend(): + song_id = request.args.get('song_id') + difficulty = request.args.get('difficulty') + headers = request.headers + token = headers['Authorization'] + token = token[7:] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + r = server.arcscore.arc_score_friend(user_id, song_id, difficulty) + if r is not None: + return jsonify({ + "success": True, + "value": r + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/score/song/me', methods=['GET']) # 我的排名,默认最多20 +def song_score_me(): + song_id = request.args.get('song_id') + difficulty = request.args.get('difficulty') + headers = request.headers + token = headers['Authorization'] + token = token[7:] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + r = server.arcscore.arc_score_me(user_id, song_id, difficulty) + if r is not None: + return jsonify({ + "success": True, + "value": r + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/score/song', methods=['GET']) # TOP20 +def song_score_top(): + song_id = request.args.get('song_id') + difficulty = request.args.get('difficulty') + headers = request.headers + token = headers['Authorization'] + token = token[7:] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + r = server.arcscore.arc_score_top(song_id, difficulty) + if r is not None: + return jsonify({ + "success": True, + "value": r + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/score/song', methods=['POST']) # 成绩上传 +def song_score_post(): + headers = request.headers + token = headers['Authorization'] + token = token[7:] + song_id = request.form['song_id'] + difficulty = int(request.form['difficulty']) + score = int(request.form['score']) + shiny_perfect_count = int(request.form['shiny_perfect_count']) + perfect_count = int(request.form['perfect_count']) + near_count = int(request.form['near_count']) + miss_count = int(request.form['miss_count']) + health = int(request.form['health']) + modifier = int(request.form['modifier']) + beyond_gauge = int(request.form['beyond_gauge']) + clear_type = int(request.form['clear_type']) + + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + r = server.arcscore.arc_score_post(user_id, song_id, difficulty, score, shiny_perfect_count, + perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type) + if r is not None: + return jsonify({ + "success": True, + "value": {"user_rating": r} + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/score/token', methods=['GET']) # 成绩上传所需的token,显然我不想验证 +def score_token(): + return jsonify({ + "success": True, + "value": { + "token": "1145141919810" + } + }) + + +@app.route('/coffee/12/user/me/save', methods=['GET']) # 从云端同步 +def cloud_get(): + headers = request.headers + token = headers['Authorization'] + token = token[7:] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + r = server.arcscore.arc_all_get(user_id) + if r is not None: + return jsonify({ + "success": True, + "value": r + }) + else: + return error_return(108) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/user/me/save', methods=['POST']) # 向云端同步 +def cloud_post(): + headers = request.headers + token = headers['Authorization'] + token = token[7:] + scores_data = request.form['scores_data'] + clearlamps_data = request.form['clearlamps_data'] + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + server.arcscore.arc_all_post(user_id, scores_data, clearlamps_data) + return jsonify({ + "success": True, + "value": { + "user_id": user_id + } + }) + else: + return error_return(108) + except: + return error_return(108) + + +@app.route('/coffee/12/purchase/me/redeem', methods=['POST']) # 兑换码,自然没有用 +def redeem(): + return error_return(504) + + +@app.route('/coffee/', methods=['POST']) # 三个设置,写在最后降低优先级 +def sys_set(path): + set_arg = path[10:] + headers = request.headers + token = headers['Authorization'] + token = token[7:] + value = request.form['value'] + + try: + user_id = server.auth.token_get_id(token) + if user_id is not None: + server.setme.arc_sys_set(user_id, value, set_arg) + r = server.info.arc_aggregate_small(user_id) + r['value'] = r['value'][0]['value'] + return jsonify(r) + else: + return error_return(108) + except: + return error_return(108) + + +def main(): + config = configparser.ConfigParser() + path = r'setting.ini' + config.read(path, encoding="utf-8") + HOST = config.get('CONFIG', 'HOST') + PORT = config.get('CONFIG', 'PORT') + app.config.from_mapping(SECRET_KEY='1145141919810') + app.register_blueprint(web.login.bp) + app.register_blueprint(web.index.bp) + + app.run(HOST, PORT) + + +if __name__ == '__main__': + main() + + +# Made By Lost 2020.9.11 diff --git a/v1.2/run.bat b/v1.2/run.bat new file mode 100644 index 0000000..7a84f97 --- /dev/null +++ b/v1.2/run.bat @@ -0,0 +1 @@ +python main.py \ No newline at end of file diff --git a/v1.2/server/arcscore.py b/v1.2/server/arcscore.py new file mode 100644 index 0000000..fd5e11b --- /dev/null +++ b/v1.2/server/arcscore.py @@ -0,0 +1,1248 @@ +import sqlite3 +import time +import json + + +def b2int(x): + # int与布尔值转换 + if x: + return 1 + else: + return 0 + + +def int2b(x): + # int与布尔值转换 + if x is None or x == 0: + return False + else: + return True + + +def get_score(c, user_id, song_id, difficulty): + # 根据user_id、song_id、难度得到该曲目最好成绩,返回字典 + c.execute('''select * from best_score where user_id = :a and song_id = :b and difficulty = :c''', + {'a': user_id, 'b': song_id, 'c': difficulty}) + x = c.fetchone() + if x is not None: + c.execute('''select name, character_id, is_skill_sealed, is_char_uncapped from user where user_id = :a''', { + 'a': user_id}) + y = c.fetchone() + if y is not None: + return { + "user_id": x[0], + "song_id": x[1], + "difficulty": x[2], + "score": x[3], + "shiny_perfect_count": x[4], + "perfect_count": x[5], + "near_count": x[6], + "miss_count": x[7], + "health": x[8], + "modifier": x[9], + "time_played": x[10], + "best_clear_type": x[11], + "clear_type": x[12], + "name": y[0], + "character": y[1], + "is_skill_sealed": int2b(y[2]), + "is_char_uncapped": int2b(y[3]) + } + else: + return {} + else: + return {} + + +def arc_score_friend(user_id, song_id, difficulty, limit=50): + # 得到用户好友分数表,默认最大50个 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select user_id from best_score where user_id in (select :user_id union select user_id_other from friend where user_id_me = :user_id) and song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit''', { + 'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty, 'limit': limit}) + x = c.fetchall() + r = [] + if x != []: + rank = 0 + for i in x: + rank += 1 + y = get_score(c, i[0], song_id, difficulty) + y['rank'] = rank + r.append(y) + + conn.commit() + conn.close() + return r + + +def arc_score_top(song_id, difficulty, limit=20): + # 得到top分数表,默认最多20个,如果是负数则全部查询 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + if limit >= 0: + c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit''', { + 'song_id': song_id, 'difficulty': difficulty, 'limit': limit}) + else: + c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC''', { + 'song_id': song_id, 'difficulty': difficulty}) + x = c.fetchall() + r = [] + if x != []: + rank = 0 + for i in x: + rank += 1 + y = get_score(c, i[0], song_id, difficulty) + y['rank'] = rank + r.append(y) + + conn.commit() + conn.close() + return r + + +def arc_score_me(user_id, song_id, difficulty, limit=20): + # 得到用户的排名,默认最大20个 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + r = [] + c.execute('''select exists(select * from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty)''', { + 'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty}) + if c.fetchone() == (1,): + c.execute('''select count(*) from best_score where song_id = :song_id and difficulty = :difficulty and (score>(select score from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty) or (score>(select score from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty) and time_played > (select time_played from best_score where user_id = :user_id and song_id = :song_id and difficulty = :difficulty)) )''', { + 'user_id': user_id, 'song_id': song_id, 'difficulty': difficulty}) + x = c.fetchone() + myrank = int(x[0]) + 1 + if myrank <= 4: # 排名在前4 + conn.commit() + conn.close() + return arc_score_top(song_id, difficulty, limit) + elif myrank >= 5 and myrank <= 9999 - limit + 4: # 万名内,前面有4个人 + c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', { + 'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': myrank - 5}) + x = c.fetchall() + if x != []: + rank = myrank - 5 + for i in x: + rank += 1 + y = get_score(c, i[0], song_id, difficulty) + y['rank'] = rank + r.append(y) + + elif myrank >= 10000: # 万名外 + c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', { + 'song_id': song_id, 'difficulty': difficulty, 'limit': limit - 1, 'offset': 9999-limit}) + x = c.fetchall() + if x != []: + rank = 9999 - limit + for i in x: + rank += 1 + y = get_score(c, i[0], song_id, difficulty) + y['rank'] = rank + r.append(y) + y = get_score(c, user_id, song_id, difficulty) + y['rank'] = -1 + r.append(y) + else: + c.execute('''select user_id from best_score where song_id = :song_id and difficulty = :difficulty order by score DESC, time_played DESC limit :limit offset :offset''', { + 'song_id': song_id, 'difficulty': difficulty, 'limit': limit, 'offset': 9998-limit}) + x = c.fetchall() + if x != []: + rank = 9998 - limit + for i in x: + rank += 1 + y = get_score(c, i[0], song_id, difficulty) + y['rank'] = rank + r.append(y) + + conn.commit() + conn.close() + return r + + +def get_one_ptt(song_id, difficulty, score: int) -> float: + # 单曲ptt计算 + conn = sqlite3.connect('./database/arcsong.db') + c = conn.cursor() + if difficulty == 0: + c.execute('''select rating_pst from songs where sid = :sid;''', { + 'sid': song_id}) + elif difficulty == 1: + c.execute('''select rating_prs from songs where sid = :sid;''', { + 'sid': song_id}) + elif difficulty == 2: + c.execute('''select rating_ftr from songs where sid = :sid;''', { + 'sid': song_id}) + elif difficulty == 3: + c.execute('''select rating_byn from songs where sid = :sid;''', { + 'sid': song_id}) + + x = c.fetchone() + defnum = 10.0 # 没在库里的全部当做定数10.0,不过要小心recent30表可能会被污染 + if x is not None and x != '': + defnum = float(x[0]) / 10 + if defnum <= 0: + defnum = 11.0 # 缺少难度的当做定数11.0 + + if score >= 10000000: + ptt = defnum + 2 + elif score < 9800000: + ptt = defnum + (score-9500000) / 300000 + if ptt < 0: + ptt = 0 + else: + ptt = defnum + 1 + (score-9800000) / 200000 + + conn.commit() + conn.close() + return ptt + + +def get_song_grade(x): + # 成绩转换评级 + if x >= 9900000: # EX+ + return 6 + elif x < 9900000 and x >= 9800000: # EX + return 5 + elif x < 9800000 and x >= 9500000: # AA + return 4 + elif x < 9500000 and x >= 9200000: # A + return 3 + elif x < 9200000 and x >= 8900000: # B + return 2 + elif x < 8900000 and x >= 8600000: # C + return 1 + else: + return 0 + + +def get_song_state(x): + # 返回成绩状态,便于比较 + if x == 3: # PM + return 5 + elif x == 2: # FC + return 4 + elif x == 5: # Hard Clear + return 3 + elif x == 1: # Clear + return 2 + elif x == 4: # Easy Clear + return 1 + else: # Track Lost + return 0 + + +def update_recent30(c, user_id, song_id, rating): + # 刷新r30,这里的判断方法存疑 + c.execute('''select * from recent30 where user_id = :a''', {'a': user_id}) + x = c.fetchone() + songs = [] + flag = True + for i in range(2, 61, 2): + if x[i] is None or x[i] == '': + r30_id = 29 + flag = False + break + if x[i] not in songs: + songs.append(x[i]) + if flag: + n = len(song_id) + if n >= 11: + r30_id = 29 + elif song_id not in songs and n == 10: + r30_id = 29 + elif song_id in songs and n == 10: + i = 29 + while x[i*2+2] == song_id: + i -= 1 + r30_id = i + elif song_id not in songs and n == 9: + i = 29 + while x[i*2+2] == song_id: + i -= 1 + r30_id = i + else: + r30_id = 29 + a = [] + b = [] + for i in range(1, 61, 2): + a.append(x[i]) + b.append(x[i+1]) + for i in range(r30_id, 0, -1): + a[i] = a[i-1] + b[i] = b[i-1] + a[0] = rating + b[0] = song_id + c.execute('''delete from recent30 where user_id = :a''', {'a': user_id}) + sql = 'insert into recent30 values(' + str(user_id) + for i in range(0, 30): + if a[i] is not None and b[i] is not None: + sql = sql + ',' + str(a[i]) + ',"' + b[i] + '"' + else: + sql = sql + ',0,""' + + sql = sql + ')' + c.execute(sql) + return None + + +def get_user_ptt(c, user_id) -> int: + # 总ptt计算 + sumr = 0 + c.execute('''select rating from best_score where user_id = :a order by rating DESC limit 30''', { + 'a': user_id}) + x = c.fetchall() + if x != []: + n = len(x) + for i in x: + sumr += float(i[0]) + c.execute('''select * from recent30 where user_id = :a''', {'a': user_id}) + x = c.fetchone() + if x is not None: + r30 = [] + s30 = [] + for i in range(1, 61, 2): + if x[i] is not None: + r30.append(float(x[i])) + s30.append(x[i+1]) + else: + r30.append(0) + s30.append('') + r30, s30 = (list(t) for t in zip(*sorted(zip(r30, s30), reverse=True))) + songs = [] + i = 0 + while len(songs) < 10 and i <= 29 and s30[i] != '' and s30[i] is not None: + if s30[i] not in songs: + sumr += r30[i] + songs.append(s30[i]) + i += 1 + + return int(sumr/40*100) + + +def arc_score_post(user_id, song_id, difficulty, score, shiny_perfect_count, perfect_count, near_count, miss_count, health, modifier, beyond_gauge, clear_type): + # 分数上传,返回变化后的ptt + # beyond_gauge是个什么呀?不管了,扔了 + + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + rating = get_one_ptt(song_id, difficulty, score) + now = int(time.time() * 1000) + # recent 更新 + c.execute('''update user set song_id = :b, difficulty = :c, score = :d, shiny_perfect_count = :e, perfect_count = :f, near_count = :g, miss_count = :h, health = :i, modifier = :j, clear_type = :k, rating = :l, time_played = :m where user_id = :a''', { + 'a': user_id, 'b': song_id, 'c': difficulty, 'd': score, 'e': shiny_perfect_count, 'f': perfect_count, 'g': near_count, 'h': miss_count, 'i': health, 'j': modifier, 'k': clear_type, 'l': rating, 'm': now}) + # recent30 更新 + update_recent30(c, user_id, song_id+str(difficulty), rating) + # 成绩录入 + c.execute('''select score, best_clear_type from best_score where user_id = :a and song_id = :b and difficulty = :c''', { + 'a': user_id, 'b': song_id, 'c': difficulty}) + now = int(now // 1000) + x = c.fetchone() + if x is None: + c.execute('''insert into best_score values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n)''', { + 'a': user_id, 'b': song_id, 'c': difficulty, 'd': score, 'e': shiny_perfect_count, 'f': perfect_count, 'g': near_count, 'h': miss_count, 'i': health, 'j': modifier, 'k': now, 'l': clear_type, 'm': clear_type, 'n': rating}) + else: + if get_song_state(clear_type) > get_song_state(int(x[1])): # 状态更新 + c.execute('''update best_score set best_clear_type = :a where user_id = :b and song_id = :c and difficulty = :d''', { + 'a': clear_type, 'b': user_id, 'c': song_id, 'd': difficulty}) + if score >= int(x[0]): # 成绩更新 + c.execute('''update best_score set score = :d, shiny_perfect_count = :e, perfect_count = :f, near_count = :g, miss_count = :h, health = :i, modifier = :j, clear_type = :k, rating = :l, time_played = :m where user_id = :a and song_id = :b and difficulty = :c ''', { + 'a': user_id, 'b': song_id, 'c': difficulty, 'd': score, 'e': shiny_perfect_count, 'f': perfect_count, 'g': near_count, 'h': miss_count, 'i': health, 'j': modifier, 'k': clear_type, 'l': rating, 'm': now}) + # 总PTT更新 + ptt = get_user_ptt(c, user_id) + c.execute('''update user set rating_ptt = :a where user_id = :b''', { + 'a': ptt, 'b': user_id}) + conn.commit() + conn.close() + return ptt + + +def arc_all_post(user_id, scores_data, clearlamps_data): + # 向云端同步,无返回 + # 注意,best_score表不比较,直接覆盖 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + scores = json.loads(scores_data)[""] + clearlamps = json.loads(clearlamps_data)[""] + clear_song_id_difficulty = [] + clear_state = [] + for i in clearlamps: + clear_song_id_difficulty.append(i['song_id']+str(i['difficulty'])) + clear_state.append(i['clear_type']) + + for i in scores: + rating = get_one_ptt(i['song_id'], i['difficulty'], i['score']) + try: + index = clear_song_id_difficulty.index( + i['song_id'] + str(i['difficulty'])) + except: + index = -1 + if index != -1: + clear_type = clear_state[index] + else: + clear_type = 0 + c.execute('''delete from best_score where user_id = :a and song_id = :b and difficulty = :c''', { + 'a': user_id, 'b': i['song_id'], 'c': i['difficulty']}) + c.execute('''insert into best_score values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n)''', { + 'a': user_id, 'b': i['song_id'], 'c': i['difficulty'], 'd': i['score'], 'e': i['shiny_perfect_count'], 'f': i['perfect_count'], 'g': i['near_count'], 'h': i['miss_count'], 'i': i['health'], 'j': i['modifier'], 'k': i['time_played'], 'l': clear_type, 'm': clear_type, 'n': rating}) + + ptt = get_user_ptt(c, user_id) # 更新PTT + c.execute('''update user set rating_ptt = :a where user_id = :b''', { + 'a': ptt, 'b': user_id}) + conn.commit() + conn.close() + return None + + +def arc_all_get(user_id): + # 从云端同步,返回字典 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select * from best_score where user_id = :a''', + {'a': user_id}) + x = c.fetchall() + song_1 = [] + song_2 = [] + song_3 = [] + if x != []: + for i in x: + if i[11] != 0: + song_1.append({ + "grade": get_song_grade(i[3]), + "difficulty": i[2], + "song_id": i[1] + }) + song_2.append({ + "ct": 0, + "clear_type": i[11], + "difficulty": i[2], + "song_id": i[1] + }) + song_3.append({ + "ct": 0, + "time_played": i[10], + "modifier": i[9], + "health": i[8], + "miss_count": i[7], + "near_count": i[6], + "perfect_count": i[5], + "shiny_perfect_count": i[4], + "score": i[3], + "difficulty": i[2], + "version": 1, + "song_id": i[1] + }) + + conn.commit() + conn.close() + return { + "user_id": user_id, + "story": { + "": [{ + "r": True, + "c": True, + "mi": 1, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 6, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 7, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 8, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 9, + "ma": 1 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 6, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 7, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 8, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 9, + "ma": 2 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 100 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 100 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 100 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 100 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 100 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 6, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 7, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 8, + "ma": 101 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 3 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 3 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 3 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 3 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 3 + }, { + "r": True, + "c": True, + "mi": 6, + "ma": 3 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 6, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 7, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 8, + "ma": 4 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 5 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 5 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 5 + }, { + "r": True, + "c": True, + "mi": 4, + "ma": 5 + }, { + "r": True, + "c": True, + "mi": 5, + "ma": 5 + }, { + "r": True, + "c": True, + "mi": 6, + "ma": 5 + }, { + "r": True, + "c": True, + "mi": 1, + "ma": 6 + }, { + "r": True, + "c": True, + "mi": 2, + "ma": 6 + }, { + "r": True, + "c": True, + "mi": 3, + "ma": 6 + }] + }, + "devicemodelname": { + "val": "MopeMope" + }, + "installid": { + "val": "b5e064cf-1a3f-4e64-9636-fce4accc9011" + }, + "unlocklist": { + "": [{ + "complete": 1, + "unlock_key": "worldvanquisher|2|0" + }, { + "complete": 1, + "unlock_key": "worldvanquisher|1|0" + }, { + "complete": 1, + "unlock_key": "worldexecuteme|2|0" + }, { + "complete": 1, + "unlock_key": "viciousheroism|2|0" + }, { + "complete": 1, + "unlock_key": "vector|2|0" + }, { + "complete": 1, + "unlock_key": "valhallazero|2|0" + }, { + "complete": 1, + "unlock_key": "tiferet|1|0" + }, { + "complete": 1, + "unlock_key": "tiemedowngently|1|0" + }, { + "complete": 1, + "unlock_key": "tempestissimo|0|101" + }, { + "complete": 1, + "unlock_key": "syro|2|0" + }, { + "complete": 1, + "unlock_key": "suomi|1|0" + }, { + "complete": 1, + "unlock_key": "solitarydream|2|0" + }, { + "complete": 1, + "unlock_key": "snowwhite|2|0" + }, { + "complete": 1, + "unlock_key": "sheriruth|2|0" + }, { + "complete": 1, + "unlock_key": "senkyou|2|0" + }, { + "complete": 1, + "unlock_key": "senkyou|1|0" + }, { + "complete": 1, + "unlock_key": "scarletlance|2|0" + }, { + "complete": 1, + "unlock_key": "scarletlance|1|0" + }, { + "complete": 1, + "unlock_key": "rugie|2|0" + }, { + "complete": 1, + "unlock_key": "rugie|1|0" + }, { + "complete": 1, + "unlock_key": "rise|2|0" + }, { + "complete": 1, + "unlock_key": "revixy|2|0" + }, { + "complete": 1, + "unlock_key": "reinvent|2|0" + }, { + "complete": 1, + "unlock_key": "reinvent|1|0" + }, { + "complete": 1, + "unlock_key": "redandblue|2|0" + }, { + "complete": 1, + "unlock_key": "redandblue|1|0" + }, { + "complete": 1, + "unlock_key": "rabbitintheblackroom|2|0" + }, { + "complete": 1, + "unlock_key": "rabbitintheblackroom|1|0" + }, { + "complete": 1, + "unlock_key": "worldexecuteme|1|0" + }, { + "complete": 1, + "unlock_key": "ringedgenesis|2|0" + }, { + "complete": 1, + "unlock_key": "quon|1|0" + }, { + "complete": 1, + "unlock_key": "qualia|2|0" + }, { + "complete": 1, + "unlock_key": "purgatorium|2|0" + }, { + "complete": 1, + "unlock_key": "supernova|2|0" + }, { + "complete": 1, + "unlock_key": "saikyostronger|2|3|einherjar|2" + }, { + "complete": 1, + "unlock_key": "purgatorium|1|0" + }, { + "complete": 1, + "unlock_key": "pragmatism|2|0" + }, { + "complete": 1, + "unlock_key": "ouroboros|2|0" + }, { + "complete": 1, + "unlock_key": "ouroboros|1|0" + }, { + "complete": 1, + "unlock_key": "oracle|1|0" + }, { + "complete": 1, + "unlock_key": "onelastdrive|2|0" + }, { + "complete": 1, + "unlock_key": "onelastdrive|1|0" + }, { + "complete": 1, + "unlock_key": "oblivia|2|0" + }, { + "complete": 1, + "unlock_key": "memoryforest|1|0" + }, { + "complete": 1, + "unlock_key": "melodyoflove|2|0" + }, { + "complete": 1, + "unlock_key": "saikyostronger|2|3|laqryma|2" + }, { + "complete": 1, + "unlock_key": "melodyoflove|1|0" + }, { + "complete": 1, + "unlock_key": "lucifer|2|0" + }, { + "complete": 1, + "unlock_key": "saikyostronger|2|3|izana|2" + }, { + "complete": 1, + "unlock_key": "halcyon|1|0" + }, { + "complete": 1, + "unlock_key": "memoryforest|2|0" + }, { + "complete": 1, + "unlock_key": "tiemedowngently|2|0" + }, { + "complete": 1, + "unlock_key": "lostdesire|1|0" + }, { + "complete": 1, + "unlock_key": "viciousheroism|1|0" + }, { + "complete": 1, + "unlock_key": "flyburg|1|0" + }, { + "complete": 1, + "unlock_key": "lostcivilization|2|0" + }, { + "complete": 1, + "unlock_key": "infinityheaven|1|0" + }, { + "complete": 1, + "unlock_key": "lostdesire|2|0" + }, { + "complete": 1, + "unlock_key": "ignotus|2|0" + }, { + "complete": 1, + "unlock_key": "harutopia|2|0" + }, { + "complete": 1, + "unlock_key": "revixy|1|0" + }, { + "complete": 1, + "unlock_key": "aterlbus|1|0" + }, { + "complete": 1, + "unlock_key": "linearaccelerator|2|0" + }, { + "complete": 1, + "unlock_key": "guardina|2|0" + }, { + "complete": 1, + "unlock_key": "corpssansorganes|2|0" + }, { + "complete": 1, + "unlock_key": "linearaccelerator|1|0" + }, { + "complete": 1, + "unlock_key": "guardina|1|0" + }, { + "complete": 1, + "unlock_key": "saikyostronger|2|0" + }, { + "complete": 1, + "unlock_key": "guardina|0|0" + }, { + "complete": 1, + "unlock_key": "valhallazero|1|0" + }, { + "complete": 1, + "unlock_key": "grimheart|1|0" + }, { + "complete": 1, + "unlock_key": "blaster|2|0" + }, { + "complete": 1, + "unlock_key": "grievouslady|2|101" + }, { + "complete": 1, + "unlock_key": "partyvinyl|2|0" + }, { + "complete": 1, + "unlock_key": "darakunosono|1|0" + }, { + "complete": 1, + "unlock_key": "grievouslady|1|101" + }, { + "complete": 1, + "unlock_key": "goodtek|1|0" + }, { + "complete": 1, + "unlock_key": "tempestissimo|3|101" + }, { + "complete": 1, + "unlock_key": "chronostasis|2|0" + }, { + "complete": 1, + "unlock_key": "gloryroad|2|0" + }, { + "complete": 1, + "unlock_key": "supernova|1|0" + }, { + "complete": 1, + "unlock_key": "singularity|2|0" + }, { + "complete": 1, + "unlock_key": "gloryroad|0|0" + }, { + "complete": 1, + "unlock_key": "shadesoflight|1|0" + }, { + "complete": 1, + "unlock_key": "kanagawa|2|0" + }, { + "complete": 1, + "unlock_key": "genesis|1|0" + }, { + "complete": 1, + "unlock_key": "fractureray|1|101" + }, { + "complete": 1, + "unlock_key": "freefall|2|0" + }, { + "complete": 1, + "unlock_key": "babaroque|1|0" + }, { + "complete": 1, + "unlock_key": "monochromeprincess|2|0" + }, { + "complete": 1, + "unlock_key": "flyburg|2|0" + }, { + "complete": 1, + "unlock_key": "shadesoflight|2|0" + }, { + "complete": 1, + "unlock_key": "espebranch|2|0" + }, { + "complete": 1, + "unlock_key": "qualia|1|0" + }, { + "complete": 1, + "unlock_key": "etherstrike|2|0" + }, { + "complete": 1, + "unlock_key": "tempestissimo|1|101" + }, { + "complete": 1, + "unlock_key": "conflict|1|0" + }, { + "complete": 1, + "unlock_key": "nhelv|1|0" + }, { + "complete": 1, + "unlock_key": "etherstrike|1|0" + }, { + "complete": 1, + "unlock_key": "syro|1|0" + }, { + "complete": 1, + "unlock_key": "anokumene|2|0" + }, { + "complete": 1, + "unlock_key": "essenceoftwilight|2|0" + }, { + "complete": 1, + "unlock_key": "snowwhite|1|0" + }, { + "complete": 1, + "unlock_key": "partyvinyl|1|0" + }, { + "complete": 1, + "unlock_key": "axiumcrisis|1|0" + }, { + "complete": 1, + "unlock_key": "ifi|2|0" + }, { + "complete": 1, + "unlock_key": "espebranch|1|0" + }, { + "complete": 1, + "unlock_key": "lostcivilization|1|0" + }, { + "complete": 1, + "unlock_key": "goodtek|2|0" + }, { + "complete": 1, + "unlock_key": "dandelion|2|0" + }, { + "complete": 1, + "unlock_key": "suomi|2|0" + }, { + "complete": 1, + "unlock_key": "dandelion|1|0" + }, { + "complete": 1, + "unlock_key": "oblivia|1|0" + }, { + "complete": 1, + "unlock_key": "cyberneciacatharsis|1|0" + }, { + "complete": 1, + "unlock_key": "quon|2|0" + }, { + "complete": 1, + "unlock_key": "bookmaker|2|0" + }, { + "complete": 1, + "unlock_key": "chronostasis|1|0" + }, { + "complete": 1, + "unlock_key": "heavensdoor|1|0" + }, { + "complete": 1, + "unlock_key": "tempestissimo|2|101" + }, { + "complete": 1, + "unlock_key": "cyaegha|2|0" + }, { + "complete": 1, + "unlock_key": "axiumcrisis|2|0" + }, { + "complete": 1, + "unlock_key": "blrink|2|0" + }, { + "complete": 1, + "unlock_key": "rise|1|0" + }, { + "complete": 1, + "unlock_key": "cyanine|1|0" + }, { + "complete": 1, + "unlock_key": "ifi|1|0" + }, { + "complete": 1, + "unlock_key": "aterlbus|2|0" + }, { + "complete": 1, + "unlock_key": "dreaminattraction|2|0" + }, { + "complete": 1, + "unlock_key": "bookmaker|1|0" + }, { + "complete": 1, + "unlock_key": "lucifer|1|0" + }, { + "complete": 1, + "unlock_key": "solitarydream|1|0" + }, { + "complete": 1, + "unlock_key": "ringedgenesis|1|0" + }, { + "complete": 1, + "unlock_key": "corpssansorganes|1|0" + }, { + "complete": 1, + "unlock_key": "vector|1|0" + }, { + "complete": 1, + "unlock_key": "infinityheaven|2|0" + }, { + "complete": 1, + "unlock_key": "essenceoftwilight|1|0" + }, { + "complete": 1, + "unlock_key": "conflict|2|0" + }, { + "complete": 1, + "unlock_key": "singularity|1|0" + }, { + "complete": 1, + "unlock_key": "harutopia|1|0" + }, { + "complete": 1, + "unlock_key": "cyberneciacatharsis|2|0" + }, { + "complete": 1, + "unlock_key": "oracle|2|0" + }, { + "complete": 1, + "unlock_key": "clotho|2|0" + }, { + "complete": 1, + "unlock_key": "corpssansorganes|0|0" + }, { + "complete": 1, + "unlock_key": "ignotus|1|0" + }, { + "complete": 1, + "unlock_key": "monochromeprincess|1|0" + }, { + "complete": 1, + "unlock_key": "nirvluce|1|0" + }, { + "complete": 1, + "unlock_key": "lethaeus|1|0" + }, { + "complete": 1, + "unlock_key": "clotho|1|0" + }, { + "complete": 1, + "unlock_key": "blaster|1|0" + }, { + "complete": 1, + "unlock_key": "fractureray|0|101" + }, { + "complete": 1, + "unlock_key": "kanagawa|1|0" + }, { + "complete": 1, + "unlock_key": "darakunosono|2|0" + }, { + "complete": 1, + "unlock_key": "freefall|1|0" + }, { + "complete": 1, + "unlock_key": "nirvluce|2|0" + }, { + "complete": 1, + "unlock_key": "cyanine|2|0" + }, { + "complete": 1, + "unlock_key": "heavensdoor|2|0" + }, { + "complete": 1, + "unlock_key": "genesis|2|0" + }, { + "complete": 1, + "unlock_key": "pragmatism|1|0" + }, { + "complete": 1, + "unlock_key": "nhelv|2|0" + }, { + "complete": 1, + "unlock_key": "halcyon|2|0" + }, { + "complete": 1, + "unlock_key": "blrink|1|0" + }, { + "complete": 1, + "unlock_key": "fractureray|2|101" + }, { + "complete": 1, + "unlock_key": "lethaeus|2|0" + }, { + "complete": 1, + "unlock_key": "sheriruth|1|0" + }, { + "complete": 1, + "unlock_key": "babaroque|2|0" + }, { + "complete": 1, + "unlock_key": "tiferet|2|0" + }, { + "complete": 1, + "unlock_key": "grimheart|2|0" + }, { + "complete": 1, + "unlock_key": "cyaegha|1|0" + }, { + "complete": 1, + "unlock_key": "aiueoon|2|0" + }, { + "complete": 1, + "unlock_key": "gloryroad|1|0" + }, { + "complete": 1, + "unlock_key": "anokumene|1|0" + }, { + "complete": 1, + "unlock_key": "grievouslady|0|101" + }, { + "complete": 1, + "unlock_key": "dreaminattraction|1|0" + }] + }, "clearedsongs": { + "": song_1 + }, + "clearlamps": { + "": song_2 + }, + "scores": { + "": song_3 + }, + "version": { + "val": 1 + } + } diff --git a/v1.2/server/auth.py b/v1.2/server/auth.py new file mode 100644 index 0000000..22fe96e --- /dev/null +++ b/v1.2/server/auth.py @@ -0,0 +1,149 @@ +import sqlite3 +import hashlib +import time + + +def arc_login(name: str, password: str) -> str: # 登录判断 + # 查询数据库中的user表,验证账号密码,返回并记录token + # token采用user_id和时间戳连接后hash生成(真的是瞎想的,没用bear) + # 密码和token的加密方式为 SHA-256 + + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest() + c.execute('''select user_id from user where name = :name and password = :password''', { + 'name': name, 'password': hash_pwd}) + x = c.fetchone() + if x is not None: + user_id = str(x[0]) + now = int(time.time() * 1000) + token = hashlib.sha256((user_id + str(now)).encode("utf8")).hexdigest() + c.execute( + '''select exists(select * from login where user_id = :user_id)''', {"user_id": user_id}) + + if c.fetchone() == (1,): # 删掉多余token + c.execute('''delete from login where user_id = :user_id''', + {'user_id': user_id}) + + c.execute('''insert into login(access_token, user_id) values(:access_token, :user_id)''', { + 'user_id': user_id, 'access_token': token}) + conn.commit() + conn.close() + return token + + conn.commit() + conn.close() + return None + + +def arc_register(name: str, password: str): # 注册 + # 账号注册,只记录hash密码和用户名,生成user_id和user_code,自动登录返回token + # token和密码的处理同登录部分 + + def build_user_code(c): + # 生成9位的user_code,用的自然是随机 + import random + flag = True + while flag: + user_code = ''.join([str(random.randint(0, 9)) for i in range(9)]) + c.execute('''select exists(select * from user where user_code = :user_code)''', + {'user_code': user_code}) + if c.fetchone() == (0,): + flag = False + return user_code + + def build_user_id(c): + # 生成user_id,往后加1 + c.execute('''select max(user_id) from user''') + x = c.fetchone() + if x[0] is not None: + return x[0] + 1 + else: + return 2000001 + + # def insert_user_char(c, user_id): + # # 为用户添加所有可用角色 + # for i in range(0, 38): + # if i in [0, 1, 2, 4, 13, 26, 27, 28, 29, 36, 21]: + # sql = 'insert into user_char values('+str(user_id)+','+str( + # i)+''',30,25000,25000,90,90,90,'',0,0,'',0,1,1)''' + # c.execute(sql) + # else: + # if i != 5: + # sql = 'insert into user_char values('+str(user_id)+','+str( + # i)+''',30,25000,25000,90,90,90,'',0,0,'',0,0,0)''' + # c.execute(sql) + def insert_user_char(c, user_id): + # 为用户添加所有可用角色 + c.execute('''select * from character''') + x = c.fetchall() + if x != []: + for i in x: + c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', { + 'a': user_id, 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]}) + + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + hash_pwd = hashlib.sha256(password.encode("utf8")).hexdigest() + 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) + values(:user_id, :name, :password, :join_date, :user_code, 0, 0, 0, 0, 0, 0, -1, 0) + ''', {'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}) + + token = hashlib.sha256( + (str(user_id) + str(now)).encode("utf8")).hexdigest() + c.execute('''insert into login(access_token, user_id) values(:access_token, :user_id)''', { + 'user_id': user_id, 'access_token': token}) + + insert_user_char(c, user_id) + conn.commit() + conn.close() + return user_id, token, 0 + else: + conn.commit() + conn.close() + return None, None, 101 + + +def token_get_id(token: str): + # 用token获取id,没有考虑不同用户token相同情况,说不定会有bug + + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select user_id from login where access_token = :token''', { + 'token': token}) + x = c.fetchone() + if x is not None: + conn.commit() + conn.close() + return x[0] + else: + conn.commit() + conn.close() + return None + + +def code_get_id(user_code): + # 用user_code获取id + + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select user_id from user where user_code = :a''', + {'a': user_code}) + x = c.fetchone() + if x is not None: + conn.commit() + conn.close() + return x[0] + else: + conn.commit() + conn.close() + return None diff --git a/v1.2/server/info.py b/v1.2/server/info.py new file mode 100644 index 0000000..72958f6 --- /dev/null +++ b/v1.2/server/info.py @@ -0,0 +1,4060 @@ +import sqlite3 + + +def int2b(x): + # int与布尔值转换 + if x is None or x == 0: + return False + else: + return True + + +def get_recent_score(c, user_id): + # 得到用户最近一次的成绩,返回列表 + c.execute('''select * from user where user_id = :x''', {'x': user_id}) + x = c.fetchone() + if x is not None: + if x[11] is not None: + c.execute('''select best_clear_type from best_score where user_id=:u and song_id=:s and difficulty=:d''', { + 'u': user_id, 's': x[11], 'd': x[12]}) + y = c.fetchone() + if y is not None: + best_clear_type = y[0] + else: + best_clear_type = x[21] + + return [{ + "rating": x[22], + "modifier": x[19], + "time_played": x[20], + "health": x[18], + "best_clear_type": best_clear_type, + "clear_type": x[21], + "miss_count": x[17], + "near_count": x[16], + "perfect_count": x[15], + "shiny_perfect_count": x[14], + "score": x[13], + "difficulty": x[12], + "song_id": x[11] + }] + return [] + + +def get_user_character(c, user_id): + # 得到用户拥有的角色列表,返回列表 + c.execute('''select * from user_char where user_id = :user_id''', + {'user_id': user_id}) + x = c.fetchall() + if x != []: + s = [] + for i in x: + char_name = '' + c.execute( + '''select name from character where character_id = :x''', {'x': i[1]}) + y = c.fetchone() + if y is not None: + char_name = y[0] + s.append({ + "is_uncapped_override": int2b(i[14]), + "is_uncapped": int2b(i[13]), + "uncap_cores": [], + "char_type": i[12], + "skill_id_uncap": i[11], + "skill_requires_uncap": int2b(i[10]), + "skill_unlock_level": i[9], + "skill_id": i[8], + "overdrive": i[7], + "prog": i[6], + "frag": i[5], + "level_exp": i[4], + "exp": i[3], + "level": i[2], + "name": char_name, + "character_id": i[1] + }) + + return s + else: + return [] + + +def get_user_friend(c, user_id): + # 得到用户的朋友列表,返回列表 + c.execute('''select user_id_other from friend where user_id_me = :user_id''', { + 'user_id': user_id}) + x = c.fetchall() + s = [] + if x != [] and x[0][0] is not None: + + for i in x: + c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''', + {'x': i[0], 'y': user_id}) + if c.fetchone() == (1,): + is_mutual = True + else: + is_mutual = False + + c.execute('''select * from user where user_id = :x''', {'x': i[0]}) + y=c.fetchone() + if y is not None: + s.append({ + "is_mutual": is_mutual, + "is_char_uncapped_override": int2b(y[9]), + "is_char_uncapped": int2b(y[8]), + "is_skill_sealed": int2b(y[7]), + "rating": y[5], + "join_date": int(y[3]), + "character": y[6], + "recent_score": get_recent_score(c, i[0]), + "name": y[1], + "user_id": i[0] + }) + + return s + + +def get_value_0(c, user_id): + # 构造value id=0的数据,返回字典 + c.execute('''select * from user where user_id = :x''', {'x': user_id}) + x = c.fetchone() + r = {} + if x is not None: + r = {"is_aprilfools": False, + "curr_available_maps": [], + "character_stats": get_user_character(c, user_id), + "friends": get_user_friend(c, user_id), + "settings": { + "favorite_character": x[23], + "is_hide_rating": int2b(x[10]), + "max_stamina_notification_enabled": int2b(x[24]) + }, + "user_id": user_id, + "name": x[1], + "user_code": x[4], + "display_name": x[1], + "ticket": 114514, + "character": x[6], + "is_locked_name_duplicate": False, + "is_skill_sealed": int2b(x[7]), + "current_map": "", + "prog_boost": 0, + "next_fragstam_ts": -1, + "max_stamina_ts": 1586274871917, + "stamina": 0, + "world_unlocks": [], + "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"], + "singles": [], + "packs": [], + "characters":[0,1,2,3,4]+[x for x in range(6, 38)], + "cores": [], + "recent_score": get_recent_score(c, user_id), + "max_friend": 50, + "rating": x[5], + "join_date": int(x[3]) + } + + return r + + +def arc_aggregate_small(user_id): + # 返回用户数据 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + r = {"success": True, + "value": [{ + "id": 0, + "value": get_value_0(c, user_id) + }]} + + conn.commit() + conn.close() + return r + + +def arc_aggregate_big(user_id): + # 返回用户数据和地图歌曲信息 + # 因为没有整理地图和曲包数据(不需要世界模式),所以直接复制了 + + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + r = {"success": True, + "value": [{ + "id": 0, + "value": get_value_0(c, user_id) + }, { + "id": 1, + "value": [{ + "name": "core", + "items": [{ + "id": "core", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "shiawase", + "items": [{ + "id": "shiawase", + "type": "pack", + "is_available": True + }, { + "id": "kou", + "type": "character", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1552089600000, + "discount_to": 1552694399000 + }, { + "name": "dynamix", + "items": [{ + "id": "dynamix", + "type": "pack", + "is_available": True + }, { + "id": "sapphire", + "type": "character", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "mirai", + "items": [{ + "id": "mirai", + "type": "pack", + "is_available": True + }, { + "id": "lethe", + "type": "character", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1552089600000, + "discount_to": 1552694399000 + }, { + "name": "yugamu", + "items": [{ + "id": "yugamu", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "lanota", + "items": [{ + "id": "lanota", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "nijuusei", + "items": [{ + "id": "nijuusei", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "rei", + "items": [{ + "id": "rei", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "tonesphere", + "items": [{ + "id": "tonesphere", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "groovecoaster", + "items": [{ + "id": "groovecoaster", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "zettai", + "items": [{ + "id": "zettai", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500, + "discount_from": 1583712000000, + "discount_to": 1584316799000 + }, { + "name": "chunithm", + "items": [{ + "id": "chunithm", + "type": "pack", + "is_available": True + }], + "price": 300, + "orig_price": 300 + }, { + "name": "prelude", + "items": [{ + "id": "prelude", + "type": "pack", + "is_available": True + }], + "price": 400, + "orig_price": 400 + }, { + "name": "omatsuri", + "items": [{ + "id": "omatsuri", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500 + }, { + "name": "vs", + "items": [{ + "id": "vs", + "type": "pack", + "is_available": True + }], + "price": 500, + "orig_price": 500 + }, { + "name": "extend", + "items": [{ + "id": "extend", + "type": "pack", + "is_available": True + }], + "price": 700, + "orig_price": 700 + }] + }, { + "id": 2, + "value": {} + }, { + "id": 3, + "value": { + "max_stamina": 12, + "stamina_recover_tick": 1800000, + "core_exp": 250, + "curr_ts": 1599547606825, + "level_steps": [{ + "level": 1, + "level_exp": 0 + }, { + "level": 2, + "level_exp": 50 + }, { + "level": 3, + "level_exp": 100 + }, { + "level": 4, + "level_exp": 150 + }, { + "level": 5, + "level_exp": 200 + }, { + "level": 6, + "level_exp": 300 + }, { + "level": 7, + "level_exp": 450 + }, { + "level": 8, + "level_exp": 650 + }, { + "level": 9, + "level_exp": 900 + }, { + "level": 10, + "level_exp": 1200 + }, { + "level": 11, + "level_exp": 1600 + }, { + "level": 12, + "level_exp": 2100 + }, { + "level": 13, + "level_exp": 2700 + }, { + "level": 14, + "level_exp": 3400 + }, { + "level": 15, + "level_exp": 4200 + }, { + "level": 16, + "level_exp": 5100 + }, { + "level": 17, + "level_exp": 6100 + }, { + "level": 18, + "level_exp": 7200 + }, { + "level": 19, + "level_exp": 8500 + }, { + "level": 20, + "level_exp": 10000 + }, { + "level": 21, + "level_exp": 11500 + }, { + "level": 22, + "level_exp": 13000 + }, { + "level": 23, + "level_exp": 14500 + }, { + "level": 24, + "level_exp": 16000 + }, { + "level": 25, + "level_exp": 17500 + }, { + "level": 26, + "level_exp": 19000 + }, { + "level": 27, + "level_exp": 20500 + }, { + "level": 28, + "level_exp": 22000 + }, { + "level": 29, + "level_exp": 23500 + }, { + "level": 30, + "level_exp": 25000 + }], + "world_ranking_enabled": False, + "is_byd_chapter_unlocked": True + } + }, { + "id": 4, + "value": [] + }, { + "id": 5, + "value": { + "user_id": user_id, + "current_map": "", + "maps": [{ + "map_id": "hikari_art", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "", + "require_value": 1, + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "-270,150", + "step_count": 91, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "id": "babaroque", + "type": "world_song" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 25 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 27 + }, { + "items": [{ + "id": "shadesoflight", + "type": "world_song" + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 45 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 50 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 55 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 60 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 65 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 70 + }, { + "items": [{ + "id": "kanagawa", + "type": "world_song" + }], + "position": 75 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 80 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 85 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 90 + }] + }, { + "map_id": "hikari_happy", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "270,150", + "step_count": 136, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 5 + }, { + "items": [{ + "id": "harutopia", + "type": "world_song" + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 25 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 30 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 32 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 45 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 50 + }, { + "items": [{ + "id": "goodtek", + "type": "world_song" + }], + "position": 55 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 60 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 65 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 70 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 75 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 80 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 82 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 85 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 90 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 95 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 97 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 100 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 105 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 110 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 115 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 120 + }, { + "items": [{ + "id": "dreaminattraction", + "type": "world_song" + }], + "position": 125 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 135 + }] + }, { + "map_id": "tairitsu_arcs", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "-270,-150", + "step_count": 136, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 5 + }, { + "items": [{ + "id": "rabbitintheblackroom", + "type": "world_song" + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 25 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 30 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 32 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 45 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 50 + }, { + "items": [{ + "id": "qualia", + "type": "world_song" + }], + "position": 55 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 60 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 65 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 70 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 75 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 80 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 82 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 85 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 90 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 95 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 97 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 100 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 105 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 110 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 115 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 120 + }, { + "items": [{ + "id": "redandblue", + "type": "world_song" + }], + "position": 125 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 135 + }] + }, { + "map_id": "tairitsu_tech", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "", + "require_value": 1, + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "270,-150", + "step_count": 91, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "id": "lucifer", + "type": "world_song" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 25 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 27 + }, { + "items": [{ + "id": "anokumene", + "type": "world_song" + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 45 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 50 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 55 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 60 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 65 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 70 + }, { + "items": [{ + "id": "ignotus", + "type": "world_song" + }], + "position": 75 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 80 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 85 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 90 + }] + }, { + "map_id": "eternal", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "core", + "require_type": "pack", + "require_value": 1, + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "-500,0", + "step_count": 66, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 10 + }, { + "items": [{ + "id": "essenceoftwilight", + "type": "world_song" + }], + "position": 15 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 28 + }, { + "items": [{ + "type": "fragment", + "amount": 200 + }], + "position": 35 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 42 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 44 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 46 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 48 + }, { + "items": [{ + "id": "pragmatism", + "type": "world_song" + }], + "position": 50 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 52 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 54 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 56 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 58 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 60 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 62 + }, { + "items": [{ + "id": "sheriruth", + "type": "world_song" + }], + "position": 65 + }] + }, { + "map_id": "axiumcrisis", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "yugamu", + "require_type": "pack", + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "500,0", + "step_count": 61, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 5 + }, { + "items": [{ + "id": "axiumcrisis", + "type": "world_song" + }], + "position": 10 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 24 + }, { + "items": [{ + "type": "fragment", + "amount": 200 + }], + "position": 30 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 37 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 39 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 41 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 43 + }, { + "items": [{ + "type": "fragment", + "amount": 150 + }], + "position": 45 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 51 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 53 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 55 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 57 + }, { + "items": [{ + "id": "6", + "type": "character" + }], + "position": 60 + }] + }, { + "map_id": "grievouslady", + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "yugamu", + "require_type": "pack", + "require_localunlock_songid": "grievouslady", + "is_legacy": True, + "stamina_cost": 1, + "coordinate": "0,-150", + "step_count": 21, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 300 + }], + "position": 18 + }, { + "items": [{ + "id": "7", + "type": "character" + }], + "position": 20 + }] + }, { + "map_id": "lanota", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "lanota", + "require_type": "pack", + "coordinate": "460,-160", + "step_count": 35, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 40 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 10 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 12 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 80 + }], + "position": 25 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 27 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 33 + }, { + "items": [{ + "id": "9", + "type": "character" + }], + "position": 34 + }] + }, { + "map_id": "nijuusei_light", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "nijuusei", + "require_type": "pack", + "coordinate": "-260,160", + "step_count": 22, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 175 + }], + "position": 10 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 14 + }, { + "items": [{ + "type": "fragment", + "amount": 178 + }], + "position": 20 + }, { + "items": [{ + "id": "11", + "type": "character" + }], + "position": 21 + }] + }, { + "map_id": "nijuusei_conflict", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "nijuusei", + "require_type": "pack", + "require_localunlock_challengeid": "singularity", + "coordinate": "260,160", + "step_count": 36, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 175 + }], + "position": 2 + }, { + "items": [{ + "type": "fragment", + "amount": 175 + }], + "position": 3 + }, { + "items": [{ + "type": "fragment", + "amount": 175 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 178 + }], + "position": 5 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 6 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 7 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 8 + }, { + "items": [{ + "id": "12", + "type": "character" + }], + "position": 35 + }] + }, { + "map_id": "extra_originals", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "", + "require_value": 1, + "coordinate": "0,-200", + "step_count": 101, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 25 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 29 + }, { + "items": [{ + "id": "syro", + "type": "world_song" + }], + "position": 30 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 31 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 80 + }], + "position": 45 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 50 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 55 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 59 + }, { + "items": [{ + "id": "blaster", + "type": "world_song" + }], + "position": 60 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 61 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 65 + }, { + "items": [{ + "type": "fragment", + "amount": 80 + }], + "position": 70 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 75 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 80 + }, { + "items": [{ + "type": "fragment", + "amount": 80 + }], + "position": 85 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 90 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 94 + }, { + "items": [{ + "id": "cyberneciacatharsis", + "type": "world_song" + }], + "position": 95 + }, { + "items": [{ + "type": "core", + "id": "core_crimson", + "amount": 5 + }], + "position": 98 + }, { + "items": [{ + "type": "core", + "id": "core_crimson", + "amount": 5 + }], + "position": 100 + }] + }, { + "map_id": "guardina", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "dynamix", + "require_type": "pack", + "coordinate": "-460,-160", + "step_count": 35, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 1 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 9 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 16 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 17 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 18 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 25 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 33 + }, { + "items": [{ + "id": "guardina", + "type": "world_song" + }], + "position": 34 + }] + }, { + "map_id": "etherstrike", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "rei", + "require_type": "pack", + "coordinate": "0,-40", + "step_count": 36, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 5 + }, { + "items": [{ + "id": "etherstrike", + "type": "world_song" + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 26 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 27 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 28 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 29 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 31 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 32 + }, { + "items": [{ + "type": "fragment", + "amount": 0 + }], + "position": 33 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 34 + }, { + "items": [{ + "id": "14", + "type": "character" + }], + "position": 35 + }] + }, { + "map_id": "fractureray", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "rei", + "require_type": "pack", + "require_localunlock_songid": "fractureray", + "coordinate": "0,160", + "step_count": 23, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 150 + }], + "position": 13 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 14 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 16 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 17 + }, { + "items": [{ + "type": "fragment", + "amount": 350 + }], + "position": 19 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 20 + }, { + "items": [{ + "id": "15", + "type": "character" + }], + "position": 22 + }] + }, { + "map_id": "chapter3_light", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "coordinate": "-480,180", + "step_count": 76, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 144 + }], + "position": 10 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 13 + }, { + "items": [{ + "type": "fragment", + "amount": 169 + }], + "position": 20 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 23 + }, { + "items": [{ + "id": "suomi", + "type": "world_song" + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 225 + }], + "position": 40 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 43 + }, { + "items": [{ + "type": "fragment", + "amount": 256 + }], + "position": 50 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 53 + }, { + "items": [{ + "id": "rugie", + "type": "world_song" + }], + "position": 59 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 65 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 70 + }, { + "items": [{ + "type": "core", + "id": "core_hollow", + "amount": 5 + }], + "position": 75 + }] + }, { + "map_id": "chapter3_conflict", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "coordinate": "-360,0", + "step_count": 85, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 10 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 15 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 19 + }, { + "items": [{ + "type": "fragment", + "amount": 15 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 150 + }], + "position": 25 + }, { + "items": [{ + "id": "bookmaker", + "type": "world_song" + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 200 + }], + "position": 35 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 39 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 45 + }, { + "items": [{ + "id": "darakunosono", + "type": "world_song" + }], + "position": 49 + }, { + "items": [{ + "type": "fragment", + "amount": 30 + }], + "position": 50 + }, { + "items": [{ + "type": "fragment", + "amount": 300 + }], + "position": 55 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 3 + }], + "position": 59 + }, { + "items": [{ + "type": "fragment", + "amount": 40 + }], + "position": 60 + }, { + "items": [{ + "id": "espebranch", + "type": "world_song" + }], + "position": 62 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 70 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 75 + }, { + "items": [{ + "type": "core", + "id": "core_desolate", + "amount": 5 + }], + "position": 84 + }] + }, { + "map_id": "chapter3_conflict_2", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "coordinate": "-240,-180", + "step_count": 51, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 125 + }], + "position": 9 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 14 + }, { + "items": [{ + "type": "fragment", + "amount": 125 + }], + "position": 19 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 24 + }, { + "items": [{ + "type": "fragment", + "amount": 125 + }], + "position": 29 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 34 + }, { + "items": [{ + "type": "fragment", + "amount": 125 + }], + "position": 39 + }, { + "items": [{ + "id": "nhelv", + "type": "world_song" + }], + "position": 40 + }, { + "items": [{ + "type": "core", + "id": "core_ambivalent", + "amount": 5 + }], + "position": 45 + }, { + "items": [{ + "type": "core", + "id": "core_ambivalent", + "amount": 5 + }], + "position": 50 + }] + }, { + "map_id": "tonesphere", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "tonesphere", + "require_type": "pack", + "coordinate": "480,180", + "step_count": 38, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 4 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 14 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 22 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 28 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 32 + }, { + "items": [{ + "id": "18", + "type": "character" + }], + "position": 37 + }] + }, { + "map_id": "groovecoaster", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "groovecoaster", + "require_type": "pack", + "coordinate": "360,0", + "step_count": 46, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 55 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 66 + }], + "position": 23 + }, { + "items": [{ + "type": "fragment", + "amount": 77 + }], + "position": 29 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 88 + }], + "position": 34 + }, { + "items": [{ + "type": "fragment", + "amount": 99 + }], + "position": 39 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 40 + }, { + "items": [{ + "id": "22", + "type": "character" + }], + "position": 45 + }] + }, { + "map_id": "solitarydream", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 5, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "core", + "require_type": "pack", + "coordinate": "-250,0", + "step_count": 10, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "id": "solitarydream", + "type": "world_song" + }], + "position": 9 + }] + }, { + "map_id": "zettai", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "zettai", + "require_type": "pack", + "coordinate": "-100,115", + "step_count": 26, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 20 + }, { + "items": [{ + "id": "23", + "type": "character" + }], + "position": 25 + }] + }, { + "map_id": "chapter4_ripples", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "coordinate": "-280,-200", + "step_count": 51, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 170 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 170 + }], + "position": 12 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 16 + }, { + "items": [{ + "id": "grimheart", + "type": "world_song" + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 190 + }], + "position": 26 + }, { + "items": [{ + "type": "fragment", + "amount": 190 + }], + "position": 42 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 46 + }, { + "items": [{ + "id": "vector", + "type": "world_song" + }], + "position": 50 + }] + }, { + "map_id": "chapter4_waves", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "coordinate": "-480,-200", + "step_count": 41, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 150 + }], + "position": 5 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 8 + }, { + "items": [{ + "type": "fragment", + "amount": 150 + }], + "position": 15 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 18 + }, { + "items": [{ + "id": "revixy", + "type": "world_song" + }], + "position": 25 + }, { + "items": [{ + "type": "fragment", + "amount": 400 + }], + "position": 30 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 33 + }, { + "items": [{ + "id": "supernova", + "type": "world_song" + }], + "position": 40 + }] + }, { + "map_id": "chunithm", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "chunithm", + "require_type": "pack", + "coordinate": "480,200", + "step_count": 28, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 110 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 120 + }], + "position": 10 + }, { + "items": [{ + "id": "worldvanquisher", + "type": "world_song" + }], + "position": 15 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 20 + }, { + "items": [{ + "id": "24", + "type": "character" + }], + "position": 27 + }] + }, { + "map_id": "omatsuri", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "omatsuri", + "require_type": "pack", + "coordinate": "280,200", + "step_count": 21, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 230 + }], + "position": 3 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 6 + }, { + "items": [{ + "type": "fragment", + "amount": 230 + }], + "position": 9 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 12 + }, { + "items": [{ + "id": "30", + "type": "character" + }], + "position": 15 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 20 + }] + }, { + "map_id": "chapter4_the_calm", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "", + "require_type": "fragment", + "require_value": 200, + "coordinate": "-380,-30", + "step_count": 146, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 10 + }], + "position": 1 + }, { + "items": [{ + "type": "fragment", + "amount": 10 + }], + "position": 2 + }, { + "items": [{ + "type": "fragment", + "amount": 10 + }], + "position": 3 + }, { + "items": [{ + "type": "fragment", + "amount": 10 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 15 + }], + "position": 11 + }, { + "items": [{ + "type": "fragment", + "amount": 15 + }], + "position": 12 + }, { + "items": [{ + "type": "fragment", + "amount": 15 + }], + "position": 13 + }, { + "items": [{ + "type": "fragment", + "amount": 15 + }], + "position": 14 + }, { + "items": [{ + "type": "fragment", + "amount": 20 + }], + "position": 21 + }, { + "items": [{ + "type": "fragment", + "amount": 20 + }], + "position": 22 + }, { + "items": [{ + "type": "fragment", + "amount": 20 + }], + "position": 23 + }, { + "items": [{ + "type": "fragment", + "amount": 20 + }], + "position": 24 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 31 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 32 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 33 + }, { + "items": [{ + "type": "fragment", + "amount": 25 + }], + "position": 34 + }, { + "items": [{ + "type": "fragment", + "amount": 30 + }], + "position": 41 + }, { + "items": [{ + "type": "fragment", + "amount": 30 + }], + "position": 42 + }, { + "items": [{ + "type": "fragment", + "amount": 30 + }], + "position": 43 + }, { + "items": [{ + "type": "fragment", + "amount": 30 + }], + "position": 44 + }, { + "items": [{ + "type": "fragment", + "amount": 35 + }], + "position": 51 + }, { + "items": [{ + "type": "fragment", + "amount": 35 + }], + "position": 52 + }, { + "items": [{ + "type": "fragment", + "amount": 35 + }], + "position": 53 + }, { + "items": [{ + "id": "diode", + "type": "world_song" + }], + "position": 54 + }, { + "items": [{ + "type": "fragment", + "amount": 40 + }], + "position": 62 + }, { + "items": [{ + "type": "fragment", + "amount": 40 + }], + "position": 64 + }, { + "items": [{ + "type": "fragment", + "amount": 45 + }], + "position": 72 + }, { + "items": [{ + "type": "fragment", + "amount": 45 + }], + "position": 74 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 82 + }, { + "items": [{ + "type": "fragment", + "amount": 50 + }], + "position": 84 + }, { + "items": [{ + "type": "fragment", + "amount": 55 + }], + "position": 92 + }, { + "items": [{ + "id": "freefall", + "type": "world_song" + }], + "position": 94 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 102 + }, { + "items": [{ + "type": "fragment", + "amount": 60 + }], + "position": 104 + }, { + "items": [{ + "type": "fragment", + "amount": 65 + }], + "position": 112 + }, { + "items": [{ + "type": "fragment", + "amount": 65 + }], + "position": 114 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 122 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 124 + }, { + "items": [{ + "type": "fragment", + "amount": 75 + }], + "position": 132 + }, { + "items": [{ + "id": "monochromeprincess", + "type": "world_song" + }], + "position": 134 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 3 + }], + "position": 145 + }] + }, { + "map_id": "gloryroad", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "shiawase", + "require_type": "pack", + "coordinate": "380,30", + "step_count": 28, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 1 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 11 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 14 + }, { + "items": [{ + "id": "gloryroad", + "type": "world_song" + }], + "position": 20 + }, { + "items": [{ + "type": "core", + "id": "core_crimson", + "amount": 5 + }], + "position": 23 + }, { + "items": [{ + "type": "core", + "id": "core_crimson", + "amount": 5 + }], + "position": 25 + }, { + "items": [{ + "type": "core", + "id": "core_crimson", + "amount": 5 + }], + "position": 27 + }] + }, { + "map_id": "blrink", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 5, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "prelude", + "require_type": "pack", + "coordinate": "250,0", + "step_count": 17, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "id": "blrink", + "type": "world_song" + }], + "position": 16 + }] + }, { + "map_id": "corpssansorganes", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 2106124800000, + "is_repeatable": False, + "require_id": "mirai", + "require_type": "pack", + "coordinate": "0,200", + "step_count": 42, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 105 + }], + "position": 6 + }, { + "items": [{ + "type": "fragment", + "amount": 105 + }], + "position": 12 + }, { + "items": [{ + "type": "fragment", + "amount": 105 + }], + "position": 22 + }, { + "items": [{ + "type": "fragment", + "amount": 105 + }], + "position": 28 + }, { + "items": [{ + "id": "corpssansorganes", + "type": "world_song" + }], + "position": 31 + }, { + "items": [{ + "type": "core", + "id": "core_ambivalent", + "amount": 5 + }], + "position": 35 + }, { + "items": [{ + "type": "core", + "id": "core_ambivalent", + "amount": 5 + }], + "position": 38 + }, { + "items": [{ + "type": "core", + "id": "core_ambivalent", + "amount": 5 + }], + "position": 41 + }] + }, { + "map_id": "lostdesire", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 5, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "vs", + "require_type": "pack", + "coordinate": "0,0", + "step_count": 36, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "id": "lostdesire", + "type": "world_song" + }], + "position": 5 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 15 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 25 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 35 + }] + }, { + "map_id": "tempestissimo", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 5, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "vs", + "require_type": "pack", + "require_localunlock_challengeid": "tempestissimo", + "coordinate": "0,160", + "step_count": 16, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "id": "35", + "type": "character" + }], + "position": 3 + }, { + "items": [{ + "type": "fragment", + "amount": 1000 + }], + "position": 15 + }] + }, { + "map_id": "chapter_1_scenery", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 1, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": True, + "require_id": "", + "require_type": "chapter_step", + "require_value": 455, + "coordinate": "0,150", + "step_count": 51, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "world_unlock", + "id": "scenery_chap1" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 70 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 80 + }], + "position": 15 + }, { + "items": [{ + "type": "fragment", + "amount": 90 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 25 + }, { + "items": [{ + "type": "fragment", + "amount": 110 + }], + "position": 30 + }, { + "items": [{ + "type": "fragment", + "amount": 120 + }], + "position": 35 + }, { + "items": [{ + "type": "fragment", + "amount": 130 + }], + "position": 40 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 50 + }] + }, { + "map_id": "chapter_2_scenery", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 2, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": True, + "require_id": "", + "require_type": "chapter_step", + "require_value": 105, + "coordinate": "0,-20", + "step_count": 51, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "world_unlock", + "id": "scenery_chap2" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 11 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 12 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 13 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 14 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 31 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 32 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 33 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 34 + }, { + "items": [{ + "type": "fragment", + "amount": 150 + }], + "position": 50 + }] + }, { + "map_id": "chapter_3_scenery", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 3, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": True, + "require_id": "", + "require_type": "chapter_step", + "require_value": 205, + "coordinate": "240,-180", + "step_count": 17, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "world_unlock", + "id": "scenery_chap3" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 400 + }], + "position": 16 + }] + }, { + "map_id": "chapter_4_scenery", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 4, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": True, + "require_id": "", + "require_type": "chapter_step", + "require_value": 240, + "coordinate": "100,-115", + "step_count": 101, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "world_unlock", + "id": "scenery_chap4" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 2000 + }], + "position": 100 + }] + }, { + "map_id": "chapter_5_scenery", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 5, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": True, + "require_id": "", + "require_type": "chapter_step", + "require_value": 75, + "coordinate": "0,-160", + "step_count": 31, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "world_unlock", + "id": "scenery_chap5" + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 10 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 20 + }, { + "items": [{ + "type": "fragment", + "amount": 500 + }], + "position": 30 + }] + }, { + "map_id": "byd_goodtek", + "is_legacy": False, + "chapter": 1001, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "goodtek2", + "require_type": "chart_unlock", + "coordinate": "900,650", + "is_beyond": True, + "stamina_cost": 3, + "beyond_health": 250, + "character_affinity": [0, 1], + "affinity_multiplier": [2.5, 1.5], + "step_count": 6, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 2 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 10 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 24 + }, { + "items": [{ + "type": "fragment", + "amount": 275 + }], + "position": 31 + }, { + "items": [{ + "id": "goodtek3", + "type": "world_song" + }], + "position": 33 + }] + }, { + "map_id": "byd_vexaria", + "is_legacy": False, + "chapter": 1001, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "vexaria2", + "require_type": "chart_unlock", + "coordinate": "650,400", + "is_beyond": True, + "stamina_cost": 3, + "beyond_health": 150, + "character_affinity": [0, 1], + "affinity_multiplier": [2.5, 1.5], + "step_count": 3, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 616 + }], + "position": 42 + }, { + "items": [{ + "id": "vexaria3", + "type": "world_song" + }], + "position": 46 + }] + }, { + "map_id": "byd_fairytale", + "is_legacy": False, + "chapter": 1001, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "fairytale2", + "require_type": "chart_unlock", + "coordinate": "400,650", + "is_beyond": True, + "stamina_cost": 3, + "beyond_health": 200, + "character_affinity": [0, 1], + "affinity_multiplier": [2.5, 1.5], + "step_count": 3, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 4 + }], + "position": 20 + }, { + "items": [{ + "id": "fairytale3", + "type": "world_song" + }], + "position": 55 + }] + }, { + "map_id": "byd_infinityheaven", + "is_legacy": False, + "chapter": 1001, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "infinityheaven2", + "require_type": "chart_unlock", + "coordinate": "650,900", + "is_beyond": True, + "stamina_cost": 3, + "beyond_health": 200, + "character_affinity": [0, 1], + "affinity_multiplier": [2.5, 1.5], + "step_count": 9, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 1 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 2 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 3 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 4 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 5 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 6 + }, { + "items": [{ + "type": "fragment", + "amount": 100 + }], + "position": 7 + }, { + "items": [{ + "id": "infinityheaven3", + "type": "world_song" + }], + "position": 8 + }] + }, { + "map_id": "byd_purgatorium", + "is_legacy": False, + "chapter": 1001, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "purgatorium2", + "require_type": "chart_unlock", + "coordinate": "-900,-650", + "is_beyond": True, + "stamina_cost": 3, + "beyond_health": 200, + "character_affinity": [13, 1], + "affinity_multiplier": [2.6, 1.6], + "step_count": 5, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 2 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 14 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 30 + }, { + "items": [{ + "id": "purgatorium3", + "type": "world_song" + }], + "position": 37 + }] + }, { + "map_id": "byd_dement", + "is_legacy": False, + "chapter": 1001, + "available_from": -1, + "available_to": 9999999999999, + "is_repeatable": False, + "require_id": "dement2", + "require_type": "chart_unlock", + "coordinate": "-400,-650", + "is_beyond": True, + "stamina_cost": 3, + "beyond_health": 250, + "character_affinity": [13, 1], + "affinity_multiplier": [2.6, 1.6], + "step_count": 4, + "custom_bg": "", + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 1 + }], + "position": 19 + }, { + "items": [{ + "type": "fragment", + "amount": 1200 + }], + "position": 39 + }, { + "items": [{ + "id": "dement3", + "type": "world_song" + }], + "position": 43 + }] + }, { + "map_id": "maliciousmischance_event", + "is_legacy": False, + "is_beyond": False, + "beyond_health": 100, + "character_affinity": [], + "affinity_multiplier": [], + "chapter": 0, + "available_from": 1599177600000, + "available_to": 1599836400000, + "is_repeatable": False, + "require_id": "maliciousmischance", + "require_type": "single", + "coordinate": "0,0", + "step_count": 87, + "custom_bg": "", + "stamina_cost": 2, + "curr_position": 0, + "curr_capture": 0, + "is_locked": True, + "rewards": [{ + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 62 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 63 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 67 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 68 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 72 + }, { + "items": [{ + "type": "core", + "id": "core_generic", + "amount": 2 + }], + "position": 73 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 77 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 78 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 82 + }, { + "items": [{ + "type": "fragment", + "amount": 250 + }], + "position": 83 + }, { + "items": [{ + "id": "37", + "type": "character" + }], + "position": 86 + }] + }] + }} + ]} + + conn.commit() + conn.close() + return r diff --git a/v1.2/server/setme.py b/v1.2/server/setme.py new file mode 100644 index 0000000..0891803 --- /dev/null +++ b/v1.2/server/setme.py @@ -0,0 +1,155 @@ +import sqlite3 +import server.info + + +def b2int(x): + # int与布尔值转换 + if x: + return 1 + else: + return 0 + + +def int2b(x): + # int与布尔值转换 + if x is None or x == 0: + return False + else: + return True + + +def change_char(user_id, character_id, skill_sealed): + # 角色改变,包括技能封印的改变,返回成功与否的布尔值 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''', + {'a': user_id, 'b': character_id}) + x = c.fetchone() + if x is not None: + if skill_sealed == 'false': + skill_sealed = False + else: + skill_sealed = True + c.execute('''update user set is_skill_sealed = :a, character_id = :b, is_char_uncapped = :c, is_char_uncapped_override = :d where user_id = :e''', { + 'a': b2int(skill_sealed), 'b': character_id, 'c': x[0], 'd': x[1], 'e': user_id}) + + conn.commit() + conn.close() + return True + + conn.commit() + conn.close() + return False + + +def change_char_uncap(user_id, character_id): + # 角色觉醒改变,返回字典 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select is_uncapped, is_uncapped_override from user_char where user_id = :a and character_id = :b''', + {'a': user_id, 'b': character_id}) + x = c.fetchone() + r = None + if x is not None and x[0] == 1: + c.execute('''update user set is_char_uncapped_override = :a where user_id = :b''', { + 'a': b2int(x[1] == 0), 'b': user_id}) + c.execute('''update user_char set is_uncapped_override = :a where user_id = :b and character_id = :c''', { + 'a': b2int(x[1] == 0), 'b': user_id, 'c': character_id}) + c.execute('''select * from user_char where user_id = :a and character_id = :b''', + {'a': user_id, 'b': character_id}) + y = c.fetchone() + c.execute( + '''select name from character where character_id = :x''', {'x': y[1]}) + z = c.fetchone() + if z is not None: + char_name = z[0] + if y is not None: + r = { + "is_uncapped_override": int2b(y[14]), + "is_uncapped": int2b(y[13]), + "uncap_cores": [], + "char_type": y[12], + "skill_id_uncap": y[11], + "skill_requires_uncap": int2b(y[10]), + "skill_unlock_level": y[9], + "skill_id": y[8], + "overdrive": y[7], + "prog": y[6], + "frag": y[5], + "level_exp": y[4], + "exp": y[3], + "level": y[2], + "name": char_name, + "character_id": y[1] + } + + conn.commit() + conn.close() + return r + + +def arc_sys_set(user_id, value, set_arg): + # 三个设置,PTT隐藏、体力满通知、最爱角色,无返回 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + if 'favorite_character' in set_arg: + value = int(value) + c.execute('''update user set favorite_character = :a where user_id = :b''', { + 'a': value, 'b': user_id}) + + else: + if value == 'false': + value = False + else: + value = True + + if 'is_hide_rating' in set_arg: + c.execute('''update user set is_hide_rating = :a where user_id = :b''', { + 'a': b2int(value), 'b': user_id}) + if 'max_stamina_notification_enabled' in set_arg: + c.execute('''update user set max_stamina_notification_enabled = :a where user_id = :b''', { + 'a': b2int(value), 'b': user_id}) + + conn.commit() + conn.close() + return None + + +def arc_add_friend(user_id, friend_id): + # 加好友,返回好友列表,或者是错误码602、604 + if user_id == friend_id: + return 604 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''', + {'x': user_id, 'y': friend_id}) + r = None + if c.fetchone() == (0,): + c.execute('''insert into friend values(:a, :b)''', + {'a': user_id, 'b': friend_id}) + r = server.info.get_user_friend(c, user_id) + + else: + return 602 + + conn.commit() + conn.close() + return r + + +def arc_delete_friend(user_id, friend_id): + # 删好友,返回好友列表 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select exists(select * from friend where user_id_me = :x and user_id_other = :y)''', + {'x': user_id, 'y': friend_id}) + r = None + if c.fetchone() == (1,): + c.execute('''delete from friend where user_id_me = :x and user_id_other = :y''', + {'x': user_id, 'y': friend_id}) + + r = server.info.get_user_friend(c, user_id) + + conn.commit() + conn.close() + return r diff --git a/v1.2/setting.ini b/v1.2/setting.ini new file mode 100644 index 0000000..3d109b5 --- /dev/null +++ b/v1.2/setting.ini @@ -0,0 +1,3 @@ +[CONFIG] +HOST = 192.168.1.113 +PORT = 80 \ No newline at end of file diff --git a/v1.2/static/style.css b/v1.2/static/style.css new file mode 100644 index 0000000..c486e03 --- /dev/null +++ b/v1.2/static/style.css @@ -0,0 +1,217 @@ +html { + font-family: sans-serif; + background: #eee; + padding: 1rem; +} + +body { + max-width: 960px; + margin: 0 auto; + background: white; +} + +h1 { + font-family: serif; + color: #377ba8; + margin: 1rem 0; +} + +a { + text-decoration: none; + color: #377ba8; +} + +hr { + border: none; + border-top: 1px solid lightgray; +} + +nav { + background: lightgray; + display: flex; + align-items: center; + padding: 0 0.5rem; +} + +nav h1 { + flex: auto; + margin: 0; +} + +nav h1 a { + text-decoration: none; + padding: 0.25rem 0.5rem; +} + +nav ul { + display: flex; + list-style: none; + margin: 0; + padding: 0; +} + +nav ul li a, +nav ul li span, +header .action { + display: block; + padding: 0.5rem; +} + +.content { + padding: 0 1rem 1rem; +} + +.content>header { + border-bottom: 1px solid lightgray; + display: flex; + align-items: flex-end; +} + +.content>header h1 { + flex: auto; + margin: 1rem 0 0.25rem 0; +} + +.flash { + margin: 1em 0; + padding: 1em; + background: #cae6f6; + border: 1px solid #377ba8; +} + +.post>header { + display: flex; + align-items: flex-end; + font-size: 0.85em; +} + +.post>header>div:first-of-type { + flex: auto; +} + +.post>header h1 { + font-size: 1.5em; + margin-bottom: 0; +} + +.post .about { + color: slategray; + font-style: italic; +} + +.post .body { + white-space: pre-line; +} + +.content:last-child { + margin-bottom: 0; +} + +.content form { + margin: 1em 0; + display: flex; + flex-direction: column; +} + +.content label { + font-weight: bold; + margin-bottom: 0.5em; +} + +.content input, +.content textarea { + margin-bottom: 1em; +} + +.content textarea { + min-height: 12em; + resize: vertical; +} + +input.danger { + color: #cc2f2e; +} + +input[type=submit] { + align-self: start; + min-width: 10em; +} + + +.score-item { + margin-bottom: 10px; + clear: both; +} + +.song-title { + font-size: 1.3em; + font-weight: bold; + display: inline-block; +} + +.difficulty_pst { + font-size: 0.9em; + background-color: rgb(10, 130, 190); + color: white; +} + +.difficulty_prs { + font-size: 0.9em; + background-color: rgb(100, 140, 60); + color: white; +} + +.difficulty_ftr { + font-size: 0.9em; + background-color: rgb(80, 25, 75); + color: white; +} + +.difficulty_byd { + font-size: 0.9em; + background-color: rgb(130, 35, 40); + color: white; +} + +.rank { + font-size: 0.8em; + margin-left: 4px; + padding: 0 4px; +} + +.rank_big { + font-size: 1.2em; + margin-left: 4px; + padding: 0 4px; +} + +.song-detail { + font-size: 0.8em; + float: right; + text-align: right; +} + +.song-score { + font-size: 1.375em; + font-family: Arial; +} + +.song-clear-type { + font-size: 0.875em; + padding-left: 0.625em; +} + +.title { + font-size: 1.375em; +} + +.name { + font-size: 1.12em; + font-weight: bold; +} + +.footer{ + font-size: 0.5em; + color: grey; + text-align: center; +} \ No newline at end of file diff --git a/v1.2/templates/base.html b/v1.2/templates/base.html new file mode 100644 index 0000000..160c5d0 --- /dev/null +++ b/v1.2/templates/base.html @@ -0,0 +1,23 @@ + +{% block title %}{% endblock %} - Arcaea Server + +

+
+
+ {% block header %}{% endblock %} +
+ {% for message in get_flashed_messages() %} +
{{ message }}
+ {% endfor %} {% block content %}{% endblock %} +
+ +
Made by Lost@2020
\ No newline at end of file diff --git a/v1.2/templates/web/allplayer.html b/v1.2/templates/web/allplayer.html new file mode 100644 index 0000000..9dd4437 --- /dev/null +++ b/v1.2/templates/web/allplayer.html @@ -0,0 +1,81 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}All players{% endblock %}

+{% endblock %} + +{% block content %} +{% if posts %} +{% for user in posts %} + +
+
{{user['name']}} + UID: {{user['user_id']}} + User code: {{user['user_code']}} +
+
注册于 Registered in: {{user['join_date']}}
+
PTT: {{user['rating_ptt']//100 ~ '.' ~ user['rating_ptt']%100}}
+
+
Recent plays:
+
+
+ + {{user['song_id']}} + + {% if user['difficulty'] == 0 %} + PST + {% elif user['difficulty'] == 1 %} + PRS + {% elif user['difficulty'] == 2 %} + FTR + {% else %} + BYD + {% endif %} + +
+
+ + + + + + + + + + + + + + + + + + +
PURE: {{user['perfect_count']}} {{'(' ~ user['shiny_perfect_count'] ~ ')'}}
FAR: {{user['near_count']}}
LOST: {{user['miss_count']}}
+
+
{{user['score']}}
+
+ {% if user['clear_type'] == 3 %}Pure Memory + {% elif user['clear_type'] == 2 %}Full Recall + {% elif user['clear_type'] == 5 %}Hard Clear + {% elif user['clear_type'] == 1 %}Normal Clear + {% elif user['clear_type'] == 4 %}Easy Clear + {% else%}Track Lost + {% endif %} +
+
成绩评价 Rating: {{user['rating']}}
+
日期 Date: + {{user['time_played']}} +
+
+
+
+
+{% if not loop.last %} +
+
+{% endif %} +{% endfor %} + +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/allsong.html b/v1.2/templates/web/allsong.html new file mode 100644 index 0000000..4aab965 --- /dev/null +++ b/v1.2/templates/web/allsong.html @@ -0,0 +1,48 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}All songs{% endblock %}

+{% endblock %} + +{% block content %} +{% if posts %}
+{% for song in posts %} + +
+ Sid: + {{song['song_id']}} +
+ Name_en: + {{song['name_en']}} +
+
铺面定数 Chart const:
+ {% if song['rating_pst'] %} + PST + {{song['rating_pst']}} + {% endif %} +
+ {% if song['rating_prs'] %} + PRS + {{song['rating_prs']}} + {% endif %} +
+ {% if song['rating_ftr'] %} + FTR + {{song['rating_ftr']}} + {% endif %} +
+ {% if song['rating_byn'] %} + BYD + {{song['rating_byn']}} + {% endif %} + + +
+{% if not loop.last %} +
+
+
+{% endif %} +{% endfor %} + +{% endif %} +{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/changesong.html b/v1.2/templates/web/changesong.html new file mode 100644 index 0000000..e03eb5a --- /dev/null +++ b/v1.2/templates/web/changesong.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Change the songs{% endblock %}

+{% endblock %} + +{% block content %} +
+
Add the song
+ + + + + + + + + + + + + +
如果没有某个铺面,应该填入-1。
+
If there is no some chart, fill in -1 please.
+ +
+
+
+
+
Delete the song
+ + + + +
+{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/index.html b/v1.2/templates/web/index.html new file mode 100644 index 0000000..bd62a7e --- /dev/null +++ b/v1.2/templates/web/index.html @@ -0,0 +1,24 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Index{% endblock %}

+{% endblock %} + +{% block content %} +

说明 Statement

+

这是Arcaea Server的后台管理协助系统,可以进行一些简单的操作
+ This is the background management assistance system of Arcaea Server, which can be used to do some simple + operations. +

+

游戏方面 Game

+单个玩家成绩查询 Single player score

+单个玩家PTT详情查询 Single player ptt

+所有玩家信息查询 All players

+铺面信息查询 All songs

+单个铺面排行榜查询 Single song chart tops +
+

系统方面 System

+数据库更新 Update databases

+歌曲修改 Change the songs + + +{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/login.html b/v1.2/templates/web/login.html new file mode 100644 index 0000000..b8ee233 --- /dev/null +++ b/v1.2/templates/web/login.html @@ -0,0 +1,14 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Log In{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + + + +
+{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/singlecharttop.html b/v1.2/templates/web/singlecharttop.html new file mode 100644 index 0000000..9e6a10a --- /dev/null +++ b/v1.2/templates/web/singlecharttop.html @@ -0,0 +1,108 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Single chart top{% endblock %}

+{% endblock %} + +{% block content %} +
+ + 模糊查询,只返回第一个 + Fuzzy query,and only return the first one. + + + +
+ +
+
+
+ {% if song_id %} +
+ + {{song_id}} + + + {% if difficulty == 0 %} + PST + {% elif difficulty == 1 %} + PRS + {% elif difficulty == 2 %} + FTR + {% else %} + BYD + + {% endif %} +
+
+ {{song_name_en}} +
+
+ {% endif %} +
+
+ {% for post in posts %} +
+
+ {{'#' ~ post['rank']}} + + {{post['name']}} + + UID: {{post['user_id']}} +
+ +
+ + + + + + + + + + + + + + + + + + +
PURE: {{post['perfect_count']}} {{'(' ~ post['shiny_perfect_count'] ~ ')'}}
FAR: {{post['near_count']}}
LOST: {{post['miss_count']}}
+
+
{{post['score']}}
+
+ {% if post['clear_type'] == 3 %}Pure Memory + {% elif post['clear_type'] == 2 %}Full Recall + {% elif post['clear_type'] == 5 %}Hard Clear + {% elif post['clear_type'] == 1 %}Normal Clear + {% elif post['clear_type'] == 4 %}Easy Clear + {% else%}Track Lost + {% endif %} + + {% if post['best_clear_type'] == 3 %}(Pure Memory) + {% elif post['best_clear_type'] == 2 %}(Full Recall) + {% elif post['best_clear_type'] == 5 %}(Hard Clear) + {% elif post['best_clear_type'] == 1 %}(Normal Clear) + {% elif post['best_clear_type'] == 4 %}(Easy Clear) + {% else%}(Track Lost) + {% endif %} + +
+
日期 Date: + {{post['time_played']}} +
+
+ + {% if not loop.last %} +
+ {% endif %} + {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/singleplayer.html b/v1.2/templates/web/singleplayer.html new file mode 100644 index 0000000..5289745 --- /dev/null +++ b/v1.2/templates/web/singleplayer.html @@ -0,0 +1,85 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Single player score{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + or
+ + + +
+
+
+ {% for post in posts %} +
+ + {{post['song_id']}} + + {% if post['difficulty'] == 0 %} + PST + {% elif post['difficulty'] == 1 %} + PRS + {% elif post['difficulty'] == 2 %} + FTR + {% else %} + BYD + {% endif %} + {{'#' ~ post['rank']}} + +
+
+ + + + + + + + + + + + + + + + + + +
PURE: {{post['perfect_count']}} {{'(' ~ post['shiny_perfect_count'] ~ ')'}}
FAR: {{post['near_count']}}
LOST: {{post['miss_count']}}
+
+
{{post['score']}}
+
+ {% if post['clear_type'] == 3 %}Pure Memory + {% elif post['clear_type'] == 2 %}Full Recall + {% elif post['clear_type'] == 5 %}Hard Clear + {% elif post['clear_type'] == 1 %}Normal Clear + {% elif post['clear_type'] == 4 %}Easy Clear + {% else%}Track Lost + {% endif %} + + {% if post['best_clear_type'] == 3 %}(Pure Memory) + {% elif post['best_clear_type'] == 2 %}(Full Recall) + {% elif post['best_clear_type'] == 5 %}(Hard Clear) + {% elif post['best_clear_type'] == 1 %}(Normal Clear) + {% elif post['best_clear_type'] == 4 %}(Easy Clear) + {% else%}(Track Lost) + {% endif %} + +
+
成绩评价 Rating: {{post['rating']}}
+
日期 Date: + {{post['time_played']}} +
+
+ + {% if not loop.last %} +
+ {% endif %} + {% endfor %} +
+{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/singleplayerptt.html b/v1.2/templates/web/singleplayerptt.html new file mode 100644 index 0000000..5ce15b1 --- /dev/null +++ b/v1.2/templates/web/singleplayerptt.html @@ -0,0 +1,191 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Single player ptt{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + or
+ + + +
+
+
+ {% if user %} +
+ +
+
玩家信息 Player information
+
{{user['name']}} + UID: {{user['user_id']}} + User code: {{user['user_code']}} +
+
注册于 Registered in: {{user['join_date']}}
+
PTT: {{user['rating_ptt']//100 ~ '.' ~ user['rating_ptt']%100}}
+
Best 30 PTT: {{bestptt}}
+
Recent 10 PTT: {{recentptt}}
+
+
Recent plays:
+
+
+ + {{user['song_id']}} + + {% if user['difficulty'] == 0 %} + PST + {% elif user['difficulty'] == 1 %} + PRS + {% elif user['difficulty'] == 2 %} + FTR + {% else %} + BYD + {% endif %} + +
+
+ + + + + + + + + + + + + + + + + + +
PURE: {{user['perfect_count']}} {{'(' ~ user['shiny_perfect_count'] ~ ')'}}
FAR: {{user['near_count']}}
LOST: {{user['miss_count']}}
+
+
{{user['score']}}
+
+ {% if user['clear_type'] == 3 %}Pure Memory + {% elif user['clear_type'] == 2 %}Full Recall + {% elif user['clear_type'] == 5 %}Hard Clear + {% elif user['clear_type'] == 1 %}Normal Clear + {% elif user['clear_type'] == 4 %}Easy Clear + {% else%}Track Lost + {% endif %} +
+
成绩评价 Rating: {{user['rating']}}
+
日期 Date: + {{user['time_played']}} +
+
+
+
+
+
+ {% if posts %} +
Best 30
+ {% endif %} + {% for post in posts %} +
+ + {{post['song_id']}} + + {% if post['difficulty'] == 0 %} + PST + {% elif post['difficulty'] == 1 %} + PRS + {% elif post['difficulty'] == 2 %} + FTR + {% else %} + BYD + {% endif %} + {{'#' ~ post['rank']}} + +
+
+ + + + + + + + + + + + + + + + + + +
PURE: {{post['perfect_count']}} {{'(' ~ post['shiny_perfect_count'] ~ ')'}}
FAR: {{post['near_count']}}
LOST: {{post['miss_count']}}
+
+
{{post['score']}}
+
+ {% if post['clear_type'] == 3 %}Pure Memory + {% elif post['clear_type'] == 2 %}Full Recall + {% elif post['clear_type'] == 5 %}Hard Clear + {% elif post['clear_type'] == 1 %}Normal Clear + {% elif post['clear_type'] == 4 %}Easy Clear + {% else%}Track Lost + {% endif %} + + {% if post['best_clear_type'] == 3 %}(Pure Memory) + {% elif post['best_clear_type'] == 2 %}(Full Recall) + {% elif post['best_clear_type'] == 5 %}(Hard Clear) + {% elif post['best_clear_type'] == 1 %}(Normal Clear) + {% elif post['best_clear_type'] == 4 %}(Easy Clear) + {% else%}(Track Lost) + {% endif %} + +
+
成绩评价 Rating: {{post['rating']}}
+
日期 Date: + {{post['time_played']}} +
+
+ + {% if not loop.last %} +
+ {% endif %} + {% endfor %} +
+ + {% if recent %} +
Recent 30
+ {% set rank = 0 %} + {% for i in recent %} + {% if i %} + {% set rank = rank + 1 %} +
+ + {{i['song_id']}} + + {% if i['difficulty'] == 0 %} + PST + {% elif i['difficulty'] == 1 %} + PRS + {% elif i['difficulty'] == 2 %} + FTR + {% else %} + BYD + {% endif %} + {{rank}} +
成绩评价 Rating: {{i['rating']}}
+
+ + {% if not loop.last %} +
+ {% endif %} + {% endif %} + {% endfor %} + {% endif %} + {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/v1.2/templates/web/updatedatabase.html b/v1.2/templates/web/updatedatabase.html new file mode 100644 index 0000000..ea47675 --- /dev/null +++ b/v1.2/templates/web/updatedatabase.html @@ -0,0 +1,21 @@ +{% extends 'base.html' %} +{% block header %} +

{% block title %}Update databases{% endblock %}

+{% endblock %} + +{% block content %} +
+ + + +
+
+ 这里可以将旧版本的数据库同步到新版本的数据库,并刷新用户拥有的角色列表。
+ 可上传文件: arcaea_database.db和arcsong.db
+ 新数据库不存在的数据会被添加,存在的数据将不会被改变。

+ Here you can synchronize the old version of the database to the new version of the database and refresh the list of + characters owned by players.
+ Uploadable files: arcaea_database.db & arcsong.db
+ Data that does not exist in the new database will be added and the existing data will not be changed. +
+{% endblock %} \ No newline at end of file diff --git a/v1.2/web/index.py b/v1.2/web/index.py new file mode 100644 index 0000000..aa9623d --- /dev/null +++ b/v1.2/web/index.py @@ -0,0 +1,357 @@ +from flask import ( + Blueprint, flash, g, redirect, render_template, request, url_for +) +from web.login import login_required +from werkzeug.utils import secure_filename +import sqlite3 +import web.webscore +import web.system +import time +import server.arcscore +import os + +UPLOAD_FOLDER = 'database' +ALLOWED_EXTENSIONS = {'db'} + +bp = Blueprint('index', __name__, url_prefix='/web') + + +def is_number(s): + try: # 判断字符串s是浮点数 + float(s) + return True + except ValueError: + pass + return False + + +@bp.route('/index') +@bp.route('/') +@login_required +def index(): + # 主页 + return render_template('web/index.html') + + +@bp.route('/singleplayer', methods=['POST', 'GET']) +@login_required +def single_player_score(): + # 单个玩家分数查询 + if request.method == 'POST': + name = request.form['name'] + user_code = request.form['user_code'] + error = None + if name or user_code: + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + if user_code: + c.execute('''select user_id from user where user_code=:a''', { + 'a': user_code}) + else: + c.execute( + '''select user_id from user where name=:a''', {'a': name}) + + user_id = c.fetchone() + posts = [] + if user_id: + user_id = user_id[0] + posts = web.webscore.get_user_score(c, user_id) + if not posts: + error = '无成绩 No score.' + else: + error = '玩家不存在 The player does not exist.' + conn.commit() + conn.close() + + else: + error = '输入为空 Null Input.' + + if error: + flash(error) + else: + return render_template('web/singleplayer.html', posts=posts) + + return render_template('web/singleplayer.html') + + +@bp.route('/singleplayerptt', methods=['POST', 'GET']) +@login_required +def single_player_ptt(): + # 单个玩家PTT详情查询 + if request.method == 'POST': + name = request.form['name'] + user_code = request.form['user_code'] + error = None + if name or user_code: + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + if user_code: + c.execute('''select user_id from user where user_code=:a''', { + 'a': user_code}) + else: + c.execute( + '''select user_id from user where name=:a''', {'a': name}) + + user_id = c.fetchone() + posts = [] + if user_id: + user_id = user_id[0] + user = web.webscore.get_user(c, user_id) + posts = web.webscore.get_user_score(c, user_id, 30) + recent, recentptt = web.webscore.get_user_recent30(c, user_id) + if not posts: + error = '无成绩 No score.' + else: + bestptt = 0 + for i in posts: + if i['rating']: + bestptt += i['rating'] + bestptt = bestptt / 30 + else: + error = '玩家不存在 The player does not exist.' + + conn.commit() + conn.close() + else: + error = '输入为空 Null Input.' + + if error: + flash(error) + else: + return render_template('web/singleplayerptt.html', posts=posts, user=user, recent=recent, recentptt=recentptt, bestptt=bestptt) + + return render_template('web/singleplayerptt.html') + + +@bp.route('/allplayer', methods=['GET']) +@login_required +def all_player(): + # 所有玩家数据,按照ptt排序 + conn = sqlite3.connect('./database/arcaea_database.db') + c = conn.cursor() + c.execute('''select * from user order by rating_ptt DESC''') + x = c.fetchall() + error = None + if x: + posts = [] + for i in x: + join_data = None + time_played = None + if i[3]: + join_date = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(int(i[3])//1000)) + if i[20]: + time_played = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(int(i[20])//1000)) + posts.append({'name': i[1], + 'user_id': i[0], + 'join_date': join_date, + 'user_code': i[4], + 'rating_ptt': i[5], + 'song_id': i[11], + 'difficulty': i[12], + 'score': i[13], + 'shiny_perfect_count': i[14], + 'perfect_count': i[15], + 'near_count': i[16], + 'miss_count': i[17], + 'time_played': time_played, + 'clear_type': i[21], + 'rating': i[22] + }) + else: + error = '没有玩家数据 No player data.' + + conn.commit() + conn.close() + if error: + flash(error) + return render_template('web/allplayer.html') + else: + return render_template('web/allplayer.html', posts=posts) + + +@bp.route('/allsong', methods=['GET']) +@login_required +def all_song(): + # 所有歌曲数据 + def defnum(x): + # 定数转换 + if x >= 0: + return x / 10 + else: + return None + + conn = sqlite3.connect('./database/arcsong.db') + c = conn.cursor() + c.execute('''select * from songs''') + x = c.fetchall() + error = None + if x: + posts = [] + for i in x: + posts.append({'song_id': i[0], + 'name_en': i[1], + 'rating_pst': defnum(i[12]), + 'rating_prs': defnum(i[13]), + 'rating_ftr': defnum(i[14]), + 'rating_byn': defnum(i[15]) + }) + else: + error = '没有铺面数据 No song data.' + + conn.commit() + conn.close() + if error: + flash(error) + return render_template('web/allsong.html') + else: + return render_template('web/allsong.html', posts=posts) + + +@bp.route('/singlecharttop', methods=['GET', 'POST']) +@login_required +def single_chart_top(): + # 歌曲排行榜 + if request.method == 'POST': + song_name = request.form['sid'] + difficulty = request.form['difficulty'] + if difficulty.isdigit(): + difficulty = int(difficulty) + error = None + conn = sqlite3.connect('./database/arcsong.db') + c = conn.cursor() + song_name = '%'+song_name+'%' + c.execute('''select sid, name_en from songs where sid like :a limit 1''', + {'a': song_name}) + x = c.fetchone() + conn.commit() + conn.close() + print(x) + if x: + song_id = x[0] + posts = server.arcscore.arc_score_top(song_id, difficulty, -1) + for i in posts: + i['time_played'] = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(i['time_played'])) + else: + error = '查询为空 No song.' + + if not error: + return render_template('web/singlecharttop.html', posts=posts, song_name_en=x[1], song_id=song_id, difficulty=difficulty) + else: + flash(error) + + return render_template('web/singlecharttop.html') + + +def allowed_file(filename): + return '.' in filename and \ + filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS + + +@bp.route('/updatedatabase', methods=['GET', 'POST']) +@login_required +def update_database(): + # 更新数据库 + error = None + if request.method == 'POST': + if 'file' not in request.files: + flash('无文件 No file part.') + return redirect(request.url) + file = request.files['file'] + if file.filename == '': + flash('未选择文件 No selected file.') + return redirect(request.url) + + if file and allowed_file(file.filename): + filename = 'old_' + secure_filename(file.filename) + file.save(os.path.join(UPLOAD_FOLDER, filename)) + flash('上传成功 Success upload.') + web.system.update_database() + flash('数据更新成功 Success update data.') + else: + error = '上传失败 Upload error.' + + if error: + flash(error) + + return render_template('web/updatedatabase.html') + + +@bp.route('/changesong', methods=['GET']) +@login_required +def change_song(): + # 修改歌曲数据 + return render_template('web/changesong.html') + + +@bp.route('/changesong/addsong', methods=['POST']) +@login_required +def add_song(): + # 添加歌曲数据 + def get_rating(x): + # 换算定数 + if is_number(x): + x = float(x) + if x >= 0: + return int(x*10) + else: + return -1 + else: + return -1 + + error = None + song_id = request.form['sid'] + name_en = request.form['name_en'] + rating_pst = get_rating(request.form['rating_pst']) + rating_prs = get_rating(request.form['rating_prs']) + rating_ftr = get_rating(request.form['rating_ftr']) + rating_byd = get_rating(request.form['rating_byd']) + if len(song_id) >= 256: + song_id = song_id[:200] + if len(name_en) >= 256: + name_en = name_en[:200] + conn = sqlite3.connect('./database/arcsong.db') + c = conn.cursor() + c.execute( + '''select exists(select * from songs where sid=:a)''', {'a': song_id}) + if c.fetchone() == (0,): + c.execute('''insert into songs(sid,name_en,rating_pst,rating_prs,rating_ftr,rating_byn) values(:a,:b,:c,:d,:e,:f)''', { + 'a': song_id, 'b': name_en, 'c': rating_pst, 'd': rating_prs, 'e': rating_ftr, 'f': rating_byd}) + flash('歌曲添加成功 Successfully add the song.') + else: + error = '歌曲已存在 The song exists.' + + conn.commit() + conn.close() + + if error: + flash(error) + + return redirect(url_for('index.change_song')) + + +@bp.route('/changesong/deletesong', methods=['POST']) +@login_required +def delete_song(): + # 删除歌曲数据 + + error = None + song_id = request.form['sid'] + conn = sqlite3.connect('./database/arcsong.db') + c = conn.cursor() + c.execute( + '''select exists(select * from songs where sid=:a)''', {'a': song_id}) + if c.fetchone() == (1,): + c.execute('''delete from songs where sid=:a''', {'a': song_id}) + flash('歌曲删除成功 Successfully delete the song.') + else: + error = "歌曲不存在 The song doesn't exist." + + conn.commit() + conn.close() + if error: + flash(error) + + return redirect(url_for('index.change_song')) diff --git a/v1.2/web/login.py b/v1.2/web/login.py new file mode 100644 index 0000000..5c8e007 --- /dev/null +++ b/v1.2/web/login.py @@ -0,0 +1,50 @@ +#import sqlite3 +from flask import (Blueprint, flash, g, redirect, + render_template, request, session, url_for) +import functools + +bp = Blueprint('login', __name__, url_prefix='/web') + + +@bp.route('/login', methods=('GET', 'POST')) +def login(): + # 登录 + if request.method == 'POST': + username = request.form['username'] + password = request.form['password'] + error = None + + if username != 'admin' and password != 'admin': + error = '错误的用户名或密码 Incorrect username or password.' + + if error is None: + session.clear() + session['user_id'] = 'admin' + return redirect(url_for('index.index')) + + flash(error) + + return render_template('web/login.html') + + +@bp.route('/logout') +def logout(): + # 登出 + session.clear() + flash('成功登出 Successfully log out.') + return redirect(url_for('index.index')) + + +def login_required(view): + # 登录验证,写成了修饰器 + @functools.wraps(view) + def wrapped_view(**kwargs): + x = session.get('user_id') + # 少用户存在验证 + if x is None: + return redirect(url_for('login.login')) + + g.user = {'user_id': x, 'username': 'admin'} + return view(**kwargs) + + return wrapped_view diff --git a/v1.2/web/system.py b/v1.2/web/system.py new file mode 100644 index 0000000..4b228a8 --- /dev/null +++ b/v1.2/web/system.py @@ -0,0 +1,102 @@ +import os +import sqlite3 + + +def update_user_char(c): + # 用character数据更新user_char + c.execute('''select * from character''') + x = c.fetchall() + c.execute('''select user_id from user''') + y = c.fetchall() + if x and y: + for j in y: + for i in x: + c.execute('''delete from user_char where user_id=:a and character_id=:b''', { + 'a': j[0], 'b': i[0]}) + c.execute('''insert into user_char values(:a,:b,:c,:d,:e,:f,:g,:h,:i,:j,:k,:l,:m,:n,:o)''', { + 'a': j[0], 'b': i[0], 'c': i[2], 'd': i[3], 'e': i[4], 'f': i[5], 'g': i[6], 'h': i[7], 'i': i[8], 'j': i[9], 'k': i[10], 'l': i[11], 'm': i[12], 'n': i[14], 'o': i[15]}) + + +def update_database(): + # 将old数据库不存在数据加入到新数据库上,并删除old数据库 + # 对于arcaea_datebase.db,更新best_score,friend,recent30,user,并用character数据更新user_char + # 对于arcsong.db,更新songs + if os.path.isfile("database/old_arcaea_database.db") and os.path.isfile("database/arcaea_database.db"): + conn1 = sqlite3.connect('./database/old_arcaea_database.db') + c1 = conn1.cursor() + conn2 = sqlite3.connect('./database/arcaea_database.db') + c2 = conn2.cursor() + + # user + c1.execute('''select * from user''') + x = c1.fetchall() + if x: + for i in x: + c2.execute( + '''select exists(select * from user where user_id=:a)''', {'a': i[0]}) + if c2.fetchone() == (0,): + c2.execute('''insert into user values(:a0,:a1,:a2,:a3,:a4,:a5,:a6,:a7,:a8,:a9,:a10,:a11,:a12,:a13,:a14,:a15,:a16,:a17,:a18,:a19,:a20,:a21,:a22,:a23,:a24)''', { + 'a0': i[0], 'a1': i[1], 'a2': i[2], 'a3': i[3], 'a4': i[4], 'a5': i[5], 'a6': i[6], 'a7': i[7], 'a8': i[8], 'a9': i[9], 'a10': i[10], 'a11': i[11], 'a12': i[12], 'a13': i[13], 'a14': i[14], 'a15': i[15], 'a16': i[16], 'a17': i[17], 'a18': i[18], 'a19': i[19], 'a20': i[20], 'a21': i[21], 'a22': i[22], 'a23': i[23], 'a24': i[24]}) + + # friend + c1.execute('''select * from friend''') + x = c1.fetchall() + if x: + for i in x: + c2.execute( + '''select exists(select * from friend where user_id_me=:a and user_id_other=:b)''', {'a': i[0], 'b': i[1]}) + if c2.fetchone() == (0,): + c2.execute('''insert into friend values(:a,:b)''', { + 'a': i[0], 'b': i[1]}) + + # best_score + c1.execute('''select * from best_score''') + x = c1.fetchall() + if x: + for i in x: + c2.execute('''select exists(select * from best_score where user_id=:a and song_id=:b and difficulty=:c)''', { + 'a': i[0], 'b': i[1], 'c': i[2]}) + if c2.fetchone() == (0,): + c2.execute('''insert into best_score values(:a0,:a1,:a2,:a3,:a4,:a5,:a6,:a7,:a8,:a9,:a10,:a11,:a12,:a13)''', { + 'a0': i[0], 'a1': i[1], 'a2': i[2], 'a3': i[3], 'a4': i[4], 'a5': i[5], 'a6': i[6], 'a7': i[7], 'a8': i[8], 'a9': i[9], 'a10': i[10], 'a11': i[11], 'a12': i[12], 'a13': i[13]}) + + # recent30 + c1.execute('''select * from recent30''') + x = c1.fetchall() + if x: + for i in x: + c2.execute( + '''select exists(select * from recent30 where user_id=:a)''', {'a': i[0]}) + if c2.fetchone() == (0,): + c2.execute('''insert into recent30 values(:a0,:a1,:a2,:a3,:a4,:a5,:a6,:a7,:a8,:a9,:a10,:a11,:a12,:a13,:a14,:a15,:a16,:a17,:a18,:a19,:a20,:a21,:a22,:a23,:a24,:a25,:a26,:a27,:a28,:a29,:a30,:a31,:a32,:a33,:a34,:a35,:a36,:a37,:a38,:a39,:a40,:a41,:a42,:a43,:a44,:a45,:a46,:a47,:a48,:a49,:a50,:a51,:a52,:a53,:a54,:a55,:a56,:a57,:a58,:a59,:a60)''', {'a0': i[0], 'a1': i[1], 'a2': i[2], 'a3': i[3], 'a4': i[4], 'a5': i[5], 'a6': i[6], 'a7': i[7], 'a8': i[8], 'a9': i[9], 'a10': i[10], 'a11': i[11], 'a12': i[12], 'a13': i[13], 'a14': i[14], 'a15': i[15], 'a16': i[16], 'a17': i[17], 'a18': i[ + 18], 'a19': i[19], 'a20': i[20], 'a21': i[21], 'a22': i[22], 'a23': i[23], 'a24': i[24], 'a25': i[25], 'a26': i[26], 'a27': i[27], 'a28': i[28], 'a29': i[29], 'a30': i[30], 'a31': i[31], 'a32': i[32], 'a33': i[33], 'a34': i[34], 'a35': i[35], 'a36': i[36], 'a37': i[37], 'a38': i[38], 'a39': i[39], 'a40': i[40], 'a41': i[41], 'a42': i[42], 'a43': i[43], 'a44': i[44], 'a45': i[45], 'a46': i[46], 'a47': i[47], 'a48': i[48], 'a49': i[49], 'a50': i[50], 'a51': i[51], 'a52': i[52], 'a53': i[53], 'a54': i[54], 'a55': i[55], 'a56': i[56], 'a57': i[57], 'a58': i[58], 'a59': i[59], 'a60': i[60]}) + + update_user_char(c2) + conn1.commit() + conn1.close() + conn2.commit() + conn2.close() + os.remove('database/old_arcaea_database.db') + + # songs + if os.path.isfile("database/old_arcsong.db") and os.path.isfile("database/arcsong.db"): + conn1 = sqlite3.connect('./database/old_arcsong.db') + c1 = conn1.cursor() + conn2 = sqlite3.connect('./database/arcsong.db') + c2 = conn2.cursor() + + c1.execute('''select * from songs''') + x = c1.fetchall() + if x: + for i in x: + c2.execute( + '''select exists(select * from songs where sid=:a)''', {'a': i[0]}) + if c2.fetchone() == (0,): + c2.execute('''insert into songs values(:a0,:a1,:a2,:a3,:a4,:a5,:a6,:a7,:a8,:a9,:a10,:a11,:a12,:a13,:a14,:a15,:a16,:a17,:a18,:a19,:a20,:a21,:a22,:a23,:a24,:a25,:a26,:a27)''', {'a0': i[0], 'a1': i[1], 'a2': i[2], 'a3': i[3], 'a4': i[4], 'a5': i[5], 'a6': i[6], 'a7': i[7], 'a8': i[ + 8], 'a9': i[9], 'a10': i[10], 'a11': i[11], 'a12': i[12], 'a13': i[13], 'a14': i[14], 'a15': i[15], 'a16': i[16], 'a17': i[17], 'a18': i[18], 'a19': i[19], 'a20': i[20], 'a21': i[21], 'a22': i[22], 'a23': i[23], 'a24': i[24], 'a25': i[25], 'a26': i[26], 'a27': i[27]}) + + conn1.commit() + conn1.close() + conn2.commit() + conn2.close() + os.remove('database/old_arcsong.db') diff --git a/v1.2/web/webscore.py b/v1.2/web/webscore.py new file mode 100644 index 0000000..cc99365 --- /dev/null +++ b/v1.2/web/webscore.py @@ -0,0 +1,104 @@ +import time + + +def get_user_score(c, user_id, limit=-1): + # 返回用户的所有歌曲数据,带排名,返回字典列表 + if limit >= 0: + c.execute('''select * from best_score where user_id =:a order by rating DESC limit :b''', + {'a': user_id, 'b': limit}) + else: + c.execute( + '''select * from best_score where user_id =:a order by rating DESC''', {'a': user_id}) + x = c.fetchall() + r = [] + if x: + rank = 0 + for i in x: + rank += 1 + r.append({ + "song_id": i[1], + "difficulty": i[2], + "score": i[3], + "shiny_perfect_count": i[4], + "perfect_count": i[5], + "near_count": i[6], + "miss_count": i[7], + "health": i[8], + "modifier": i[9], + "time_played": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(i[10])), + "best_clear_type": i[11], + "clear_type": i[12], + "rating": i[13], + "rank": rank + }) + + return r + + +def get_user(c, user_id): + # 得到user表部分用户信息,返回字典 + c.execute('''select * from user where user_id = :a''', {'a': user_id}) + x = c.fetchone() + r = None + if x: + join_date = None + time_played = None + if x[3]: + join_date = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(int(x[3])//1000)) + if x[20]: + time_played = time.strftime('%Y-%m-%d %H:%M:%S', + time.localtime(int(x[20])//1000)) + + r = {'name': x[1], + 'user_id': user_id, + 'join_date': join_date, + 'user_code': x[4], + 'rating_ptt': x[5], + 'song_id': x[11], + 'difficulty': x[12], + 'score': x[13], + 'shiny_perfect_count': x[14], + 'perfect_count': x[15], + 'near_count': x[16], + 'miss_count': x[17], + 'time_played': time_played, + 'clear_type': x[21], + 'rating': x[22] + } + + return r + + +def get_user_recent30(c, user_id): + # 获取玩家recent30信息并计算这一部分的ptt,返回字典列表和一个值 + c.execute('''select * from recent30 where user_id=:a''', {'a': user_id}) + sumr = 0 + x = c.fetchone() + r = [] + if x is not None: + r30 = [] + s30 = [] + for i in range(1, 61, 2): + if x[i] is not None: + r30.append(float(x[i])) + s30.append(x[i+1]) + else: + r30.append(0) + s30.append('') + r30, s30 = (list(t) for t in zip(*sorted(zip(r30, s30), reverse=True))) + songs = [] + i = 0 + while len(songs) < 10 and i <= 29 and s30[i] != '' and s30[i] is not None: + if s30[i] not in songs: + sumr += r30[i] + songs.append(s30[i]) + i += 1 + for i in range(0, 30): + if s30[i]: + r.append({ + 'song_id': s30[i][:-1], + 'difficulty': int(s30[i][-1]), + 'rating': r30[i] + }) + return r, sumr / 10