diff --git a/v1.2/database/arcaea_database.db b/v1.2/database/arcaea_database.db new file mode 100644 index 0000000..18b28cf Binary files /dev/null and b/v1.2/database/arcaea_database.db differ diff --git a/v1.2/database/arcsong.db b/v1.2/database/arcsong.db new file mode 100644 index 0000000..2a7629d Binary files /dev/null and b/v1.2/database/arcsong.db differ 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