mirror of
https://github.com/Lost-MSth/Arcaea-server.git
synced 2026-02-16 13:37:26 +08:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df181f104e | ||
|
|
a890a9af4a |
26
README.md
26
README.md
@@ -2,14 +2,21 @@
|
||||
一个微型的Arcaea本地服务器 A small local server for Arcaea
|
||||
|
||||
## 简介 Introduction
|
||||
这是基于Python以及Flask的微型本地Arcaea服务器,可以模拟游戏的主要功能,一时兴起在五天之内糊了出来。这可能是我第一次写这种大程序,若有不妥之处,敬请谅解。
|
||||
This is a small local Arcaea server based on Python and Flask, which can simulate the main functions of the game. I completed it on the spur of the moment in five days. This may be the first time I have written such a large program. Please understand if there is something wrong with it.
|
||||
这是基于Python以及Flask的微型本地Arcaea服务器,可以模拟游戏的主要功能。这可能是我第一次写这种大程序,若有不妥之处,敬请谅解。
|
||||
|
||||
本程序主要用于学习研究,不得用于任何商业行为,否则后果自负,这不是强制要求,只是一个提醒与警告。
|
||||
|
||||
This is a small local Arcaea server based on Python and Flask, which can simulate the main functions of the game. This may be the first time I have written such a large program. Please understand if there is something wrong with it.
|
||||
|
||||
This procedure is mainly used for study and research, and shall not be used for any commercial activities, otherwise the consequences will be borne by oneself. This is not a mandatory requirement, just a reminder and warning.
|
||||
|
||||
> 虽然看起来很蠢,但是可以用!
|
||||
> It looks stupid, but it works!
|
||||
|
||||
## 特性 Features
|
||||
有以下 We have:
|
||||
- 登录、注册 Login and registration
|
||||
- 多设备登录 Multi device login
|
||||
- 成绩上传 Score upload
|
||||
- PTT
|
||||
- 排名 Rank
|
||||
@@ -37,6 +44,7 @@ This is a small local Arcaea server based on Python and Flask, which can simulat
|
||||
可能有问题 There may be problems:
|
||||
- Recent 30
|
||||
- 一些歌曲的解锁 Some songs' unlocking
|
||||
- 同设备多共存登录 Multiple app logins on the same device
|
||||
|
||||
## 说明 Statement
|
||||
只是很有趣,用处探索中。
|
||||
@@ -55,13 +63,17 @@ It is just so interesting. What it can do is under exploration.
|
||||
>
|
||||
> Tips: When updating, please keep the original database in case of data loss.
|
||||
|
||||
### Version 2.1
|
||||
- 适用于Arcaea 3.5.0版本 For Arcaea 3.5.0
|
||||
### Version 2.2
|
||||
- 适用于Arcaea 3.5.1版本 For Arcaea 3.5.1
|
||||
- 更新了歌曲数据库 Update the song database.
|
||||
- 新搭档**阿莱乌斯**、**希尔**、**伊莎贝尔**已解锁 Unlock the new characters **Areus**, **Seele** and **Isabelle**.
|
||||
- 源韵强化机制修改,现在源点会正常扣去 The mechanism of Memory Boost has been modified. Now the memories will be decreased normally.
|
||||
- 新增多设备登录 Add multi device login.
|
||||
- 修改了配置文件结构 The configuration file structure has been modified.
|
||||
- 优化了代码结构 Optimize the code structure.
|
||||
- 修复了一些Bug Fix some bugs.
|
||||
- 修复了一堆Bug Fix many bugs.
|
||||
|
||||
> 提醒:本次更新需重新登录
|
||||
>
|
||||
> Tip: You need to log in Arcaea again in this update.
|
||||
|
||||
|
||||
## 运行环境与依赖 Running environment and requirements
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -37,10 +37,11 @@ ticket int,
|
||||
prog_boost int
|
||||
);''')
|
||||
c.execute('''create table if not exists login(access_token text,
|
||||
user_id int primary key,
|
||||
last_login_time int,
|
||||
last_login_ip text,
|
||||
last_login_device text
|
||||
user_id int,
|
||||
login_time int,
|
||||
login_ip text,
|
||||
login_device text,
|
||||
primary key(access_token, user_id)
|
||||
);''')
|
||||
c.execute('''create table if not exists friend(user_id_me int,
|
||||
user_id_other int,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# encoding: utf-8
|
||||
|
||||
from flask import Flask, request, jsonify, make_response, send_from_directory
|
||||
from logging.config import dictConfig
|
||||
import configparser
|
||||
from setting import Config
|
||||
import base64
|
||||
import server.auth
|
||||
import server.info
|
||||
@@ -87,32 +89,29 @@ def login():
|
||||
id_pwd = headers['Authorization']
|
||||
id_pwd = base64.b64decode(id_pwd[6:]).decode()
|
||||
name, password = id_pwd.split(':', 1)
|
||||
try:
|
||||
token, error_code = server.auth.arc_login(name, password)
|
||||
if not error_code:
|
||||
r = {"success": True, "token_type": "Bearer"}
|
||||
r['access_token'] = token
|
||||
return jsonify(r)
|
||||
else:
|
||||
return error_return(error_code)
|
||||
except:
|
||||
return error_return(108)
|
||||
device_id = headers['DeviceId']
|
||||
token, error_code = server.auth.arc_login(name, password, device_id)
|
||||
if not error_code:
|
||||
r = {"success": True, "token_type": "Bearer"}
|
||||
r['access_token'] = token
|
||||
return jsonify(r)
|
||||
else:
|
||||
return error_return(error_code)
|
||||
|
||||
|
||||
@app.route('/latte/13/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)
|
||||
device_id = request.form['device_id']
|
||||
user_id, token, error_code = server.auth.arc_register(
|
||||
name, password, device_id)
|
||||
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,用户名被占用,毕竟电子邮箱没记录
|
||||
|
||||
|
||||
# 集成式请求,没想到什么好办法处理,就先这样写着
|
||||
@@ -149,7 +148,9 @@ def character_change(user_id):
|
||||
@app.route('/latte/<path:path>/toggle_uncap', methods=['POST']) # 角色觉醒切换
|
||||
@server.auth.auth_required(request)
|
||||
def character_uncap(user_id, path):
|
||||
character_id = int(path[22:])
|
||||
while '//' in path:
|
||||
path = path.replace('//', '/')
|
||||
character_id = int(path[21:])
|
||||
r = server.setme.change_char_uncap(user_id, character_id)
|
||||
if r is not None:
|
||||
return jsonify({
|
||||
@@ -280,7 +281,7 @@ def song_score_post(user_id):
|
||||
|
||||
r, re = 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:
|
||||
if r is not None:
|
||||
if re:
|
||||
return jsonify({
|
||||
"success": True,
|
||||
@@ -512,12 +513,7 @@ def sys_set(user_id, path):
|
||||
|
||||
|
||||
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.config.from_mapping(SECRET_KEY=Config.SECRET_KEY)
|
||||
app.register_blueprint(web.login.bp)
|
||||
app.register_blueprint(web.index.bp)
|
||||
|
||||
@@ -541,7 +537,7 @@ def main():
|
||||
else:
|
||||
app.logger.info('Complete!')
|
||||
|
||||
app.run(HOST, PORT)
|
||||
app.run(Config.HOST, Config.PORT)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -1 +1 @@
|
||||
python main.py
|
||||
python -B main.py
|
||||
@@ -4,9 +4,10 @@ from flask import url_for
|
||||
import sqlite3
|
||||
from server.sql import Connect
|
||||
import time
|
||||
from setting import Config
|
||||
|
||||
time_limit = 3000 # 每个玩家24小时下载次数限制
|
||||
time_gap_limit = 1000 # 下载链接有效秒数
|
||||
time_limit = Config.DOWNLOAD_TIMES_LIMIT # 每个玩家24小时下载次数限制
|
||||
time_gap_limit = Config.DOWNLOAD_TIME_GAP_LIMIT # 下载链接有效秒数
|
||||
|
||||
|
||||
def get_file_md5(file_path):
|
||||
|
||||
@@ -130,8 +130,6 @@ def arc_score_me(user_id, song_id, difficulty, limit=20):
|
||||
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''', {
|
||||
@@ -364,7 +362,7 @@ def update_recent30(c, user_id, song_id, rating, is_protected):
|
||||
|
||||
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,和世界模式变化
|
||||
ptt = 0
|
||||
ptt = None
|
||||
re = None
|
||||
with Connect() as c:
|
||||
rating = get_one_ptt(song_id, difficulty, score)
|
||||
|
||||
@@ -3,9 +3,10 @@ import time
|
||||
import server.arcworld
|
||||
from server.sql import Connect
|
||||
import functools
|
||||
from setting import Config
|
||||
|
||||
|
||||
def arc_login(name: str, password: str) -> str: # 登录判断
|
||||
def arc_login(name: str, password: str, device_id: str): # 登录判断
|
||||
# 查询数据库中的user表,验证账号密码,返回并记录token,多返回个error code
|
||||
# token采用user_id和时间戳连接后hash生成(真的是瞎想的,没用bear)
|
||||
# 密码和token的加密方式为 SHA-256
|
||||
@@ -27,14 +28,32 @@ def arc_login(name: str, password: str) -> str: # 登录判断
|
||||
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})
|
||||
'''select login_device from login where user_id = :user_id''', {"user_id": user_id})
|
||||
y = c.fetchall()
|
||||
if y:
|
||||
device_list = []
|
||||
for i in y:
|
||||
if i[0]:
|
||||
device_list.append(i[0])
|
||||
else:
|
||||
device_list.append('')
|
||||
|
||||
if c.fetchone() == (1,): # 删掉多余token
|
||||
c.execute('''delete from login where user_id = :user_id''',
|
||||
{'user_id': user_id})
|
||||
should_delete_num = len(
|
||||
device_list) + 1 - Config.LOGIN_DEVICE_NUMBER_LIMIT
|
||||
|
||||
c.execute('''insert into login(access_token, user_id) values(:access_token, :user_id)''', {
|
||||
'user_id': user_id, 'access_token': token})
|
||||
if not Config.ALLOW_LOGIN_SAME_DEVICE:
|
||||
if device_id in device_list: # 对相同设备进行删除
|
||||
c.execute('''delete from login where login_device=:a''', {
|
||||
'a': device_id})
|
||||
should_delete_num = len(
|
||||
device_list) + 1 - device_list.count(device_id) - Config.LOGIN_DEVICE_NUMBER_LIMIT
|
||||
|
||||
if should_delete_num >= 1: # 删掉多余token
|
||||
c.execute('''delete from login where rowid in (select rowid from login where user_id=:user_id limit :a);''',
|
||||
{'user_id': user_id, 'a': int(should_delete_num)})
|
||||
|
||||
c.execute('''insert into login(access_token, user_id, login_device) values(:access_token, :user_id, :device_id)''', {
|
||||
'user_id': user_id, 'access_token': token, 'device_id': device_id})
|
||||
error_code = None
|
||||
else:
|
||||
# 密码错误
|
||||
@@ -46,7 +65,7 @@ def arc_login(name: str, password: str) -> str: # 登录判断
|
||||
return token, error_code
|
||||
|
||||
|
||||
def arc_register(name: str, password: str): # 注册
|
||||
def arc_register(name: str, password: str, device_id: str): # 注册
|
||||
# 账号注册,只记录hash密码和用户名,生成user_id和user_code,自动登录返回token
|
||||
# token和密码的处理同登录部分
|
||||
|
||||
@@ -100,8 +119,8 @@ def arc_register(name: str, password: str): # 注册
|
||||
|
||||
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})
|
||||
c.execute('''insert into login(access_token, user_id, login_device) values(:access_token, :user_id, :device_id)''', {
|
||||
'user_id': user_id, 'access_token': token, 'device_id': device_id})
|
||||
|
||||
insert_user_char(c, user_id)
|
||||
error_code = 0
|
||||
|
||||
@@ -197,7 +197,7 @@ def get_value_0(c, user_id):
|
||||
"max_stamina_ts": 1586274871917,
|
||||
"stamina": 12,
|
||||
"world_unlocks": ["scenery_chap1", "scenery_chap2", "scenery_chap3", "scenery_chap4", "scenery_chap5"],
|
||||
"world_songs": ["babaroque", "shadesoflight", "kanagawa", "lucifer", "anokumene", "ignotus", "rabbitintheblackroom", "qualia", "redandblue", "bookmaker", "darakunosono", "espebranch", "blacklotus", "givemeanightmare", "vividtheory", "onefr", "gekka", "vexaria3", "infinityheaven3", "fairytale3", "goodtek3", "suomi", "rugie", "faintlight", "harutopia", "goodtek", "dreaminattraction", "syro", "diode", "freefall", "grimheart", "blaster", "cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow"],
|
||||
"world_songs": ["babaroque", "shadesoflight", "kanagawa", "lucifer", "anokumene", "ignotus", "rabbitintheblackroom", "qualia", "redandblue", "bookmaker", "darakunosono", "espebranch", "blacklotus", "givemeanightmare", "vividtheory", "onefr", "gekka", "vexaria3", "infinityheaven3", "fairytale3", "goodtek3", "suomi", "rugie", "faintlight", "harutopia", "goodtek", "dreaminattraction", "syro", "diode", "freefall", "grimheart", "blaster", "cyberneciacatharsis", "monochromeprincess", "revixy", "vector", "supernova", "nhelv", "purgatorium3", "dement3", "crossover", "guardina", "axiumcrisis", "worldvanquisher", "sheriruth", "pragmatism", "gloryroad", "etherstrike", "corpssansorganes", "lostdesire", "blrink", "essenceoftwilight", "lapis", "solitarydream", "lumia3", "purpleverse", "moonheart3", "glow", "enchantedlove", "take"],
|
||||
"singles": get_user_singles(c, user_id), # ["dataerror", "yourvoiceso", "crosssoul", "impurebird", "auxesia", "modelista", "yozakurafubuki", "surrender", "metallicpunisher", "carminescythe", "bethere", "callmyname", "fallensquare", "dropdead", "alexandrite", "astraltale", "phantasia", "empireofwinter", "libertas", "dottodot", "dreadnought", "mirzam", "heavenlycaress", "filament", "avantraze", "battlenoone", "saikyostronger", "izana", "einherjar", "laqryma", "amygdata", "altale", "feelssoright", "scarletcage", "teriqma", "mahoroba", "badtek", "maliciousmischance", "buchigireberserker", "galaxyfriends", "xeraphinite", "xanatos"]
|
||||
"packs": get_user_packs(c, user_id),
|
||||
# ["vs", "extend", "dynamix", "prelude", "core", "yugamu", "omatsuri", "zettai", "mirai", "shiawase", "chunithm", "nijuusei", "groovecoaster", "rei", "tonesphere", "lanota"]
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
[CONFIG]
|
||||
HOST = 192.168.1.113
|
||||
PORT = 80
|
||||
|
||||
[WEB]
|
||||
USERNAME = admin
|
||||
PASSWORD = admin
|
||||
67
latest version/setting.py
Normal file
67
latest version/setting.py
Normal file
@@ -0,0 +1,67 @@
|
||||
class Config():
|
||||
'''
|
||||
This is the setting file. You can change some parameters here.
|
||||
'''
|
||||
|
||||
'''
|
||||
--------------------
|
||||
主机的地址和端口号
|
||||
Host and port of your server
|
||||
'''
|
||||
HOST = '192.168.1.101'
|
||||
PORT = '80'
|
||||
'''
|
||||
--------------------
|
||||
'''
|
||||
|
||||
'''
|
||||
--------------------
|
||||
Web后台管理页面的用户名和密码
|
||||
Username and password of web background management page
|
||||
'''
|
||||
USERNAME = 'admin'
|
||||
PASSWORD = 'admin'
|
||||
'''
|
||||
--------------------
|
||||
'''
|
||||
|
||||
'''
|
||||
--------------------
|
||||
Web后台管理页面的session秘钥,如果不知道是什么,请不要修改
|
||||
Session key of web background management page
|
||||
If you don't know what it is, please don't modify it.
|
||||
'''
|
||||
SECRET_KEY = '1145141919810'
|
||||
'''
|
||||
--------------------
|
||||
'''
|
||||
|
||||
'''
|
||||
--------------------
|
||||
玩家歌曲下载的24小时次数限制
|
||||
Player's song download limit times in 24 hours
|
||||
'''
|
||||
DOWNLOAD_TIMES_LIMIT = 3000
|
||||
'''
|
||||
歌曲下载链接的有效时长,单位:秒
|
||||
Effective duration of song download link, unit: seconds
|
||||
'''
|
||||
DOWNLOAD_TIME_GAP_LIMIT = 1000
|
||||
'''
|
||||
--------------------
|
||||
'''
|
||||
|
||||
'''
|
||||
--------------------
|
||||
Arcaea登录的最大允许设备数量,最小值为1
|
||||
The maximum number of devices allowed to log in Arcaea, minimum: 1
|
||||
'''
|
||||
LOGIN_DEVICE_NUMBER_LIMIT = 1
|
||||
'''
|
||||
是否允许同设备多应用共存登录
|
||||
If multiple applications on the same device to log in is allowed
|
||||
'''
|
||||
ALLOW_LOGIN_SAME_DEVICE = False
|
||||
'''
|
||||
--------------------
|
||||
'''
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
from flask import (Blueprint, flash, g, redirect,
|
||||
render_template, request, session, url_for)
|
||||
import functools
|
||||
import configparser
|
||||
from setting import Config
|
||||
|
||||
bp = Blueprint('login', __name__, url_prefix='/web')
|
||||
|
||||
@@ -15,18 +15,12 @@ def login():
|
||||
password = request.form['password']
|
||||
error = None
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
path = r'setting.ini'
|
||||
config.read(path, encoding="utf-8")
|
||||
USERNAME = config.get('WEB', 'USERNAME')
|
||||
PASSWORD = config.get('WEB', 'PASSWORD')
|
||||
|
||||
if username != USERNAME and password != PASSWORD:
|
||||
if username != Config.USERNAME or password != Config.PASSWORD:
|
||||
error = '错误的用户名或密码 Incorrect username or password.'
|
||||
|
||||
if error is None:
|
||||
session.clear()
|
||||
session['user_id'] = USERNAME + PASSWORD
|
||||
session['user_id'] = Config.USERNAME + Config.PASSWORD
|
||||
return redirect(url_for('index.index'))
|
||||
|
||||
flash(error)
|
||||
@@ -48,16 +42,10 @@ def login_required(view):
|
||||
def wrapped_view(**kwargs):
|
||||
x = session.get('user_id')
|
||||
|
||||
config = configparser.ConfigParser()
|
||||
path = r'setting.ini'
|
||||
config.read(path, encoding="utf-8")
|
||||
USERNAME = config.get('WEB', 'USERNAME')
|
||||
PASSWORD = config.get('WEB', 'PASSWORD')
|
||||
|
||||
if x != USERNAME + PASSWORD:
|
||||
if x != Config.USERNAME + Config.PASSWORD:
|
||||
return redirect(url_for('login.login'))
|
||||
|
||||
g.user = {'user_id': x, 'username': USERNAME}
|
||||
g.user = {'user_id': x, 'username': Config.USERNAME}
|
||||
return view(**kwargs)
|
||||
|
||||
return wrapped_view
|
||||
|
||||
Reference in New Issue
Block a user