From 40c6d84a0f182794b657e6bed53c88adfa12cb2c Mon Sep 17 00:00:00 2001 From: q1139 <1139168548@qq.com> Date: Thu, 28 Dec 2023 00:13:26 +0800 Subject: [PATCH 1/9] fix: session token invalid way --- main/main.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/main/main.py b/main/main.py index fee483e..bf60345 100644 --- a/main/main.py +++ b/main/main.py @@ -152,10 +152,16 @@ def refresh(user_id): try: access_token_result = login_tools.get_access_token(user['session_token']) except Exception as e: + # session token过期,重新登录 logger.error(e) - raise e + try: + access_token_result = login_tools.login(user['email'], user['password']) + except Exception as e: + logger.error(e) + raise e access_token = access_token_result['access_token'] session_token = access_token_result['session_token'] + # 更新session_token 和 access_token g.db.execute( f"UPDATE users SET session_token = '{session_token}',access_token = '{access_token}',update_time = '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}' WHERE id = {user['id']}") From 0d23bd87e420208dfeddd52a98ef62431275293d Mon Sep 17 00:00:00 2001 From: q1139 <1139168548@qq.com> Date: Thu, 28 Dec 2023 23:19:25 +0800 Subject: [PATCH 2/9] refactor: use sqlalchemy --- app.py | 61 ++++-------------------- login_tools.py | 2 + main/main.py | 124 ++++++++++++++++++++++++------------------------- model.py | 19 ++++++++ 4 files changed, 90 insertions(+), 116 deletions(-) create mode 100644 model.py diff --git a/app.py b/app.py index a843591..0c01ad0 100644 --- a/app.py +++ b/app.py @@ -41,57 +41,6 @@ def context_api_prefix(): return dict(api_prefix=app.config['proxy_api_prefix']) -def init_db(): - with app.app_context(): - db = connect_db() - db.execute(''' - create table if not exists users - ( - id INTEGER - primary key autoincrement, - email TEXT not null, - password TEXT not null, - session_token TEXT, - access_token TEXT, - share_list TEXT, - create_time datetime, - update_time datetime, - shared INT default 0 - ) - ''') - db.commit() - - -def connect_db(): - db = getattr(g, '_database', None) - if db is None: - db = g._database = sqlite3.connect(os.path.join(app.config['pandora_path'], DATABASE)) - db.row_factory = sqlite3.Row - return db - - -def query_db(query, args=(), one=False): - # 查看g对象是否存在db属性,如果不存在则创建 - if not hasattr(g, 'db'): - g.db = connect_db() - cur = g.db.execute(query, args) - rv = [dict((cur.description[idx][0], value) - for idx, value in enumerate(row)) for row in cur.fetchall()] - return (rv[0] if rv else None) if one else rv - - -@app.before_request -def before_request(): - g.db = connect_db() - - -@app.after_request -def after_request(result): - if hasattr(g, 'db'): - g.db.close() - return result - - def check_require_config(): PANDORA_NEXT_PATH = os.getenv('PANDORA_NEXT_PATH') # 如果PANDORA_NEXT_PATH 为空则检查/data下是否存在config.json @@ -148,11 +97,11 @@ def check_require_config(): from auth import auth from main import main +from model import db check_require_config() -init_db() -#scheduler jobstore +# scheduler jobstore app.config['SCHEDULER_JOBSTORES'] = { 'default': SQLAlchemyJobStore(url='sqlite:///' + os.path.join(app.config['pandora_path'], DATABASE)) } @@ -160,6 +109,10 @@ def check_require_config(): scheduler.init_app(app) scheduler.start() +app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(app.config['pandora_path'], DATABASE) +app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True +db.init_app(app) + def create_app(): app.register_blueprint(auth.auth_bp, url_prefix='/' + app.config['proxy_api_prefix']) @@ -168,6 +121,8 @@ def create_app(): import logging logging.basicConfig() logging.getLogger('apscheduler').setLevel(logging.DEBUG) + with app.app_context(): + db.create_all() return app diff --git a/login_tools.py b/login_tools.py index 87a3e5c..5826f00 100644 --- a/login_tools.py +++ b/login_tools.py @@ -20,6 +20,8 @@ def login(username, password): } response = requests.request("POST", host + "/api/auth/login", headers=headers, data=payload) if response.status_code != 200: + if response.status_code == 404: + raise Exception("接口不存在,请检查Pandora是否配置正确") raise Exception(response.text) logger.info("登录结果:{}", response.json()) return response.json() diff --git a/main/main.py b/main/main.py index bf60345..cace34f 100644 --- a/main/main.py +++ b/main/main.py @@ -2,13 +2,14 @@ from datetime import datetime from loguru import logger -from flask import Blueprint, render_template, request, redirect, url_for, g, flash, jsonify +from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from flask_login import login_required +from app import db import login_tools import share_tools import pandora_tools - +from model import User main_bp = Blueprint('main', __name__) @@ -16,13 +17,12 @@ @main_bp.route('/manage-users') @login_required def manage_users(): - from app import query_db,scheduler - users = query_db("select * from users") + from app import scheduler + users = db.session.query(User).all() # 将share_list转换为json对象 for user in users: - user['share_list'] = json.loads(user['share_list']) + user.share_list = json.loads(user.share_list) job_started = scheduler.get_job(id='my_job') is not None - return render_template('manage_users.html', users=users, job_started=job_started, balance=pandora_tools.get_balance()) @@ -36,45 +36,44 @@ def add_user(): shared = 1 if request.form['shared'] == 'on' else 0 else: shared = 0 - g.db.execute('insert into users (email, password, shared, share_list, create_time, update_time) values (?, ?, ?, ' - '?, ?, ?)', - [email, password, shared, '[]', datetime.now().strftime("%Y-%m-%d %H:%M:%S"), - datetime.now().strftime("%Y-%m-%d %H:%M:%S")]) - g.db.commit() + db.session.add(User(email=email, password=password, shared=shared, + share_list='[]', + create_time=datetime.now(), + update_time=datetime.now())) + db.session.commit() return redirect(url_for('main.manage_users')) @main_bp.route('/delete-user/') @login_required def delete_user(user_id): - g.db.execute('delete from users where id = ?', [user_id]) - g.db.commit() + db.session.query(User).filter_by(id=user_id).delete() + db.session.commit() return redirect(url_for('main.manage_users')) @main_bp.route('/add-share', methods=['POST']) @login_required def add_share(): - from app import query_db user_id = request.form.get('user_id') unique_name = request.form.get('unique_name') password = request.form.get('password') - user = query_db('select * from users where id = ?', one=True, args=(user_id,)) + user = db.session.query(User).filter_by(id=user_id).first() try: - share_token = share_tools.get_share_token(user['access_token'], unique_name) + share_token = share_tools.get_share_token(user.access_token, unique_name) except Exception as e: flash('添加失败: ' + str(e), 'error') return redirect(url_for('main.manage_users')) - share_list = json.loads(user['share_list']) + share_list = json.loads(user.share_list) # 检查是否已经存在 for share in share_list: if share['unique_name'] == unique_name: # 删除原有的share_token share_list.remove(share) share_list.append({'unique_name': unique_name, 'password': password, 'share_token': share_token['token_key']}) - g.db.execute( - f"UPDATE users SET share_list = '{json.dumps(share_list)}',update_time = '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}' WHERE id = {user_id}") - g.db.commit() + db.session.query(User).filter_by(id=user_id).update( + {'share_list': json.dumps(share_list), 'update_time': datetime.now()}) + db.session.commit() flash('添加成功', 'success') return redirect(url_for('main.manage_users')) @@ -82,17 +81,17 @@ def add_share(): @main_bp.route('/delete-share//') @login_required def delete_share(user_id, unique_name): - from app import query_db - user = query_db('select * from users where id = ?', one=True, args=(user_id,)) - share_list = json.loads(user['share_list']) + # user = query_db('select * from users where id = ?', one=True, args=(user_id,)) + user = db.session.query(User).filter_by(id=user_id).first() + share_list = json.loads(user.share_list) for share in share_list: if share['unique_name'] == unique_name: share_list.remove(share) break - g.db.execute( - f"UPDATE users SET share_list = '{json.dumps(share_list)}',update_time = '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}' WHERE id = {user_id}") - g.db.commit() - share_tools.get_share_token(user['access_token'], unique_name, expires_in=-1) + db.session.query(User).filter_by(id=user_id).update( + {'share_list': json.dumps(share_list), 'update_time': datetime.now()}) + db.session.commit() + share_tools.get_share_token(user.access_token, unique_name, expires_in=-1) flash('删除成功', 'success') return redirect(url_for('main.manage_users')) @@ -100,15 +99,15 @@ def delete_share(user_id, unique_name): # 获取ShareToken用量信息 @main_bp.route('/share-info/') def share_info(user_id): - from app import query_db - user = query_db('select * from users where id = ?', one=True, args=(user_id,)) - if user['access_token'] is None: + # user = query_db('select * from users where id = ?', one=True, args=(user_id,)) + user = db.session.query(User).filter_by(id=user_id).first() + if user.access_token is None: return jsonify({'code': 500, 'msg': '请先刷新'}) - share_list = json.loads(user['share_list']) + share_list = json.loads(user.share_list) dims = [] sources = [] for share in share_list: - info = share_tools.get_share_token_info(share['share_token'], user['access_token']) + info = share_tools.get_share_token_info(share['share_token'], user.access_token) if 'usage' in info: if 'range' in info['usage']: # 删除range键值对 @@ -133,15 +132,14 @@ def share_info(user_id): def refresh(user_id): - from app import query_db - user = query_db('select * from users where id = ?', one=True, args=(user_id,)) + user = db.session.query(User).filter_by(id=user_id).first() if user is None: return redirect(url_for('main.manage_users')) # 获取access_token - if user['session_token'] is None: + if user.session_token is None: # 登录获取session_token 扣额度 try: - login_result = login_tools.login(user['email'], user['password']) + login_result = login_tools.login(user.email, user.password) except Exception as e: logger.error(e) raise e @@ -150,12 +148,12 @@ def refresh(user_id): else: # 使用session_token 刷新access_token try: - access_token_result = login_tools.get_access_token(user['session_token']) + access_token_result = login_tools.get_access_token(user.session_token) except Exception as e: # session token过期,重新登录 logger.error(e) try: - access_token_result = login_tools.login(user['email'], user['password']) + access_token_result = login_tools.login(user.email, user.password) except Exception as e: logger.error(e) raise e @@ -163,11 +161,12 @@ def refresh(user_id): session_token = access_token_result['session_token'] # 更新session_token 和 access_token - g.db.execute( - f"UPDATE users SET session_token = '{session_token}',access_token = '{access_token}',update_time = '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}' WHERE id = {user['id']}") - g.db.commit() + db.session.query(User).filter_by(id=user_id).update( + {'session_token': session_token, 'access_token': access_token, + 'update_time': datetime.now()}) + db.session.commit() # 刷新share_token - share_list = json.loads(user['share_list']) + share_list = json.loads(user.share_list) if len(share_list) == 0: # 没有share token,直接返回 return redirect(url_for('main.manage_users')) @@ -180,30 +179,30 @@ def refresh(user_id): raise e share['share_token'] = share_token['token_key'] # 更新share_list - g.db.execute( - f"UPDATE users SET share_list = '{json.dumps(share_list)}',update_time = '{datetime.now().strftime('%Y-%m-%d %H:%M:%S')}' WHERE id = {user['id']}") - g.db.commit() + db.session.query(User).filter_by(id=user_id).update( + {'share_list': json.dumps(share_list), 'update_time': datetime.now()}) + db.session.commit() + return redirect(url_for('main.manage_users')) def refresh_all_user(): - from app import scheduler, app + from app import scheduler flag = False with scheduler.app.app_context(): - from app import query_db - users = query_db('select * from users') + users = db.session.query(User).all() for user in users: try: # jwt解析access_token 检查access_token是否过期 - if 'access_token' not in user or user['access_token'] is None: + if user.access_token is None: continue else: - token_info = pandora_tools.get_email_by_jwt(user['access_token']) + token_info = pandora_tools.get_email_by_jwt(user.access_token) # 根据exp判断是否过期,如果过期则刷新 exp_time = datetime.fromtimestamp(token_info['exp']) if exp_time > datetime.now(): continue flag = True - refresh(user['user_id']) + refresh(user.id) except Exception as e: logger.error(e) if flag: @@ -241,17 +240,17 @@ def refresh_route(user_id): def make_json(): - from app import query_db from flask import current_app import os - users = query_db("select * from users") + # users = query_db("select * from users") + users = db.session.query(User).all() with open(os.path.join(current_app.config['pandora_path'], 'tokens.json'), 'r') as f: tokens = json.loads(f.read()) # 将share_list转换为json对象 for user in users: # 当存在share_list时, 取所有share_token, 并写入tokens.json - if user['share_list'] is not None and user['share_list'] != '[]': - share_list = json.loads(user['share_list']) + if user.share_list is not None and user.share_list != '[]': + share_list = json.loads(user.share_list) for share in share_list: # json中有share_token和password才写入 if 'share_token' in share and 'password' in share: @@ -260,22 +259,21 @@ def make_json(): 'password': share['password'] } else: - if user['session_token'] is None: + if user.session_token is None: continue # 当不存在share_list时, 取session_token, 并写入tokens.json # Todo 自定义 plus / show_user_info - tokens[user['email']] = { - 'token': user['access_token'], + tokens[user.email] = { + 'token': user.access_token, # Todo 自定义 'show_user_info': False, } - if user['shared'] == 1: - tokens[user['email']]['shared'] = True + if user.shared == 1: + tokens[user.email]['shared'] = True else: - tokens[user['email']]['shared'] = False - tokens[user['email']]['password'] = user['password'] + tokens[user.email]['shared'] = False + tokens[user.email]['password'] = user.password # 检测当前是否存在tokens.json,如果有则备份,文件名为tokens.json + 当前时间 - if os.path.exists(os.path.join(current_app.config['pandora_path'], 'tokens.json')): import time os.rename(os.path.join(current_app.config['pandora_path'], 'tokens.json'), diff --git a/model.py b/model.py new file mode 100644 index 0000000..e63fb59 --- /dev/null +++ b/model.py @@ -0,0 +1,19 @@ +from flask_sqlalchemy import SQLAlchemy + +db = SQLAlchemy() + + +class User(db.Model): + __tablename__ = 'users' + id = db.Column(db.Integer, primary_key=True) + email = db.Column(db.Text) + password = db.Column(db.Text) + session_token = db.Column(db.Text) + access_token = db.Column(db.Text) + share_list = db.Column(db.Text) + create_time = db.Column(db.DateTime) + update_time = db.Column(db.DateTime) + shared = db.Column(db.Integer) + + def __repr__(self): + return '' % (self.username) From 16f4ee5e17a0a5923c5d62b391afc93e5b5516df Mon Sep 17 00:00:00 2001 From: q1139 <1139168548@qq.com> Date: Fri, 29 Dec 2023 00:22:03 +0800 Subject: [PATCH 3/9] feat: use flask-migrate --- app.py | 14 ++++++++++++++ main/main.py | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app.py b/app.py index 0c01ad0..2d698b3 100644 --- a/app.py +++ b/app.py @@ -8,6 +8,7 @@ from flask import Flask, g, redirect, url_for from flask_bootstrap import Bootstrap5 from flask_login import LoginManager +from flask_migrate import Migrate from flask_moment import Moment from flask_apscheduler import APScheduler from loguru import logger @@ -114,6 +115,19 @@ def check_require_config(): db.init_app(app) +def include_object(object, name, type_, reflected, compare_to): + if ( + type_ == "table" and name == "apscheduler_jobs" + ): + return False + else: + return True + + +migrate = Migrate(include_object=include_object) +migrate.init_app(app, db) + + def create_app(): app.register_blueprint(auth.auth_bp, url_prefix='/' + app.config['proxy_api_prefix']) app.register_blueprint(main.main_bp, url_prefix='/' + app.config['proxy_api_prefix']) diff --git a/main/main.py b/main/main.py index cace34f..0ab39cb 100644 --- a/main/main.py +++ b/main/main.py @@ -5,7 +5,7 @@ from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from flask_login import login_required -from app import db +from model import db import login_tools import share_tools import pandora_tools From 9c22e2e9364997ce5d3682087ceddebcc30fe96a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BD=AD=E5=BE=B7=E5=8D=8E?= <1139168548@qq.com> Date: Fri, 29 Dec 2023 17:08:52 +0800 Subject: [PATCH 4/9] feat: custom token input --- main/main.py | 3 - templates/manage_users.html | 685 +++++++++++++++++++----------------- 2 files changed, 370 insertions(+), 318 deletions(-) diff --git a/main/main.py b/main/main.py index 0ab39cb..7ac5d15 100644 --- a/main/main.py +++ b/main/main.py @@ -81,7 +81,6 @@ def add_share(): @main_bp.route('/delete-share//') @login_required def delete_share(user_id, unique_name): - # user = query_db('select * from users where id = ?', one=True, args=(user_id,)) user = db.session.query(User).filter_by(id=user_id).first() share_list = json.loads(user.share_list) for share in share_list: @@ -99,7 +98,6 @@ def delete_share(user_id, unique_name): # 获取ShareToken用量信息 @main_bp.route('/share-info/') def share_info(user_id): - # user = query_db('select * from users where id = ?', one=True, args=(user_id,)) user = db.session.query(User).filter_by(id=user_id).first() if user.access_token is None: return jsonify({'code': 500, 'msg': '请先刷新'}) @@ -242,7 +240,6 @@ def refresh_route(user_id): def make_json(): from flask import current_app import os - # users = query_db("select * from users") users = db.session.query(User).all() with open(os.path.join(current_app.config['pandora_path'], 'tokens.json'), 'r') as f: tokens = json.loads(f.read()) diff --git a/templates/manage_users.html b/templates/manage_users.html index 2660a7a..e042ced 100644 --- a/templates/manage_users.html +++ b/templates/manage_users.html @@ -1,137 +1,185 @@ {% extends 'layout.html' %} {% set page_title = 'PandoraNext Helper' %} {% block content %} -
-
- - {# 操作栏#} -
-
- - {% if not job_started %} - - - 定时刷新 +
+ - -