13
13
14
14
# Exceptions
15
15
class AuthorizationFailed (Exception ):
16
- pass
16
+ def __init__ (self , message : str , json : dict | None = None ) -> None :
17
+ super ().__init__ (message )
18
+ self .json = json
17
19
18
20
# Handle state
19
21
class ClientState :
@@ -64,6 +66,12 @@ def __init__(self) -> None:
64
66
self .__state = ClientState ()
65
67
self .__session = requests .Session ()
66
68
69
+ # Public attributes (provided just for the hell of it)
70
+ self .user : User | None = None
71
+ """The current user this client is connected as."""
72
+ self .address : str | None = None
73
+ """The address this client is connected to."""
74
+
67
75
# Events (for overwriting)
68
76
async def on_connect (self , ctx : Context ) -> None :
69
77
"""Listen to the :connect: event."""
@@ -105,12 +113,12 @@ async def __authorize(self, username: str, hex: str, address: str) -> tuple[str,
105
113
# Handle payload
106
114
payload = response .json ()
107
115
if payload ["code" ] != 200 :
108
- raise AuthorizationFailed (response )
116
+ raise AuthorizationFailed ("Connection failed!" , payload )
109
117
110
118
return host , int (port ), f"ws{ protocol } ://" , payload ["authorization" ]
111
119
112
- except requests .RequestException :
113
- raise AuthorizationFailed ("Connection failed!" )
120
+ except requests .RequestException as e :
121
+ raise AuthorizationFailed ("Connection failed!" , e . response . json () if e . response is not None else None )
114
122
115
123
async def __match_event (self , event : dict [str , typing .Any ]) -> None :
116
124
match event :
@@ -129,6 +137,9 @@ async def __match_event(self, event: dict[str, typing.Any]) -> None:
129
137
130
138
case {"type" : "join" , "data" : payload }:
131
139
user = from_dict (User , payload ["user" ])
140
+ if user == self .user :
141
+ return
142
+
132
143
self .__state .user_list .append (user )
133
144
await self .on_join (Context (self .__state , user = user ))
134
145
@@ -137,16 +148,22 @@ async def __match_event(self, event: dict[str, typing.Any]) -> None:
137
148
self .__state .user_list .remove (user )
138
149
await self .on_leave (Context (self .__state , user = user ))
139
150
140
- async def __event_loop (self , username : str , hex : str , address : str ) -> None :
151
+ async def event_loop (self , username : str , hex : str , address : str ) -> None :
141
152
"""Establish a connection and listen to websocket messages.
142
153
This method shouldn't be called directly, use :Client.run: instead."""
143
154
144
155
host , port , protocol , auth = await self .__authorize (username , hex , address )
156
+ self .user , self .address = User (username , hex , False , True ), address
157
+
145
158
async with connect (f"{ protocol } { host } :{ port } /api/ws?authorization={ auth } " ) as socket :
146
159
self .__state .socket = socket
147
160
while socket .state == 1 :
148
161
await self .__match_event (orjson .loads (await socket .recv ()))
149
162
163
+ async def close (self ) -> None :
164
+ """Closes the websocket connection."""
165
+ await self .__state .socket .close ()
166
+
150
167
def run (
151
168
self ,
152
169
username : str ,
@@ -160,4 +177,4 @@ def run(
160
177
:hex: (str) -- the hex color code to connect with
161
178
:address: (str) -- the FQDN to connect to
162
179
"""
163
- asyncio .run (self .__event_loop (username , hex , address ))
180
+ asyncio .run (self .event_loop (username , hex , address ))
0 commit comments