Skip to content

Commit

Permalink
Refactor frontend to react (#51)
Browse files Browse the repository at this point in the history
* refactor: clone slash-admin

* refactor: clone slash-admin

* feat: account page list and operation

* feat: share page list and operation

* feat: add share modal

* feat: mutation loading

* refactor: frontend refactor and delete old file
feat: share info statistic
feat: share refresh
feat: refresh task timer

* doc: v0.7.0 doc and image
  • Loading branch information
nianhua99 authored Jan 14, 2024
1 parent 8341140 commit e58f9fe
Show file tree
Hide file tree
Showing 296 changed files with 26,138 additions and 1,091 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ venv
build
dist
*.db
*.json

node_modules
28 changes: 8 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
* 一键启动定时器,自动检测Token失效后刷新`Access Token``Share Token`
* 在以上操作完成后,会自动更新`config.json`文件,并调用`reload` Api,直接生效 !
* 本项目保持低侵入性,不参与管理PandoraNext程序。只是方便刷新、管理账号和各种Token。
![image](https://github.com/nianhua99/PandoraNext-Helper/assets/48168645/bbe7b786-0d0b-4de5-afa0-280f7386e592)
![shareinfo.png](shareinfo.png)
![1](./doc/1.png)
![2](./doc/2.png)
![3](./doc/3.png)
![4](./doc/4.png)
## Docker部署
```shell
$ docker pull q11391/pandora-next-helper
Expand All @@ -26,7 +28,7 @@ $ docker run -d --restart=always --name PandoraNext-Helper --net=bridge \
```
* 请替换`<YOUR_PANDORA_NEXT_PATH>`为你的PandoraNext路径, 如`/opt/pandora-next`, 请确保PandoraNext的`config.json`文件在此目录下。
* 请替换`<YOUR_PANDORA_NEXT_DOMAIN>`为你的PandoraNext域名, 如`https://www.baidu.com`,没有域名的话也可以使用IP,比如http://192.168.1.1:8181 这样,只能要访问到你的PandoraNext即可
* **请访问`IP:8182/<PROXY_API_PREFIX>/login`进行使用!**

## 原生Python部署(Python3)
```shell
$ git clone https://github.com/nianhua99/PandoraNext-Helper.git
Expand All @@ -42,7 +44,7 @@ $ python3 waitress_run.py
# 或者在后台启动
$ nohup python3 waitress_run.py &
```
**请访问`IP:8182/<PROXY_API_PREFIX>/login`进行使用!**

## 如何借助本项目管理共享ChatGPT车?
首先需要搭建完成PandoraNext项目,以及本项目
在Helper中,添加你的OpenAI账号 -> 点击刷新(获取登录凭证) -> 点击Share列的添加按钮 -> 定义乘客登录时需要的账号密码
Expand All @@ -51,26 +53,12 @@ $ nohup python3 waitress_run.py &
## 注意事项
* 本项目复用了PandoraNext的`config.json`文件,包括`setup_password`|`captcha`|`proxy_api_prefix`
* 你的PandoraNext 必须启动Proxy模式,详情请看PandoraNext文档:[https://docs.pandoranext.com/zh-CN/configuration/config#proxy_api_prefix](https://docs.pandoranext.com/zh-CN/configuration/config#proxy_api_prefix)
* 项目的首页是:`IP:8182/<PROXY_API_PREFIX>/login`
* 项目依赖两个环境变量
* 项目的首页是:`IP:8182`
* 项目依赖两个环境变量,均为**必填**
* `PANDORA_NEXT_PATH`: 指向PandoraNext的路径,如`/opt/pandora-next`,Docker部署不需要这个,只需挂载正确即可
* `PANDORA_NEXT_DOMAIN`: 你的PandoraNext域名,如`https://www.baidu.com`
* 目前验证码只支持`hcaptcha`,你可以在这里获得 hcaptcha :https://www.hcaptcha.com
* 以上配置全部是**必选**,否则无法使用本项目
* 项目会在你的`YOUR_PANDORA_NEXT_PATH`中生成`helper.db`文件,用于存储Token信息
## 其他说明
> [!CAUTION]
> 如果你实在不想开启**验证码**
> 项目本身不提供开关,这道门槛可以让小白也注意到安全问题。
> ~~如果你确保你的网络环境安全,你可以尝试使用 hcaptcha 提供的测试Key,它将直接Pass,无需你打码~~
```json
"captcha": {
"provider": "hcaptcha",
"site_key": "10000000-ffff-ffff-ffff-000000000001",
"site_secret": "0x0000000000000000000000000000000000000000",
// 其他配置
}
```
## Todo
- [x] 展示Pandora额度信息
- [x] 生成指定账号下各Share Token的用量情况柱状图
Expand Down
1 change: 1 addition & 0 deletions account/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from account.account import account_bp
263 changes: 263 additions & 0 deletions account/account.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
import json
from datetime import datetime

from flask import Blueprint, request
from flask_jwt_extended import jwt_required

from model import db, User
from util import login_tools, share_tools, pandora_tools
from util.api_response import ApiResponse
from loguru import logger

from util.pandora_tools import sync_pandora

account_bp = Blueprint('account_bp', __name__)


@jwt_required()
@account_bp.route('/list')
def account_list():
accounts = db.session.query(User).all()
return ApiResponse.success(accounts)



@account_bp.route('/search', methods=['POST'])
@jwt_required()
def account_search():
email = request.json.get('email') if request.json.get('email') else ''
accounts = db.session.query(User).filter(User.email.like(f'%{email}%')).all()
return ApiResponse.success(accounts)



@account_bp.route('/add', methods=['POST'])
@jwt_required()
def account_add():
email = request.json.get('email')
password = request.json.get('password')
custom_token_type = request.json.get('custom_type')
custom_token = request.json.get('custom_token')
shared = 1 if request.json.get('shared') else 0

user = User(email=email, password=password, shared=shared,
share_list='[]',
create_time=datetime.now(),
update_time=datetime.now())

if custom_token != '':
if custom_token_type == 'session_token':
user.session_token = custom_token
elif custom_token_type == 'refresh_token':
user.refresh_token = custom_token

if custom_token == '' and custom_token_type == 'refresh_token':
try:
res = login_tools.get_refresh_token(email, password)
user.refresh_token = res['refresh_token']
except Exception as e:
logger.error(e)

logger.info(user)

db.session.add(user)
db.session.commit()

sync_pandora()
return ApiResponse.success({})


@account_bp.route('/update', methods=['POST'])
@jwt_required()
def account_update():
account_id = request.json.get('id')
email = request.json.get('email')
password = request.json.get('password')
custom_token_type = request.json.get('custom_type')
custom_token = request.json.get('custom_token')
shared = 1 if request.json.get('shared') else 0

user = db.session.query(User).filter_by(id=account_id).first()
user.email = email
user.password = password
user.shared = shared
user.update_time = datetime.now()

if custom_token != '':
if custom_token_type == 'session_token':
user.session_token = custom_token
elif custom_token_type == 'refresh_token':
user.refresh_token = custom_token

if custom_token == '' and custom_token_type == 'refresh_token':
try:
res = login_tools.get_refresh_token(email, password)
user.refresh_token = res['refresh_token']
except Exception as e:
logger.error(e)
# 更新用户

db.session.commit()
sync_pandora()
return ApiResponse.success({})


@account_bp.route('/delete', methods=['POST'])
@jwt_required()
def account_delete():
account_id = request.json.get('id')
user = db.session.query(User).filter_by(id=account_id).first()
db.session.delete(user)
db.session.commit()
return ApiResponse.success({})


@account_bp.route('/refresh', methods=['POST'])
@jwt_required()
def account_refresh():
account_id = request.json.get('id')
try:
refresh(account_id)
except Exception as e:
return ApiResponse.error(str(e))
sync_pandora()
return ApiResponse.success('刷新成功')


def refresh_all_user():
from app import scheduler
flag = False
with scheduler.app.app_context():
users = db.session.query(User).all()
for user in users:
try:
# jwt解析access_token 检查access_token是否过期
if user.access_token is None:
continue
else:
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.id)
except Exception as e:
logger.error(e)
if flag:
sync_pandora()
logger.info('刷新成功')


@account_bp.route('/start', methods=['post'])
@jwt_required()
def refresh_task():
from app import scheduler
scheduler.add_job(func=refresh_all_user, trigger='interval', minutes=1, id='my_job')
if not scheduler.running:
scheduler.start()
return ApiResponse.success('定时刷新已开启')


@account_bp.route('/stop', methods=['post'])
@jwt_required()
def kill_refresh_task():
from app import scheduler
scheduler.remove_job(id='my_job')
return ApiResponse.success('定时刷新已关闭')


@account_bp.route('/task_status')
@jwt_required()
def refresh_status():
from app import scheduler
if scheduler.running and scheduler.get_job(id='my_job') is not None:
return ApiResponse.success({'status': True})
else:
return ApiResponse.success({'status': False})


def refresh(user_id):
user = db.session.query(User).filter_by(id=user_id).first()

if user is None:
raise Exception('用户不存在')

def login_by_refresh_token():
try:
refresh_token_result = login_tools.get_access_token_by_refresh_token(user.refresh_token)
except Exception as e:
raise e
return refresh_token_result['access_token']

def login_by_password():
try:
login_result = login_tools.login(user.email, user.password)
except Exception as e:
raise e
access_token = login_result['access_token']
session_token = login_result['session_token']
return access_token, session_token

def login_by_session_token():
try:
access_token_result = login_tools.get_access_token(user.session_token)
except Exception as e:
raise e
access_token = access_token_result['access_token']
session_token = access_token_result['session_token']
return access_token, session_token

# 如果Refresh Token存在则使用Refresh Token刷新,否则使用Session Token刷新,都为空或失败则使用密码刷新保底
# Refresh Token刷新仅更新access_token
# Session Token刷新和登录刷新,则更新access_token和session_token,并且以后使用Session Token刷新

access_token, session_token = None, None

if user.refresh_token is not None:
try:
access_token = login_by_refresh_token()
db.session.query(User).filter_by(id=user_id).update(
{'access_token': access_token, 'update_time': datetime.now()})
db.session.commit()
except Exception as e:
logger.error(e)

else:
if user.session_token is not None:
try:
access_token, session_token = login_by_session_token()
db.session.query(User).filter_by(id=user_id).update(
{'access_token': access_token, 'session_token': session_token, 'update_time': datetime.now()})
db.session.commit()
except Exception as e:
logger.error(e)

if access_token is None:
try:
access_token, session_token = login_by_password()
db.session.query(User).filter_by(id=user_id).update(
{'access_token': access_token, 'session_token': session_token, 'update_time': datetime.now()})
db.session.commit()
except Exception as e:
logger.error(e)
# 保底刷新失败
raise e

# 刷新share_token
share_list = json.loads(user.share_list)
if len(share_list) == 0:
# 没有share token,直接返回
return
for share in share_list:
try:
share_token = share_tools.get_share_token(access_token, share['unique_name'])
except Exception as e:
# 返回刷新失败
logger.error(e)
raise e
share['share_token'] = share_token['token_key']
# 更新share_list
db.session.query(User).filter_by(id=user_id).update(
{'share_list': json.dumps(share_list), 'update_time': datetime.now()})
db.session.commit()
Loading

0 comments on commit e58f9fe

Please sign in to comment.