upload tokens

This commit is contained in:
LanQian 2024-05-21 16:40:47 +08:00
parent ad24c33d12
commit cdf926dce1
11 changed files with 115 additions and 15 deletions

View File

@ -3,4 +3,5 @@
/.git/
/.idea/
/docs/
/tmp/
/tmp/
/data/

View File

@ -35,7 +35,7 @@ jobs:
images: lanqian528/chat2api
tags: |
type=raw,value=latest,enable={{is_default_branch}}
type=raw,value=v1.1.0
type=raw,value=v1.1.2
- name: Build and push
uses: docker/build-push-action@v5

3
.gitignore vendored
View File

@ -2,4 +2,5 @@
*.pyc
/.git/
/.idea/
/tmp/
/tmp/
/data/

View File

@ -137,7 +137,7 @@ curl --location 'http://127.0.0.1:5005/v1/chat/completions' \
```
# 安全相关
API_PREFIX=your_prefix // API前缀密码不设置容易被人访问设置后需请求 /your_prefix/v1/chat/completions
AUTHORIZATION=sk-xxxxxxxx,sk-yyyyyyyy // 填写data文件夹下的token.txt后请求时传入AUTHORIZATION可多账号轮询
AUTHORIZATION=sk-xxxxxxxx,sk-yyyyyyyy // 先到 /tokens 上传ac或rt请求时传入 AUTHORIZATION 可多账号轮询
# 请求相关
CHATGPT_BASE_URL=https://chatgpt.com // ChatGPT网关地址设置后会改变请求的网站多个网关用逗号分隔

View File

@ -1,23 +1,24 @@
import os
import types
import warnings
from fastapi import FastAPI, Request, Depends, HTTPException
from fastapi import FastAPI, Request, Depends, HTTPException, Form
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import StreamingResponse, JSONResponse
from fastapi.templating import Jinja2Templates
from fastapi.responses import HTMLResponse
from starlette.background import BackgroundTask
from chatgpt.ChatService import ChatService
from chatgpt.reverseProxy import chatgpt_reverse_proxy
from utils.Logger import logger
from utils.authorization import verify_token
from utils.authorization import verify_token, token_list
from utils.config import api_prefix
from utils.retry import async_retry
import warnings
warnings.filterwarnings("ignore")
app = FastAPI()
templates = Jinja2Templates(directory="templates")
app.add_middleware(
CORSMiddleware,
@ -65,6 +66,35 @@ async def send_conversation(request: Request, token=Depends(verify_token)):
raise HTTPException(status_code=500, detail="Server error")
@app.get(f"/{api_prefix}/tokens" if api_prefix else "/upload", response_class=HTMLResponse)
async def upload_html(request: Request):
tokens_count = len(token_list)
return templates.TemplateResponse("tokens.html", {"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
@app.post(f"/{api_prefix}/tokens/upload" if api_prefix else "/tokens/upload")
async def upload_post(request: Request, text: str = Form(...)):
lines = text.split("\n")
for line in lines:
if line.strip() and not line.startswith("#"):
token_list.append(line.strip())
with open("data/token.txt", "a", encoding="utf-8") as f:
f.write(line.strip() + "\n")
logger.info(f"Token list count: {len(token_list)}")
tokens_count = len(token_list)
return templates.TemplateResponse("tokens.html", {"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
@app.post(f"/{api_prefix}/tokens/clear" if api_prefix else "/tokens/clear")
async def upload_post(request: Request):
token_list.clear()
with open("data/token.txt", "w", encoding="utf-8") as f:
pass
logger.info(f"Token list count: {len(token_list)}")
tokens_count = len(token_list)
return templates.TemplateResponse("tokens.html", {"request": request, "api_prefix": api_prefix, "tokens_count": tokens_count})
@app.api_route("/{path:path}", methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "HEAD", "PATCH", "TRACE"])
async def reverse_proxy(request: Request, path: str):
return await chatgpt_reverse_proxy(request, path)
return await chatgpt_reverse_proxy(request, path)

View File

@ -1,5 +1,8 @@
import json
import os
import random
import time
from functools import cache
from fastapi import HTTPException
@ -7,11 +10,30 @@ from utils.Client import Client
from utils.Logger import logger
from utils.config import proxy_url_list
refresh_map = {}
fake_map = {}
DATA_FOLDER = "data"
REFRESH_MAP_FILE = os.path.join(DATA_FOLDER, "refresh_map.json")
if not os.path.exists(DATA_FOLDER):
os.makedirs(DATA_FOLDER)
@cache
def load_refresh_map():
if os.path.exists(REFRESH_MAP_FILE):
with open(REFRESH_MAP_FILE, "r") as file:
return json.load(file)
else:
return {}
def save_refresh_map(refresh_map):
with open(REFRESH_MAP_FILE, "w") as file:
json.dump(refresh_map, file)
async def rt2ac(refresh_token):
refresh_map = load_refresh_map()
if refresh_token in refresh_map and int(time.time()) - refresh_map.get(refresh_token, {}).get("timestamp", 0) < 24 * 60 * 60:
access_token = refresh_map[refresh_token]["token"]
logger.info(f"refresh_token -> access_token from cache")
@ -19,6 +41,7 @@ async def rt2ac(refresh_token):
else:
access_token = await chat_refresh(refresh_token)
refresh_map[refresh_token] = {"token": access_token, "timestamp": int(time.time())}
save_refresh_map(refresh_map)
logger.info(f"refresh_token -> access_token with openai: {access_token}")
return access_token

View File

@ -8,7 +8,7 @@ services:
ports:
- '5005:5005'
volumes:
- ./data:/app/data # 挂载tokens的目录
- ./data:/app/data # 挂载一些需要保存的数据
environment:
- ARKOSE_TOKEN_URL=http://arkose:5006/token

View File

@ -5,4 +5,5 @@ tiktoken
python-dotenv
websockets
pillow
pybase64
pybase64
jinja2

39
templates/tokens.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tokens 管理</title>
<script src="https://cdn.tailwindcss.com"></script>
<script>
document.addEventListener('DOMContentLoaded', () => {
const apiPrefix = "{{ api_prefix }}";
const uploadForm = document.getElementById('uploadForm');
const clearForm = document.getElementById('clearForm');
if (apiPrefix) {
uploadForm.action = `/${apiPrefix}/tokens/upload`;
clearForm.action = `/${apiPrefix}/tokens/clear`;
} else {
uploadForm.action = "/tokens/upload";
clearForm.action = "/tokens/clear";
}
});
</script>
</head>
<body class="bg-gradient-to-r from-blue-200 via-purple-200 to-pink-200 flex justify-center items-center min-h-screen">
<div class="bg-white p-10 rounded-lg shadow-2xl w-128 text-center">
<h1 class="text-4xl font-extrabold text-gray-900 mb-6">Tokens 管理</h1>
<p class="text-gray-600 mb-6">当前可用 Tokens 数量:<span class="text-blue-600">{{ tokens_count }}</span></p>
<form id="uploadForm" method="post" class="mb-6">
<textarea name="text" rows="10" placeholder="一行一个Token可以是 AccessToken 或 RefreshToken" class="w-full p-4 mb-4 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-400 resize-none"></textarea>
<p class="text-gray-600 mb-4">使用docker时如果挂载了data文件夹则重启后不需要再次上传</p>
<button type="submit" class="w-full bg-blue-600 text-white py-3 rounded-md hover:bg-blue-700 transition duration-300 mb-4">上传</button>
</form>
<form id="clearForm" method="post">
<button type="submit" class="w-full bg-red-600 text-white py-3 rounded-md hover:bg-red-700 transition duration-300">清空Tokens</button>
</form>
<p class="text-gray-600 mt-6">点击清空,将会清空所有已保存的 Tokens</p>
</div>
</body>
</html>

View File

@ -12,6 +12,11 @@ oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
count = 0
token_list = []
DATA_FOLDER = "data"
TOKENS_FILE = os.path.join(DATA_FOLDER, "token.txt")
if not os.path.exists(DATA_FOLDER):
os.makedirs(DATA_FOLDER)
if os.path.exists("data/token.txt"):
with open("data/token.txt", "r", encoding="utf-8") as f:
for line in f:

View File

@ -35,7 +35,7 @@ arkose_token_url_list = arkose_token_url.split(',') if arkose_token_url else []
proxy_url_list = proxy_url.split(',') if proxy_url else []
logger.info("-" * 60)
logger.info("Chat2Api v1.1.0 | https://github.com/lanqian528/chat2api")
logger.info("Chat2Api v1.1.2 | https://github.com/lanqian528/chat2api")
logger.info("-" * 60)
logger.info("Environment variables:")
logger.info("API_PREFIX: " + str(api_prefix))