├── .gitignore ├── README.md ├── main.py ├── requirements.txt └── src ├── chat ├── browser.py └── chat_api.py ├── config.py └── server ├── handler.py └── server.py /.gitignore: -------------------------------------------------------------------------------- 1 | # chrome 2 | browser/ 3 | 4 | # Python 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | *.so 9 | .Python 10 | build/ 11 | develop-eggs/ 12 | dist/ 13 | downloads/ 14 | eggs/ 15 | .eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | wheels/ 22 | *.egg-info/ 23 | .installed.cfg 24 | *.egg 25 | 26 | # Virtual Environment 27 | venv/ 28 | env/ 29 | ENV/ 30 | .env 31 | .venv 32 | 33 | # IDE 34 | .idea/ 35 | .vscode/ 36 | *.swp 37 | *.swo 38 | .project 39 | .pydevproject 40 | .settings/ 41 | 42 | # Logs 43 | *.log 44 | logs/ 45 | log/ 46 | 47 | # Chrome Driver 48 | *.exe 49 | chromedriver* 50 | 51 | # Local development 52 | .DS_Store 53 | Thumbs.db 54 | .env.local 55 | .env.development.local 56 | .env.test.local 57 | .env.production.local 58 | 59 | # Project specific 60 | config.local.py 61 | debug/ 62 | temp/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ReverseSillyTavernToAPI 2 | 3 | ReverseSillyTavernToAPI是一个基于 Selenium 的 SillyTavern API 代理服务,通过Selenium自动化操作SillyTavern网页,实现了SillyTavern的API接口。 4 | 5 | ## ⚠️ 免责声明 6 | 7 | 本项目仅用于学习和研究目的: 8 | 1. 请遵守 SillyTavern 的使用条款和规则 9 | 2. 不得用于任何商业用途 10 | 3. 使用本项目造成的任何问题由使用者自行承担 11 | 12 | ## 功能特点 13 | 14 | - 使用 Selenium 自动化操作 SillyTavern 网页界面 15 | - 提供 HTTP API 接口,支持发送消息和查询状态 16 | - 自动等待页面加载和响应 17 | - 支持命令行交互模式 18 | 19 | ## 系统要求 20 | 21 | - Python 3.7+ 22 | - Google Chrome 浏览器 23 | - ChromeDriver(与 Chrome 版本匹配) 24 | 25 | ## 安装 26 | 27 | 1. 克隆仓库: 28 | ```bash 29 | git clone https://github.com/the-lazy-me/ReverseSillyTavernToAPI.git 30 | ``` 31 | 32 | 2. 设置chrome浏览器 33 | - 点击下载chrome:https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.159/win64/chrome-win64.zip 34 | - 点击下载chrome driver:https://storage.googleapis.com/chrome-for-testing-public/132.0.6834.159/win64/chromedriver-win64.zip 35 | 36 | 3. 配置`config.py`文件 37 | 38 | - 关键是设置,也可以使用相对路径,默认如下 39 | 40 | ```python 41 | CHROME_PATH = r'browser/chrome.exe' 42 | CHROME_DRIVER_PATH = r'browser/driver/chromedriver.exe' 43 | ``` 44 | 45 | 4. 安装依赖,程序根目录 46 | 47 | ```bash 48 | pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 49 | ``` 50 | 51 | 5. 运行 52 | 53 | ```bash 54 | python main.py 55 | ``` 56 | 57 | ## 使用限制 58 | 59 | 1. 请控制请求频率,避免对服务器造成压力 60 | 2. 建议在个人或测试环境中使用 61 | 3. 遵守相关法律法规和服务条款 62 | 63 | ## API 接口说明 64 | 65 | ### 1. 状态检查 66 | 67 | 检查服务是否就绪。 68 | 69 | ```bash 70 | curl http://localhost:4444/status 71 | ``` 72 | 73 | 响应示例: 74 | ```json 75 | { 76 | "status": "ready" // 或 "initializing" 77 | } 78 | ``` 79 | 80 | ### 2. 发送消息 81 | 82 | 向 SillyTavern 发送消息并获取回复。 83 | 84 | ```bash 85 | curl -X POST http://localhost:4444 \ 86 | -H "Content-Type: application/json" \ 87 | -d '{"message": "Hi!"}' 88 | ``` 89 | 90 | 响应示例: 91 | ```json 92 | { 93 | "response": "Hello! How can I help you today?" 94 | } 95 | ``` 96 | 97 | 错误响应示例: 98 | ```json 99 | { 100 | "error": "聊天系统正在初始化,请稍后再试" // 503 Service Unavailable 101 | } 102 | ``` 103 | ```json 104 | { 105 | "error": "Message is required" // 400 Bad Request 106 | } 107 | ``` 108 | 109 | ## 使用示例 110 | 111 | ### Python 示例 112 | ```python 113 | # 创建一个 SillyTavernAPI 实例 114 | api = SillyTavernAPI() 115 | 116 | # 发送消息 117 | response = api.send_message("Hi!") 118 | print(response) 119 | 120 | # 查询状态 121 | status = api.get_status() 122 | print(status) 123 | ``` 124 | 125 | ### curl 示例 126 | ```bash 127 | # 检查状态 128 | curl http://localhost:4444/status 129 | 130 | # 发送消息 131 | curl -X POST http://localhost:4444 \ 132 | -H "Content-Type: application/json" \ 133 | -d '{"message": "Hi!"}' 134 | ``` 135 | 136 | ### JavaScript 示例 137 | ```javascript 138 | // 发送消息 139 | fetch('http://localhost:4444', { 140 | method: 'POST', 141 | headers: { 142 | 'Content-Type': 'application/json' 143 | }, 144 | body: JSON.stringify({ 145 | message: 'Hi!' 146 | }) 147 | }) 148 | .then(response => response.json()) 149 | .then(data => console.log(data)); 150 | 151 | // 检查状态 152 | fetch('http://localhost:4444/status') 153 | .then(response => response.json()) 154 | .then(data => console.log(data)); 155 | ``` 156 | 157 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import threading 2 | import argparse 3 | import signal 4 | import time 5 | import os 6 | from src.chat.chat_api import ChatAPI 7 | from src.server.server import run_server 8 | 9 | # 添加关闭标志和上次按下Ctrl+C的时间 10 | is_shutting_down = False 11 | last_sigint_time = 0 12 | FORCE_QUIT_INTERVAL = 1 # 1秒内按两次Ctrl+C将强制退出 13 | 14 | def console_chat(chat): 15 | print("开始对话(输入 !!!exit 结束对话)...") 16 | while True: 17 | user_input = input("你: ") 18 | if user_input.strip() == "!!!exit": 19 | print("结束对话") 20 | break 21 | 22 | response = chat.send_message(user_input) 23 | print("AI回复:", response) 24 | print() 25 | 26 | def main(): 27 | # 创建命令行参数解析器 28 | parser = argparse.ArgumentParser(description='SillyTavernAPI对话') 29 | parser.add_argument('--console', action='store_true', 30 | help='是否启用控制台对话模式') 31 | args = parser.parse_args() 32 | 33 | chat = ChatAPI() 34 | try: 35 | # 启动HTTP服务器 36 | server_thread = threading.Thread(target=run_server, args=(chat,)) 37 | server_thread.daemon = True 38 | server_thread.start() 39 | 40 | # 根据参数决定是否启动控制台对话 41 | if args.console: 42 | console_chat(chat) 43 | else: 44 | # 添加信号处理,使得可以通过Ctrl+C优雅地退出 45 | def signal_handler(signum, frame): 46 | global is_shutting_down, last_sigint_time 47 | current_time = time.time() 48 | 49 | # 检查是否在短时间内按下两次Ctrl+C 50 | if current_time - last_sigint_time < FORCE_QUIT_INTERVAL: 51 | print("\n检测到连续两次Ctrl+C,强制退出...") 52 | os._exit(1) # 强制退出所有进程 53 | 54 | last_sigint_time = current_time 55 | 56 | if is_shutting_down: 57 | print("\n正在强制退出...") 58 | os._exit(1) # 如果正常退出卡住,强制退出 59 | return 60 | 61 | is_shutting_down = True 62 | print("\n正在关闭服务...") 63 | try: 64 | chat.close() 65 | exit(0) 66 | except: 67 | os._exit(1) # 如果关闭出错,强制退出 68 | 69 | signal.signal(signal.SIGINT, signal_handler) 70 | print("服务器已启动,按 Ctrl+C 退出,连按两次 Ctrl+C 强制退出...") 71 | 72 | # 保持程序运行 73 | while True: 74 | time.sleep(1) 75 | 76 | finally: 77 | chat.close() 78 | 79 | if __name__ == "__main__": 80 | main() 81 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | selenium>=4.10.0 2 | webdriver-manager>=4.0.0 -------------------------------------------------------------------------------- /src/chat/browser.py: -------------------------------------------------------------------------------- 1 | from selenium import webdriver 2 | from selenium.webdriver.chrome.service import Service 3 | from selenium.webdriver.chrome.options import Options 4 | from ..config import CHROME_PATH, CHROME_DRIVER_PATH 5 | import os 6 | 7 | def create_browser(): 8 | options = webdriver.ChromeOptions() 9 | options.binary_location = CHROME_PATH 10 | options.set_capability('goog:loggingPrefs', {'performance': 'ALL'}) 11 | 12 | # 添加抑制日志的选项 13 | options.add_argument('--log-level=3') # 只显示致命错误 14 | options.add_argument('--silent') 15 | options.add_argument('--disable-logging') 16 | options.add_argument('--disable-dev-shm-usage') 17 | options.add_argument('--no-sandbox') 18 | options.add_experimental_option('excludeSwitches', ['enable-logging']) 19 | 20 | # 重定向错误输出 21 | if os.name == 'nt': # Windows系统 22 | os.environ['WDM_LOG_LEVEL'] = '0' 23 | 24 | service = Service(CHROME_DRIVER_PATH, log_output=os.devnull) 25 | driver = webdriver.Chrome(service=service, options=options) 26 | 27 | return driver -------------------------------------------------------------------------------- /src/chat/chat_api.py: -------------------------------------------------------------------------------- 1 | from selenium.webdriver.common.by import By 2 | from selenium.webdriver.common.keys import Keys 3 | from selenium.webdriver.support.ui import WebDriverWait 4 | from selenium.webdriver.support import expected_conditions as EC 5 | from selenium.common.exceptions import TimeoutException 6 | import json 7 | import time 8 | from ..config import CHAT_URL, MAX_WAIT_TIME, PAGE_LOAD_WAIT 9 | from .browser import create_browser 10 | 11 | class ChatAPI: 12 | def __init__(self): 13 | self.ready = False 14 | print("正在打开聊天界面...") 15 | self.driver = create_browser() 16 | self.driver.get(CHAT_URL) 17 | self._wait_for_chat_ready() 18 | 19 | def _wait_for_chat_ready(self): 20 | try: 21 | # 等待输入框出现,最多等待30秒 22 | WebDriverWait(self.driver, 30).until( 23 | EC.presence_of_element_located((By.ID, "send_textarea")) 24 | ) 25 | print("聊天界面已就绪") 26 | self.ready = True 27 | except TimeoutException: 28 | print("等待聊天界面超时") 29 | self.ready = False 30 | 31 | def is_ready(self): 32 | return self.ready 33 | 34 | def get_chat_response(self): 35 | logs = self.driver.get_log('performance') 36 | for log in logs: 37 | message = json.loads(log['message'])['message'] 38 | # 检查是否是网络响应 39 | if 'Network.responseReceived' not in message['method']: 40 | continue 41 | 42 | # 获取响应URL 43 | response_url = message['params'].get('response', {}).get('url', '') 44 | 45 | # 检查是否是我们需要的API响应 46 | if not (response_url.endswith('/api/backends/text-completions/generate') or 47 | response_url.endswith('/api/backends/chat-completions/generate')): 48 | continue 49 | 50 | request_id = message['params']['requestId'] 51 | try: 52 | response = self.driver.execute_cdp_cmd('Network.getResponseBody', {'requestId': request_id}) 53 | response_data = json.loads(response['body']) 54 | 55 | # 处理不同的响应格式 56 | if 'response' in response_data: 57 | return response_data['response'] 58 | elif 'choices' in response_data and len(response_data['choices']) > 0: 59 | message_content = response_data['choices'][0].get('message', {}).get('content') 60 | if message_content: 61 | return message_content 62 | 63 | except Exception: 64 | continue 65 | return None 66 | 67 | def send_message(self, message): 68 | if not self.ready: 69 | return "聊天系统尚未就绪,请稍后再试" 70 | 71 | try: 72 | input_box = self.driver.find_element(By.ID, 'send_textarea') 73 | input_box.clear() 74 | input_box.send_keys(message) 75 | input_box.send_keys(Keys.RETURN) 76 | 77 | start_time = time.time() 78 | while True: 79 | response = self.get_chat_response() 80 | if response: 81 | return response 82 | 83 | if time.time() - start_time > MAX_WAIT_TIME: 84 | return "等待响应超时" 85 | 86 | time.sleep(0.5) 87 | 88 | except Exception as e: 89 | return f"发生错误:{str(e)}" 90 | 91 | def close(self): 92 | self.driver.quit() -------------------------------------------------------------------------------- /src/config.py: -------------------------------------------------------------------------------- 1 | # 浏览器配置 2 | # 相对于项目根目录的路径 3 | CHROME_PATH = r'browser/chrome.exe' 4 | CHROME_DRIVER_PATH = r'browser/driver/chromedriver.exe' 5 | CHAT_URL = 'http://127.0.0.1:8000' 6 | 7 | # 服务器配置 8 | SERVER_HOST = '0.0.0.0' 9 | SERVER_PORT = 4444 10 | 11 | # 聊天配置 12 | MAX_WAIT_TIME = 360 # 最大等待响应时间(秒) 13 | PAGE_LOAD_WAIT = 30 # 页面加载等待时间(秒) -------------------------------------------------------------------------------- /src/server/handler.py: -------------------------------------------------------------------------------- 1 | from http.server import BaseHTTPRequestHandler 2 | import json 3 | 4 | class ChatRequestHandler(BaseHTTPRequestHandler): 5 | chat_api = None 6 | 7 | def do_GET(self): 8 | if self.path == '/status': 9 | self.send_response(200) 10 | self.send_header('Content-type', 'application/json') 11 | self.end_headers() 12 | 13 | status = "ready" if self.chat_api.is_ready() else "initializing" 14 | response_data = json.dumps({ 15 | 'status': status 16 | }).encode('utf-8') 17 | self.wfile.write(response_data) 18 | return 19 | 20 | self.send_error(404, "Not Found") 21 | 22 | def do_POST(self): 23 | if not self.chat_api.is_ready(): 24 | self.send_response(503) # Service Unavailable 25 | self.send_header('Content-type', 'application/json') 26 | self.end_headers() 27 | 28 | response_data = json.dumps({ 29 | 'error': '聊天系统正在初始化,请稍后再试' 30 | }, ensure_ascii=False).encode('utf-8') 31 | self.wfile.write(response_data) 32 | return 33 | 34 | content_length = int(self.headers['Content-Length']) 35 | post_data = self.rfile.read(content_length) 36 | request_data = json.loads(post_data.decode('utf-8')) 37 | 38 | message = request_data.get('message', '') 39 | if not message: 40 | self.send_error(400, "Message is required") 41 | return 42 | 43 | response = self.chat_api.send_message(message) 44 | 45 | self.send_response(200) 46 | self.send_header('Content-type', 'application/json') 47 | self.end_headers() 48 | 49 | response_data = json.dumps({ 50 | 'response': response 51 | }, ensure_ascii=False).encode('utf-8') 52 | self.wfile.write(response_data) -------------------------------------------------------------------------------- /src/server/server.py: -------------------------------------------------------------------------------- 1 | from http.server import HTTPServer 2 | from .handler import ChatRequestHandler 3 | from ..config import SERVER_HOST, SERVER_PORT 4 | 5 | def run_server(chat_api): 6 | ChatRequestHandler.chat_api = chat_api 7 | server = HTTPServer((SERVER_HOST, SERVER_PORT), ChatRequestHandler) 8 | print(f"服务器启动在端口 {SERVER_PORT}") 9 | server.serve_forever() --------------------------------------------------------------------------------