python library for communicating with the server through websocket

This commit is contained in:
2026-07-30 14:43:00 +02:00
parent 384ba26170
commit e0a9408b01
+157
View File
@@ -0,0 +1,157 @@
import asyncio, queue, json, threading, hashlib, websockets, inspect
from websockets.asyncio.client import connect as wsconnect
class BasicServerConnection():
def __init__(self,uri: str) -> None:
self.uri = uri
self.callbacks = {}
self.resq = queue.Queue()
self.asklock = asyncio.Lock()
self.needsClose = threading.Event()
def addCallback(self,name,func):
# print("addcallback",name,func)
if name in self.callbacks:
self.callbacks[name].append(func)
else:
self.callbacks[name] = [func]
def removeCallback(self,name,func):
if not name in self.callbacks: return
self.callbacks[name].remove(func)
async def __runCallback(self,name,*args):
# print("runcallback",name,args)
if not name in self.callbacks: return
for func in self.callbacks[name]:
# print(func)
if inspect.iscoroutinefunction(func):
await func(*args)
else:
func(*args)
def __runCallbackInThread(self,name,*args):
def __run():
asyncio.run(self.__runCallback(name,*args))
thread = threading.Thread(target=__run)
thread.start()
async def connect(self) -> None:
uri = self.uri
async with wsconnect(uri) as ws:
self.ws = ws
while True:
if self.needsClose.is_set():
await self.ws.close()
break
try:
txt = await asyncio.wait_for(ws.recv(), timeout=0.1)
# print("recv",txt)
res = json.loads(txt)
if "connected" == res["type"]:
self.__runCallbackInThread("connect")
if res["type"] in ["response","error"]:
self.resq.put(res)
except websockets.exceptions.ConnectionClosed:
break
except TimeoutError:
pass
except Exception as e:
pass
async def disconnect(self) -> None:
if not self.ws: return
self.needsClose.set()
await self.__runCallback("connect")
async def waitConnect(self) -> None:
# wait for it to reconnect
# print("waiting for reconnection.")
event = threading.Event()
def evset():
event.set()
self.addCallback("connect",evset)
self.addCallback("disconnect",evset)
#self.callbacks["connect"].insert(0,evset)
await asyncio.to_thread(event.wait)
# print("reconnected!! hell yeah")
self.removeCallback("connect",evset)
self.removeCallback("disconnect",evset)
async def ask(self,obj: dict,t:int=None) -> dict | None:
# print("ask",obj)
while True:
if self.needsClose.is_set():
return None
try:
async with self.asklock:
await self.ws.send(json.dumps(obj))
e = self.resq.get(timeout=t)
except queue.Empty:
e = None
except websockets.exceptions.ConnectionClosed:
await self.waitConnect()
continue
return e
class ServerConnection(BasicServerConnection):
def __init__(self,uri: str = "ws://localhost:30341"):
super().__init__(uri)
self.logincmd = None
super().addCallback("connect",self.__login)
async def __connectloop(self):
self.mustConnect = True
first = True
while self.mustConnect:
if not first:
await asyncio.sleep(1)
first=False
try:
await super().connect()
except OSError:
pass
async def connect(self):
self.thread = threading.Thread(target=lambda: asyncio.run(self.__connectloop()))
self.thread.start()
await super().waitConnect()
async def disconnect(self):
self.mustConnect = False
await super().disconnect()
self.thread.join()
async def status(self):
return await super().ask({"type": "status"})
async def __login(self):
# print("__login")
if self.logincmd == None: return {"type": "error", "error": "No credentials found.", "server": False}
stat = await self.status()
if stat==None: return
if stat["type"]=="error" or stat["response"]["login"]==False:
return await super().ask(self.logincmd)
async def login(self,name: str,pwrd: str):
res = hashlib.md5(pwrd.encode("utf8"))
psum = res.hexdigest()
self.logincmd = {"type": "login", "name": name, "pass": psum}
return await self.__login()
async def logout(self):
self.logincmd = None
return await super().ask({"type": "logout"})
async def createuser(self,name: str,pwrd: str):
res = hashlib.md5(pwrd.encode("utf8"))
psum = res.hexdigest()
return await super().ask({"type": "createuser", "name": name, "pass": psum})
async def changename(self,name: str):
return await super().ask({"type": "changename", "name": name})
async def changepass(self,old: str,new: str):
osum = hashlib.md5(old.encode("utf8")).hexdigest()
nsum = hashlib.md5(new.encode("utf8")).hexdigest()
return await super().ask({"type": "changepass", "oldpass": osum, "newpass": nsum})