Skip to content

Commit

Permalink
Merge pull request #73 from Lost-MSth/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
Lost-MSth authored Nov 5, 2022
2 parents 2a3ee0f + e6cb0f8 commit 65aed56
Show file tree
Hide file tree
Showing 42 changed files with 1,406 additions and 1,077 deletions.
17 changes: 16 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,20 @@
# log files
*.log
*.log.*

# SSL cert
*.pem
*.key

# sqlite3 database
*.db
*.db.bak*

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
*$py.class

# setting/config files
latest version/config/
latest version/config.py
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ 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.

### **2022.11.5**
**修复一长期BUG,影响到自2022.7.4以来所有版本(表现为数据库锁以及服务端运行缓慢),请拉取或下载`master`分支最新提交**

**Fix a long-term bug that affects all versions since Jul/4/2022 (shown as database lock and slow running). Please pull or download the latest commit from the `master` branch.**

### Version 2.10.0
- 适用于Arcaea 4.1.0版本 For Arcaea 4.1.0
- 新搭档 **咲姬** 已解锁 Unlock the character **Saki**.
Expand Down Expand Up @@ -98,8 +103,8 @@ It is just so interesting. What it can do is under exploration.

## 运行环境与依赖 Running environment and requirements
- Windows/Linux/Mac OS/Android...
- Python 3
- Flask module >= 2.0, Cryptography module >= 35.0.0
- Python >= 3.6
- Flask module >= 2.0, Cryptography module >= 3.0.0, limits >= 2.7.0
- Charles, IDA, proxy app... (optional)

<!--
Expand Down
38 changes: 22 additions & 16 deletions latest version/api/api_auth.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
from functools import wraps
from traceback import format_exc
from base64 import b64decode
from json import loads

from core.api_user import APIUser
from core.config_manager import Config
from core.error import ArcError, NoAccess, PostError
from core.sql import Connect
from flask import current_app
from setting import Config

from .api_code import error_return

Expand All @@ -25,20 +27,17 @@ def wrapped_view(*args, **kwargs):
return error_return(PostError('No token', api_error_code=-1), 401)

user = APIUser()
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
user.user_id = 0
elif powers == []:
# 无powers则非本地权限(API_TOKEN规定的)无法访问
return error_return(NoAccess('No permission', api_error_code=-1), 403)
else:
with Connect() as c:
with Connect() as c:
user.c = c
if Config.API_TOKEN == request.headers['Token'] and Config.API_TOKEN != '':
user.set_role_system()
else:
try:
user.c = c
user.select_user_id_from_api_token(
request.headers['Token'])
user.select_role_and_powers()

if not any([y in [x.power_name for x in user.role.powers] for y in powers]):
if not any(user.role.has_power(y) for y in powers):
return error_return(NoAccess('No permission', api_error_code=-1), 403)
except ArcError as e:
return error_return(e, 401)
Expand All @@ -63,17 +62,24 @@ def decorator(view):
def wrapped_view(*args, **kwargs):

data = {}
if not request.data:
return view(data, *args, **kwargs)
if request.data:
json_data = request.json
else:
if request.method == 'GET' and 'query' in request.args:
# 处理axios没法GET传data的问题
json_data = loads(
b64decode(request.args['query']).decode())
else:
return view(data, *args, **kwargs)

for key in required_keys:
if key not in request.json:
if key not in json_data:
return error_return(PostError('Missing parameter: ' + key, api_error_code=-100))
data[key] = request.json[key]
data[key] = json_data[key]

for key in optional_keys:
if key in request.json:
data[key] = request.json[key]
if key in json_data:
data[key] = json_data[key]

return view(data, *args, **kwargs)

Expand Down
1 change: 1 addition & 0 deletions latest version/api/api_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
-202: 'User is banned',
-203: 'Username exists',
-204: 'Email address exists',
-205: 'Too many login attempts',
-999: 'Unknown error'
}

Expand Down
2 changes: 1 addition & 1 deletion latest version/api/songs.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def songs_get(data, user):
B = ['song_id', 'name', 'rating_pst',
'rating_prs', 'rating_ftr', 'rating_byn']
with Connect() as c:
query = Query(A, A, B).from_data(data)
query = Query(A, A, B).from_dict(data)
x = Sql(c).select('chart', query=query)
r = []
for i in x:
Expand Down
38 changes: 30 additions & 8 deletions latest version/api/users.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from core.score import Potential, UserScoreList
from core.sql import Connect, Query, Sql
from core.user import UserInfo, UserRegister
from core.api_user import APIUser
from flask import Blueprint, request

from .api_auth import api_try, request_json_handle, role_required
Expand All @@ -15,7 +16,7 @@
@role_required(request, ['change'])
@request_json_handle(request, ['name', 'password', 'email'])
@api_try
def users_post(data, _):
def users_post(data, user):
'''注册一个用户'''
with Connect() as c:
new_user = UserRegister(c)
Expand All @@ -36,7 +37,7 @@ def users_get(data, user):
B = ['user_id', 'name', 'user_code', 'join_date',
'rating_ptt', 'time_played', 'ticket', 'world_rank_score']
with Connect() as c:
query = Query(A, A, B).from_data(data)
query = Query(A, A, B).from_dict(data)
x = Sql(c).select('user', query=query)
r = []
for i in x:
Expand All @@ -50,7 +51,7 @@ def users_get(data, user):
'name': x.name,
'join_date': x.join_date,
'user_code': x.user_code,
'rating_ptt': x.rating_ptt/100,
'rating_ptt': x.rating_ptt,
'character_id': x.character.character_id,
'is_char_uncapped': x.character.is_uncapped,
'is_char_uncapped_override': x.character.is_uncapped_override,
Expand All @@ -67,7 +68,7 @@ def users_user_get(user, user_id):
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
if user_id != user.user_id and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)

with Connect() as c:
Expand All @@ -83,7 +84,7 @@ def users_user_b30_get(user, user_id):
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
if user_id != user.user_id and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)

with Connect() as c:
Expand All @@ -105,12 +106,12 @@ def users_user_best_get(data, user, user_id):
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
if user_id != user.user_id and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)

with Connect() as c:
x = UserScoreList(c, UserInfo(c, user_id))
x.query.from_data(data)
x.query.from_dict(data)
x.select_from_user()
r = x.to_dict_list()
return success_return({'user_id': user_id, 'data': r})
Expand All @@ -125,9 +126,30 @@ def users_user_r30_get(user, user_id):
if user_id <= 0:
return error_return(InputError(api_error_code=-4))
# 查别人需要select权限
if user_id != user.user_id and user.user_id != 0 and not user.role.has_power('select'):
if user_id != user.user_id and not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)

with Connect() as c:
p = Potential(c, UserInfo(c, user_id))
return success_return({'user_id': user_id, 'r10_ptt': p.recent_10 / 10, 'data': p.recent_30_to_dict_list()})


@bp.route('/<int:user_id>/role', methods=['GET'])
@role_required(request, ['select', 'select_me'])
@api_try
def users_user_role_get(user, user_id):
'''查询用户role和powers'''

if user_id <= 0:
return error_return(InputError(api_error_code=-4))

if user_id == user.user_id:
return success_return({'user_id': user.user_id, 'role': user.role.role_id, 'powers': [i.power_id for i in user.role.powers]})
# 查别人需要select权限
if not user.role.has_power('select'):
return error_return(NoAccess('No permission', api_error_code=-1), 403)

with Connect() as c:
x = APIUser(c, user_id)
x.select_role_and_powers()
return success_return({'user_id': x.user_id, 'role': x.role.role_id, 'powers': [i.power_id for i in x.role.powers]})
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
class Config():
'''
This is the setting file. You can change some parameters here.
This is the example setting file.
The user's setting file's name is `config.py`.
'''

'''
Expand All @@ -9,7 +10,7 @@ class Config():
Host and port of your server
'''
HOST = '0.0.0.0'
PORT = '80'
PORT = 80
'''
--------------------
'''
Expand Down
45 changes: 24 additions & 21 deletions latest version/core/api_user.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,49 @@
from os import urandom
from time import time

from .error import NoAccess, NoData, UserBan
from .config_manager import Config
from .error import NoAccess, NoData, RateLimit, UserBan
from .limiter import ArcLimiter
from .user import UserOnline


class Power:
def __init__(self, c=None):
self.c = c
self.power_id: int = None
self.power_name: str = None
self.power_id: str = None
self.caption: str = None

@classmethod
def from_dict(cls, d: dict, c=None) -> 'Power':
p = cls(c)
p.power_id = d['power_id']
p.power_name = d['power_name']
p.caption = d['caption']
return p

def select_from_name(self, power_name: str) -> 'Power':
pass


class Role:
def __init__(self, c=None):
self.c = c
self.role_id: int = None
self.role_name: str = None
self.role_id: str = None
self.caption: str = None

self.powers: list = None

def has_power(self, power_name: str) -> bool:
def has_power(self, power_id: str) -> bool:
'''判断role是否有power'''
for i in self.powers:
if i.power_name == power_name:
return True
return False
return any(power_id == i.power_id for i in self.powers)

def select_from_id(self, role_id: int = None) -> 'Role':
'''用role_id查询role'''
if role_id is not None:
self.role_id = role_id
self.c.execute('''select role_name, caption from role where role_id = :a''',
self.c.execute('''select caption from role where role_id = :a''',
{'a': self.role_id})
x = self.c.fetchone()
if x is None:
raise NoData('The role `%s` does not exist.' %
self.role_id, api_error_code=-200)
self.role_name = x[0]
self.caption = x[1]
self.caption = x[0]
return self

def select_powers(self) -> None:
Expand All @@ -63,17 +55,26 @@ def select_powers(self) -> None:
x = self.c.fetchall()
for i in x:
self.powers.append(Power.from_dict(
{'power_id': i[0], 'power_name': i[1], 'caption': i[2]}, self.c))
{'power_id': i[0], 'caption': i[1]}, self.c))


class APIUser(UserOnline):
limiter = ArcLimiter(Config.API_LOGIN_RATE_LIMIT, 'api_login')

def __init__(self, c=None, user_id=None) -> None:
super().__init__(c, user_id)
self.api_token: str = None
self.role: 'Role' = None

self.ip: str = None

def set_role_system(self) -> None:
'''设置为最高权限用户,API接口'''
self.user_id = 0
self.role = Role(self.c)
self.role.role_id = 'system'
self.role.select_powers()

def select_role(self) -> None:
'''查询user的role'''
self.c.execute('''select role_id from user_role where user_id = :a''',
Expand All @@ -82,10 +83,9 @@ def select_role(self) -> None:
self.role = Role(self.c)
if x is None:
# 默认role为user
self.role.role_id = 1
self.role.role_id = 'user'
else:
self.role.role_id = int(x[0])
self.role.select_from_id()
self.role.role_id = x[0]

def select_role_and_powers(self) -> None:
'''查询user的role,以及role的powers'''
Expand Down Expand Up @@ -113,6 +113,9 @@ def login(self, name: str = None, password: str = None, ip: str = None) -> None:
self.password = password
if ip is not None:
self.ip = ip
if not self.limiter.hit(name):
raise RateLimit('Too many login attempts', api_error_code=-205)

self.c.execute('''select user_id, password from user where name = :a''', {
'a': self.name})
x = self.c.fetchone()
Expand Down
4 changes: 2 additions & 2 deletions latest version/core/character.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from setting import Config
from .error import ArcError, InputError, NoData, ItemNotEnough
from .config_manager import Config
from .constant import Constant
from .error import ArcError, InputError, ItemNotEnough, NoData
from .item import Item, ItemCore


Expand Down
Loading

0 comments on commit 65aed56

Please sign in to comment.