Added instant messaging api

This commit is contained in:
zhaoyafan 2025-02-11 01:01:14 +08:00
parent cfa9ab049e
commit 5b7aafa61c
4 changed files with 108 additions and 29 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.idea/
Packages/
Chrome/
PyQt5/

View File

@ -14,6 +14,7 @@ import inspect
import uvicorn
import zipfile
import hashlib
import asyncio
import tempfile
import platform
import requests
@ -42,9 +43,10 @@ from PyQt5.QtWidgets import QApplication, QMainWindow, QMessageBox, QSystemTrayI
from PyQt5.QtCore import Qt, QCoreApplication, QTimer, QUrl
from PyQt5.QtGui import QIcon
from typing import List
from fastapi import FastAPI, Response, UploadFile, File, HTTPException
from fastapi import FastAPI, Response, WebSocket, UploadFile, File, HTTPException
from pydantic import BaseModel
from starlette.responses import JSONResponse, FileResponse
from starlette.websockets import WebSocketDisconnect
from winotify import Notification, audio
from func_timeout import func_set_timeout, FunctionTimedOut
from pathlib import Path
@ -1069,31 +1071,66 @@ class Browser(InspectRequestsMixin, DriverCommonMixin, Chrome):
len(self.window_handles) > 0 and self.switch_to.window(self.window_handles[0])
class BrowserPluginRunningParam(dict):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
def __setitem__(self, key, value):
pass
def __setattr__(self, key, value):
pass
def __getitem__(self, item):
try:
return super().__getitem__(item)
except KeyError:
return None
def __getattr__(self, item):
try:
return super().__getitem__(item)
except KeyError:
return None
class BrowserPluginParent:
id = None
name = None
requirements = []
def __init__(self):
self.thread = None
self.thread_exception = None
self.app_id = None
self.thread = None
self.run_task_exception = None
self.user_id = None
self.instant_message = None
def _run_task(self, *args, **kwargs):
try:
self.running(*args, **kwargs)
except Exception as e:
self.thread_exception = e
self.notification_except(e)
self.run_task_exception = e
self.message_except(e)
def notification(self, message=None):
def message(self, message=None):
notification_send(app_id=self.app_id, title=self.name, message='%s' % (message,))
self.instant_message and self.instant_message(json.dumps({
'time': int(time.time()), 'type': 'message', 'data': {'user_id': str(self.user_id), 'content': str(message), 'title': str(self.app_id)}
}))
def notification_except(self, exception_info):
def message_except(self, exception_info):
notification_send(app_id=self.app_id, title='%s ' % (self.name,), message='%s' % (exception_info,))
def run(self, *args, **kwargs) -> threading.Thread:
thread = threading.Thread(target=self._run_task, args=args, kwargs={**kwargs, **{'notification': self.notification}})
def logging(self, logging=None):
self.instant_message and self.instant_message(json.dumps({
'time': int(time.time()), 'type': 'logging', 'data': {'user_id': str(self.user_id), 'content': str(logging)}
}))
def run(self, app_id, user_id, driver, requirements, instant_message) -> threading.Thread:
self.app_id, self.user_id, self.instant_message = app_id, user_id, instant_message
params = BrowserPluginRunningParam({'driver': driver, 'message': self.message, 'logging': self.logging, 'requirements': requirements})
thread = threading.Thread(target=self._run_task, args=(params,))
self.thread = thread
thread.daemon = False
thread.start()
@ -1111,8 +1148,8 @@ class BrowserPluginParent:
return self.thread.is_alive() if self.thread else None
@staticmethod
def running(driver: Browser, notification, requirements):
time.sleep(5)
def running(param: BrowserPluginRunningParam):
param.driver.wait(5)
class BrowserPluginFileTest(BrowserPluginParent):
@ -1125,8 +1162,8 @@ class BrowserPluginFileTest(BrowserPluginParent):
]
@staticmethod
def running(driver, notification, requirements):
notification(requirements)
def running(param):
param.message(param.requirements)
class BrowserPluginMultFileTest(BrowserPluginParent):
@ -1139,8 +1176,8 @@ class BrowserPluginMultFileTest(BrowserPluginParent):
]
@staticmethod
def running(driver, notification, requirements):
notification(requirements)
def running(param):
param.message(param.requirements)
class BrowserPluginTextTest(BrowserPluginParent):
@ -1153,8 +1190,8 @@ class BrowserPluginTextTest(BrowserPluginParent):
]
@staticmethod
def running(driver, notification, requirements):
notification(requirements)
def running(param):
param.message(param.requirements)
class BrowserPluginMultTextTest(BrowserPluginParent):
@ -1167,8 +1204,8 @@ class BrowserPluginMultTextTest(BrowserPluginParent):
]
@staticmethod
def running(driver, notification, requirements):
notification(requirements)
def running(param):
param.message(param.requirements)
class BrowserPluginFileCommandDebug(BrowserPluginParent):
@ -1181,8 +1218,8 @@ class BrowserPluginFileCommandDebug(BrowserPluginParent):
]
@staticmethod
def running(driver, notification, requirements):
file = requirements[0][0]['path']
def running(param):
file = param.requirements[0][0]['path']
exec(open(file, mode='r', encoding='utf-8').read())
@ -1196,8 +1233,8 @@ class BrowserPluginTextCommandDebug(BrowserPluginParent):
]
@staticmethod
def running(driver, notification, requirements):
code = requirements[0]
def running(param):
code = param.requirements[0]
code and exec(code)
@ -1368,7 +1405,8 @@ class BrowserManagerUserRunning:
class BrowserManager:
def __init__(self, driver: str, binary: str, manager_data_file: str, browser_data_home: str, browser_init_home: str, use_selenium_wire: int = 0):
def __init__(self, runner, driver: str, binary: str, manager_data_file: str, browser_data_home: str, browser_init_home: str, use_selenium_wire: int = 0):
self.runner = runner
if not os.path.exists(driver):
raise FileNotFoundError('The driver executable file does not exist.')
if not os.path.exists(binary):
@ -1429,7 +1467,7 @@ class BrowserManager:
def _get_user_app_id(self, user_id: str):
user_name = self.data_storage['browser_user'][user_id]['user_name']
return '%s%s%s' % (user_id, user_name and ' - ', user_name or '')
return user_name or user_id
def _initialize_user_running(self, user_id: str):
return BrowserManagerUserRunning(
@ -1576,8 +1614,7 @@ class BrowserManager:
if (driver is None) == 1:
raise Exception('No driver object.')
plugin = self.plugins[plugin_id]()
plugin.app_id = self._get_user_app_id(user_id)
plugin.run(driver=driver, requirements=requirements)
plugin.run(self._get_user_app_id(user_id), user_id, driver, requirements, self.runner.web_server.websocket_connection_manager.send_broadcast_use_sync)
running.set_plugin(plugin)
finally:
self.user_operate_complete(user_id)
@ -1903,12 +1940,40 @@ class WebServerAPIJSONData(BaseModel):
data: dict = None
class WebSocketConnectionManager:
def __init__(self):
self.active_connections = []
async def connect(self, websocket: WebSocket, disconnect=None):
if disconnect:
self.active_connections.remove(websocket)
else:
await websocket.accept()
await websocket.send_text('Hello, %s:%s' % (websocket.client.host, websocket.client.port))
self.active_connections.append(websocket)
async def send_broadcast(self, message: str):
dead_connections = []
for conn in self.active_connections:
try:
await conn.send_text(str(message))
except WebSocketDisconnect:
dead_connections.append(conn)
for conn in dead_connections:
await self.connect(conn, 1)
def send_broadcast_use_sync(self, text):
asyncio.run(self.send_broadcast(text))
class WebServer:
def __init__(self, root: str, data: str, default_plugins=None):
def __init__(self, runner, root: str, data: str, default_plugins=None):
self.runner = runner
self.app = FastAPI()
self.www = os.path.join(root, 'www')
self.upload_dir = os.path.join(data, 'upload')
self.browser_manager = BrowserManager(
runner=runner,
driver=os.path.join(root, 'Chrome/chromedriver.exe'),
binary=os.path.join(root, 'Chrome/chrome.exe'),
manager_data_file=os.path.join(data, 'manager.json'),
@ -1918,6 +1983,17 @@ class WebServer:
)
self.browser_manager.load_plugins(default_plugins, is_external=0)
self.browser_manager.load_plugins_from_external_module()
self.websocket_connection_manager = WebSocketConnectionManager()
@self.app.websocket('/instant_message')
async def websocket_instant_message(websocket: WebSocket):
await self.websocket_connection_manager.connect(websocket)
try:
while True:
await websocket.receive_text()
except WebSocketDisconnect:
pass
await self.websocket_connection_manager.connect(websocket, 1)
@self.app.api_route(methods=['POST'], path='/plugin_run')
def api_plugin_run(data: WebServerAPIJSONData):
@ -2099,7 +2175,7 @@ class MainRunner:
def run(self):
os.path.exists(self.app_data) or os.makedirs(self.app_data)
self.web_server = WebServer(root=self.app_root, data=self.app_data, default_plugins=self.plugin_list)
self.web_server = WebServer(runner=self, root=self.app_root, data=self.app_data, default_plugins=self.plugin_list)
self.web_server_thread = threading.Thread(target=self.web_server.run, kwargs={'host': self.web_server_host, 'port': self.web_server_port})
self.web_server_thread.daemon = True
self.web_server_thread.start()

View File

@ -0,0 +1 @@
!function(a,b){"function"==typeof define&&define.amd?define([],b):"undefined"!=typeof module&&module.exports?module.exports=b():a.ReconnectingWebSocket=b()}(this,function(){function a(b,c,d){function l(a,b){var c=document.createEvent("CustomEvent");return c.initCustomEvent(a,!1,!1,b),c}var e={debug:!1,automaticOpen:!0,reconnectInterval:1e3,maxReconnectInterval:3e4,reconnectDecay:1.5,timeoutInterval:2e3};d||(d={});for(var f in e)this[f]="undefined"!=typeof d[f]?d[f]:e[f];this.url=b,this.reconnectAttempts=0,this.readyState=WebSocket.CONNECTING,this.protocol=null;var h,g=this,i=!1,j=!1,k=document.createElement("div");k.addEventListener("open",function(a){g.onopen(a)}),k.addEventListener("close",function(a){g.onclose(a)}),k.addEventListener("connecting",function(a){g.onconnecting(a)}),k.addEventListener("message",function(a){g.onmessage(a)}),k.addEventListener("error",function(a){g.onerror(a)}),this.addEventListener=k.addEventListener.bind(k),this.removeEventListener=k.removeEventListener.bind(k),this.dispatchEvent=k.dispatchEvent.bind(k),this.open=function(b){h=new WebSocket(g.url,c||[]),b||k.dispatchEvent(l("connecting")),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","attempt-connect",g.url);var d=h,e=setTimeout(function(){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","connection-timeout",g.url),j=!0,d.close(),j=!1},g.timeoutInterval);h.onopen=function(){clearTimeout(e),(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onopen",g.url),g.protocol=h.protocol,g.readyState=WebSocket.OPEN,g.reconnectAttempts=0;var d=l("open");d.isReconnect=b,b=!1,k.dispatchEvent(d)},h.onclose=function(c){if(clearTimeout(e),h=null,i)g.readyState=WebSocket.CLOSED,k.dispatchEvent(l("close"));else{g.readyState=WebSocket.CONNECTING;var d=l("connecting");d.code=c.code,d.reason=c.reason,d.wasClean=c.wasClean,k.dispatchEvent(d),b||j||((g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onclose",g.url),k.dispatchEvent(l("close")));var e=g.reconnectInterval*Math.pow(g.reconnectDecay,g.reconnectAttempts);setTimeout(function(){g.reconnectAttempts++,g.open(!0)},e>g.maxReconnectInterval?g.maxReconnectInterval:e)}},h.onmessage=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onmessage",g.url,b.data);var c=l("message");c.data=b.data,k.dispatchEvent(c)},h.onerror=function(b){(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","onerror",g.url,b),k.dispatchEvent(l("error"))}},1==this.automaticOpen&&this.open(!1),this.send=function(b){if(h)return(g.debug||a.debugAll)&&console.debug("ReconnectingWebSocket","send",g.url,b),h.send(b);throw"INVALID_STATE_ERR : Pausing to reconnect websocket"},this.close=function(a,b){"undefined"==typeof a&&(a=1e3),i=!0,h&&h.close(a,b)},this.refresh=function(){h&&h.close()}}return a.prototype.onopen=function(){},a.prototype.onclose=function(){},a.prototype.onconnecting=function(){},a.prototype.onmessage=function(){},a.prototype.onerror=function(){},a.debugAll=!1,a.CONNECTING=WebSocket.CONNECTING,a.OPEN=WebSocket.OPEN,a.CLOSING=WebSocket.CLOSING,a.CLOSED=WebSocket.CLOSED,a});

File diff suppressed because one or more lines are too long